Linux wireless drivers development
 help / color / mirror / Atom feed
* [RFC PATCH wireless-next 0/4] wifi: aic: add AIC8800 SDIO FullMAC driver
@ 2026-07-21 10:20 Yanli Yang
  2026-07-21 10:21 ` [RFC PATCH wireless-next 1/4] wifi: aic: add SDIO BSP and build configuration Yanli Yang
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Yanli Yang @ 2026-07-21 10:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg, Zhirun Liu, dijiaxu, chunqiuliu, liheng.wei

This RFC adds an SDIO FullMAC driver for AIC8801, AIC8800DC and
AIC8800D80 devices. It contains an SDIO/BSP layer, the firmware protocol,
the cfg80211 FullMAC implementation, and diagnostic and compatibility
interfaces.

The series is organized as follows:

  1. build configuration, SDIO BSP, firmware loading and BSP chip support;
  2. FullMAC firmware protocol, platform bring-up and control path;
  3. cfg80211 core, data path, SDIO/chip support, wireless features,
     diagnostics and compatibility interfaces;
  4. parent wireless build integration and MAINTAINERS.

The parent build integration is added last so that no incomplete driver
state can be selected by Kconfig.

Validation performed for this series:

  - default, W=1 and allmodconfig AIC object builds with GCC 11.4.0;
  - strict checkpatch validation for all four patches;
  - sequential git am application on wireless-next commit ac798f757d6475.

Runtime testing on production hardware has not yet been performed.

Known limitations of this RFC are:

  - firmware redistribution permission and the corresponding linux-firmware
    submission are not yet available;
  - DT power and wake bindings are not included;
  - private generic-netlink and vendor interfaces, the regulatory database,
    and rwnx_settings.ini require follow-up work.

Zhirun Liu (4):
  wifi: aic: add SDIO BSP and build configuration
  wifi: aic: add FullMAC firmware and platform interface
  wifi: aic: add FullMAC WLAN driver
  wifi: aic: integrate AIC8800 driver

 MAINTAINERS                                   |   10 +
 drivers/net/wireless/Kconfig                  |    1 +
 drivers/net/wireless/Makefile                 |    1 +
 drivers/net/wireless/aic/Kconfig              |  135 +
 drivers/net/wireless/aic/Makefile             |   17 +
 drivers/net/wireless/aic/aic8800_bsp/Kconfig  |   97 +
 drivers/net/wireless/aic/aic8800_bsp/Makefile |   42 +
 .../aic/aic8800_bsp/aic8800d80_compat.c       |  290 +
 .../aic/aic8800_bsp/aic8800d80_compat.h       |   11 +
 .../aic/aic8800_bsp/aic8800dc_compat.c        | 1353 +++++
 .../aic/aic8800_bsp/aic8800dc_compat.h        |   40 +
 .../wireless/aic/aic8800_bsp/aic_bsp_driver.c | 1659 +++++
 .../wireless/aic/aic8800_bsp/aic_bsp_driver.h |  621 ++
 .../wireless/aic/aic8800_bsp/aic_bsp_export.h |   70 +
 .../wireless/aic/aic8800_bsp/aic_bsp_main.c   |  556 ++
 .../aic/aic8800_bsp/aic_chip_8800d80.c        |  116 +
 .../aic/aic8800_bsp/aic_chip_8800dc.c         |  133 +
 .../wireless/aic/aic8800_bsp/aic_chip_8801.c  |  117 +
 .../wireless/aic/aic8800_bsp/aic_chip_ops.c   |   77 +
 .../wireless/aic/aic8800_bsp/aic_chip_ops.h   |   71 +
 .../net/wireless/aic/aic8800_bsp/aicsdio.c    | 1999 ++++++
 .../net/wireless/aic/aic8800_bsp/aicsdio.h    |  176 +
 .../wireless/aic/aic8800_bsp/aicsdio_txrxif.c |  465 ++
 .../wireless/aic/aic8800_bsp/aicsdio_txrxif.h |  180 +
 .../aic/aic8800_bsp/aicwf_txq_prealloc.c      |   66 +
 .../aic/aic8800_bsp/aicwf_txq_prealloc.h      |    8 +
 drivers/net/wireless/aic/aic8800_bsp/md5.c    |  165 +
 drivers/net/wireless/aic/aic8800_bsp/md5.h    |   77 +
 .../aic/aic8800_bsp/rwnx_version_gen.h        |    5 +
 drivers/net/wireless/aic/aic8800_fdrv/Kconfig |  539 ++
 .../net/wireless/aic/aic8800_fdrv/Makefile    |  117 +
 .../wireless/aic/aic8800_fdrv/aic_br_ext.c    | 1598 +++++
 .../wireless/aic/aic8800_fdrv/aic_br_ext.h    |   72 +
 .../aic/aic8800_fdrv/aic_bsp_export.h         |   62 +
 .../wireless/aic/aic8800_fdrv/aic_btsdio.c    | 1164 ++++
 .../wireless/aic/aic8800_fdrv/aic_btsdio.h    |  472 ++
 .../net/wireless/aic/aic8800_fdrv/aic_bus.h   |  191 +
 .../wireless/aic/aic8800_fdrv/aic_priv_cmd.c  | 1967 ++++++
 .../wireless/aic/aic8800_fdrv/aic_priv_cmd.h  |  127 +
 .../wireless/aic/aic8800_fdrv/aic_vendor.c    |  950 +++
 .../wireless/aic/aic8800_fdrv/aic_vendor.h    |  370 ++
 .../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         |  267 +
 .../aic/aic8800_fdrv/aicwf_chip_ops.h         |  186 +
 .../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_debug.h   |   37 +
 .../wireless/aic/aic8800_fdrv/aicwf_genl.c    |  932 +++
 .../wireless/aic/aic8800_fdrv/aicwf_genl.h    |  112 +
 .../aic/aic8800_fdrv/aicwf_rx_prealloc.c      |  102 +
 .../aic/aic8800_fdrv/aicwf_rx_prealloc.h      |   32 +
 .../wireless/aic/aic8800_fdrv/aicwf_sdio.c    | 2813 +++++++++
 .../wireless/aic/aic8800_fdrv/aicwf_sdio.h    |  264 +
 .../wireless/aic/aic8800_fdrv/aicwf_tcp_ack.c |  572 ++
 .../wireless/aic/aic8800_fdrv/aicwf_tcp_ack.h |  104 +
 .../wireless/aic/aic8800_fdrv/aicwf_txrxif.c  |  753 +++
 .../wireless/aic/aic8800_fdrv/aicwf_txrxif.h  |  217 +
 .../aic/aic8800_fdrv/aicwf_wext_linux.c       | 1137 ++++
 .../aic/aic8800_fdrv/aicwf_wext_linux.h       |   15 +
 .../net/wireless/aic/aic8800_fdrv/hal_desc.h  |  362 ++
 .../wireless/aic/aic8800_fdrv/ipc_compat.h    |   25 +
 .../net/wireless/aic/aic8800_fdrv/ipc_host.c  |   42 +
 .../net/wireless/aic/aic8800_fdrv/ipc_host.h  |  169 +
 .../wireless/aic/aic8800_fdrv/ipc_shared.h    |  781 +++
 .../net/wireless/aic/aic8800_fdrv/lmac_mac.h  |  470 ++
 .../net/wireless/aic/aic8800_fdrv/lmac_msg.h  | 3124 ++++++++++
 .../wireless/aic/aic8800_fdrv/lmac_types.h    |   37 +
 drivers/net/wireless/aic/aic8800_fdrv/md5.c   |  175 +
 drivers/net/wireless/aic/aic8800_fdrv/md5.h   |   82 +
 .../wireless/aic/aic8800_fdrv/reg_access.h    |  146 +
 drivers/net/wireless/aic/aic8800_fdrv/regdb.c | 2770 +++++++++
 .../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_cmds.c |  471 ++
 .../net/wireless/aic/aic8800_fdrv/rwnx_cmds.h |   99 +
 .../wireless/aic/aic8800_fdrv/rwnx_compat.h   |   93 +
 .../wireless/aic/aic8800_fdrv/rwnx_debugfs.c  | 2585 ++++++++
 .../wireless/aic/aic8800_fdrv/rwnx_debugfs.h  |  134 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_defs.h |  744 +++
 .../wireless/aic/aic8800_fdrv/rwnx_events.h   |  938 +++
 .../wireless/aic/aic8800_fdrv/rwnx_fw_trace.c |   50 +
 .../wireless/aic/aic8800_fdrv/rwnx_fw_trace.h |   28 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_gki.c  |  406 ++
 .../net/wireless/aic/aic8800_fdrv/rwnx_gki.h  |   46 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_irqs.c |   44 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_irqs.h |   12 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_main.c | 5406 +++++++++++++++++
 .../net/wireless/aic/aic8800_fdrv/rwnx_main.h |   34 +
 .../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        | 1295 ++++
 .../aic/aic8800_fdrv/rwnx_mod_params.h        |   70 +
 .../wireless/aic/aic8800_fdrv/rwnx_msg_rx.c   | 1742 ++++++
 .../wireless/aic/aic8800_fdrv/rwnx_msg_rx.h   |    8 +
 .../wireless/aic/aic8800_fdrv/rwnx_msg_tx.c   | 3815 ++++++++++++
 .../wireless/aic/aic8800_fdrv/rwnx_msg_tx.h   |  285 +
 .../wireless/aic/aic8800_fdrv/rwnx_mu_group.c |  646 ++
 .../wireless/aic/aic8800_fdrv/rwnx_mu_group.h |  159 +
 .../wireless/aic/aic8800_fdrv/rwnx_platform.c | 2946 +++++++++
 .../wireless/aic/aic8800_fdrv/rwnx_platform.h |  186 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_prof.h |  142 +
 .../wireless/aic/aic8800_fdrv/rwnx_radar.c    | 1965 ++++++
 .../wireless/aic/aic8800_fdrv/rwnx_radar.h    |  278 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_rx.c   | 2443 ++++++++
 .../net/wireless/aic/aic8800_fdrv/rwnx_rx.h   |  358 ++
 .../net/wireless/aic/aic8800_fdrv/rwnx_strs.c |  268 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_strs.h |   31 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_tdls.c |  776 +++
 .../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   | 1805 ++++++
 .../net/wireless/aic/aic8800_fdrv/rwnx_tx.h   |  193 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_txq.c  | 1379 +++++
 .../net/wireless/aic/aic8800_fdrv/rwnx_txq.h  |  372 ++
 .../wireless/aic/aic8800_fdrv/rwnx_utils.c    |   36 +
 .../wireless/aic/aic8800_fdrv/rwnx_utils.h    |  131 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_v7.c   |  182 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_v7.h   |   21 +
 .../wireless/aic/aic8800_fdrv/rwnx_version.h  |   14 +
 .../aic/aic8800_fdrv/rwnx_version_gen.h       |    5 +
 .../wireless/aic/aic8800_fdrv/rwnx_wakelock.c |  109 +
 .../wireless/aic/aic8800_fdrv/rwnx_wakelock.h |   24 +
 .../net/wireless/aic/aic8800_fdrv/sdio_host.c |  132 +
 .../net/wireless/aic/aic8800_fdrv/sdio_host.h |   39 +
 132 files changed, 67273 insertions(+)
 create mode 100644 drivers/net/wireless/aic/Kconfig
 create mode 100644 drivers/net/wireless/aic/Makefile
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/Kconfig
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/Makefile
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic8800d80_compat.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic8800d80_compat.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic8800dc_compat.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic8800dc_compat.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_bsp_driver.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_bsp_driver.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_bsp_export.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_bsp_main.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_chip_8800d80.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_chip_8800dc.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_chip_8801.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_chip_ops.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_chip_ops.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aicsdio.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aicsdio.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aicsdio_txrxif.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aicsdio_txrxif.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aicwf_txq_prealloc.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aicwf_txq_prealloc.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/md5.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/md5.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/rwnx_version_gen.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/Kconfig
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/Makefile
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_bsp_export.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_bus.h
 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/aic_vendor.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_vendor.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_debug.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/aicwf_wext_linux.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_wext_linux.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/hal_desc.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/ipc_compat.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/ipc_host.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/ipc_host.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/ipc_shared.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/lmac_mac.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/lmac_msg.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/lmac_types.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/md5.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/md5.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/reg_access.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_cmds.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_cmds.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_compat.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_debugfs.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_debugfs.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_defs.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_events.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_fw_trace.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_fw_trace.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_gki.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_gki.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_irqs.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_irqs.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_msg_rx.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_rx.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_tx.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_tx.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_platform.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_platform.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_prof.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_strs.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_strs.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/rwnx_utils.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_utils.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_v7.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_v7.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_version.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_version_gen.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_wakelock.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_wakelock.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


base-commit: ac798f757d6475dc6fee2ec899980d6740714596

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [RFC PATCH wireless-next 1/4] wifi: aic: add SDIO BSP and build configuration
  2026-07-21 10:20 [RFC PATCH wireless-next 0/4] wifi: aic: add AIC8800 SDIO FullMAC driver Yanli Yang
@ 2026-07-21 10:21 ` Yanli Yang
  2026-07-21 10:21 ` [RFC PATCH wireless-next 2/4] wifi: aic: add FullMAC firmware and platform interface Yanli Yang
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Yanli Yang @ 2026-07-21 10:21 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg, Zhirun Liu, dijiaxu, chunqiuliu, liheng.wei

From: Zhirun Liu <zhirunliu@aicsemi.com>

Add the vendor, BSP and FullMAC build descriptions together with
the shared SDIO BSP, firmware loader and BSP chipset support. Keep parent
wireless build integration in the final patch.

Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
 drivers/net/wireless/aic/Kconfig              |  135 ++
 drivers/net/wireless/aic/Makefile             |   17 +
 drivers/net/wireless/aic/aic8800_bsp/Kconfig  |   97 +
 drivers/net/wireless/aic/aic8800_bsp/Makefile |   42 +
 .../aic/aic8800_bsp/aic8800d80_compat.c       |  290 +++
 .../aic/aic8800_bsp/aic8800d80_compat.h       |   11 +
 .../aic/aic8800_bsp/aic8800dc_compat.c        | 1353 +++++++++++
 .../aic/aic8800_bsp/aic8800dc_compat.h        |   40 +
 .../wireless/aic/aic8800_bsp/aic_bsp_driver.c | 1659 ++++++++++++++
 .../wireless/aic/aic8800_bsp/aic_bsp_driver.h |  621 +++++
 .../wireless/aic/aic8800_bsp/aic_bsp_export.h |   70 +
 .../wireless/aic/aic8800_bsp/aic_bsp_main.c   |  556 +++++
 .../aic/aic8800_bsp/aic_chip_8800d80.c        |  116 +
 .../aic/aic8800_bsp/aic_chip_8800dc.c         |  133 ++
 .../wireless/aic/aic8800_bsp/aic_chip_8801.c  |  117 +
 .../wireless/aic/aic8800_bsp/aic_chip_ops.c   |   77 +
 .../wireless/aic/aic8800_bsp/aic_chip_ops.h   |   71 +
 .../net/wireless/aic/aic8800_bsp/aicsdio.c    | 1999 +++++++++++++++++
 .../net/wireless/aic/aic8800_bsp/aicsdio.h    |  176 ++
 .../wireless/aic/aic8800_bsp/aicsdio_txrxif.c |  465 ++++
 .../wireless/aic/aic8800_bsp/aicsdio_txrxif.h |  180 ++
 .../aic/aic8800_bsp/aicwf_txq_prealloc.c      |   66 +
 .../aic/aic8800_bsp/aicwf_txq_prealloc.h      |    8 +
 drivers/net/wireless/aic/aic8800_bsp/md5.c    |  165 ++
 drivers/net/wireless/aic/aic8800_bsp/md5.h    |   77 +
 .../aic/aic8800_bsp/rwnx_version_gen.h        |    5 +
 drivers/net/wireless/aic/aic8800_fdrv/Kconfig |  539 +++++
 .../net/wireless/aic/aic8800_fdrv/Makefile    |  117 +
 28 files changed, 9202 insertions(+)
 create mode 100644 drivers/net/wireless/aic/Kconfig
 create mode 100644 drivers/net/wireless/aic/Makefile
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/Kconfig
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/Makefile
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic8800d80_compat.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic8800d80_compat.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic8800dc_compat.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic8800dc_compat.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_bsp_driver.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_bsp_driver.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_bsp_export.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_bsp_main.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_chip_8800d80.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_chip_8800dc.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_chip_8801.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_chip_ops.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aic_chip_ops.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aicsdio.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aicsdio.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aicsdio_txrxif.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aicsdio_txrxif.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aicwf_txq_prealloc.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/aicwf_txq_prealloc.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/md5.c
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/md5.h
 create mode 100644 drivers/net/wireless/aic/aic8800_bsp/rwnx_version_gen.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/Kconfig
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/Makefile

diff --git a/drivers/net/wireless/aic/Kconfig b/drivers/net/wireless/aic/Kconfig
new file mode 100644
index 0000000000000..ed62a5f2d9333
--- /dev/null
+++ b/drivers/net/wireless/aic/Kconfig
@@ -0,0 +1,135 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config WLAN_VENDOR_AIC
+	bool "AIC devices"
+	default y
+	help
+	  Say Y to expose drivers for wireless devices based on AIC
+	  chipsets. The individual bus and WLAN drivers remain separately
+	  selectable below this vendor menu. Saying N hides those driver
+	  questions without changing any unrelated wireless drivers.
+
+if WLAN_VENDOR_AIC
+
+menuconfig AIC8800_BSP_SUPPORT
+	tristate "AIC8800 SDIO bus support"
+	depends on MMC
+	help
+	  Enable the AIC8800 bus support package. It provides the SDIO
+	  transport, chip operations and firmware download infrastructure
+	  shared by the AIC8800 FullMAC WLAN driver.
+	  Firmware files are loaded from the aic8800/ subdirectory of the
+	  kernel firmware search path, normally /lib/firmware/aic8800/.
+
+	  If unsure, say N.
+
+if AIC8800_BSP_SUPPORT
+
+# The current driver only implements an SDIO transport. Keep this internal
+# symbol for the existing Makefile and C preprocessor conditionals.
+config AIC8800_SDIO_SUPPORT
+	def_bool y
+
+menu "AIC8800 common firmware and transport options"
+
+config AIC8800_SDIO_PWRCTRL
+	bool "SDIO power control"
+	default y
+	help
+	  Allow the driver to control the SDIO interface power state while
+	  bringing the AIC device up and down. The BSP coordinates this
+	  state with firmware download and the WLAN function. This is the
+	  normal setting for supported AIC8800 SDIO modules.
+
+	  If unsure, say Y.
+
+config AIC8800_GPIO_WAKEUP
+	bool "GPIO wakeup support"
+	default y
+	help
+	  Enable a dedicated GPIO signal for device-to-host wakeup. The
+	  signal lets the device wake the host without polling the SDIO
+	  bus. Board wiring and firmware must both provide the wake signal.
+	  Disable this when the board has no dedicated wake GPIO.
+
+	  If unsure, say Y.
+
+config AIC8800_OOB
+	bool "Out-of-band interrupt support"
+	depends on AIC8800_GPIO_WAKEUP
+	help
+	  Use a dedicated GPIO interrupt instead of the in-band SDIO
+	  interrupt. The GPIO must be described by the platform and wired
+	  to the AIC device interrupt output. This mode is only suitable
+	  for boards whose firmware and hardware use out-of-band signaling.
+
+config AIC8800_PREALLOC_TXQ
+	bool "Preallocate TX queues"
+	default y
+	help
+	  Preallocate transmit queue buffers during driver initialization.
+	  This avoids data-path allocations after the interface starts and
+	  can reduce latency under memory pressure. It also reserves the
+	  queue memory for the lifetime of the driver.
+
+	  If unsure, say Y.
+
+config AIC8800_DPD
+	bool "DPD calibration support"
+	default y
+	help
+	  Enable Digital Pre-Distortion calibration support for radio
+	  transmit-path linearization. The driver loads or generates DPD
+	  data expected by supported firmware. Keep this enabled for boards
+	  whose supplied firmware requires DPD calibration data.
+
+	  If unsure, say Y.
+
+config AIC8800_FORCE_DPD_CALIB
+	bool "Force DPD calibration"
+	depends on AIC8800_DPD
+	default y
+	help
+	  Force DPD calibration instead of using existing calibration data.
+	  This makes initialization regenerate the result even when stored
+	  calibration data is available. It increases startup work but can
+	  be useful while validating a new board or radio configuration.
+
+config AIC8800_RESV_MEM_SUPPORT
+	bool "Reserved memory support"
+	default y
+	help
+	  Enable the driver's reserved-memory allocation path for transport
+	  buffers. Platforms may use this to avoid allocating large buffers
+	  during device initialization. The reserved area must be supplied
+	  by the platform integration when this option is selected.
+
+	  If unsure, say Y.
+
+config AIC8800_SDIO_BT
+	bool "Bluetooth over SDIO support"
+	depends on BROKEN
+	help
+	  Enable the downstream Bluetooth transport sharing the Wi-Fi SDIO
+	  interface. The retained implementation uses interfaces that are
+	  not available to a current mainline kernel. It remains disabled
+	  until it is converted to the upstream Bluetooth HCI interfaces.
+
+config AIC8800_VRF_DCDC_MODE
+	bool "VRF DCDC mode"
+	default y
+	help
+	  Configure the chipset's VRF rail to use its internal DC-DC mode.
+	  This affects radio power delivery and must agree with the module
+	  and board power design. The standard supported configuration uses
+	  the internal converter. Check the board design before changing it.
+
+	  If unsure, say Y.
+
+endmenu
+
+source "drivers/net/wireless/aic/aic8800_bsp/Kconfig"
+source "drivers/net/wireless/aic/aic8800_fdrv/Kconfig"
+
+endif # AIC8800_BSP_SUPPORT
+endif # WLAN_VENDOR_AIC
diff --git a/drivers/net/wireless/aic/Makefile b/drivers/net/wireless/aic/Makefile
new file mode 100644
index 0000000000000..247f7fbbc83b4
--- /dev/null
+++ b/drivers/net/wireless/aic/Makefile
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_AIC8800_WLAN_SUPPORT) += aic8800_fdrv/
+obj-$(CONFIG_AIC8800_BSP_SUPPORT) += aic8800_bsp/
+
+ccflags-$(CONFIG_AIC8800_SDIO_SUPPORT)       += -DAICWF_SDIO_SUPPORT
+ccflags-$(CONFIG_AIC8800_SDIO_PWRCTRL)       += -DCONFIG_SDIO_PWRCTRL
+ccflags-$(CONFIG_AIC8800_GPIO_WAKEUP)        += -DCONFIG_GPIO_WAKEUP
+ccflags-$(CONFIG_AIC8800_OOB)                += -DCONFIG_OOB
+ccflags-$(CONFIG_AIC8800_PREALLOC_TXQ)       += -DCONFIG_PREALLOC_TXQ
+ccflags-$(CONFIG_AIC8800_PREALLOC_RX_SKB)    += -DCONFIG_PREALLOC_RX_SKB
+ccflags-$(CONFIG_AIC8800_DPD)                += -DCONFIG_DPD
+ccflags-$(CONFIG_AIC8800_FORCE_DPD_CALIB)    += -DCONFIG_FORCE_DPD_CALIB
+ccflags-$(CONFIG_AIC8800_RESV_MEM_SUPPORT)   += -DCONFIG_RESV_MEM_SUPPORT
+ccflags-$(CONFIG_AIC8800_SDIO_BT)            += -DCONFIG_SDIO_BT
+ccflags-$(CONFIG_AIC8800_FDRV_NO_REG_SDIO)   += -DCONFIG_FDRV_NO_REG_SDIO
+ccflags-$(CONFIG_AIC8800_VRF_DCDC_MODE)      += -DCONFIG_VRF_DCDC_MODE
diff --git a/drivers/net/wireless/aic/aic8800_bsp/Kconfig b/drivers/net/wireless/aic/aic8800_bsp/Kconfig
new file mode 100644
index 0000000000000..858685c9cf8ca
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/Kconfig
@@ -0,0 +1,97 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+menu "AIC8800 BSP options"
+
+config AIC8800_BSP_M2D_OTA_AUTO
+	bool "M2D OTA auto support"
+	default n
+	help
+	  Enable automatic M2D (Module-to-Device) over-the-air firmware
+	  update support. When enabled, the driver will automatically
+	  handle firmware download and update from the host to the device
+	  over the SDIO interface.
+
+	  If unsure, say N.
+
+config AIC8800_BSP_M2D_OTA_LZMA
+	bool "M2D OTA LZMA support"
+	depends on AIC8800_BSP_M2D_OTA_AUTO
+	default n
+	help
+	  Enable LZMA compression support for M2D over-the-air firmware
+	  updates. This reduces the size of firmware images transferred
+	  over the SDIO bus at the cost of additional CPU time for
+	  decompression.
+
+	  If unsure, say N.
+
+config AIC8800_BSP_LINK_DET_5G
+	bool "5G link detect"
+	default y
+	help
+	  Enable 5 GHz band link detection support. When enabled, the
+	  driver performs additional link quality monitoring on the 5 GHz
+	  band to detect and recover from link degradation.
+
+	  If unsure, say Y.
+
+config AIC8800_BSP_AMSDU_RX
+	bool "AMSDU receive"
+	default n
+	help
+	  Enable A-MSDU (Aggregate MAC Service Data Unit) receive support
+	  at the BSP layer. This allows the SDIO transport layer to handle
+	  aggregated frames, improving receive throughput for large data
+	  transfers.
+
+	  If unsure, say N.
+
+config AIC8800_BSP_IRQ_FALL
+	bool "Falling edge IRQ"
+	depends on AIC8800_GPIO_WAKEUP
+	default n
+	help
+	  Use falling edge trigger for SDIO interrupts instead of the
+	  default rising edge. Some platform designs require falling edge
+	  IRQ configuration due to hardware signal inversion.
+
+	  Only enable this if your hardware design requires it.
+
+	  If unsure, say N.
+
+config AIC8800_BSP_CUSTOM_PWF
+	bool "Custom power waveform"
+	depends on AIC8800_BSP_PLATFORM_EXTERNAL && BROKEN
+	default n
+	help
+	  Enable custom power waveform configuration for the AIC8800
+	  chipset. This allows fine-tuning of power delivery parameters
+	  for specific hardware designs that require non-standard power
+	  profiles.
+
+	  If unsure, say N.
+
+config AIC8800_BSP_TEDIOUS_PRT
+	bool "Verbose print log"
+	default n
+	help
+	  Enable verbose debug logging in the BSP layer. When enabled,
+	  detailed SDIO bus transactions, firmware loading progress, and
+	  chip operation traces are printed to the kernel log.
+
+	  This is intended for development and debugging only.
+
+	  If unsure, say N.
+
+config AIC8800_BSP_PLATFORM_EXTERNAL
+	bool "External platform"
+	default n
+	help
+	  Enable support for external (non-integrated) platform
+	  configurations. This option enables code paths required when
+	  the AIC8800 chipset is used on a development board or external
+	  adapter rather than an integrated system-on-module design.
+
+	  If unsure, say N.
+
+endmenu
diff --git a/drivers/net/wireless/aic/aic8800_bsp/Makefile b/drivers/net/wireless/aic/aic8800_bsp/Makefile
new file mode 100644
index 0000000000000..3d915020d742a
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/Makefile
@@ -0,0 +1,42 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_AIC8800_BSP_SUPPORT) += aic8800_bsp.o
+
+aic8800_bsp-y := \
+	aic_chip_8801.o \
+	aic_chip_8800dc.o \
+	aic_chip_8800d80.o \
+	aic_chip_ops.o \
+	aic8800dc_compat.o \
+	aic8800d80_compat.o \
+	aic_bsp_main.o \
+	aic_bsp_driver.o \
+	aicsdio.o \
+	aicsdio_txrxif.o \
+	md5.o
+
+aic8800_bsp-$(CONFIG_AIC8800_PREALLOC_TXQ) += aicwf_txq_prealloc.o
+
+ccflags-y += -I$(src)/../aic8800_fdrv
+
+ccflags-$(CONFIG_AIC8800_SDIO_SUPPORT) += -DAICWF_SDIO_SUPPORT
+ccflags-$(CONFIG_AIC8800_SDIO_PWRCTRL) += -DCONFIG_SDIO_PWRCTRL
+
+ccflags-$(CONFIG_AIC8800_GPIO_WAKEUP) += -DCONFIG_GPIO_WAKEUP
+ccflags-$(CONFIG_AIC8800_BSP_M2D_OTA_AUTO) += -DCONFIG_M2D_OTA_AUTO_SUPPORT
+ccflags-$(CONFIG_AIC8800_BSP_M2D_OTA_LZMA) += -DCONFIG_M2D_OTA_LZMA_SUPPORT
+ccflags-$(CONFIG_AIC8800_BSP_LINK_DET_5G) += -DCONFIG_LINK_DET_5G
+
+ccflags-$(CONFIG_AIC8800_FDRV_NO_REG_SDIO) += -DCONFIG_FDRV_NO_REG_SDIO
+ccflags-$(CONFIG_AIC8800_VRF_DCDC_MODE) += -DCONFIG_VRF_DCDC_MODE
+ccflags-$(CONFIG_AIC8800_OOB) += -DCONFIG_OOB
+ccflags-$(CONFIG_AIC8800_PREALLOC_TXQ) += -DCONFIG_PREALLOC_TXQ
+ccflags-$(CONFIG_AIC8800_DPD) += -DCONFIG_DPD
+ccflags-$(CONFIG_AIC8800_FORCE_DPD_CALIB) += -DCONFIG_FORCE_DPD_CALIB
+ccflags-$(CONFIG_AIC8800_RESV_MEM_SUPPORT) += -DCONFIG_RESV_MEM_SUPPORT
+ccflags-$(CONFIG_AIC8800_BSP_AMSDU_RX) += -DCONFIG_AMSDU_RX
+ccflags-$(CONFIG_AIC8800_BSP_IRQ_FALL) += -DCONFIG_IRQ_FALL
+ccflags-$(CONFIG_AIC8800_SDIO_BT) += -DCONFIG_SDIO_BT
+ccflags-$(CONFIG_AIC8800_BSP_PLATFORM_EXTERNAL) += -DCONFIG_PLATFORM_EXTERNAL
+ccflags-$(CONFIG_AIC8800_BSP_CUSTOM_PWF) += -DCONFIG_CUSTOM_PWF
+ccflags-$(CONFIG_AIC8800_BSP_TEDIOUS_PRT) += -DCONFIG_TEDIOUS_PRT
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aic8800d80_compat.c b/drivers/net/wireless/aic/aic8800_bsp/aic8800d80_compat.c
new file mode 100644
index 0000000000000..16cc1dd10f55f
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aic8800d80_compat.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief 8800d80 chip config
+ *
+ ******************************************************************************
+ */
+
+#include "aic8800d80_compat.h"
+#include "aic_bsp_driver.h"
+
+extern struct aicbsp_info_t aicbsp_info;
+
+typedef u32 (*array2_tbl_t)[2];
+
+#define AIC_PATCH_MAGIG_NUM   0x48435450 // "PTCH"
+#define AIC_PATCH_MAGIG_NUM_2 0x50544348 // "HCTP"
+#define AIC_PATCH_BLOCK_MAX   4
+
+struct aic_patch_t {
+	u32 magic_num;
+	u32 pair_start;
+	u32 magic_num_2;
+	u32 pair_count;
+	u32 block_dst[AIC_PATCH_BLOCK_MAX];
+	u32 block_src[AIC_PATCH_BLOCK_MAX];
+	u32 block_size[AIC_PATCH_BLOCK_MAX]; // word count
+};
+
+//#define AIC_PATCH_OFST(mem) ((size_t)&((struct aic_patch_t *)0)->(mem))
+#define AIC_PATCH_OFST(mem) offsetof(struct aic_patch_t, mem)
+#define AIC_PATCH_ADDR(mem) ((u32)(aic_patch_str_base + AIC_PATCH_OFST(mem)))
+
+u32 aicbsp_syscfg_tbl_8800d80[][2] = {};
+
+int aicbsp_system_config_8800d80(struct aic_sdio_dev *sdiodev)
+{
+	int syscfg_num = sizeof(aicbsp_syscfg_tbl_8800d80) / sizeof(u32) / 2;
+	int ret, cnt;
+
+	for (cnt = 0; cnt < syscfg_num; cnt++) {
+		ret = rwnx_send_dbg_mem_write_req(sdiodev,
+						  aicbsp_syscfg_tbl_8800d80[cnt][0],
+						  aicbsp_syscfg_tbl_8800d80[cnt][1]);
+		if (ret) {
+			pr_err("%x write fail: %d\n", aicbsp_syscfg_tbl_8800d80[cnt][0],
+			       ret);
+			return ret;
+		}
+	}
+	return 0;
+}
+
+u32 adaptivity_patch_tbl_8800d80[][2] = {
+	{0x000C, 0x0000320A}, // linkloss_thd
+	{0x009C, 0x00000000}, // ac_param_conf
+	{0x0168, 0x00010000}, // tx_adaptivity_en
+};
+
+#define USER_CHAN_MAX_TXPWR_EN_FLAG (0x01U << 1)
+#define USER_TX_USE_ANA_F_FLAG      (0x01U << 2)
+
+#define CFG_USER_CHAN_MAX_TXPWR_EN 1
+#define CFG_USER_TX_USE_ANA_F      0
+
+#define CFG_USER_EXT_FLAGS_EN                                                  \
+	(CFG_USER_CHAN_MAX_TXPWR_EN || CFG_USER_TX_USE_ANA_F)
+
+u32 patch_tbl_8800d80[][2] = {
+#ifdef AIC8800_USE_5G
+	{0x00b4, 0xf3010001},
+#else
+	{0x00b4, 0xf3010000},
+#endif
+#if defined(CONFIG_AMSDU_RX)
+	{0x170, 0x0100000a},
+#endif
+#ifdef CONFIG_IRQ_FALL
+	{0x00000170, 0x0000010a}, // irqf
+#endif
+
+#if CFG_USER_EXT_FLAGS_EN
+	{0x0188, 0x00000001
+#if CFG_USER_CHAN_MAX_TXPWR_EN
+				 | USER_CHAN_MAX_TXPWR_EN_FLAG
+#endif
+#if CFG_USER_TX_USE_ANA_F
+				 | USER_TX_USE_ANA_F_FLAG
+#endif
+	}, // user_ext_flags
+#endif
+};
+
+#ifdef CONFIG_OOB
+// for 8800d40/d80     map data1 isr to gpiob1
+u32 gpio_cfg_tbl_8800d40d80[][2] = {
+	{0x40504084, 0x00000006}, {0x40500040, 0x00000000},
+	{0x40100030, 0x00000001}, {0x40241020, 0x00000001},
+	{0x40240030, 0x00000004}, {0x40240020, 0x03020700},
+};
+#endif
+
+int aicwifi_sys_config_8800d80(struct aic_sdio_dev *sdiodev)
+{
+#ifdef CONFIG_OOB
+	int ret, cnt;
+	int gpiocfg_num = sizeof(gpio_cfg_tbl_8800d40d80) / sizeof(u32) / 2;
+
+	for (cnt = 0; cnt < gpiocfg_num; cnt++) {
+		ret = rwnx_send_dbg_mem_write_req(sdiodev,
+						  gpio_cfg_tbl_8800d40d80[cnt][0],
+						  gpio_cfg_tbl_8800d40d80[cnt][1]);
+		if (ret) {
+			pr_err("%x write fail: %d\n", gpio_cfg_tbl_8800d40d80[cnt][0], ret);
+			return ret;
+		}
+	}
+#endif
+
+	return 0;
+}
+
+#define NEW_PATCH_BUFFER_MAP 1
+
+int aicwifi_patch_config_8800d80(struct aic_sdio_dev *sdiodev)
+{
+	const u32 rd_patch_addr = RAM_FMAC_FW_ADDR + 0x0198;
+	u32 aic_patch_addr;
+	u32 config_base, aic_patch_str_base;
+#if (NEW_PATCH_BUFFER_MAP)
+	u32 patch_buff_addr, patch_buff_base, rd_version_addr, rd_version_val;
+#endif
+	u32 start_addr = 0x0016F800;
+	u32 patch_addr = start_addr;
+	u32 patch_cnt = sizeof(patch_tbl_8800d80) / sizeof(u32) / 2;
+	struct dbg_mem_read_cfm rd_patch_addr_cfm;
+	int ret = 0;
+	int cnt = 0;
+	// adap test
+	int adap_patch_cnt = 0;
+
+	if (aicbsp_info.adap_test) { //(adap_test) {
+		pr_info("%s for adaptivity test \r\n", __func__);
+		adap_patch_cnt = sizeof(adaptivity_patch_tbl_8800d80) / sizeof(u32) / 2;
+	}
+
+	aic_patch_addr = rd_patch_addr + 8;
+
+	ret =
+		rwnx_send_dbg_mem_read_req(sdiodev, rd_patch_addr, &rd_patch_addr_cfm);
+	if (ret) {
+		pr_info("patch rd fail\n");
+		return ret;
+	}
+
+	config_base = rd_patch_addr_cfm.memdata;
+
+	ret =
+		rwnx_send_dbg_mem_read_req(sdiodev, aic_patch_addr, &rd_patch_addr_cfm);
+	if (ret) {
+		pr_info("patch str rd fail\n");
+		return ret;
+	}
+	aic_patch_str_base = rd_patch_addr_cfm.memdata;
+
+#if (NEW_PATCH_BUFFER_MAP)
+	rd_version_addr = RAM_FMAC_FW_ADDR + 0x01C;
+	ret = rwnx_send_dbg_mem_read_req(sdiodev, rd_version_addr,
+					 &rd_patch_addr_cfm);
+	if (ret) {
+		pr_err("version val[0x%x] rd fail: %d\n", rd_version_addr, ret);
+		return ret;
+	}
+	rd_version_val = rd_patch_addr_cfm.memdata;
+	pr_info("AICWF rd_version_val=%08X\n", rd_version_val);
+	sdiodev->fw_version_uint = rd_version_val;
+	if (rd_version_val > 0x06090100) {
+		patch_buff_addr = rd_patch_addr + 12;
+		ret = rwnx_send_dbg_mem_read_req(sdiodev, patch_buff_addr,
+						 &rd_patch_addr_cfm);
+		if (ret) {
+			pr_err("patch buf rd fail\n");
+			return ret;
+		}
+		patch_buff_base = rd_patch_addr_cfm.memdata;
+		start_addr = patch_buff_base;
+		patch_addr = start_addr;
+	}
+#endif
+
+	ret = rwnx_send_dbg_mem_write_req(sdiodev, AIC_PATCH_ADDR(magic_num),
+					  AIC_PATCH_MAGIG_NUM);
+	if (ret) {
+		pr_err("0x%x write fail\n", AIC_PATCH_ADDR(magic_num));
+		return ret;
+	}
+
+	ret = rwnx_send_dbg_mem_write_req(sdiodev, AIC_PATCH_ADDR(magic_num_2),
+					  AIC_PATCH_MAGIG_NUM_2);
+	if (ret) {
+		pr_err("0x%x write fail\n", AIC_PATCH_ADDR(magic_num_2));
+		return ret;
+	}
+
+	ret = rwnx_send_dbg_mem_write_req(sdiodev, AIC_PATCH_ADDR(pair_start),
+					  patch_addr);
+	if (ret) {
+		pr_err("0x%x write fail\n", AIC_PATCH_ADDR(pair_start));
+		return ret;
+	}
+
+	ret = rwnx_send_dbg_mem_write_req(sdiodev, AIC_PATCH_ADDR(pair_count),
+					  patch_cnt + adap_patch_cnt);
+	if (ret) {
+		pr_err("0x%x write fail\n", AIC_PATCH_ADDR(pair_count));
+		return ret;
+	}
+
+	for (cnt = 0; cnt < patch_cnt; cnt++) {
+		ret = rwnx_send_dbg_mem_write_req(sdiodev, start_addr + 8 * cnt,
+						  patch_tbl_8800d80[cnt][0] +
+						  config_base);
+		if (ret) {
+			pr_err("%x write fail\n", start_addr + 8 * cnt);
+			return ret;
+		}
+		ret = rwnx_send_dbg_mem_write_req(sdiodev, start_addr + 8 * cnt + 4,
+						  patch_tbl_8800d80[cnt][1]);
+		if (ret) {
+			pr_err("%x write fail\n", start_addr + 8 * cnt + 4);
+			return ret;
+		}
+	}
+
+	if (aicbsp_info.adap_test) {
+		int tmp_cnt = patch_cnt + adap_patch_cnt;
+
+		for (cnt = patch_cnt; cnt < tmp_cnt; cnt++) {
+			int tbl_idx = cnt - patch_cnt;
+
+			ret = rwnx_send_dbg_mem_write_req(sdiodev, start_addr + 8 * cnt,
+							  adaptivity_patch_tbl_8800d80[tbl_idx][0]
+							  + config_base);
+			if (ret) {
+				pr_err("%x write fail\n", start_addr + 8 * cnt);
+				return ret;
+			}
+			ret = rwnx_send_dbg_mem_write_req(sdiodev, start_addr + 8 * cnt + 4,
+							  adaptivity_patch_tbl_8800d80[tbl_idx][1]);
+			if (ret) {
+				pr_err("%x write fail\n", start_addr + 8 * cnt + 4);
+				return ret;
+			}
+		}
+	}
+
+	ret =
+		rwnx_send_dbg_mem_write_req(sdiodev, AIC_PATCH_ADDR(block_size[0]), 0);
+	if (ret) {
+		pr_err("block_size[0x%x] write fail: %d\n",
+		       AIC_PATCH_ADDR(block_size[0]), ret);
+		return ret;
+	}
+	ret =
+		rwnx_send_dbg_mem_write_req(sdiodev, AIC_PATCH_ADDR(block_size[1]), 0);
+	if (ret) {
+		pr_err("block_size[0x%x] write fail: %d\n",
+		       AIC_PATCH_ADDR(block_size[1]), ret);
+		return ret;
+	}
+	ret =
+		rwnx_send_dbg_mem_write_req(sdiodev, AIC_PATCH_ADDR(block_size[2]), 0);
+	if (ret) {
+		pr_err("block_size[0x%x] write fail: %d\n",
+		       AIC_PATCH_ADDR(block_size[2]), ret);
+		return ret;
+	}
+	ret =
+		rwnx_send_dbg_mem_write_req(sdiodev, AIC_PATCH_ADDR(block_size[3]), 0);
+	if (ret) {
+		pr_err("block_size[0x%x] write fail: %d\n",
+		       AIC_PATCH_ADDR(block_size[3]), ret);
+		return ret;
+	}
+
+	return 0;
+}
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aic8800d80_compat.h b/drivers/net/wireless/aic/aic8800_bsp/aic8800d80_compat.h
new file mode 100644
index 0000000000000..a22e440b09a66
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aic8800d80_compat.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AIC8800D80_COMPAT_H_
+#define _AIC8800D80_COMPAT_H_
+
+#include "aicsdio.h"
+
+int aicbsp_system_config_8800d80(struct aic_sdio_dev *sdiodev);
+int aicwifi_sys_config_8800d80(struct aic_sdio_dev *sdiodev);
+int aicwifi_patch_config_8800d80(struct aic_sdio_dev *sdiodev);
+
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aic8800dc_compat.c b/drivers/net/wireless/aic/aic8800_bsp/aic8800dc_compat.c
new file mode 100644
index 0000000000000..cb7e876c0349f
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aic8800dc_compat.c
@@ -0,0 +1,1353 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief 8800dc chip config
+ *
+ ******************************************************************************
+ */
+
+#include "aic8800dc_compat.h"
+#include "aic_bsp_driver.h"
+#include <linux/version.h>
+
+u8 chip_sub_id;
+u8 chip_mcu_id;
+
+u32 syscfg_tbl_8800dc[][2] = {
+	{0x40500010, 0x00000004}, {0x40500010, 0x00000006}, // 160m clk
+};
+
+u32 syscfg_tbl_8800dc_sdio_u01[][2] = {
+	{0x40030000, 0x00036724}, // loop forever after assert_err
+	{0x0011E800, 0xE7FE4070}, {0x40030084, 0x0011E800},
+	{0x40030080, 0x00000001}, {0x4010001C, 0x00000000},
+};
+
+u32 syscfg_tbl_8800dc_sdio_u02[][2] = {
+	{0x40030000, 0x00036DA4}, // loop forever after assert_err
+	{0x0011E800, 0xE7FE4070}, {0x40030084, 0x0011E800},
+	{0x40030080, 0x00000001}, {0x4010001C, 0x00000000},
+};
+
+#ifdef CONFIG_OOB
+u32 oobcfg_tbl_8800dc_sdio_u02[][2] = {
+	{0x40504044, 0x2}, // oob_enable
+	{0x40500060, 0x03020700}, {0x40500040, 0},        {0x40100030, 1},
+	{0x40241020, 1},          {0x402400f0, 0x340022},
+};
+#endif // CONFIG_OOB
+
+u32 syscfg_tbl_masked_8800dc[][3] = {
+// #ifdef CONFIG_PMIC_SETTING
+#if defined(CONFIG_VRF_DCDC_MODE)
+	{0x7000216C, (0x3 << 2), (0x1 << 2)}, // pmic_pmu_init
+	{0x700021BC, (0x3 << 2), (0x1 << 2)},
+	{0x70002118, ((0x7 << 4) | (0x1 << 7)), ((0x2 << 4) | (0x1 << 7))},
+	{0x70002104, ((0x3F << 0) | (0x1 << 6)), ((0x2 << 0) | (0x1 << 6))},
+	{0x7000210C, ((0x3F << 0) | (0x1 << 6)), ((0x2 << 0) | (0x1 << 6))},
+	{0x70002170, (0xF << 0), (0x1 << 0)},
+	{0x70002190, (0x3F << 0), (24 << 0)},
+	{0x700021CC, ((0x7 << 4) | (0x1 << 7)), ((0x0 << 4) | (0x0 << 7))},
+	{0x700010A0, (0x1 << 11), (0x1 << 11)},
+	{0x70001034, ((0x1 << 20) | (0x7 << 26)), ((0x0 << 20) | (0x2 << 26))},
+	{0x70001038, (0x1 << 8), (0x1 << 8)},
+	{0x70001094, (0x3 << 2), (0x0 << 2)},
+	{0x700021D0, ((0x1 << 5) | (0x1 << 6)), ((0x1 << 5) | (0x1 << 6))},
+	{0x70001000, ((0x1 << 0) | (0x1 << 20) | (0x1 << 22)),
+	 ((0x1 << 0) | (0x1 << 20) | (0x0 << 22))},
+	{0x70001028, (0xf << 2), (0x1 << 2)},
+#else
+	{0x7000216C, (0x3 << 2), (0x1 << 2)}, // pmic_pmu_init
+	{0x700021BC, (0x3 << 2), (0x1 << 2)},
+	{0x70002118, ((0x7 << 4) | (0x1 << 7)), ((0x2 << 4) | (0x1 << 7))},
+	{0x70002104, ((0x3F << 0) | (0x1 << 6)), ((0x2 << 0) | (0x1 << 6))},
+	{0x7000210C, ((0x3F << 0) | (0x1 << 6)), ((0x2 << 0) | (0x1 << 6))},
+	{0x70002170, (0xF << 0), (0x1 << 0)},
+	{0x70002190, (0x3F << 0), (24 << 0)},
+	{0x700021CC, ((0x7 << 4) | (0x1 << 7)), ((0x0 << 4) | (0x0 << 7))},
+	{0x700010A0, (0x1 << 11), (0x1 << 11)},
+	{0x70001034, ((0x1 << 20) | (0x7 << 26)), ((0x0 << 20) | (0x2 << 26))},
+	{0x70001038, (0x1 << 8), (0x1 << 8)},
+	{0x70001094, (0x3 << 2), (0x0 << 2)},
+	{0x700021D0, ((0x1 << 5) | (0x1 << 6)), ((0x1 << 5) | (0x1 << 6))},
+	{0x70001000, ((0x1 << 0) | (0x1 << 20) | (0x1 << 22)),
+	 ((0x0 << 0) | (0x1 << 20) | (0x0 << 22))},
+	{0x70001028, (0xf << 2), (0x1 << 2)},
+#endif
+	// #endif /* CONFIG_PMIC_SETTING */
+	{0x00000000, 0x00000000, 0x00000000}, // last one
+};
+
+u32 syscfg_tbl_masked_8800dc_h[][3] = {
+	{0x7000216C, ((0x3 << 2) | (0x3 << 4)),
+	 ((0x2 << 2) | (0x2 << 4))}, // pmic_pmu_init
+	{0x70002138, (0xFF << 0), (0xFF << 0)},
+	{0x7000213C, (0xFF << 0), (0xFF << 0)},
+	{0x70002144, (0xFF << 0), (0xFF << 0)},
+	{0x700021BC, (0x3 << 2), (0x1 << 2)},
+	{0x70002118, ((0x7 << 4) | (0x1 << 7)), ((0x2 << 4) | (0x1 << 7))},
+	{0x70002104, ((0x3F << 0) | (0x1 << 6)), ((0x2 << 0) | (0x1 << 6))},
+	{0x7000210C, ((0x3F << 0) | (0x1 << 6)), ((0x2 << 0) | (0x1 << 6))},
+	{0x70002170, (0xF << 0), (0x1 << 0)},
+	{0x70002190, (0x3F << 0), (24 << 0)},
+	{0x700021CC, ((0x7 << 4) | (0x1 << 7)), ((0x0 << 4) | (0x0 << 7))},
+	{0x700010A0, (0x1 << 11), (0x1 << 11)},
+	//{0x70001034, ((0x1 << 20) | (0x7 << 26)), ((0x0 << 20) | (0x2 << 26))},
+	{0x70001038, (0x1 << 8), (0x1 << 8)},
+	{0x70001094, (0x3 << 2), (0x0 << 2)},
+	{0x700021D0, ((0x1 << 5) | (0x1 << 6)), ((0x1 << 5) | (0x1 << 6))},
+#if defined(CONFIG_VRF_DCDC_MODE)
+	{0x70001000, ((0x1 << 0) | (0x1 << 20) | (0x1 << 22)),
+	 ((0x1 << 0) | (0x1 << 20) | (0x0 << 22))},
+#else
+	{0x70001000, ((0x1 << 0) | (0x1 << 20) | (0x1 << 22)),
+	 ((0x0 << 0) | (0x1 << 20) | (0x0 << 22))},
+#endif
+	{0x70001028, (0xf << 2), (0x1 << 2)},
+
+	{0x00000000, 0x00000000, 0x00000000}, // last one
+};
+
+u32 syscfg_tbl_masked_8800dc_u01[][3] = {
+	// #ifdef CONFIG_PMIC_SETTING
+	{0x70001000, (0x1 << 16), (0x1 << 16)}, // for low temperature
+	{0x70001028, (0x1 << 6), (0x1 << 6)},
+	{0x70001000, (0x1 << 16), (0x0 << 16)},
+	// #endif /* CONFIG_PMIC_SETTING */
+};
+
+u32 patch_tbl_wifisetting_8800dc_u01[][2] = {{0x010c, 0x01001E01}};
+
+u32 patch_tbl_wifisetting_8800dc_u02[][2] = {
+#if defined(CONFIG_SDIO_PWRCTRL)
+	{0x0124, 0x01011E01}
+#else
+	{0x0124, 0x01001E01}
+#endif
+};
+
+u32 ldpc_cfg_ram[] = {
+	0x00767679, 0x1DF8F870, 0x1DF8F870, 0x1DF8F870, 0x1DF8F870, 0x006E6E72,
+	0x1DF8F869, 0x1DF8F869, 0x1DF8F869, 0x1DF8F869, 0x0076767B, 0x1DF8F870,
+	0x1DF8F870, 0x1DF8F870, 0x1DF8F870, 0x007E7E85, 0x1DF4F876, 0x1DF4F876,
+	0x1DF4F876, 0x1DF8F876, 0x0081818A, 0x1DF8F87B, 0x1DF8F87B, 0x1DF8F87B,
+	0x1DF8F87B, 0x0081818D, 0x1DF8F87B, 0x1DF8F87B, 0x1DF8F87B, 0x1DF8F87B,
+	0x0081818A, 0x1DF8F87B, 0x1DF8F87C, 0x1DF8F87B, 0x1DF8F87B, 0x007E7E40,
+	0x1DF8F87B, 0x1DF8F87B, 0x1DF8F87B, 0x1DF8F87B, 0x008B8B92, 0x1DF8F887,
+	0x1DF8F889, 0x1DF8F887, 0x1DF8F887, 0x00515155, 0x1DF8F84C, 0x1DF8F84C,
+	0x1DF8F889, 0x1DF8F889, 0x00515154, 0x1DF8F84C, 0x1DF8F84C, 0x1DF8F888,
+	0x1DF8F888, 0x004F4F53, 0x1DF8F84A, 0x1DF8F84A, 0x1DF8F84A, 0x1DF8F84A,
+	0x004F4F53, 0x1DF8F84A, 0x1DF8F84A, 0x1DF8F84A, 0x1DF8F84A, 0x004F4F53,
+	0x1DF8F84A, 0x1DF8F84A, 0x1DF8F84A, 0x1DF8F84A, 0x004E4E53, 0x1DF8F849,
+	0x1DF8F848, 0x1DF8F848, 0x1DF8F848, 0x004D4D52, 0x1DF8F847, 0x1DF8F847,
+	0x1DF8F847, 0x1DF8F847, 0x004F4F55, 0x1DF8F84B, 0x1DF8F84A, 0x1DF8F84A,
+	0x1DF8F84A, 0x004E4E53, 0x1DF8F849, 0x1DF8F848, 0x1DF8F848, 0x1DF8F848,
+	0x0049494D, 0x1DF8F844, 0x1DF8F844, 0x1DF8F844, 0x1DF8F844, 0x0051518F,
+	0x1DF8F849, 0x1DF8F848, 0x1DF8F848, 0x1DF8F848, 0x00424277, 0x1DF8F83F,
+	0x1DF8F83C, 0x1DF8F83C, 0x1DF8F83C, 0x00424275, 0x1DF8F89E, 0x1DF8F83C,
+	0x1DF8F83C, 0x1DF8F83C, 0x0055555C, 0x1DF8F84C, 0x1DF8F84C, 0x1DF8F84C,
+	0x1DF8F84C, 0x0053535C, 0x1DF8F84C, 0x1DF8F84B, 0x1DF8F84B, 0x1DF8F84B,
+	0x00F8F89E, 0x1DF8F88C, 0x1DF8F84A, 0x1DF8F84A, 0x1DF8F84A, 0x00898940,
+	0x18F8F846, 0x18CFF845, 0x18CFF844, 0x18CFF844, 0x0056565F, 0x1DF8F84F,
+	0x1DF8F84F, 0x1DF8F84F, 0x1DF8F84F, 0x0055555E, 0x1DF8F84E, 0x1DF8F84E,
+	0x1DF8F84E, 0x1DF8F84E, 0x0056565F, 0x1DF8F84F, 0x1DF8F84F, 0x1DF8F84F,
+	0x1DF8F84F, 0x00555561, 0x1DF8F850, 0x1DF8F84E, 0x1DF8F84E, 0x1DF8F84E,
+	0x0053535F, 0x1DF8F84D, 0x1DF8F84C, 0x1DF8F84C, 0x1DF8F84C, 0x0055555F,
+	0x1DF8F84F, 0x1DF8F84E, 0x1DF8F84E, 0x1DF8F84E, 0x005555AA, 0x1DF8F854,
+	0x1DF8F84E, 0x1DF8F84E, 0x1DF8F84E, 0x005959A6, 0x1DF8F84D, 0x1DF8F84C,
+	0x1DF8F84C, 0x1DF8F84C, 0x004F4F9B, 0x1DF8F84E, 0x1DF8F846, 0x1DF8F846,
+	0x1DF8F846, 0x00F8F8A5, 0x1DF8F894, 0x1DF8F84C, 0x1DF8F84C, 0x1DF8F84C,
+	0x009898A4, 0x1DF8F84D, 0x1DF8F84C, 0x1DF8F84C, 0x1DF8F84C, 0x00464686,
+	0x1DF8F8B3, 0x1DF8F83D, 0x1DF8F83D, 0x1DF8F83D, 0x008E8E40, 0x1AF8F848,
+	0x1ADFF848, 0x1ADFF846, 0x1ADFF846, 0x007F7F40, 0x18D2D275, 0x18D2D23A,
+	0x18D2D23A, 0x18D2D239, 0x00454540, 0x0F868664, 0x0F86863E, 0x0F86863D,
+	0x0F86863D, 0x005C5C64, 0x1DF8F856, 0x1DF8F855, 0x1DF8F855, 0x1DF8F855,
+	0x005B5B68, 0x1DF8F858, 0x1DF8F855, 0x1DF8F855, 0x1DF8F855, 0x005A5A64,
+	0x1DF8F855, 0x1DF8F854, 0x1DF8F854, 0x1DF8F854, 0x005A5AB5, 0x1DF8F85B,
+	0x1DF8F855, 0x1DF8F854, 0x1DF8F854, 0x00F8F8B0, 0x1DF8F8A3, 0x1DF8F852,
+	0x1DF8F852, 0x1DF8F852, 0x00A4A4AE, 0x1DF8F854, 0x1DF8F852, 0x1DF8F852,
+	0x1DF8F852, 0x009A9A40, 0x1DF8F84E, 0x1DF8F84D, 0x1DF8F84C, 0x1DF8F84C,
+	0x009C9C40, 0x1DF8F895, 0x1DF8F849, 0x1DF8F84A, 0x1DF8F84A, 0x00494940,
+	0x1197976F, 0x11979742, 0x11979741, 0x11979741, 0x006E6E74, 0x1DF8F869,
+	0x1DF8F869, 0x1DF8F869, 0x1DF8F869, 0x006E6E40, 0x1ADEF869, 0x1ADEF869,
+	0x1ADEF869, 0x1ADEF869, 0x00757540, 0x0D78F86E, 0x0D78F86E, 0x0D78F86E,
+	0x0D79F86E, 0x00787885, 0x1DF8F873, 0x1DF8F873, 0x1DF8F873, 0x1DF8F873,
+	0x00787840, 0x1DF8F873, 0x1DF8F873, 0x1DF8F873, 0x1DF8F873, 0x00787840,
+	0x0E81F873, 0x0E81F873, 0x0E81F873, 0x0E82F873, 0x00404040, 0x0E82F873,
+	0x0E82F873, 0x0E82F873, 0x0E82F873, 0x00818140, 0x1092F87E, 0x1092F87E,
+	0x1092F87E, 0x1092F87E, 0x00404040, 0x1092F87E, 0x1092F87E, 0x1092F87E,
+	0x1092F87E, 0x00737340, 0x14B2B26B, 0x14B2B235, 0x14B2B235, 0x14B2B235,
+	0x00404040, 0x0E828260, 0x0E82823D, 0x0E82823C, 0x0E82823C, 0x00404040,
+	0x0F8B8B66, 0x0F8B8B3F, 0x0F8B8B3D, 0x0F8B8B3D, 0x00404040, 0x0B68683D,
+	0x0B68681E, 0x0B68681E, 0x0B68681E, 0x00222240, 0x06434318, 0x06434329,
+	0x06434318, 0x06434318, 0x00404040, 0x129D9D72, 0x129D9D43, 0x129D9D41,
+	0x129D9D41, 0x00404040, 0x0D757542, 0x0D757520, 0x0D757520, 0x0D757520,
+	0x00232340, 0x084C4C19, 0x084C4C2C, 0x084C4C19, 0x084C4C19
+};
+
+u32 agc_cfg_ram[] = {
+	0x20000000, 0x0400000E, 0x3000200E, 0x5B000000, 0x0400004B, 0x3000008E,
+	0x32000000, 0x0400007B, 0x40000000, 0xF8000026, 0x04000011, 0x4819008E,
+	0x9C000020, 0x08000191, 0x38008000, 0x0A000000, 0x08104411, 0x38018000,
+	0x0C004641, 0x08D00014, 0x30000000, 0x01000000, 0x04000017, 0x30000000,
+	0x3C000000, 0x0400001A, 0x38020000, 0x40000001, 0x0800001D, 0x3808008E,
+	0x14000050, 0x08000020, 0x4000008E, 0xA400007B, 0x00000101, 0x3000339F,
+	0x41000700, 0x04104420, 0x90000000, 0x49000000, 0xF00E842F, 0xEC0E842C,
+	0xEC0E842C, 0x04000032, 0x30000000, 0x48000101, 0x04000032, 0x30000000,
+	0x48000202, 0x04000032, 0x30000000, 0x46000000, 0x04000011, 0x58010006,
+	0x3D040472, 0xDC204439, 0x081DD4D2, 0x480A0006, 0xDC2044DC, 0x081DD43C,
+	0x38050004, 0x0EF1F1C3, 0x342044DC, 0x30000000, 0x01000000, 0x04000042,
+	0x30000000, 0x33000000, 0x04104445, 0x38008000, 0x2200109C, 0x08104448,
+	0x38008000, 0x23D4509C, 0x08104417, 0x9000A000, 0x32000000, 0x18000063,
+	0x14000060, 0x1C000051, 0x10000057, 0x38028000, 0x0C000001, 0x08D04466,
+	0x3000200F, 0x00000000, 0x00000000, 0x38030000, 0x0C002601, 0x08D0445A,
+	0x30000000, 0x3D020230, 0x0400005D, 0x30000000, 0x3E000100, 0x04000066,
+	0x38028000, 0x0C001601, 0x34204466, 0x38028000, 0x0C000A01, 0x34204466,
+	0x38008004, 0xFF000000, 0x0800007B, 0x3800802F, 0x26000000, 0x0800006C,
+	0x380404AF, 0x1F191010, 0x0800006F, 0x20000CAF, 0x04000071, 0x60000CAF,
+	0x18700079, 0x14000077, 0x10000075, 0x28140CAF, 0x09B00084, 0x280A0CAF,
+	0x09B00084, 0x28060CAF, 0x09B00084, 0x28048086, 0x0800007D, 0x38000086,
+	0x22800000, 0x04000080, 0x30000000, 0x0EF1F101, 0x36004883, 0x28020000,
+	0x08000085, 0x3802008E, 0x3D040431, 0x08000088, 0x3805008E, 0x1F241821,
+	0x0800008B, 0x3000008E, 0xA0163021, 0x0400008E, 0x3000008E, 0x0EF10012,
+	0x34000091, 0x300000CC, 0x50000000, 0x04000094, 0x380095FE, 0x32010000,
+	0x04000097, 0x50001FFE, 0x5A010000, 0x6DC9989B, 0xFC19D4B9, 0x30000186,
+	0x3D840373, 0x0400009E, 0x3000008E, 0x0A000000, 0x040000A1, 0x3000008E,
+	0x22C00000, 0x040000A4, 0x9000028E, 0x32010001, 0x8E4000AA, 0xC80000B0,
+	0x00000000, 0x00000000, 0x3000008E, 0x32010001, 0x040000CB, 0x3000008E,
+	0x29000000, 0x94045011, 0x300019B6, 0x32010000, 0x040000B3, 0x300019B6,
+	0x3D040431, 0x040000B6, 0x300019B6, 0x22800000, 0x04000097, 0x30000186,
+	0x3D840473, 0x040000BC, 0x3000008E, 0x29030000, 0x040000BF, 0x9AEE028E,
+	0x32010100, 0x7C0000C5, 0xCC0000B0, 0x080000B0, 0x00000000, 0x3000008E,
+	0x32010100, 0x040000C8, 0x3000028E, 0x29000000, 0x94045011, 0x5000038E,
+	0x29000000, 0x94045011, 0xC0000035, 0x38010006, 0x3D040472, 0x080000D2,
+	0x30000004, 0x0EF1F141, 0x340000D5, 0x28040004, 0x080000D7, 0x2808000E,
+	0x080000D9, 0x3000018E, 0x0EF10052, 0x340000DC, 0x3000038E, 0x29000000,
+	0x94045011, 0x38020000, 0x32000000, 0x080000E2, 0x60000000, 0xD80000E6,
+	0xD40000E9, 0x040000EC, 0x30000000, 0x0EF1F121, 0x360048EF, 0x30000000,
+	0x0C002421, 0x360048EF, 0x30000000, 0x0C000021, 0x360048EF, 0x28020000,
+	0x0800007B, 0x50001EFE, 0x5A010000, 0x6DC998F5, 0xFC19D4F8, 0x3000028E,
+	0x32000040, 0x040000FB, 0x3AEE028E, 0x32000080, 0x040000FB, 0x30000000,
+	0x0EF1F101, 0x360048FE, 0x28020000, 0x08000100, 0x3802008E, 0x3D040431,
+	0x08000103, 0x3805008E, 0x1F241821, 0x08000106, 0x3000008E, 0xA0163021,
+	0x04000109, 0x3000008E, 0x0EF10012, 0x3400010C, 0x300014F6, 0x32010000,
+	0x04000114, 0x20000000, 0x04000111, 0x300000EC, 0x50000000, 0x040000F1,
+	0x300014F6, 0x32030000, 0x04000117, 0x30001086, 0x3D840473, 0x0400011A,
+	0x5000108E, 0x22C00000, 0x8E47C0CB, 0xCB30011E, 0x300019B6, 0x32040000,
+	0x04000121, 0x300019B6, 0x3D040431, 0x04000124, 0x300019B6, 0x22800000,
+	0x04000111, 0x00000000, 0x00000000, 0x00000000, 0x30000186, 0x3D840473,
+	0x0400012D, 0x5000038E, 0x29000000, 0x94045011, 0xC0000131, 0x380C800E,
+	0xFF000000, 0x08000134, 0x30000004, 0x0FF1F103, 0x34000137, 0x28020000,
+	0x08000139, 0x3000038E, 0x29000000, 0x94045011, 0x00000000, 0x00000000,
+	0x00000000, 0x58010006, 0x3D040472, 0xDC204543, 0x081DD4D2, 0x480A0006,
+	0xDC2044DC, 0x081DD546, 0x38050004, 0x0EF1F141, 0x342044DC, 0x2802800E,
+	0x080000DC, 0x48000035, 0x0400014A, 0x7896638F, 0x4100000F, 0x8C00014F,
+	0x080450C4, 0x90104574, 0x88C8620F, 0xC000015A, 0x90104574, 0x08104554,
+	0x94104557, 0x3000628F, 0x29000000, 0x9404517A, 0x3000638F, 0x29000000,
+	0x0410457A, 0x3800E005, 0x3D010131, 0x0810455D, 0xA832600F, 0x90104574,
+	0x08000154, 0x94104557, 0xC6104567, 0xC4185563, 0x5802E00F, 0x0FEEEA07,
+	0x80000174, 0x3420456B, 0x5802E00F, 0x0EEEEA07, 0x80000174, 0x3420456B,
+	0x30004000, 0x33000001, 0x0400016E, 0x38034005, 0x3D030373, 0x08000171,
+	0x30006007, 0x33000000, 0x04000174, 0x3000608F, 0x29000000, 0x94045177,
+	0x4000608F, 0xA010457D, 0x0410457A, 0x3000608F, 0x64000101, 0x04104411,
+	0x3000608F, 0x64000101, 0x04104580, 0x3000618F, 0x42000001, 0x04000183,
+	0x38028000, 0x32000000, 0x08104586, 0x280A618F, 0x08000188, 0x480A618F,
+	0xBC00018B, 0x0800018E, 0x3000618F, 0x34000001, 0x04000005, 0x3000618F,
+	0x34000000, 0x04000008, 0x3000008F, 0x0EEAED0F, 0x36000194, 0x38038000,
+	0x34000000, 0x08000197, 0x38028005, 0x29010002, 0x0800019A, 0x3000028F,
+	0x2200209C, 0x0400019D, 0x3000028F, 0x23D4509C, 0x040001A0, 0x2814028F,
+	0x080001A2, 0x3000028F, 0x43010201, 0x040001A5, 0x3000128F, 0x32000100,
+	0x040001A8, 0x5AEE138F, 0x4100000F, 0x7C0001AC, 0x080000F9, 0x592C138F,
+	0x29000000, 0x8C0001B0, 0x080000F9, 0x2000138F, 0x94045011, 0x00000000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+	0x00000000, 0x00000000};
+
+u32 txgain_map[96] = {
+#ifdef CONFIG_FPGA_VERIFICATION
+	0x20c0c971, 0x20c0c980, 0x20c0c992, 0x20c0c9a6, 0x20c0c9bf, 0x20c0caa5,
+	0x20c0cabd, 0x20c0cba0, 0x20c0cbb6, 0x20c0cbea, 0x20c0ccc5, 0x20c0cdac,
+	0x20c0cdd0, 0x20c0ceb2, 0x20c0ceff, 0x20c0cfff, 0x20c0c922, 0x20c0c922,
+	0x20c0c922, 0x20c0c922, 0x20c0c922, 0x20c0c922, 0x20c0c922, 0x20c0c927,
+	0x20c0c92c, 0x20c0c931, 0x20c0c937, 0x20c0c93f, 0x20c0c946, 0x20c0c94f,
+	0x20c0c959, 0x20c0c964, 0x20c0cbee, 0x20c0cce0, 0x20c0ccff, 0x20c0cde2,
+	0x20c0cdfe, 0x20c0cede, 0x20c0cefc, 0x20c0cfd9, 0x20c0cff8, 0x20c0cfff,
+	0x20c0cfff, 0x20c0cfff, 0x20c0cfff, 0x20c0cfff, 0x20c0cfff, 0x20c0cfff,
+	0x20c0c97c, 0x20c0c97c, 0x20c0c97c, 0x20c0c97c, 0x20c0c97c, 0x20c0c97c,
+	0x20c0c97c, 0x20c0c98c, 0x20c0ca79, 0x20c0ca89, 0x20c0cb74, 0x20c0cb84,
+	0x20c0cb94, 0x20c0cba8, 0x20c0cbbb, 0x20c0cbd2, 0x20c0cbee, 0x20c0cce0,
+	0x20c0ccff, 0x20c0cde2, 0x20c0cdfe, 0x20c0cede, 0x20c0cefc, 0x20c0cfd9,
+	0x20c0cff8, 0x20c0cfff, 0x20c0cfff, 0x20c0cfff, 0x20c0cfff, 0x20c0cfff,
+	0x20c0cfff, 0x20c0cfff, 0x20c0c97c, 0x20c0c97c, 0x20c0c97c, 0x20c0c97c,
+	0x20c0c97c, 0x20c0c97c, 0x20c0c97c, 0x20c0c98c, 0x20c0ca79, 0x20c0ca89,
+	0x20c0cb74, 0x20c0cb84, 0x20c0cb94, 0x20c0cba8, 0x20c0cbbb, 0x20c0cbd2,
+#else
+	// 11b
+	0x00ffd780,
+	0x00ffd872,
+	0x00ffd880,
+	0x00ffd972,
+	0x00ffd980,
+	0x00ffda75,
+	0x00ffda86,
+	0x00ffdb77,
+	0x00ffdb86,
+	0x00ffdc78,
+	0x00ffdc89,
+	0x00ffdd79,
+	0x00ffdd89,
+	0x00ffde83,
+	0x00ffdf79,
+	0x00ffdf8b,
+	0x00ffd072,
+	0x00ffd072,
+	0x00ffd080,
+	0x00ffd172,
+	0x00ffd180,
+	0x00ffd272,
+	0x00ffd280,
+	0x00ffd36d,
+	0x00ffd379,
+	0x00ffd46d,
+	0x00ffd479,
+	0x00ffd572,
+	0x00ffd580,
+	0x00ffd672,
+	0x00ffd680,
+	0x00ffd772,
+	// high
+	0x00ffc87d,
+	0x00ffc88b,
+	0x00ffc979,
+	0x00ffc989,
+	0x00ffcc4b,
+	0x00ffcc54,
+	0x00ffcc5e,
+	0x00ffcc69,
+	0x00ffcc78,
+	0x00ffcc85,
+	0x00ffcd70,
+	0x00ffcd80,
+	0x00ffce70,
+	0x00ffce80,
+	0x00ffcf7d,
+	0x00ffcf90,
+	0x00ffc080,
+	0x00ffc090,
+	0x00ffc180,
+	0x00ffc190,
+	0x00ffc27b,
+	0x00ffc28b,
+	0x00ffc37b,
+	0x00ffc390,
+	0x00ffc485,
+	0x00ffc495,
+	0x00ffc579,
+	0x00ffc589,
+	0x00ffc679,
+	0x00ffc689,
+	0x00ffc780,
+	0x00ffc790,
+	// low
+	0x00ffc87d,
+	0x00ffc88b,
+	0x00ffc979,
+	0x00ffc989,
+	0x00ffcc4b,
+	0x00ffcc54,
+	0x00ffcc5e,
+	0x00ffcc69,
+	0x00ffcc78,
+	0x00ffcc85,
+	0x00ffcd70,
+	0x00ffcd80,
+	0x00ffcd90,
+	0x00ffce80,
+	0x00ffce93,
+	0x00ffcf90,
+	0x00ffc080,
+	0x00ffc090,
+	0x00ffc180,
+	0x00ffc190,
+	0x00ffc27b,
+	0x00ffc28b,
+	0x00ffc37b,
+	0x00ffc390,
+	0x00ffc485,
+	0x00ffc495,
+	0x00ffc579,
+	0x00ffc589,
+	0x00ffc679,
+	0x00ffc689,
+	0x00ffc780,
+	0x00ffc790,
+#endif
+};
+
+const u32 txgain_map_h[96] = {
+	// 11b
+	0xffd888, // 11
+	0xffd979, // 12
+	0xffd988, // 13
+	0xffda79, // 14
+	0xffda88, // 15
+	0xffdb79, // 16
+	0xffdb88, // 17
+	0xffdc72, // 18
+	0xffdc80, // 19
+	0xffdd80, // 20
+	0xffde66, // 21
+	0xffde72, // 22
+	0xffde80, // 23
+	0xffdf79, // 24
+	0xffdf88, // 25
+	0xffdf98, // 26
+	0xffd079, //-5
+	0xffd088, //-4
+	0xffd179, //-3
+	0xffd188, //-2
+	0xffd288, //-1
+	0xffd36c, // 0
+	0xffd379, // 1
+	0xffd388, // 2
+	0xffd479, // 3
+	0xffd488, // 4
+	0xffd579, // 5
+	0xffd588, // 6
+	0xffd679, // 7
+	0xffd688, // 8
+	0xffd779, // 9
+	0xffd879, // 10
+	// high
+	0xffc879, // 8
+	0xffc96b, // 9
+	0xffc979, // 10
+	0xffcc45, // 11
+	0xffcc4d, // 12
+	0xffcc56, // 13
+	0xffcc60, // 14
+	0xffcc6b, // 15
+	0xffcc79, // 16
+	0xffcd72, // 17
+	0xffce60, // 18
+	0xffce72, // 19
+	0xffcf72, // 20
+	0xffcf80, // 21
+	0xffcf90, // 22
+	0xffcf90, // 23
+	0xffc079, //-8
+	0xffc16b, //-7
+	0xffc179, //-6
+	0xffc26b, //-5
+	0xffc279, //-4
+	0xffc36b, //-3
+	0xffc379, //-2
+	0xffc46b, //-1
+	0xffc479, // 0
+	0xffc56b, // 1
+	0xffc579, // 2
+	0xffc66b, // 3
+	0xffc679, // 4
+	0xffc76b, // 5
+	0xffc779, // 6
+	0xffc86b, // 7
+	// low
+	0xffc879, // 8
+	0xffc96b, // 9
+	0xffc979, // 10
+	0xffcc45, // 11
+	0xffcc4d, // 12
+	0xffcc56, // 13
+	0xffcc60, // 14
+	0xffcc6b, // 15
+	0xffcc79, // 16
+	0xffcd72, // 17
+	0xffce60, // 18
+	0xffce72, // 19
+	0xffcf72, // 20
+	0xffcf80, // 21
+	0xffcf90, // 22
+	0xffcf90, // 23
+	0xffc079, //-8
+	0xffc16b, //-7
+	0xffc179, //-6
+	0xffc26b, //-5
+	0xffc279, //-4
+	0xffc36b, //-3
+	0xffc379, //-2
+	0xffc46b, //-1
+	0xffc479, // 0
+	0xffc56b, // 1
+	0xffc579, // 2
+	0xffc66b, // 3
+	0xffc679, // 4
+	0xffc76b, // 5
+	0xffc779, // 6
+	0xffc86b, // 7
+};
+
+u32 jump_tbl[][2] = {
+#ifndef CONFIG_FOR_IPCOM
+	{296, 0x180001}, {137, 0x180011}, {303, 0x1810f9},
+	{168, 0x18186d}, {308, 0x181bbd}, {288, 0x1820c1},
+#else
+	{308, 0x181001}, {288, 0x181031}, {296, 0x18120d}, {137, 0x18121d},
+	{303, 0x182305}, {168, 0x182a79}, {258, 0x182ae1},
+#endif
+};
+
+u32 jump_tbl_u02[][2] = {
+	{303, 0x00180d25}, {168, 0x001814a5}, {265, 0x001816b1}, {266, 0x00181849},
+	{256, 0x001818ad}, {288, 0x00181bf9}, {333, 0x00182d0d}, {26, 0x00182d45}};
+
+u32 patch_tbl_func[][2] = {
+#ifndef CONFIG_FOR_IPCOM
+	{0x00110054, 0x0018186D}, // same as jump_tbl idx 168
+	{0x0011005C, 0x0018186D}, // same as jump_tbl idx 168
+#else
+	{0x00110054, 0x00182A79}, // same as jump_tbl idx 168
+	{0x0011005C, 0x00182A79}, // same as jump_tbl idx 168
+	{0x001118D4, 0x00000011},
+#endif
+};
+
+u32 patch_tbl_func_u02[][2] = {
+	{0x00110054, 0x001814a5}, // same as jump_tbl idx 168
+	{0x0011005C, 0x001814a5}, // same as jump_tbl idx 168
+	{0x001109c0, 0x00181e3d}, {0x00110bb4, 0x001824e1},
+	{0x00110f08, 0x00182d25},
+};
+
+u32 patch_tbl_rf_func[][2] = {
+	{0x00110bf0, 0x00180001},
+};
+
+// adap test
+u32 adaptivity_patch_tbl_8800dc[][2] = {
+	{0x000C, 0x0000320A}, // linkloss_thd
+	{0x009C, 0x00000000}, // ac_param_conf
+	{0x0128, 0xF6140001}, // tx_adaptivity_en
+};
+
+static u8 chip_id;
+#define CHIP_ID_H_MASK 0xC0
+#define IS_CHIP_ID_H() ((chip_id & CHIP_ID_H_MASK) == CHIP_ID_H_MASK)
+
+// Crystal provided by CPU (start)
+int set_bbpll_config(struct aic_sdio_dev *rwnx_hw)
+{
+	int ret = 0;
+	struct dbg_mem_read_cfm rd_mem_addr_cfm;
+
+	// Read crystal provided by CPU or not.
+	ret = rwnx_send_dbg_mem_read_req(rwnx_hw, 0x40500148, &rd_mem_addr_cfm);
+	if (ret) {
+		AICWFDBG(LOGERROR, "%x rd fail: %d\n", 0x40500148, ret);
+		return -1;
+	}
+
+	AICWFDBG(LOGDEBUG, "%s rd_mem_addr_cfm.memdata:%x \r\n", __func__,
+		 rd_mem_addr_cfm.memdata);
+
+	if (!(rd_mem_addr_cfm.memdata & 0x01)) {
+		AICWFDBG(LOGINFO, "%s Crystal not provided by CPU \r\n", __func__);
+		return 0;
+	}
+	AICWFDBG(LOGINFO, "%s Crystal provided by CPU \r\n", __func__);
+	// Read 0x40505010 value to check bbpll set or not.
+	ret = rwnx_send_dbg_mem_read_req(rwnx_hw, 0x40505010, &rd_mem_addr_cfm);
+	if (ret < 0) {
+		AICWFDBG(LOGERROR, "%s error ret_val:%d\r\n", __func__, ret);
+		return -1;
+	}
+
+	if ((rd_mem_addr_cfm.memdata >> 29) == 3) {
+		AICWFDBG(LOGERROR, "%s Not need to set \r\n", __func__);
+		return 0;
+	}
+	rd_mem_addr_cfm.memdata |= ((0x1 << 29) | (0x1 << 30));
+	rd_mem_addr_cfm.memdata &= (~(0x1 << 31));
+	rwnx_send_dbg_mem_write_req(rwnx_hw, 0x40505010,
+				    rd_mem_addr_cfm.memdata);
+	return 0;
+}
+
+void system_config_8800dc(struct aic_sdio_dev *rwnx_hw)
+{
+	int syscfg_num;
+	array3_tbl_t p_syscfg_msk_tbl;
+	int ret, cnt;
+	const u32 mem_addr = 0x40500000;
+	struct dbg_mem_read_cfm rd_mem_addr_cfm;
+#ifdef CONFIG_OOB
+	int oobcfg_num;
+#endif
+
+	ret = rwnx_send_dbg_mem_read_req(rwnx_hw, mem_addr, &rd_mem_addr_cfm);
+	if (ret) {
+		AICWFDBG(LOGERROR, "%x rd fail: %d\n", mem_addr, ret);
+		return;
+	}
+	chip_id = (u8)(rd_mem_addr_cfm.memdata >> 16);
+	// printk("%x=%x\n", rd_mem_addr_cfm.memaddr, rd_mem_addr_cfm.memdata);
+	if (((rd_mem_addr_cfm.memdata >> 25) & 0x01UL) == 0x00UL)
+		chip_mcu_id = 1;
+
+	ret = rwnx_send_dbg_mem_read_req(rwnx_hw, 0x00000020, &rd_mem_addr_cfm);
+	if (ret) {
+		AICWFDBG(LOGERROR, "[0x00000020] rd fail: %d\n", ret);
+		return;
+	}
+	chip_sub_id = (u8)(rd_mem_addr_cfm.memdata);
+	AICWFDBG(LOGINFO, "chip_id=%x, chip_sub_id=%x!!\n", chip_id, chip_sub_id);
+
+	// Crystal provided by CPU (start)
+	ret = set_bbpll_config(rwnx_hw);
+	if (ret) {
+		AICWFDBG(LOGERROR, "set_bbpll_config fail: %d\n", ret);
+		return;
+	}
+	// Crystal provided by CPU (end)
+
+	ret = rwnx_send_dbg_mem_read_req(rwnx_hw, 0x40500010, &rd_mem_addr_cfm);
+	AICWFDBG(LOGDEBUG, "[0x40500010]=%x\n", rd_mem_addr_cfm.memdata);
+	if (ret) {
+		AICWFDBG(LOGERROR, "[0x40500010] rd fail: %d\n", ret);
+		return;
+	}
+
+	syscfg_num = sizeof(syscfg_tbl_8800dc) / sizeof(uint32_t) / 2;
+
+	for (cnt = 0; cnt < syscfg_num; cnt++) {
+		ret = rwnx_send_dbg_mem_write_req(rwnx_hw, syscfg_tbl_8800dc[cnt][0],
+						  syscfg_tbl_8800dc[cnt][1]);
+		if (ret) {
+			AICWFDBG(LOGERROR, "%x write fail: %d\n", syscfg_tbl_8800dc[cnt][0],
+				 ret);
+			return;
+		}
+	}
+
+	u32 tmp_mem_addr = 0;
+	u32 mem_mask = 0;
+	u32 mem_data = 0;
+
+	if (chip_mcu_id == 0) {
+		if (chip_sub_id == 0) {
+			syscfg_num = sizeof(syscfg_tbl_8800dc_sdio_u01) / sizeof(u32) / 2;
+			for (cnt = 0; cnt < syscfg_num; cnt++) {
+				tmp_mem_addr = syscfg_tbl_8800dc_sdio_u01[cnt][0];
+				mem_data = syscfg_tbl_8800dc_sdio_u01[cnt][1];
+				ret = rwnx_send_dbg_mem_write_req(rwnx_hw,
+								  tmp_mem_addr,
+								  mem_data);
+				if (ret) {
+					AICWFDBG(LOGERROR, "%x write fail: %d\n",
+						 syscfg_tbl_8800dc_sdio_u01[cnt][0], ret);
+					return;
+				}
+			}
+		} else if ((chip_sub_id == 1) || (chip_sub_id == 2)) {
+			syscfg_num = sizeof(syscfg_tbl_8800dc_sdio_u02) / sizeof(u32) / 2;
+			for (cnt = 0; cnt < syscfg_num; cnt++) {
+				tmp_mem_addr = syscfg_tbl_8800dc_sdio_u02[cnt][0];
+				mem_data = syscfg_tbl_8800dc_sdio_u02[cnt][1];
+				ret = rwnx_send_dbg_mem_write_req(rwnx_hw,
+								  tmp_mem_addr,
+								  mem_data);
+				if (ret) {
+					AICWFDBG(LOGERROR, "%x write fail: %d\n",
+						 syscfg_tbl_8800dc_sdio_u02[cnt][0], ret);
+					return;
+				}
+			}
+		}
+	}
+#ifdef CONFIG_OOB
+	if (chip_sub_id == 1 || chip_sub_id == 2) {
+		oobcfg_num = sizeof(oobcfg_tbl_8800dc_sdio_u02) / sizeof(u32) / 2;
+		for (cnt = 0; cnt < oobcfg_num; cnt++) {
+			tmp_mem_addr = oobcfg_tbl_8800dc_sdio_u02[cnt][0];
+			mem_data = oobcfg_tbl_8800dc_sdio_u02[cnt][1];
+			ret = rwnx_send_dbg_mem_write_req(rwnx_hw,
+							  tmp_mem_addr,
+							  mem_data);
+			if (ret) {
+				AICWFDBG(LOGERROR, "%x write fail: %d\n",
+					 oobcfg_tbl_8800dc_sdio_u02[cnt][0], ret);
+				return;
+			}
+		}
+	}
+#endif
+	if (IS_CHIP_ID_H()) {
+		syscfg_num = sizeof(syscfg_tbl_masked_8800dc_h) / sizeof(u32) / 3;
+		p_syscfg_msk_tbl = syscfg_tbl_masked_8800dc_h;
+	} else {
+		syscfg_num = sizeof(syscfg_tbl_masked_8800dc) / sizeof(u32) / 3;
+		p_syscfg_msk_tbl = syscfg_tbl_masked_8800dc;
+	}
+
+	for (cnt = 0; cnt < syscfg_num; cnt++) {
+		if (p_syscfg_msk_tbl[cnt][0] == 0x00000000) {
+			break;
+		} else if (p_syscfg_msk_tbl[cnt][0] == 0x70001000) {
+			if (chip_mcu_id == 0) {
+				p_syscfg_msk_tbl[cnt][1] |= ((0x1 << 8) | (0x1 << 15)); // mask
+				p_syscfg_msk_tbl[cnt][2] |= ((0x1 << 8) | (0x1 << 15));
+			}
+		}
+
+		ret = rwnx_send_dbg_mem_mask_write_req(rwnx_hw, p_syscfg_msk_tbl[cnt][0],
+						       p_syscfg_msk_tbl[cnt][1],
+						       p_syscfg_msk_tbl[cnt][2]);
+		if (ret) {
+			AICWFDBG(LOGERROR, "%x mask write fail: %d\n",
+				 p_syscfg_msk_tbl[cnt][0], ret);
+			return;
+		}
+	}
+
+	if (chip_sub_id == 0) {
+		syscfg_num = sizeof(syscfg_tbl_masked_8800dc_u01) / sizeof(u32) / 3;
+		for (cnt = 0; cnt < syscfg_num; cnt++) {
+			tmp_mem_addr = syscfg_tbl_masked_8800dc_u01[cnt][0];
+			mem_mask = syscfg_tbl_masked_8800dc_u01[cnt][1];
+			mem_data = syscfg_tbl_masked_8800dc_u01[cnt][2];
+			ret = rwnx_send_dbg_mem_mask_write_req(rwnx_hw,
+							       tmp_mem_addr,
+							       mem_mask,
+							       mem_data);
+			if (ret) {
+				AICWFDBG(LOGERROR, "%x mask write fail: %d\n",
+					 syscfg_tbl_masked_8800dc_u01[cnt][0], ret);
+				return;
+			}
+		}
+	}
+}
+
+extern struct aicbsp_info_t aicbsp_info;
+
+void aicwf_patch_config_8800dc(struct aic_sdio_dev *rwnx_hw)
+{
+	int ret = 0;
+	int cnt = 0;
+	u32 mem_addr = 0;
+	u32 mem_data = 0;
+
+	if (testmode == 0) {
+		const u32 cfg_base = 0x10164;
+		struct dbg_mem_read_cfm cfm;
+		int i;
+		u32 wifisetting_cfg_addr;
+		u32 ldpc_cfg_addr;
+		u32 agc_cfg_addr;
+		u32 txgain_cfg_addr;
+		u32 jump_tbl_addr = 0;
+
+		u32 patch_tbl_wifisetting_num; // =
+		// sizeof(patch_tbl_wifisetting_8800dc_u02)/sizeof(u32)/2;
+		u32 ldpc_cfg_size = sizeof(ldpc_cfg_ram);
+		u32 agc_cfg_size = sizeof(agc_cfg_ram);
+		u32 txgain_cfg_size, *txgain_cfg_array;
+		u32 jump_tbl_size = 0;
+		u32 patch_tbl_func_num = 0;
+
+		// adap test
+		int adap_patch_num = 0;
+		// adap test
+
+		array2_tbl_t jump_tbl_base = NULL;
+		array2_tbl_t patch_tbl_func_base = NULL;
+		array2_tbl_t patch_tbl_wifisetting_8800dc_base = NULL;
+
+		if (chip_sub_id == 0) {
+			jump_tbl_base = jump_tbl;
+			jump_tbl_size = sizeof(jump_tbl) / 2;
+			patch_tbl_func_base = patch_tbl_func;
+			patch_tbl_func_num = sizeof(patch_tbl_func) / sizeof(u32) / 2;
+			patch_tbl_wifisetting_num =
+				sizeof(patch_tbl_wifisetting_8800dc_u01) / sizeof(u32) / 2;
+			patch_tbl_wifisetting_8800dc_base =
+				patch_tbl_wifisetting_8800dc_u01;
+		} else if ((chip_sub_id == 1) || (chip_sub_id == 2)) {
+			patch_tbl_wifisetting_num =
+				sizeof(patch_tbl_wifisetting_8800dc_u02) / sizeof(u32) / 2;
+			patch_tbl_wifisetting_8800dc_base =
+				patch_tbl_wifisetting_8800dc_u02;
+		} else {
+			pr_info("unsupported id: %d", chip_sub_id);
+			return;
+		}
+
+		ret = rwnx_send_dbg_mem_read_req(rwnx_hw, cfg_base, &cfm);
+		if (ret) {
+			AICWFDBG(LOGERROR, "setting base[0x%x] rd fail: %d\n", cfg_base,
+				 ret);
+		}
+		wifisetting_cfg_addr = cfm.memdata;
+
+		if (chip_sub_id == 0) {
+			ret = rwnx_send_dbg_mem_read_req(rwnx_hw, cfg_base + 4, &cfm);
+			if (ret) {
+				AICWFDBG(LOGERROR, "setting base[0x%x] rd fail: %d\n",
+					 cfg_base + 4, ret);
+			}
+			jump_tbl_addr = cfm.memdata;
+		}
+
+		ret = rwnx_send_dbg_mem_read_req(rwnx_hw, cfg_base + 8, &cfm);
+		if (ret) {
+			AICWFDBG(LOGERROR, "setting base[0x%x] rd fail: %d\n", cfg_base + 8,
+				 ret);
+		}
+		ldpc_cfg_addr = cfm.memdata;
+
+		ret = rwnx_send_dbg_mem_read_req(rwnx_hw, cfg_base + 0xc, &cfm);
+		if (ret) {
+			AICWFDBG(LOGERROR, "setting base[0x%x] rd fail: %d\n",
+				 cfg_base + 0xc, ret);
+		}
+		agc_cfg_addr = cfm.memdata;
+
+		ret = rwnx_send_dbg_mem_read_req(rwnx_hw, cfg_base + 0x10, &cfm);
+		if (ret) {
+			AICWFDBG(LOGERROR, "setting base[0x%x] rd fail: %d\n",
+				 cfg_base + 0x10, ret);
+		}
+		txgain_cfg_addr = cfm.memdata;
+
+		AICWFDBG(LOGINFO,
+			 "wifisetting_cfg_addr=%x, ldpc_cfg_addr=%x, agc_cfg_addr=%x, txgain_cfg_addr=%x\n",
+			 wifisetting_cfg_addr, ldpc_cfg_addr, agc_cfg_addr,
+			 txgain_cfg_addr);
+
+		for (cnt = 0; cnt < patch_tbl_wifisetting_num; cnt++) {
+			mem_addr = wifisetting_cfg_addr +
+				patch_tbl_wifisetting_8800dc_base[cnt][0];
+			mem_data = patch_tbl_wifisetting_8800dc_base[cnt][1];
+			ret = rwnx_send_dbg_mem_write_req(rwnx_hw, mem_addr, mem_data);
+			if (ret) {
+				AICWFDBG(LOGERROR, "wifisetting %x write fail\n",
+					 patch_tbl_wifisetting_8800dc_base[cnt][0]);
+			}
+		}
+
+		// adap test
+		if (aicbsp_info.adap_test) {
+			AICWFDBG(LOGINFO, "%s for adaptivity test \r\n", __func__);
+			adap_patch_num =
+				sizeof(adaptivity_patch_tbl_8800dc) / sizeof(u32) / 2;
+			for (cnt = 0; cnt < adap_patch_num; cnt++) {
+				mem_addr = wifisetting_cfg_addr +
+					adaptivity_patch_tbl_8800dc[cnt][0];
+				mem_data = adaptivity_patch_tbl_8800dc[cnt][1];
+				ret = rwnx_send_dbg_mem_write_req(rwnx_hw, mem_addr, mem_data);
+				if (ret) {
+					AICWFDBG(LOGERROR, "%x write fail\n",
+						 wifisetting_cfg_addr +
+						 adaptivity_patch_tbl_8800dc[cnt][0]);
+				}
+			}
+		}
+		// adap test
+
+		if (ldpc_cfg_size > 512) { // > 0.5KB data
+			for (i = 0; i < (ldpc_cfg_size - 512);
+				 i += 512) { // each time write 0.5KB
+				ret = rwnx_send_dbg_mem_block_write_req(rwnx_hw, ldpc_cfg_addr + i,
+									512, ldpc_cfg_ram + i / 4);
+				if (ret) {
+					AICWFDBG(LOGERROR, "ldpc upload fail: %x, err:%d\r\n",
+						 ldpc_cfg_addr + i, ret);
+					break;
+				}
+			}
+		}
+
+		if (!ret && i < ldpc_cfg_size) { // < 0.5KB data
+			ret = rwnx_send_dbg_mem_block_write_req(rwnx_hw, ldpc_cfg_addr + i,
+								ldpc_cfg_size - i,
+								ldpc_cfg_ram + i / 4);
+			if (ret) {
+				AICWFDBG(LOGERROR, "ldpc upload fail: %x, err:%d\r\n",
+					 ldpc_cfg_addr + i, ret);
+			}
+		}
+
+		if (agc_cfg_size > 512) { // > 0.5KB data
+			for (i = 0; i < (agc_cfg_size - 512);
+				 i += 512) { // each time write 0.5KB
+				ret = rwnx_send_dbg_mem_block_write_req(rwnx_hw, agc_cfg_addr + i,
+									512, agc_cfg_ram + i / 4);
+				if (ret) {
+					AICWFDBG(LOGERROR, "agc upload fail: %x, err:%d\r\n",
+						 agc_cfg_addr + i, ret);
+					break;
+				}
+			}
+		}
+
+		if (!ret && i < agc_cfg_size) { // < 0.5KB data
+			ret = rwnx_send_dbg_mem_block_write_req(rwnx_hw, agc_cfg_addr + i,
+								agc_cfg_size - i,
+								agc_cfg_ram + i / 4);
+			if (ret) {
+				AICWFDBG(LOGERROR, "agc upload fail: %x, err:%d\r\n",
+					 agc_cfg_addr + i, ret);
+			}
+		}
+
+#if !defined(CONFIG_FPGA_VERIFICATION)
+		if ((IS_CHIP_ID_H())) {
+			txgain_cfg_size = sizeof(txgain_map_h);
+			txgain_cfg_array = (u32 *)txgain_map_h;
+		} else {
+			txgain_cfg_size = sizeof(txgain_map);
+			txgain_cfg_array = (u32 *)txgain_map;
+		}
+		ret = rwnx_send_dbg_mem_block_write_req(rwnx_hw, txgain_cfg_addr,
+							txgain_cfg_size, txgain_cfg_array);
+		if (ret) {
+			AICWFDBG(LOGERROR, "txgain upload fail: %x, err:%d\r\n",
+				 txgain_cfg_addr, ret);
+		}
+
+		if (chip_sub_id == 0) {
+			for (cnt = 0; cnt < jump_tbl_size / 4; cnt += 1) {
+				AICWFDBG(LOGDEBUG, "%x = %x\n",
+					 jump_tbl_base[cnt][0] * 4 + jump_tbl_addr,
+						 jump_tbl_base[cnt][1]);
+				ret = rwnx_send_dbg_mem_write_req(rwnx_hw,
+								  jump_tbl_base[cnt][0] *
+								  4 + jump_tbl_addr,
+								  jump_tbl_base[cnt][1]);
+				if (ret) {
+					AICWFDBG(LOGERROR, "%x write fail\n",
+						 jump_tbl_addr + 8 * cnt);
+				}
+			}
+			for (cnt = 0; cnt < patch_tbl_func_num; cnt++) {
+				mem_addr = patch_tbl_func_base[cnt][0];
+				mem_data = patch_tbl_func_base[cnt][1];
+				ret = rwnx_send_dbg_mem_write_req(rwnx_hw, mem_addr, mem_data);
+				if (ret) {
+					AICWFDBG(LOGERROR, "patch_tbl_func %x write fail\n",
+						 patch_tbl_func_base[cnt][0]);
+				}
+			}
+		} else if (chip_sub_id == 1) {
+			ret = aicwf_patch_table_load(rwnx_hw,
+						     RWNX_MAC_PATCH_TABLE_8800DC_U02);
+			if (ret)
+				pr_warn("patch_tbl upload fail: err:%d\r\n", ret);
+		} else if (chip_sub_id == 2) {
+			ret = aicwf_patch_table_load(rwnx_hw,
+						     RWNX_MAC_PATCH_TABLE_8800DC_H_U02);
+			if (ret)
+				pr_warn("patch_tbl upload fail: err:%d\r\n", ret);
+		} else {
+			pr_info("unsupported id: %d\n", chip_sub_id);
+		}
+
+#endif
+	} else {
+		if (chip_sub_id == 0) {
+			u32 patch_tbl_rf_func_num =
+				sizeof(patch_tbl_rf_func) / sizeof(u32) / 2;
+			for (cnt = 0; cnt < patch_tbl_rf_func_num; cnt++) {
+				mem_addr = patch_tbl_rf_func[cnt][0];
+				mem_data = patch_tbl_rf_func[cnt][1];
+				ret = rwnx_send_dbg_mem_write_req(rwnx_hw, mem_addr, mem_data);
+				if (ret) {
+					AICWFDBG(LOGERROR, "patch_tbl_rf_func %x write fail\n",
+						 patch_tbl_rf_func[cnt][0]);
+				}
+			}
+		}
+	}
+}
+
+int rwnx_plat_patch_load_8800dc(struct aic_sdio_dev *sdiodev)
+{
+	int ret = 0;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	AICWFDBG(LOGINFO, "testmode=%d\n", testmode);
+	if (chip_sub_id == 0) {
+		if (testmode == FW_NORMAL_MODE) {
+			AICWFDBG(LOGINFO, "rwnx_plat_patch_loading\n");
+			ret = aicwf_plat_patch_load_8800dc(sdiodev);
+			if (ret) {
+				AICWFDBG(LOGINFO, "load patch bin fail: %d\n", ret);
+				return ret;
+			}
+		} else if (testmode == FW_RFTEST_MODE) {
+			ret = aicwf_plat_rftest_load_8800dc(sdiodev);
+			if (ret) {
+				AICWFDBG(LOGINFO, "load rftest bin fail: %d\n", ret);
+				return ret;
+			}
+		}
+	} else if (chip_sub_id >= 1) {
+		if (testmode == FW_NORMAL_MODE) {
+			AICWFDBG(LOGINFO, "rwnx_plat_patch_loading\n");
+			ret = aicwf_plat_patch_load_8800dc(sdiodev);
+			if (ret) {
+				AICWFDBG(LOGINFO, "load patch bin fail: %d\n", ret);
+				return ret;
+			}
+#ifdef CONFIG_DPD
+#ifdef CONFIG_FORCE_DPD_CALIB
+			if (1) {
+				AICWFDBG(LOGINFO, "dpd calib & write\n");
+				ret = aicwf_dpd_calib_8800dc(sdiodev, &dpd_res);
+				if (ret) {
+					AICWFDBG(LOGINFO, "dpd calib fail: %d\n", ret);
+					return ret;
+				}
+			}
+#else
+			if (is_file_exist(FW_DPDRESULT_NAME_8800DC) == 1) {
+				AICWFDBG(LOGINFO, "dpd bin load\n");
+				ret = aicwf_dpd_result_load_8800dc(sdiodev, &dpd_res);
+				if (ret) {
+					AICWFDBG(LOGINFO, "load dpd bin fail: %d\n", ret);
+					return ret;
+				}
+				ret = aicwf_dpd_result_apply_8800dc(sdiodev, &dpd_res);
+				if (ret) {
+					AICWFDBG(LOGINFO, "apply dpd bin fail: %d\n", ret);
+					return ret;
+				}
+			}
+#endif
+			else
+#endif
+			{
+				ret = aicwf_misc_ram_init_8800dc(sdiodev);
+				if (ret) {
+					AICWFDBG(LOGINFO, "misc ram init fail: %d\n", ret);
+					return ret;
+				}
+			}
+		} else if (testmode == FW_RFTEST_MODE) {
+#ifdef CONFIG_DPD
+#ifdef CONFIG_FORCE_DPD_CALIB
+			if (1) {
+				AICWFDBG(LOGINFO, "patch load\n");
+				ret = aicwf_plat_patch_load_8800dc(sdiodev);
+				if (ret) {
+					AICWFDBG(LOGINFO, "load patch bin fail: %d\n", ret);
+					return ret;
+				}
+				AICWFDBG(LOGINFO, "dpd calib & write\n");
+				ret = aicwf_dpd_calib_8800dc(sdiodev, &dpd_res);
+				if (ret) {
+					AICWFDBG(LOGINFO, "dpd calib fail: %d\n", ret);
+					return ret;
+				}
+			}
+#endif
+#endif
+			ret = aicwf_plat_rftest_load_8800dc(sdiodev);
+			if (ret) {
+				AICWFDBG(LOGINFO, "load rftest bin fail: %d\n", ret);
+				return ret;
+			}
+		} else if (testmode == FW_DPDCALIB_MODE) {
+#if (defined(CONFIG_DPD) && !defined(CONFIG_FORCE_DPD_CALIB))
+			if (is_file_exist(FW_DPDRESULT_NAME_8800DC) == 0) {
+				AICWFDBG(LOGINFO, "patch load\n");
+				ret = aicwf_plat_patch_load_8800dc(sdiodev);
+				if (ret) {
+					AICWFDBG(LOGINFO, "load patch bin fail: %d\n", ret);
+					return ret;
+				}
+				AICWFDBG(LOGINFO, "dpd calib & write\n");
+				ret = aicwf_dpd_calib_8800dc(sdiodev, &dpd_res);
+				if (ret) {
+					AICWFDBG(LOGINFO, "dpd calib fail: %d\n", ret);
+					return ret;
+				}
+				ret = aicwf_dpd_result_write_8800dc((void *)&dpd_res,
+								    DPD_RESULT_SIZE_8800DC);
+				if (ret) {
+					AICWFDBG(LOGINFO, "file write fail: %d\n", ret);
+					return ret;
+				}
+			}
+#endif
+			return 1; // exit calib mode
+		}
+	}
+
+	return ret;
+}
+
+int aicwf_misc_ram_init_8800dc(struct aic_sdio_dev *sdiodev)
+{
+	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 (testmode == FW_RFTEST_MODE)
+		cfg_base = RAM_LMAC_FW_ADDR + 0x0164;
+	// init misc ram
+	ret = rwnx_send_dbg_mem_read_req(sdiodev, 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(sdiodev, 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 start_from_bootrom_8800DC(struct aic_sdio_dev *sdiodev)
+{
+	int ret = 0;
+	u32 rd_addr;
+	u32 fw_addr;
+	u32 boot_type;
+	struct dbg_mem_read_cfm rd_cfm;
+
+	/* memory access */
+	if (testmode == 1) {
+		rd_addr = RAM_LMAC_FW_ADDR;
+		fw_addr = RAM_LMAC_FW_ADDR;
+	} else {
+		rd_addr = RAM_FMAC_FW_ADDR;
+		fw_addr = RAM_FMAC_FW_ADDR;
+	}
+
+	AICWFDBG(LOGINFO, "Read FW mem: %08x\n", rd_addr);
+	ret = rwnx_send_dbg_mem_read_req(sdiodev, rd_addr, &rd_cfm);
+	if (ret)
+		return -1;
+	AICWFDBG(LOGINFO, "cfm: [%08x] = %08x\n", rd_cfm.memaddr, rd_cfm.memdata);
+
+	if (testmode == 0)
+		boot_type = HOST_START_APP_DUMMY;
+	else
+		boot_type = HOST_START_APP_AUTO;
+	/* fw start */
+	AICWFDBG(LOGINFO, "Start app: %08x, %d\n", fw_addr, boot_type);
+	ret = rwnx_send_dbg_start_app_req(sdiodev, fw_addr, boot_type, NULL);
+	if (ret)
+		return -1;
+	return 0;
+}
+
+#ifdef CONFIG_DPD
+int aicwf_dpd_calib_8800dc(struct aic_sdio_dev *sdiodev,
+			   struct rf_misc_ram_lite_t *dpd_res)
+{
+	int ret = 0;
+	u32 fw_addr, boot_type;
+	int valid_flag;
+
+	ret = aicwf_misc_ram_valid_check_8800dc(sdiodev, &valid_flag);
+	if (ret) {
+		AICWFDBG(LOGINFO, "misc ram check fail: %d\n", ret);
+		return ret;
+	}
+	if (valid_flag) {
+		AICWFDBG(LOGINFO, "misc ram valid, skip calib process\n");
+		return ret;
+	}
+	ret = aicwf_plat_calib_load_8800dc(sdiodev);
+	if (ret) {
+		AICWFDBG(LOGINFO, "load calib bin fail: %d\n", ret);
+		return ret;
+	}
+	/* fw start */
+	fw_addr = 0x00130009;
+	boot_type = HOST_START_APP_FNCALL;
+	AICWFDBG(LOGINFO, "Start app: %08x, %d\n", fw_addr, boot_type);
+	ret = rwnx_send_dbg_start_app_req(sdiodev, fw_addr, boot_type, NULL);
+	if (ret) {
+		AICWFDBG(LOGINFO, "start app fail: %d\n", ret);
+		return ret;
+	}
+	{ // read dpd res
+		const u32 cfg_base = 0x10164;
+		struct dbg_mem_read_cfm cfm;
+		u32 misc_ram_addr;
+		u32 ram_base_addr, ram_word_cnt;
+		int i;
+
+		ret = rwnx_send_dbg_mem_read_req(sdiodev, 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;
+		// bit_mask
+		ram_base_addr = misc_ram_addr + offsetof(struct rf_misc_ram_t, bit_mask);
+		ram_word_cnt = (MEMBER_SIZE(struct rf_misc_ram_t, bit_mask) +
+						MEMBER_SIZE(struct rf_misc_ram_t, reserved)) /
+					   4;
+		for (i = 0; i < ram_word_cnt; i++) {
+			ret = rwnx_send_dbg_mem_read_req(sdiodev, ram_base_addr + i * 4,
+							 &cfm);
+			if (ret) {
+				AICWFDBG(LOGERROR, "bit_mask[0x%x] rd fail: %d\n",
+					 ram_base_addr + i * 4, ret);
+				return ret;
+			}
+			dpd_res->bit_mask[i] = cfm.memdata;
+		}
+		// dpd_high
+		ram_base_addr = misc_ram_addr + offsetof(struct rf_misc_ram_t, dpd_high);
+		ram_word_cnt = MEMBER_SIZE(struct rf_misc_ram_t, dpd_high) / 4;
+		for (i = 0; i < ram_word_cnt; i++) {
+			ret = rwnx_send_dbg_mem_read_req(sdiodev, ram_base_addr + i * 4,
+							 &cfm);
+			if (ret) {
+				AICWFDBG(LOGERROR, "bit_mask[0x%x] rd fail: %d\n",
+					 ram_base_addr + i * 4, ret);
+				return ret;
+			}
+			dpd_res->dpd_high[i] = cfm.memdata;
+		}
+		// loft_res
+		ram_base_addr = misc_ram_addr + offsetof(struct rf_misc_ram_t, loft_res);
+		ram_word_cnt = MEMBER_SIZE(struct rf_misc_ram_t, loft_res) / 4;
+		for (i = 0; i < ram_word_cnt; i++) {
+			ret = rwnx_send_dbg_mem_read_req(sdiodev, ram_base_addr + i * 4,
+							 &cfm);
+			if (ret) {
+				AICWFDBG(LOGERROR, "bit_mask[0x%x] rd fail: %d\n",
+					 ram_base_addr + i * 4, ret);
+				return ret;
+			}
+			dpd_res->loft_res[i] = cfm.memdata;
+		}
+	}
+	return ret;
+}
+
+int aicwf_dpd_result_apply_8800dc(struct aic_sdio_dev *sdiodev,
+				  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 (testmode == FW_RFTEST_MODE)
+		cfg_base = RAM_LMAC_FW_ADDR + 0x0164;
+	ret = rwnx_send_dbg_mem_read_req(sdiodev, 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(sdiodev, 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(sdiodev, 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(sdiodev, 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_dpd_result_load_8800dc(struct aic_sdio_dev *sdiodev,
+				 struct rf_misc_ram_lite_t *dpd_res)
+{
+	int ret = 0;
+	int size;
+	u32 *dst = NULL;
+	char *filename = FW_DPDRESULT_NAME_8800DC;
+	struct device *dev = sdiodev->dev;
+
+	AICWFDBG(LOGINFO, "%s: dpd_res file path:%s \r\n", __func__, filename);
+	/* load file */
+	size = rwnx_load_firmware(&dst, filename, dev);
+	if (size <= 0) {
+		AICWFDBG(LOGERROR, "wrong size of dpd_res file\n");
+		if (dst) {
+			vfree(dst);
+			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) {
+		vfree(dst);
+		dst = NULL;
+	}
+	return ret;
+}
+
+#endif /* !CONFIG_FORCE_DPD_CALIB */
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aic8800dc_compat.h b/drivers/net/wireless/aic/aic8800_bsp/aic8800dc_compat.h
new file mode 100644
index 0000000000000..3db89a92dfe01
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aic8800dc_compat.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AIC8800DC_COMPAT_H_
+#define _AIC8800DC_COMPAT_H_
+
+#include "aicsdio.h"
+typedef u32 (*array2_tbl_t)[2];
+typedef u32 (*array3_tbl_t)[3];
+
+typedef u8 u8_l;
+typedef s8 s8_l;
+typedef u16 u16_l;
+typedef s16 s16_l;
+typedef u32 u32_l;
+typedef s32 s32_l;
+typedef u64 u64_l;
+
+extern u8 chip_sub_id;
+extern u8 chip_mcu_id;
+#define FW_PATH_MAX_LEN 200
+
+void aicwf_patch_config_8800dc(struct aic_sdio_dev *rwnx_hw);
+void system_config_8800dc(struct aic_sdio_dev *rwnx_hw);
+int rwnx_plat_patch_load_8800dc(struct aic_sdio_dev *sdiodev);
+int aicwf_misc_ram_init_8800dc(struct aic_sdio_dev *sdiodev);
+int start_from_bootrom_8800DC(struct aic_sdio_dev *sdiodev);
+
+#ifdef CONFIG_DPD
+int aicwf_dpd_calib_8800dc(struct aic_sdio_dev *sdiodev,
+			   struct rf_misc_ram_lite_t *dpd_res);
+int aicwf_dpd_result_apply_8800dc(struct aic_sdio_dev *sdiodev,
+				  struct rf_misc_ram_lite_t *dpd_res);
+#ifndef CONFIG_FORCE_DPD_CALIB
+int aicwf_dpd_result_load_8800dc(struct aic_sdio_dev *sdiodev,
+				 struct rf_misc_ram_lite_t *dpd_res);
+#endif /* !CONFIG_FORCE_DPD_CALIB */
+#endif
+
+int set_bbpll_config(struct aic_sdio_dev *rwnx_hw);
+
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aic_bsp_driver.c b/drivers/net/wireless/aic/aic8800_bsp/aic_bsp_driver.c
new file mode 100644
index 0000000000000..b78bff4d7b63c
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aic_bsp_driver.c
@@ -0,0 +1,1659 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief bsp sdio declarations
+ *
+ ******************************************************************************
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/sprintf.h>
+#include <linux/string.h>
+#include <linux/version.h>
+#include <linux/vmalloc.h>
+
+#include "aic8800d80_compat.h"
+#include "aic8800dc_compat.h"
+#include "aic_bsp_driver.h"
+#include "aicsdio.h"
+#include "aicsdio_txrxif.h"
+#include "md5.h"
+#include "aic_chip_ops.h"
+#include <linux/fs.h>
+
+int cur_mode;
+
+static void cmd_dump(const struct rwnx_cmd *cmd)
+{
+	if (!cmd) {
+		pr_err("aicbsp %s cmd is null\n", __func__);
+		return;
+	}
+	pr_crit("aicbsp tkn[%d]  flags:%04x  result:%3d  cmd:%4d - reqcfm(%4d)\n",
+		cmd->tkn, cmd->flags, cmd->result, cmd->id, cmd->reqid);
+}
+
+static void cmd_complete(struct rwnx_cmd_mgr *cmd_mgr, struct rwnx_cmd *cmd)
+{
+	lockdep_assert_held(&cmd_mgr->lock);
+
+	list_del(&cmd->list);
+	cmd_mgr->queue_sz--;
+
+	cmd->flags |= RWNX_CMD_FLAG_DONE;
+	if (cmd->flags & RWNX_CMD_FLAG_NONBLOCK) {
+		kfree(cmd);
+	} else {
+		if (RWNX_CMD_WAIT_COMPLETE(cmd->flags)) {
+			cmd->result = 0;
+			complete(&cmd->complete);
+		}
+	}
+}
+
+static int cmd_mgr_queue(struct rwnx_cmd_mgr *cmd_mgr, struct rwnx_cmd *cmd)
+{
+	bool defer_push = false;
+	int err = 0;
+
+	spin_lock_bh(&cmd_mgr->lock);
+
+	if (cmd_mgr->state == RWNX_CMD_MGR_STATE_CRASHED) {
+		pr_crit("aicbsp cmd queue crashed\n");
+		cmd->result = -EPIPE;
+		spin_unlock_bh(&cmd_mgr->lock);
+		return -EPIPE;
+	}
+
+	if (!list_empty(&cmd_mgr->cmds)) {
+		struct rwnx_cmd *last;
+
+		if (cmd_mgr->queue_sz == cmd_mgr->max_queue_sz) {
+			pr_crit("aicbsp Too many cmds (%d) already queued\n",
+				cmd_mgr->max_queue_sz);
+			cmd->result = -ENOMEM;
+			spin_unlock_bh(&cmd_mgr->lock);
+			return -ENOMEM;
+		}
+		last = list_entry(cmd_mgr->cmds.prev, struct rwnx_cmd, list);
+		if (last->flags & (RWNX_CMD_FLAG_WAIT_ACK | RWNX_CMD_FLAG_WAIT_PUSH)) {
+			cmd->flags |= RWNX_CMD_FLAG_WAIT_PUSH;
+			defer_push = true;
+		}
+	}
+
+	if (cmd->flags & RWNX_CMD_FLAG_REQ_CFM)
+		cmd->flags |= RWNX_CMD_FLAG_WAIT_CFM;
+
+	cmd->tkn = cmd_mgr->next_tkn++;
+	cmd->result = -EINTR;
+
+	if (!(cmd->flags & RWNX_CMD_FLAG_NONBLOCK))
+		init_completion(&cmd->complete);
+
+	list_add_tail(&cmd->list, &cmd_mgr->cmds);
+	cmd_mgr->queue_sz++;
+	spin_unlock_bh(&cmd_mgr->lock);
+
+	if (!defer_push) {
+		rwnx_set_cmd_tx((void *)(cmd_mgr->sdiodev), cmd->a2e_msg,
+				sizeof(struct lmac_msg) + cmd->a2e_msg->param_len);
+		kfree(cmd->a2e_msg);
+	} else {
+		pr_err("aicbsp ERR: never defer push!!!!");
+		return 0;
+	}
+
+	if (!(cmd->flags & RWNX_CMD_FLAG_NONBLOCK)) {
+		unsigned long tout =
+			msecs_to_jiffies(RWNX_80211_CMD_TIMEOUT_MS * cmd_mgr->queue_sz);
+		if (!wait_for_completion_timeout(&cmd->complete, tout)) {
+			pr_crit("aicbsp cmd timed-out\n");
+			cmd_dump(cmd);
+			spin_lock_bh(&cmd_mgr->lock);
+			cmd_mgr->state = RWNX_CMD_MGR_STATE_CRASHED;
+			if (!(cmd->flags & RWNX_CMD_FLAG_DONE)) {
+				cmd->result = -ETIMEDOUT;
+				cmd_complete(cmd_mgr, cmd);
+			}
+			spin_unlock_bh(&cmd_mgr->lock);
+			err = -ETIMEDOUT;
+		} else {
+			kfree(cmd);
+		}
+	} else {
+		cmd->result = 0;
+	}
+
+	return err;
+}
+
+static int cmd_mgr_run_callback(struct rwnx_cmd_mgr *cmd_mgr,
+				struct rwnx_cmd *cmd,
+				struct rwnx_cmd_e2amsg *msg, msg_cb_fct cb)
+{
+	int res;
+
+	if (!cb)
+		return 0;
+	spin_lock(&cmd_mgr->cb_lock);
+	res = cb(cmd, msg);
+	spin_unlock(&cmd_mgr->cb_lock);
+
+	return res;
+}
+
+static int cmd_mgr_msgind(struct rwnx_cmd_mgr *cmd_mgr,
+			  struct rwnx_cmd_e2amsg *msg, msg_cb_fct cb)
+{
+	struct rwnx_cmd *cmd;
+	bool found = false;
+
+	// printk("aicbsp cmd->id=%x\n", msg->id);
+	spin_lock(&cmd_mgr->lock);
+	list_for_each_entry(cmd, &cmd_mgr->cmds, list) {
+		if (cmd->reqid == msg->id && (cmd->flags & RWNX_CMD_FLAG_WAIT_CFM)) {
+			if (!cmd_mgr_run_callback(cmd_mgr, cmd, msg, cb)) {
+				found = true;
+				cmd->flags &= ~RWNX_CMD_FLAG_WAIT_CFM;
+
+				if (WARN(msg->param_len > RWNX_CMD_E2AMSG_LEN_MAX,
+					 "Unexpect E2A msg len %d > %d\n", msg->param_len,
+						 RWNX_CMD_E2AMSG_LEN_MAX)) {
+					msg->param_len = RWNX_CMD_E2AMSG_LEN_MAX;
+				}
+
+				if (cmd->e2a_msg && msg->param_len)
+					memcpy(cmd->e2a_msg, &msg->param, msg->param_len);
+
+				if (RWNX_CMD_WAIT_COMPLETE(cmd->flags))
+					cmd_complete(cmd_mgr, cmd);
+
+				break;
+			}
+		}
+	}
+	spin_unlock(&cmd_mgr->lock);
+
+	if (!found)
+		cmd_mgr_run_callback(cmd_mgr, NULL, msg, cb);
+
+	return 0;
+}
+
+static void cmd_mgr_print(struct rwnx_cmd_mgr *cmd_mgr)
+{
+	struct rwnx_cmd *cur;
+
+	if (!cmd_mgr) {
+		pr_err("aicbsp %s cmd_mgr is null\n", __func__);
+		return;
+	}
+
+	spin_lock_bh(&cmd_mgr->lock);
+	list_for_each_entry(cur, &cmd_mgr->cmds, list) {
+		cmd_dump(cur);
+	}
+	spin_unlock_bh(&cmd_mgr->lock);
+}
+
+static void cmd_mgr_drain(struct rwnx_cmd_mgr *cmd_mgr)
+{
+	struct rwnx_cmd *cur, *nxt;
+
+	if (!cmd_mgr) {
+		pr_err("aicbsp %s cmd_mgr is null\n", __func__);
+		return;
+	}
+
+	spin_lock_bh(&cmd_mgr->lock);
+	list_for_each_entry_safe(cur, nxt, &cmd_mgr->cmds, list) {
+		list_del(&cur->list);
+		cmd_mgr->queue_sz--;
+		if (!(cur->flags & RWNX_CMD_FLAG_NONBLOCK))
+			complete(&cur->complete);
+	}
+	spin_unlock_bh(&cmd_mgr->lock);
+}
+
+void rwnx_cmd_mgr_init(struct rwnx_cmd_mgr *cmd_mgr)
+{
+	cmd_mgr->max_queue_sz = RWNX_CMD_MAX_QUEUED;
+	INIT_LIST_HEAD(&cmd_mgr->cmds);
+	cmd_mgr->state = RWNX_CMD_MGR_STATE_INITED;
+	spin_lock_init(&cmd_mgr->lock);
+	spin_lock_init(&cmd_mgr->cb_lock);
+	cmd_mgr->queue = &cmd_mgr_queue;
+	cmd_mgr->print = &cmd_mgr_print;
+	cmd_mgr->drain = &cmd_mgr_drain;
+	cmd_mgr->llind = NULL;
+	cmd_mgr->msgind = &cmd_mgr_msgind;
+}
+
+void rwnx_cmd_mgr_deinit(struct rwnx_cmd_mgr *cmd_mgr)
+{
+	if (!cmd_mgr) {
+		pr_err("aicbsp %s cmd_mgr is null\n", __func__);
+		return;
+	}
+	cmd_mgr->print(cmd_mgr);
+	cmd_mgr->drain(cmd_mgr);
+	cmd_mgr->print(cmd_mgr);
+	memset(cmd_mgr, 0, sizeof(*cmd_mgr));
+}
+
+void rwnx_set_cmd_tx(void *dev, struct lmac_msg *msg, uint len)
+{
+	struct aic_sdio_dev *sdiodev = (struct aic_sdio_dev *)dev;
+	struct aicwf_bus *bus = sdiodev->bus_if;
+	u8 *buffer = bus->cmd_buf;
+	u16 index = 0;
+
+	memset(buffer, 0, CMD_BUF_MAX);
+	buffer[0] = (len + 4) & 0x00ff;
+	buffer[1] = ((len + 4) >> 8) & 0x0f;
+	buffer[2] = 0x11;
+	if (sdiodev->chip_ops->use_hdr_checksum)
+		buffer[3] = crc8_ponl_107(&buffer[0], 3); // crc8
+	else
+		buffer[3] = 0x0;
+
+	index += 4;
+	// there is a dummy word
+	index += 4;
+
+	// make sure little endian
+	put_u16(&buffer[index], msg->id);
+	index += 2;
+	put_u16(&buffer[index], msg->dest_id);
+	index += 2;
+	put_u16(&buffer[index], msg->src_id);
+	index += 2;
+	put_u16(&buffer[index], msg->param_len);
+	index += 2;
+	memcpy(&buffer[index], (u8 *)msg->param, msg->param_len);
+
+	aicwf_bus_txmsg(bus, buffer, len + 8);
+}
+
+static inline void *rwnx_msg_zalloc(lmac_msg_id_t const id,
+				    lmac_task_id_t const dest_id,
+									lmac_task_id_t const src_id,
+									uint16_t const param_len)
+{
+	struct lmac_msg *msg;
+	gfp_t flags;
+
+	if (in_softirq())
+		flags = GFP_ATOMIC;
+	else
+		flags = GFP_KERNEL;
+
+	msg =
+		kzalloc(sizeof(struct lmac_msg) + param_len, flags);
+	if (!msg) {
+		pr_crit("aicbsp %s: msg allocation failed\n", __func__);
+		return NULL;
+	}
+	msg->id = id;
+	msg->dest_id = dest_id;
+	msg->src_id = src_id;
+	msg->param_len = param_len;
+
+	return msg->param;
+}
+
+static void rwnx_msg_free(struct lmac_msg *msg, const void *msg_params)
+{
+	kfree(msg);
+}
+
+static int rwnx_send_msg(struct aic_sdio_dev *sdiodev, const void *msg_params,
+			 int reqcfm, lmac_msg_id_t reqid, void *cfm)
+{
+	struct lmac_msg *msg;
+	struct rwnx_cmd *cmd;
+	bool nonblock;
+	int ret = 0;
+
+	msg = container_of((void *)msg_params, struct lmac_msg, param);
+	if (sdiodev->bus_if->state == BUS_DOWN_ST) {
+		rwnx_msg_free(msg, msg_params);
+		pr_info("aicbsp bus is down\n");
+		return 0;
+	}
+
+	nonblock = 0;
+	cmd = kzalloc_obj(*cmd, nonblock ? GFP_ATOMIC : GFP_KERNEL);
+	cmd->result = -EINTR;
+	cmd->id = msg->id;
+	cmd->reqid = reqid;
+	cmd->a2e_msg = msg;
+	cmd->e2a_msg = cfm;
+	if (nonblock)
+		cmd->flags = RWNX_CMD_FLAG_NONBLOCK;
+	if (reqcfm)
+		cmd->flags |= RWNX_CMD_FLAG_REQ_CFM;
+
+	if (reqcfm) {
+		cmd->flags &= ~RWNX_CMD_FLAG_WAIT_ACK; // we don't need ack any more
+		ret = sdiodev->cmd_mgr.queue(&sdiodev->cmd_mgr, cmd);
+	} else {
+		rwnx_set_cmd_tx((void *)(sdiodev), cmd->a2e_msg,
+				sizeof(struct lmac_msg) + cmd->a2e_msg->param_len);
+	}
+
+	if (!reqcfm)
+		kfree(cmd);
+
+	return ret;
+}
+
+int rwnx_send_dbg_mem_block_write_req(struct aic_sdio_dev *sdiodev,
+				      u32 mem_addr, u32 mem_size, u32 *mem_data)
+{
+	struct dbg_mem_block_write_req *mem_blk_write_req;
+
+	/* Build the DBG_MEM_BLOCK_WRITE_REQ message */
+	mem_blk_write_req =
+		rwnx_msg_zalloc(DBG_MEM_BLOCK_WRITE_REQ, TASK_DBG, DRV_TASK_ID,
+				sizeof(struct dbg_mem_block_write_req));
+	if (!mem_blk_write_req)
+		return -ENOMEM;
+
+	/* Set parameters for the DBG_MEM_BLOCK_WRITE_REQ message */
+	mem_blk_write_req->memaddr = mem_addr;
+	mem_blk_write_req->memsize = mem_size;
+	memcpy(mem_blk_write_req->memdata, mem_data, mem_size);
+
+	/* Send the DBG_MEM_BLOCK_WRITE_REQ message to LMAC FW */
+	return rwnx_send_msg(sdiodev, mem_blk_write_req, 1, DBG_MEM_BLOCK_WRITE_CFM,
+						 NULL);
+}
+
+int rwnx_send_dbg_mem_read_req(struct aic_sdio_dev *sdiodev, u32 mem_addr,
+			       struct dbg_mem_read_cfm *cfm)
+{
+	struct dbg_mem_read_req *mem_read_req;
+
+	/* Build the DBG_MEM_READ_REQ message */
+	mem_read_req = rwnx_msg_zalloc(DBG_MEM_READ_REQ, TASK_DBG, DRV_TASK_ID,
+				       sizeof(struct dbg_mem_read_req));
+	if (!mem_read_req)
+		return -ENOMEM;
+
+	/* Set parameters for the DBG_MEM_READ_REQ message */
+	mem_read_req->memaddr = mem_addr;
+
+	/* Send the DBG_MEM_READ_REQ message to LMAC FW */
+	return rwnx_send_msg(sdiodev, mem_read_req, 1, DBG_MEM_READ_CFM, cfm);
+}
+
+int rwnx_send_dbg_mem_write_req(struct aic_sdio_dev *sdiodev, u32 mem_addr,
+				u32 mem_data)
+{
+	struct dbg_mem_write_req *mem_write_req;
+
+	/* Build the DBG_MEM_WRITE_REQ message */
+	mem_write_req = rwnx_msg_zalloc(DBG_MEM_WRITE_REQ, TASK_DBG, DRV_TASK_ID,
+					sizeof(struct dbg_mem_write_req));
+	if (!mem_write_req)
+		return -ENOMEM;
+
+	/* Set parameters for the DBG_MEM_WRITE_REQ message */
+	mem_write_req->memaddr = mem_addr;
+	mem_write_req->memdata = mem_data;
+
+	/* Send the DBG_MEM_WRITE_REQ message to LMAC FW */
+	return rwnx_send_msg(sdiodev, mem_write_req, 1, DBG_MEM_WRITE_CFM, NULL);
+}
+
+int rwnx_send_dbg_mem_mask_write_req(struct aic_sdio_dev *sdiodev, u32 mem_addr,
+				     u32 mem_mask, u32 mem_data)
+{
+	struct dbg_mem_mask_write_req *mem_mask_write_req;
+
+	/* Build the DBG_MEM_MASK_WRITE_REQ message */
+	mem_mask_write_req =
+		rwnx_msg_zalloc(DBG_MEM_MASK_WRITE_REQ, TASK_DBG, DRV_TASK_ID,
+				sizeof(struct dbg_mem_mask_write_req));
+	if (!mem_mask_write_req)
+		return -ENOMEM;
+
+	/* Set parameters for the DBG_MEM_MASK_WRITE_REQ message */
+	mem_mask_write_req->memaddr = mem_addr;
+	mem_mask_write_req->memmask = mem_mask;
+	mem_mask_write_req->memdata = mem_data;
+
+	/* Send the DBG_MEM_MASK_WRITE_REQ message to LMAC FW */
+	return rwnx_send_msg(sdiodev, mem_mask_write_req, 1, DBG_MEM_MASK_WRITE_CFM,
+						 NULL);
+}
+
+int rwnx_send_dbg_start_app_req(struct aic_sdio_dev *sdiodev, u32 boot_addr,
+				u32 boot_type,
+				struct dbg_start_app_cfm *start_app_cfm)
+{
+	struct dbg_start_app_req *start_app_req;
+
+	/* Build the DBG_START_APP_REQ message */
+	start_app_req = rwnx_msg_zalloc(DBG_START_APP_REQ, TASK_DBG, DRV_TASK_ID,
+					sizeof(struct dbg_start_app_req));
+	if (!start_app_req) {
+		pr_err("aicbsp start app nomen\n");
+		return -ENOMEM;
+	}
+
+	/* Set parameters for the DBG_START_APP_REQ message */
+	start_app_req->bootaddr = boot_addr;
+	start_app_req->boottype = boot_type;
+
+	/* Send the DBG_START_APP_REQ message to LMAC FW */
+	return rwnx_send_msg(sdiodev, start_app_req, 1, DBG_START_APP_CFM,
+						 start_app_cfm);
+}
+
+static msg_cb_fct dbg_hdlrs[MSG_I(DBG_MAX)] = {};
+
+static msg_cb_fct *msg_hdlrs[] = {
+	[TASK_DBG] = dbg_hdlrs,
+};
+
+void rwnx_rx_handle_msg(struct aic_sdio_dev *sdiodev, struct ipc_e2a_msg *msg)
+{
+	sdiodev->cmd_mgr.msgind(&sdiodev->cmd_mgr, msg,
+							msg_hdlrs[MSG_T(msg->id)][MSG_I(msg->id)]);
+}
+
+static inline void print_md5(const u8 *x)
+{
+	pr_info("aicbsp file md5:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\r\n",
+		x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7],
+		x[8], x[9], x[10], x[11], x[12], x[13], x[14], x[15]);
+}
+
+static int aicbsp_request_firmware(const struct firmware **fw,
+				   const char *name, struct device *dev)
+{
+	char *fw_name;
+	int ret;
+
+	if (!name || !name[0])
+		return -EINVAL;
+
+	fw_name = kasprintf(GFP_KERNEL, AIC8800_FW_DIR "%s", name);
+	if (!fw_name)
+		return -ENOMEM;
+
+	ret = request_firmware(fw, fw_name, dev);
+	kfree(fw_name);
+
+	return ret;
+}
+
+int rwnx_load_firmware(u32 **fw_buf, const char *name, struct device *device)
+{
+	const struct firmware *fw = NULL;
+	u32 *dst = NULL;
+	void *buffer = NULL;
+#ifdef CONFIG_TEDIOUS_PRT
+	struct MD5_CTX md5;
+	unsigned char decrypt[16];
+#endif
+	int size = 0;
+	int ret = 0;
+
+	pr_info("aicbsp %s: request firmware = %s\n", __func__, name);
+
+	ret = aicbsp_request_firmware(&fw, name, device);
+
+	if (ret < 0) {
+		pr_err("aicbsp Load %s fail\n", name);
+		return ret;
+	}
+
+	size = fw->size;
+	dst = (u32 *)fw->data;
+
+	if (size <= 0) {
+		pr_err("aicbsp wrong size of firmware file\n");
+		release_firmware(fw);
+		return -1;
+	}
+
+	buffer = vmalloc(size);
+	if (!buffer) {
+		release_firmware(fw);
+		return -ENOMEM;
+	}
+	memset(buffer, 0, size);
+	memcpy(buffer, dst, size);
+
+	*fw_buf = buffer;
+
+#ifdef CONFIG_TEDIOUS_PRT
+	md5_init(&md5);
+	md5_update(&md5, (unsigned char *)buffer, size);
+	md5_final(&md5, decrypt);
+	print_md5(decrypt);
+#endif
+
+	release_firmware(fw);
+
+	return size;
+}
+
+extern int testmode;
+
+#ifdef CONFIG_M2D_OTA_AUTO_SUPPORT
+extern char saved_sdk_ver[64];
+
+int rwnx_plat_m2d_flash_ota(struct aic_sdio_dev *sdiodev,
+			    char *filename)
+{
+	struct device *dev = sdiodev->dev;
+	unsigned int i = 0;
+	int size;
+	u32 *dst = NULL;
+	int err = 0;
+	int ret;
+	u8 bond_id;
+	const u32 mem_addr = 0x40500000;
+	struct dbg_mem_read_cfm rd_mem_addr_cfm;
+
+	ret = rwnx_send_dbg_mem_read_req(sdiodev, mem_addr, &rd_mem_addr_cfm);
+	if (ret) {
+		pr_err("aicbsp m2d %x rd fail: %d\n", mem_addr, ret);
+		return ret;
+	}
+	bond_id = (u8)(rd_mem_addr_cfm.memdata >> 24);
+	pr_info("aicbsp %x=%x\n", rd_mem_addr_cfm.memaddr, rd_mem_addr_cfm.memdata);
+	if (bond_id & (1 << 1)) {
+		// flash is invalid
+		pr_err("aicbsp m2d flash is invalid\n");
+		return -1;
+	}
+
+	/* load aic firmware */
+	size = rwnx_load_firmware(&dst, filename, dev);
+	if (size <= 0) {
+		pr_err("aicbsp wrong size of m2d file\n");
+		vfree(dst);
+		dst = NULL;
+		return -1;
+	}
+
+	/* Copy the file on the Embedded side */
+	pr_info("aicbsp ### Upload m2d %s flash, size=%d\n", filename, size);
+
+	/*send info first */
+	err = rwnx_send_dbg_mem_block_write_req(sdiodev, AIC_M2D_OTA_INFO_ADDR, 4,
+						(u32 *)&size);
+
+	/*send data first */
+	if (size > 1024) {                              // > 1KB data
+		for (i = 0; i < (size - 1024); i += 1024) { // each time write 1KB
+			err = rwnx_send_dbg_mem_block_write_req(sdiodev,
+								AIC_M2D_OTA_DATA_ADDR,
+								1024, dst + i / 4);
+			if (err) {
+				pr_err("aicbsp m2d upload fail: %x, err:%d\r\n",
+				       AIC_M2D_OTA_DATA_ADDR, err);
+				break;
+			}
+		}
+	}
+
+	if (!err && i < size) { // <1KB data
+		err = rwnx_send_dbg_mem_block_write_req(sdiodev, AIC_M2D_OTA_DATA_ADDR,
+							size - i, dst + i / 4);
+		if (err) {
+			pr_err("aicbsp m2d upload fail: %x, err:%d\r\n", AIC_M2D_OTA_DATA_ADDR,
+			       err);
+		}
+	}
+
+	if (dst) {
+		vfree(dst);
+		dst = NULL;
+	}
+	testmode = FW_NORMAL_MODE;
+	aicbsp_info.cpmode = testmode;
+
+	pr_info("aicbsp m2d flash update complete\n\n");
+
+	return err;
+}
+
+int rwnx_plat_m2d_flash_ota_check(struct aic_sdio_dev *sdiodev, char *filename)
+{
+	struct device *dev = sdiodev->dev;
+	unsigned int i = 0, j = 0;
+	int size;
+	u32 *dst = NULL;
+	int err = 0;
+	int ret = 0;
+	u8 bond_id;
+	const u32 mem_addr = 0x40500000;
+	const u32 mem_addr_code_start = AIC_M2D_OTA_CODE_START_ADDR;
+	const u32 mem_addr_sdk_ver = AIC_M2D_OTA_VER_ADDR;
+	const u32 driver_code_start_idx =
+		(AIC_M2D_OTA_CODE_START_ADDR - AIC_M2D_OTA_FLASH_ADDR) / 4;
+	const u32 driver_sdk_ver_idx =
+		(AIC_M2D_OTA_VER_ADDR - AIC_M2D_OTA_FLASH_ADDR) / 4;
+	u32 driver_sdk_ver_addr_idx = 0;
+	u32 code_start_addr = 0xffffffff;
+	u32 sdk_ver_addr = 0xffffffff;
+	u32 drv_code_start_addr = 0xffffffff;
+	u32 drv_sdk_ver_addr = 0xffffffff;
+	struct dbg_mem_read_cfm rd_mem_addr_cfm;
+	char m2d_sdk_ver[64];
+	char flash_sdk_ver[64];
+	u32 flash_ver[16];
+	u32 ota_ver[16];
+
+	ret = rwnx_send_dbg_mem_read_req(sdiodev, mem_addr, &rd_mem_addr_cfm);
+	if (ret) {
+		pr_err("aicbsp m2d %x rd fail: %d\n", mem_addr, ret);
+		return ret;
+	}
+	bond_id = (u8)(rd_mem_addr_cfm.memdata >> 24);
+	pr_info("aicbsp %x=%x\n", rd_mem_addr_cfm.memaddr, rd_mem_addr_cfm.memdata);
+	if (bond_id & (1 << 1)) {
+		// flash is invalid
+		pr_err("aicbsp m2d flash is invalid\n");
+		return -1;
+	}
+	ret = rwnx_send_dbg_mem_read_req(sdiodev, mem_addr_code_start,
+					 &rd_mem_addr_cfm);
+	if (ret) {
+		pr_err("mem_addr_code_start %x rd fail: %d\n", mem_addr_code_start,
+		       ret);
+		return ret;
+	}
+	code_start_addr = rd_mem_addr_cfm.memdata;
+
+#if !defined(CONFIG_M2D_OTA_LZMA_SUPPORT)
+	ret =
+		rwnx_send_dbg_mem_read_req(sdiodev, mem_addr_sdk_ver, &rd_mem_addr_cfm);
+	if (ret) {
+		pr_err("aicbsp mem_addr_sdk_ver %x rd fail: %d\n", mem_addr_code_start, ret);
+		return ret;
+	}
+	sdk_ver_addr = rd_mem_addr_cfm.memdata;
+#else
+	sdk_ver_addr = mem_addr_sdk_ver;
+#endif
+	pr_info("aicbsp code_start_addr: 0x%x,	sdk_ver_addr: 0x%x\n", code_start_addr,
+		sdk_ver_addr);
+
+	/* load aic firmware */
+	size = rwnx_load_firmware(&dst, filename, dev);
+	if (size <= 0) {
+		pr_err("aicbsp wrong size of m2d file\n");
+		vfree(dst);
+		dst = NULL;
+		return -1;
+	}
+	if (code_start_addr == 0xffffffff && sdk_ver_addr == 0xffffffff) {
+		pr_warn("aicbsp ########m2d flash old version , must be upgrade\n");
+		drv_code_start_addr = dst[driver_code_start_idx];
+		drv_sdk_ver_addr = dst[driver_sdk_ver_idx];
+
+		pr_info("aicbsp drv_code_start_addr: 0x%x,	drv_sdk_ver_addr: 0x%x\n",
+			drv_code_start_addr, drv_sdk_ver_addr);
+
+		if (drv_sdk_ver_addr == 0xffffffff) {
+			pr_err("aicbsp ########driver m2d_ota.bin is old ,not need upgrade\n");
+			return -1;
+		}
+
+	} else {
+		for (i = 0; i < 16; i++) {
+			ret = rwnx_send_dbg_mem_read_req(sdiodev, (sdk_ver_addr + i * 4),
+							 &rd_mem_addr_cfm);
+			if (ret) {
+				pr_err("aicbsp mem_addr_sdk_ver %x rd fail: %d\n",
+				       mem_addr_code_start, ret);
+				return ret;
+			}
+			flash_ver[i] = rd_mem_addr_cfm.memdata;
+		}
+		memcpy((u8 *)flash_sdk_ver, (u8 *)flash_ver, 64);
+		memcpy((u8 *)saved_sdk_ver, (u8 *)flash_sdk_ver, 64);
+		pr_info("aicbsp flash SDK Version: %s\r\n\r\n", flash_sdk_ver);
+
+		drv_code_start_addr = dst[driver_code_start_idx];
+		drv_sdk_ver_addr = dst[driver_sdk_ver_idx];
+
+		pr_info("aicbsp drv_code_start_addr: 0x%x,	drv_sdk_ver_addr: 0x%x\n",
+			drv_code_start_addr, drv_sdk_ver_addr);
+
+		if (drv_sdk_ver_addr == 0xffffffff) {
+			pr_err("aicbsp ########driver m2d_ota.bin is old ,not need upgrade\n");
+			return -1;
+		}
+#if !defined(CONFIG_M2D_OTA_LZMA_SUPPORT)
+		driver_sdk_ver_addr_idx = (drv_sdk_ver_addr - drv_code_start_addr) / 4;
+#else
+		driver_sdk_ver_addr_idx = driver_sdk_ver_idx;
+#endif
+		pr_info("aicbsp driver_sdk_ver_addr_idx %d\n", driver_sdk_ver_addr_idx);
+
+		if (driver_sdk_ver_addr_idx) {
+			for (j = 0; j < 16; j++)
+				ota_ver[j] = dst[driver_sdk_ver_addr_idx + j];
+
+			memcpy((u8 *)m2d_sdk_ver, (u8 *)ota_ver, 64);
+			pr_info("aicbsp m2d_ota SDK Version: %s\r\n\r\n", m2d_sdk_ver);
+		} else {
+			return -1;
+		}
+
+		if (!strcmp(m2d_sdk_ver, flash_sdk_ver)) {
+			pr_err("aicbsp ######## m2d %s flash is not need upgrade\r\n", filename);
+			return -1;
+		}
+	}
+
+	/* Copy the file on the Embedded side */
+	pr_info("aicbsp ### Upload m2d %s flash, size=%d\n", filename, size);
+
+	/*send info first */
+	err = rwnx_send_dbg_mem_block_write_req(sdiodev, AIC_M2D_OTA_INFO_ADDR, 4,
+						(u32 *)&size);
+
+	/*send data first */
+	if (size > 1024) {                              // > 1KB data
+		for (i = 0; i < (size - 1024); i += 1024) { // each time write 1KB
+			err = rwnx_send_dbg_mem_block_write_req(sdiodev, AIC_M2D_OTA_DATA_ADDR,
+								1024, dst + i / 4);
+			if (err) {
+				pr_err("aicbsp m2d upload fail: %x, err:%d\r\n",
+				       AIC_M2D_OTA_DATA_ADDR, err);
+				break;
+			}
+		}
+	}
+
+	if (!err && i < size) {
+		err = rwnx_send_dbg_mem_block_write_req(sdiodev, AIC_M2D_OTA_DATA_ADDR,
+							size - i, dst + i / 4);
+		if (err) {
+			pr_err("aicbsp m2d upload fail: %x, err:%d\r\n", AIC_M2D_OTA_DATA_ADDR,
+			       err);
+		}
+	}
+
+	if (dst) {
+		vfree(dst);
+		dst = NULL;
+	}
+	testmode = FW_NORMAL_MODE;
+
+	pr_info("aicbsp m2d flash update complete\n\n");
+
+	return err;
+}
+#endif // CONFIG_M2D_OTA_AUTO_SUPPORT
+
+int aicwf_patch_table_load(struct aic_sdio_dev *rwnx_hw, char *filename)
+{
+	struct device *dev = rwnx_hw->dev;
+	int err = 0;
+	unsigned int i = 0, size;
+	u32 *dst = NULL;
+	u8 *describle;
+	u32 fmacfw_patch_tbl_8800dc_u02_describe_size = 124;
+	u32 fmacfw_patch_tbl_8800dc_u02_describe_base; // read from patch_tbl
+
+	/* Copy the file on the Embedded side */
+	pr_info("aicbsp ### Upload %s\n", filename);
+
+	size = rwnx_load_firmware(&dst, filename, dev);
+	if (!dst) {
+		pr_err("aicbsp No such file or directory\n");
+		return -1;
+	}
+	if (size <= 0) {
+		pr_err("aicbsp wrong size of firmware file\n");
+		dst = NULL;
+		err = -1;
+	}
+
+	pr_info("aicbsp tbl size = %d\n", size);
+
+	fmacfw_patch_tbl_8800dc_u02_describe_base = dst[0];
+	AICWFDBG(LOGINFO, "FMACFW_PATCH_TBL_8800DC_U02_DESCRIBE_BASE = %x\n",
+		 fmacfw_patch_tbl_8800dc_u02_describe_base);
+
+	if (!err && i < size) {
+		err = rwnx_send_dbg_mem_block_write_req(rwnx_hw,
+							fmacfw_patch_tbl_8800dc_u02_describe_base,
+			fmacfw_patch_tbl_8800dc_u02_describe_size + 4, dst);
+		if (err)
+			pr_err("aicbsp write describe information fail\n");
+
+		describle =
+			kzalloc(fmacfw_patch_tbl_8800dc_u02_describe_size, GFP_KERNEL);
+		memcpy(describle, &dst[1], fmacfw_patch_tbl_8800dc_u02_describe_size);
+		pr_info("aicbsp %s", describle);
+		kfree(describle);
+		describle = NULL;
+	}
+
+	if (!err && i < size) {
+		for (i = (128 / 4); i < (size / 4); i += 2) {
+			pr_info("aicbsp patch_tbl:  %x  %x\n", dst[i], dst[i + 1]);
+			err = rwnx_send_dbg_mem_write_req(rwnx_hw, dst[i], dst[i + 1]);
+		}
+		if (err)
+			pr_err("aicbsp bin upload fail: %x, err:%d\r\n", dst[i], err);
+	}
+
+	if (dst) {
+		vfree(dst);
+		dst = NULL;
+	}
+
+	return err;
+}
+
+int aicwf_plat_patch_load_8800dc(struct aic_sdio_dev *sdiodev)
+{
+	int ret = 0;
+#if !defined(CONFIG_FPGA_VERIFICATION)
+	if (chip_sub_id == 0) {
+		pr_info("aicbsp u01 is loaing ###############\n");
+		ret = rwnx_plat_bin_fw_upload(sdiodev, ROM_FMAC_PATCH_ADDR,
+					      RWNX_MAC_PATCH_NAME2_8800DC);
+	} else if (chip_sub_id == 1) {
+		pr_info("aicbsp u02 is loaing ###############\n");
+		ret = rwnx_plat_bin_fw_upload(sdiodev, ROM_FMAC_PATCH_ADDR,
+					      RWNX_MAC_PATCH_NAME2_8800DC_U02);
+	} else if (chip_sub_id == 2) {
+		pr_info("aicbsp h_u02 is loaing ###############\n");
+		ret = rwnx_plat_bin_fw_upload(sdiodev, ROM_FMAC_PATCH_ADDR,
+					      RWNX_MAC_PATCH_NAME2_8800DC_H_U02);
+	} else {
+		pr_info("aicbsp unsupported id: %d\n", chip_sub_id);
+	}
+#endif
+	return ret;
+}
+
+int aicwf_plat_rftest_load_8800dc(struct aic_sdio_dev *sdiodev)
+{
+	int ret = 0;
+
+	ret = rwnx_plat_bin_fw_upload(sdiodev, RAM_LMAC_FW_ADDR,
+				      RWNX_MAC_FW_RF_BASE_NAME_8800DC);
+	if (ret) {
+		AICWFDBG(LOGINFO, "load rftest bin fail: %d\n", ret);
+		return ret;
+	}
+	return ret;
+}
+
+#ifdef CONFIG_DPD
+int aicwf_misc_ram_valid_check_8800dc(struct aic_sdio_dev *sdiodev,
+				      int *valid_out)
+{
+	int ret = 0;
+	u32 cfg_base = 0x10164;
+	struct dbg_mem_read_cfm cfm;
+	u32 misc_ram_addr;
+	u32 ram_base_addr, ram_word_cnt;
+	u32 bit_mask[4];
+	int i;
+
+	if (valid_out)
+		*valid_out = 0;
+
+	if (testmode == FW_RFTEST_MODE) {
+		u32 vect1 = 0;
+		u32 vect2 = 0;
+
+		cfg_base = RAM_LMAC_FW_ADDR + 0x0004;
+		ret = rwnx_send_dbg_mem_read_req(sdiodev, cfg_base, &cfm);
+		if (ret) {
+			AICWFDBG(LOGERROR, "cfg_base:%x vcet1 rd fail: %d\n", cfg_base,
+				 ret);
+			return ret;
+		}
+		vect1 = cfm.memdata;
+		if ((vect1 & 0xFFFF0000) != (RAM_LMAC_FW_ADDR & 0xFFFF0000)) {
+			AICWFDBG(LOGERROR, "vect1 invalid: %x\n", vect1);
+			return ret;
+		}
+		cfg_base = RAM_LMAC_FW_ADDR + 0x0008;
+		ret = rwnx_send_dbg_mem_read_req(sdiodev, cfg_base, &cfm);
+		if (ret) {
+			AICWFDBG(LOGERROR, "cfg_base:%x vcet2 rd fail: %d\n", cfg_base,
+				 ret);
+			return ret;
+		}
+		vect2 = cfm.memdata;
+		if ((vect2 & 0xFFFF0000) != (RAM_LMAC_FW_ADDR & 0xFFFF0000)) {
+			AICWFDBG(LOGERROR, "vect2 invalid: %x\n", vect2);
+			return ret;
+		}
+
+		cfg_base = RAM_LMAC_FW_ADDR + 0x0164;
+	}
+	// init misc ram
+	ret = rwnx_send_dbg_mem_read_req(sdiodev, 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);
+	// bit_mask
+	ram_base_addr = misc_ram_addr + offsetof(struct rf_misc_ram_t, bit_mask);
+	ram_word_cnt = (MEMBER_SIZE(struct rf_misc_ram_t, bit_mask) +
+					MEMBER_SIZE(struct rf_misc_ram_t, reserved)) /
+				   4;
+	for (i = 0; i < ram_word_cnt; i++) {
+		ret = rwnx_send_dbg_mem_read_req(sdiodev, ram_base_addr + i * 4, &cfm);
+		if (ret) {
+			AICWFDBG(LOGERROR, "bit_mask[0x%x] rd fail: %d\n",
+				 ram_base_addr + i * 4, ret);
+			return ret;
+		}
+		bit_mask[i] = cfm.memdata;
+	}
+	AICWFDBG(LOGTRACE, "bit_mask:%x,%x,%x,%x\n", bit_mask[0], bit_mask[1],
+		 bit_mask[2], bit_mask[3]);
+	if (bit_mask[0] == 0 && ((bit_mask[1] & 0xFFF00000) == 0x80000000) &&
+	    bit_mask[2] == 0 && ((bit_mask[3] & 0xFFFFFF00) == 0x00000000)) {
+		if (valid_out)
+			*valid_out = 1;
+	}
+	return ret;
+}
+
+int aicwf_plat_calib_load_8800dc(struct aic_sdio_dev *sdiodev)
+{
+	int ret = 0;
+
+	if (chip_sub_id == 1) {
+		ret = rwnx_plat_bin_fw_upload(sdiodev, ROM_FMAC_CALIB_ADDR,
+					      RWNX_MAC_CALIB_NAME_8800DC_U02);
+		if (ret) {
+			AICWFDBG(LOGINFO, "load rftest bin fail: %d\n", ret);
+			return ret;
+		}
+	} else if (chip_sub_id == 2) {
+		ret = rwnx_plat_bin_fw_upload(sdiodev, ROM_FMAC_CALIB_ADDR,
+					      RWNX_MAC_CALIB_NAME_8800DC_H_U02);
+		if (ret) {
+			AICWFDBG(LOGINFO, "load rftest bin fail: %d\n", ret);
+			return ret;
+		}
+	}
+	return ret;
+}
+
+#endif
+
+#ifdef CONFIG_DPD
+struct rf_misc_ram_lite_t dpd_res = {
+	{0},
+};
+EXPORT_SYMBOL_GPL(dpd_res);
+#endif
+
+int rwnx_plat_bin_fw_upload(struct aic_sdio_dev *sdiodev, u32 fw_addr,
+			    const char *filename)
+{
+	struct device *dev = sdiodev->dev;
+	unsigned int i = 0;
+	int size;
+	u32 *dst = NULL;
+	int err = 0;
+
+	pr_info("aicbsp %s\n", __func__);
+
+	/* load aic firmware */
+	size = rwnx_load_firmware(&dst, filename, dev);
+	if (size <= 0) {
+		pr_err("aicbsp wrong size of firmware file\n");
+		vfree(dst);
+		dst = NULL;
+		return -1;
+	}
+
+	/* Copy the file on the Embedded side */
+	if (size > 1024) {                              // > 1KB data
+		for (i = 0; i < (size - 1024); i += 1024) { // each time write 1KB
+			err = rwnx_send_dbg_mem_block_write_req(sdiodev, fw_addr + i, 1024,
+								dst + i / 4);
+			if (err) {
+				pr_err("aicbsp bin upload fail: %x, err:%d\r\n", fw_addr + i, err);
+				break;
+			}
+		}
+	}
+
+	if (!err && i < size) { // <1KB data
+		err = rwnx_send_dbg_mem_block_write_req(sdiodev, fw_addr + i, size - i,
+							dst + i / 4);
+		if (err)
+			pr_err("aicbsp bin upload fail: %x, err:%d\r\n", fw_addr + i, err);
+	}
+
+	if (dst) {
+		vfree(dst);
+		dst = NULL;
+	}
+
+	return err;
+}
+
+int aicbt_patch_table_free(struct aicbt_patch_table **head)
+{
+	struct aicbt_patch_table *p = *head, *n = NULL;
+
+	while (p) {
+		n = p->next;
+		vfree(p->name);
+		vfree(p->data);
+		vfree(p);
+		p = n;
+	}
+	*head = NULL;
+	return 0;
+}
+
+struct aicbt_patch_table *aicbt_patch_table_alloc(const char *filename)
+{
+	u8 *rawdata = NULL, *p;
+	int size;
+	struct aicbt_patch_table *head = NULL, *new = NULL, *cur = NULL;
+
+	/* load aic firmware */
+	size = rwnx_load_firmware((u32 **)&rawdata, filename, NULL);
+	if (size <= 0) {
+		pr_err("aicbsp wrong size of firmware file\n");
+		goto err;
+	}
+
+	p = rawdata;
+	if (memcmp(p, AICBT_PT_TAG,
+		   sizeof(AICBT_PT_TAG) < 16 ? sizeof(AICBT_PT_TAG) : 16)) {
+		pr_err("aicbsp TAG err\n");
+		goto err;
+	}
+	p += 16;
+
+	while (p - rawdata < size) {
+		new = vmalloc(sizeof(*new));
+		memset(new, 0, sizeof(struct aicbt_patch_table));
+		if (!head) {
+			head = new;
+			cur = new;
+		} else {
+			cur->next = new;
+			cur = cur->next;
+		}
+
+		cur->name = vmalloc(sizeof(char) * 16);
+		memset(cur->name, 0, sizeof(char) * 16);
+		memcpy(cur->name, p, 16);
+		p += 16;
+
+		cur->type = *(uint32_t *)p;
+		p += 4;
+
+		cur->len = *(uint32_t *)p;
+		p += 4;
+
+		if (cur->type >= 1000) { // Temp Workaround
+			cur->len = 0;
+		} else {
+			if (cur->len > 0) {
+				cur->data = vmalloc(sizeof(uint8_t) * cur->len * 8);
+				memset(cur->data, 0, sizeof(uint8_t) * cur->len * 8);
+				memcpy(cur->data, p, cur->len * 8);
+				p += cur->len * 8;
+			}
+		}
+	}
+	vfree(rawdata);
+	return head;
+
+err:
+	aicbt_patch_table_free(&head);
+	if (rawdata)
+		vfree(rawdata);
+	return NULL;
+}
+
+int aicbt_patch_info_unpack(struct aicbt_patch_info_t *patch_info,
+			    struct aicbt_patch_table *head_t)
+{
+	u8 *patch_info_array = (u8 *)patch_info;
+
+	if (head_t->type == AICBT_PT_INF) {
+		patch_info->info_len = head_t->len;
+		if (patch_info->info_len == 0)
+			return 0;
+
+		memcpy(patch_info_array + sizeof(patch_info->info_len),
+		       head_t->data,
+		       patch_info->info_len * sizeof(uint32_t) * 2);
+		AICWFDBG(LOGDEBUG, "%s adid_addrinf:%x addr_adid:%x \r\n", __func__,
+			 ((struct aicbt_patch_info_t *)patch_info_array)->adid_addrinf,
+			 ((struct aicbt_patch_info_t *)patch_info_array)->addr_adid);
+	}
+	return 0;
+}
+
+int aicbt_patch_trap_data_load(struct aic_sdio_dev *sdiodev,
+			       struct aicbt_patch_table *head)
+{
+	struct aicbt_patch_info_t patch_info = {
+		.info_len = 0,
+		.adid_addrinf = 0,
+		.addr_adid = 0,
+		.patch_addrinf = 0,
+		.addr_patch = 0,
+		.reset_addr = 0,
+		.reset_val = 0,
+		.adid_flag_addr = 0,
+		.adid_flag = 0,
+	};
+
+	int ret;
+
+	if (!head)
+		return -1;
+
+	ret = aic_chip_set_patch_info(sdiodev, &patch_info, &aicbsp_info, head);
+	if (ret) {
+		pr_err("aicbsp chip set patch info fail\n");
+		return ret;
+	}
+
+	if (rwnx_plat_bin_fw_upload(sdiodev, patch_info.addr_adid,
+				    aicbsp_firmware_list[aicbsp_info.cpmode].bt_adid))
+		return -1;
+	if (rwnx_plat_bin_fw_upload(sdiodev, patch_info.addr_patch,
+				    aicbsp_firmware_list[aicbsp_info.cpmode].bt_patch))
+		return -1;
+	return 0;
+}
+
+static struct aicbt_info_t aicbt_info[] = {
+	{
+		.btmode = AICBT_BTMODE_DEFAULT,
+		.btport = AICBT_BTPORT_DEFAULT,
+		.uart_baud = AICBT_UART_BAUD_DEFAULT,
+		.uart_flowctrl = AICBT_UART_FC_DEFAULT,
+		.lpm_enable = AICBT_LPM_ENABLE_DEFAULT,
+		.txpwr_lvl = AICBT_TXPWR_LVL_DEFAULT,
+	}, // PRODUCT_ID_AIC8801
+	{
+		.btmode = AICBT_BTMODE_BT_WIFI_COMBO,
+		.btport = AICBT_BTPORT_DEFAULT,
+		.uart_baud = AICBT_UART_BAUD_DEFAULT,
+		.uart_flowctrl = AICBT_UART_FC_DEFAULT,
+		.lpm_enable = AICBT_LPM_ENABLE_DEFAULT,
+		.txpwr_lvl = AICBT_TXPWR_LVL_DEFAULT_8800dc,
+	}, // PRODUCT_ID_AIC8800DC
+	{
+		.btmode = AICBT_BTMODE_BT_WIFI_COMBO,
+		.btport = AICBT_BTPORT_DEFAULT,
+		.uart_baud = AICBT_UART_BAUD_DEFAULT,
+		.uart_flowctrl = AICBT_UART_FC_DEFAULT,
+		.lpm_enable = AICBT_LPM_ENABLE_DEFAULT,
+		.txpwr_lvl = AICBT_TXPWR_LVL_DEFAULT_8800dc,
+	}, // PRODUCT_ID_AIC8800DW
+	{
+		.btmode = AICBT_BTMODE_DEFAULT_8800d80,
+		.btport = AICBT_BTPORT_DEFAULT,
+		.uart_baud = AICBT_UART_BAUD_DEFAULT,
+		.uart_flowctrl = AICBT_UART_FC_DEFAULT,
+		.lpm_enable = AICBT_LPM_ENABLE_DEFAULT,
+		.txpwr_lvl = AICBT_TXPWR_LVL_DEFAULT_8800d80,
+	} // PRODUCT_ID_AIC8800D80
+};
+
+int aicbt_patch_table_load(struct aic_sdio_dev *sdiodev,
+			   struct aicbt_patch_table *head)
+{
+	struct aicbt_patch_table *p;
+	int ret = 0, i;
+	u32 *data = NULL;
+
+	if (!head)
+		return -1;
+
+	for (p = head; p; p = p->next) {
+		data = p->data;
+		if (p->type == AICBT_PT_BTMODE) {
+			*(data + 1) = aicbsp_info.hwinfo < 0;
+			*(data + 3) = aicbsp_info.hwinfo;
+			*(data + 5) = (sdiodev->chipid == PRODUCT_ID_AIC8800DC
+							   ? aicbsp_info.cpmode
+							   : 0); // 0;//aicbsp_info.cpmode;
+
+			*(data + 7) = aicbt_info[sdiodev->chipid].btmode;
+			*(data + 9) = aicbt_info[sdiodev->chipid].btport;
+			*(data + 11) = aicbt_info[sdiodev->chipid].uart_baud;
+			*(data + 13) = aicbt_info[sdiodev->chipid].uart_flowctrl;
+			*(data + 15) = aicbt_info[sdiodev->chipid].lpm_enable;
+			*(data + 17) = aicbt_info[sdiodev->chipid].txpwr_lvl;
+
+			pr_info("aicbsp %s bt btmode[%d]:%d \r\n", __func__, sdiodev->chipid,
+				aicbt_info[sdiodev->chipid].btmode);
+			pr_info("aicbsp %s bt uart_baud[%d]:%d \r\n", __func__, sdiodev->chipid,
+				aicbt_info[sdiodev->chipid].uart_baud);
+			pr_info("aicbsp %s bt uart_flowctrl[%d]:%d \r\n", __func__, sdiodev->chipid,
+				aicbt_info[sdiodev->chipid].uart_flowctrl);
+			pr_info("aicbsp %s bt lpm_enable[%d]:%d \r\n", __func__, sdiodev->chipid,
+				aicbt_info[sdiodev->chipid].lpm_enable);
+			pr_info("aicbsp %s bt tx_pwr[%d]:%d \r\n", __func__, sdiodev->chipid,
+				aicbt_info[sdiodev->chipid].txpwr_lvl);
+		}
+
+		if (p->type == AICBT_PT_VER) {
+			pr_info("aicbsp bt patch version: %s\n", (char *)p->data);
+			continue;
+		}
+
+		for (i = 0; i < p->len; i++) {
+			ret = rwnx_send_dbg_mem_write_req(sdiodev, *data, *(data + 1));
+			if (ret != 0)
+				return ret;
+			data += 2;
+		}
+		if (p->type == AICBT_PT_PWRON)
+			usleep_range(50, 100);
+	}
+
+	return 0;
+}
+
+int aicbt_init(struct aic_sdio_dev *sdiodev)
+{
+	int ret = 0;
+	struct aicbt_patch_table *head =
+		aicbt_patch_table_alloc(aicbsp_firmware_list[aicbsp_info.cpmode].bt_table);
+	if (!head) {
+		pr_err("aicbsp aicbt_patch_table_alloc fail\n");
+		return -1;
+	}
+
+	if (aicbt_patch_trap_data_load(sdiodev, head)) {
+		pr_err("aicbsp aicbt_patch_trap_data_load fail\n");
+		ret = -1;
+		goto err;
+	}
+
+	if (aicbt_patch_table_load(sdiodev, head)) {
+		pr_err("aicbsp aicbt_patch_table_load fail\n");
+		ret = -1;
+		goto err;
+	}
+
+err:
+	aicbt_patch_table_free(&head);
+	return ret;
+}
+
+int aicwifi_start_from_bootrom(struct aic_sdio_dev *sdiodev)
+{
+	int ret = 0;
+
+	/* memory access */
+	const u32 fw_addr = RAM_FMAC_FW_ADDR;
+	struct dbg_start_app_cfm start_app_cfm;
+
+	/* fw start */
+	ret = rwnx_send_dbg_start_app_req(sdiodev, fw_addr, HOST_START_APP_AUTO,
+					  &start_app_cfm);
+	if (ret)
+		return -1;
+	aicbsp_info.hwinfo_r = start_app_cfm.bootstatus & 0xFF;
+
+	return 0;
+}
+
+u32 adaptivity_patch_tbl[][2] = {
+	{0x0004, 0x0000320A}, // linkloss_thd
+	{0x0094, 0x00000000}, // ac_param_conf
+	{0x00F8, 0x00010138}, // tx_adaptivity_en
+};
+
+u32 patch_tbl[][2] = {
+#if !defined(CONFIG_LINK_DET_5G)
+	{0x0104, 0x00000000}, // link_det_5g
+#endif
+};
+
+u32 syscfg_tbl_masked[][3] = {
+	{0x40506024, 0x000000FF, 0x000000DF}, // for clk gate lp_level
+};
+
+u32 rf_tbl_masked[][3] = {
+	{0x40344058, 0x00800000, 0x00000000}, // pll trx
+};
+
+int aicwifi_sys_config(struct aic_sdio_dev *sdiodev)
+{
+	int ret, cnt;
+	int syscfg_num = sizeof(syscfg_tbl_masked) / sizeof(u32) / 3;
+
+	for (cnt = 0; cnt < syscfg_num; cnt++) {
+		ret = rwnx_send_dbg_mem_mask_write_req(sdiodev, syscfg_tbl_masked[cnt][0],
+						       syscfg_tbl_masked[cnt][1],
+						       syscfg_tbl_masked[cnt][2]);
+		if (ret) {
+			pr_err("aicbsp %x mask write fail: %d\n", syscfg_tbl_masked[cnt][0], ret);
+			return ret;
+		}
+	}
+
+	ret = rwnx_send_dbg_mem_mask_write_req(sdiodev, rf_tbl_masked[0][0], rf_tbl_masked[0][1],
+					       rf_tbl_masked[0][2]);
+	if (ret) {
+		pr_err("aicbsp rf config %x write fail: %d\n", rf_tbl_masked[0][0], ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+int aicwifi_patch_config(struct aic_sdio_dev *sdiodev)
+{
+	const u32 rd_patch_addr = RAM_FMAC_FW_ADDR + 0x0180;
+	u32 config_base;
+	u32 start_addr = 0x1e6000;
+	u32 patch_addr = start_addr;
+	u32 patch_num = sizeof(patch_tbl) / 4;
+	struct dbg_mem_read_cfm rd_patch_addr_cfm;
+	u32 patch_addr_reg = 0x1e5318;
+	u32 patch_num_reg = 0x1e531c;
+	int ret = 0;
+	u16 cnt = 0;
+	int tmp_cnt = 0;
+	int adap_patch_num = 0;
+
+	if (aicbsp_info.cpmode == AICBSP_CPMODE_TEST) {
+		patch_addr_reg = 0x1e5304;
+		patch_num_reg = 0x1e5308;
+	}
+
+	ret =
+		rwnx_send_dbg_mem_read_req(sdiodev, rd_patch_addr, &rd_patch_addr_cfm);
+	if (ret) {
+		pr_err("aicbsp patch rd fail\n");
+		return ret;
+	}
+
+	config_base = rd_patch_addr_cfm.memdata;
+
+	ret = rwnx_send_dbg_mem_write_req(sdiodev, patch_addr_reg, patch_addr);
+	if (ret) {
+		pr_err("aicbsp 0x%x write fail\n", patch_addr_reg);
+		return ret;
+	}
+
+	if (aicbsp_info.adap_test) {
+		pr_err("aicbsp %s for adaptivity test \r\n", __func__);
+		adap_patch_num = sizeof(adaptivity_patch_tbl) / 4;
+		ret = rwnx_send_dbg_mem_write_req(sdiodev, patch_num_reg,
+						  patch_num + adap_patch_num);
+	} else {
+		ret = rwnx_send_dbg_mem_write_req(sdiodev, patch_num_reg, patch_num);
+	}
+	if (ret) {
+		pr_err("aicbsp 0x%x write fail\n", patch_num_reg);
+		return ret;
+	}
+
+	for (cnt = 0; cnt < patch_num / 2; cnt += 1) {
+		ret = rwnx_send_dbg_mem_write_req(sdiodev, start_addr + 8 * cnt,
+						  patch_tbl[cnt][0] + config_base);
+		if (ret) {
+			pr_err("aicbsp %x write fail\n", start_addr + 8 * cnt);
+			return ret;
+		}
+
+		ret = rwnx_send_dbg_mem_write_req(sdiodev, start_addr + 8 * cnt + 4,
+						  patch_tbl[cnt][1]);
+		if (ret) {
+			pr_err("aicbsp %x write fail\n", start_addr + 8 * cnt + 4);
+			return ret;
+		}
+	}
+
+	tmp_cnt = cnt;
+
+	if (aicbsp_info.adap_test) { //(adap_test){
+		for (cnt = 0; cnt < adap_patch_num / 2; cnt += 1) {
+			ret = rwnx_send_dbg_mem_write_req(sdiodev,
+							  start_addr + 8 * (cnt + tmp_cnt),
+							  adaptivity_patch_tbl[cnt][0]
+							  + config_base);
+			if (ret)
+				pr_err("aicbsp %x write fail\n", start_addr + 8 * cnt);
+
+			ret = rwnx_send_dbg_mem_write_req(sdiodev, start_addr +
+							  8 * (cnt + tmp_cnt) + 4,
+							  adaptivity_patch_tbl[cnt][1]);
+			if (ret)
+				pr_err("aicbsp %x write fail\n", start_addr + 8 * cnt + 4);
+		}
+	}
+
+	return 0;
+}
+
+int aicwifi_init(struct aic_sdio_dev *sdiodev)
+{
+	int ret = 0;
+
+	ret = aic_chip_wifi_init(sdiodev, testmode);
+	if (ret) {
+		pr_err("aicbsp wifi init fail\n");
+		return ret;
+	}
+
+#ifdef CONFIG_GPIO_WAKEUP
+	if (aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.wakeup_reg, 4)) {
+		sdio_err("reg:%d write failed!\n", sdiodev->sdio_reg.wakeup_reg);
+		return -1;
+	}
+#endif
+	return 0;
+}
+
+u32 aicbsp_syscfg_tbl[][2] = {
+	{0x40500014, 0x00000101}, // 1)
+	{0x40500018, 0x00000109}, // 2)
+	{0x40500004, 0x00000010}, // 3) the order should not be changed
+
+	// def CONFIG_PMIC_SETTING
+	// U02 bootrom only
+	{0x40040000, 0x00001AC8}, // 1) fix panic
+	{0x40040084, 0x00011580},
+	{0x40040080, 0x00000001},
+	{0x40100058, 0x00000000},
+
+	{0x50000000, 0x03220204}, // 2) pmic interface init
+	{0x50019150, 0x00000002}, // 3) for 26m xtal, set div1
+	{0x50017008, 0x00000000}, // 4) stop wdg
+};
+
+int aicbsp_system_config(struct aic_sdio_dev *sdiodev)
+{
+	int syscfg_num = sizeof(aicbsp_syscfg_tbl) / sizeof(u32) / 2;
+	int ret, cnt;
+
+	for (cnt = 0; cnt < syscfg_num; cnt++) {
+		ret = rwnx_send_dbg_mem_write_req(sdiodev, aicbsp_syscfg_tbl[cnt][0],
+						  aicbsp_syscfg_tbl[cnt][1]);
+		if (ret) {
+			sdio_err("%x write fail: %d\n", aicbsp_syscfg_tbl[cnt][0], ret);
+			return ret;
+		}
+	}
+	return 0;
+}
+
+int aicbsp_platform_init(struct aic_sdio_dev *sdiodev)
+{
+	rwnx_cmd_mgr_init(&sdiodev->cmd_mgr);
+	sdiodev->cmd_mgr.sdiodev = (void *)sdiodev;
+
+	return 0;
+}
+
+void aicbsp_platform_deinit(struct aic_sdio_dev *sdiodev)
+{
+	(void)sdiodev;
+}
+
+int aicbsp_driver_fw_init(struct aic_sdio_dev *sdiodev)
+{
+	u32 btenable = 0;
+	int ret = 0;
+
+	testmode = aicbsp_info.cpmode;
+	cur_mode = aicbsp_info.cpmode;
+
+	ret = aic_chip_driver_fw_init(sdiodev, &btenable, &aicbsp_info, &aicbsp_firmware_list);
+	if (ret) {
+		pr_err("aicbsp chip driver fw init fail\n");
+		return -1;
+	}
+
+	AICWFDBG(LOGINFO, "aicbsp: %s, chip rev: %d\n", __func__,
+		 aicbsp_info.chip_rev);
+
+	if (testmode != 4) {
+		if (btenable == 1) {
+			if (aicbt_init(sdiodev))
+				return -1;
+		}
+	}
+
+	ret = aicwifi_init(sdiodev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+int aicbsp_get_feature(struct aicbsp_feature_t *feature)
+{
+	if (!aicbsp_sdiodev) {
+		sdio_err("%s, aicbsp_sdiodev is null\n", __func__);
+		return -1;
+	}
+
+	feature->sdio_clock = aicbsp_sdiodev->chip_ops->sdio_clock;
+
+	feature->sdio_phase = FEATURE_SDIO_PHASE;
+	feature->cpmode = aicbsp_info.cpmode;
+	feature->adapt = aicbsp_info.adap_test;
+	feature->hwinfo = aicbsp_info.hwinfo;
+	feature->fwlog_en = aicbsp_info.fwlog_en;
+	feature->irqf = aicbsp_info.irqf;
+	sdio_dbg("%s, set FEATURE_SDIO_CLOCK %d MHz\n", __func__,
+		 feature->sdio_clock / 1000000);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(aicbsp_get_feature);
+
+#ifdef CONFIG_RESV_MEM_SUPPORT
+static struct skb_buff_pool resv_skb[] = {
+	{AIC_RESV_MEM_TXDATA, 1536 * 64, "resv_mem_txdata", 0, NULL},
+};
+
+int aicbsp_resv_mem_init(void)
+{
+	int i = 0;
+
+	pr_info("aicbsp %s\n", __func__);
+	for (i = 0; i < ARRAY_SIZE(resv_skb); i++)
+		resv_skb[i].skb = dev_alloc_skb(resv_skb[i].size);
+	return 0;
+}
+
+int aicbsp_resv_mem_deinit(void)
+{
+	int i = 0;
+
+	pr_info("aicbsp %s\n", __func__);
+	for (i = 0; i < ARRAY_SIZE(resv_skb); i++) {
+		if (resv_skb[i].used == 0 && resv_skb[i].skb)
+			dev_kfree_skb(resv_skb[i].skb);
+	}
+	return 0;
+}
+
+struct sk_buff *aicbsp_resv_mem_alloc_skb(unsigned int length, uint32_t id)
+{
+	if (resv_skb[id].size < length) {
+		pr_err("aicbsp: %s, no enough mem\n", __func__);
+		goto fail;
+	}
+
+	if (resv_skb[id].used) {
+		pr_err("aicbsp: %s, mem in use\n", __func__);
+		goto fail;
+	}
+
+	if (!resv_skb[id].skb) {
+		//pr_err("aicbsp: %s, skb not initialazed\n", __func__);
+		resv_skb[id].skb =
+			dev_alloc_skb(resv_skb[id].size);
+		if (!resv_skb[id].skb) {
+			pr_err("aicbsp: %s, mem reinitial still fail\n", __func__);
+			goto fail;
+		}
+	}
+
+	pr_info("aicbsp: %s, alloc %s succuss, id: %d, size: %d\n", __func__,
+		resv_skb[id].name, resv_skb[id].id, resv_skb[id].size);
+
+	resv_skb[id].used = 1;
+	return resv_skb[id].skb;
+
+fail:
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(aicbsp_resv_mem_alloc_skb);
+
+void aicbsp_resv_mem_kfree_skb(struct sk_buff *skb, uint32_t id)
+{
+	resv_skb[id].used = 0;
+	pr_info("aicbsp: %s, free %s succuss, id: %d, size: %d\n", __func__,
+		resv_skb[id].name, resv_skb[id].id, resv_skb[id].size);
+}
+EXPORT_SYMBOL_GPL(aicbsp_resv_mem_kfree_skb);
+
+#else
+
+int aicbsp_resv_mem_init(void)
+{
+	return 0;
+}
+
+int aicbsp_resv_mem_deinit(void)
+{
+	return 0;
+}
+
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aic_bsp_driver.h b/drivers/net/wireless/aic/aic8800_bsp/aic_bsp_driver.h
new file mode 100644
index 0000000000000..37438f4a56f6c
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aic_bsp_driver.h
@@ -0,0 +1,621 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AIC_BSP_DRIVER_H
+#define _AIC_BSP_DRIVER_H
+
+#include "aic_bsp_export.h"
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+
+#define AIC8800_FW_DIR "aic8800/"
+
+#define RWNX_80211_CMD_TIMEOUT_MS 6000
+
+#define RWNX_CMD_FLAG_NONBLOCK  BIT(0)
+#define RWNX_CMD_FLAG_REQ_CFM   BIT(1)
+#define RWNX_CMD_FLAG_WAIT_PUSH BIT(2)
+#define RWNX_CMD_FLAG_WAIT_ACK  BIT(3)
+#define RWNX_CMD_FLAG_WAIT_CFM  BIT(4)
+#define RWNX_CMD_FLAG_DONE      BIT(5)
+/* ATM IPC design makes it possible to get the CFM before the ACK,
+ * otherwise this could have simply been a state enum
+ */
+#define RWNX_CMD_WAIT_COMPLETE(flags)                                          \
+	(!((flags) & (RWNX_CMD_FLAG_WAIT_ACK | RWNX_CMD_FLAG_WAIT_CFM)))
+
+#define RWNX_CMD_MAX_QUEUED 8
+
+#define IPC_E2A_MSG_PARAM_SIZE 256
+
+#define RWNX_FN_ENTRY_STR ">>> %s()\n", __func__
+
+/* message levels */
+#define LOGERROR 0x0001
+#define LOGINFO  0x0002
+#define LOGTRACE 0x0004
+#define LOGDEBUG 0x0008
+#define LOGDATA  0x0010
+
+extern int testmode;
+extern int cur_mode;
+extern int adap_test;
+extern struct aic_sdio_dev *aicbsp_sdiodev;
+extern int aicwf_dbg_level_bsp;
+
+#define AICWF_LOG "AICBSP("
+
+#define AICWFDBG(level, args, arg...)                                          \
+	do {                                                                       \
+		if (aicwf_dbg_level_bsp & (level)) {                                     \
+			pr_debug(AICWF_LOG #level ")\t" args, ##arg);                        \
+		}                                                                      \
+	} while (0)
+
+#define RWNX_DBG(fmt, ...)                                                     \
+	do {                                                                       \
+		if (aicwf_dbg_level_bsp & LOGTRACE) {                                  \
+			pr_debug(AICWF_LOG "LOGTRACE)\t" fmt, ##__VA_ARGS__);                \
+		}                                                                      \
+	} while (0)
+
+/// Message structure for MSGs from Emb to App
+struct ipc_e2a_msg {
+	u16 id; ///< Message id.
+	u16 dummy_dest_id;
+	u16 dummy_src_id;
+	u16 param_len;                     ///< Parameter embedded struct length.
+	u32 pattern;                       ///< Used to stamp a valid MSG buffer
+	u32 param[IPC_E2A_MSG_PARAM_SIZE]; ///< Parameter embedded struct. Must be
+									   ///< word-aligned.
+};
+
+typedef u16 lmac_msg_id_t;
+typedef u16 lmac_task_id_t;
+
+struct lmac_msg {
+	lmac_msg_id_t id;       ///< Message id.
+	lmac_task_id_t dest_id; ///< Destination kernel identifier.
+	lmac_task_id_t src_id;  ///< Source kernel identifier.
+	u16 param_len;          ///< Parameter embedded struct length.
+	u32 param[]; ///< Parameter embedded struct. Must be word-aligned.
+};
+
+#define rwnx_cmd_e2amsg         ipc_e2a_msg
+#define rwnx_cmd_a2emsg         lmac_msg
+#define RWNX_CMD_A2EMSG_LEN(m)  (sizeof(struct lmac_msg) + (m)->param_len)
+#define RWNX_CMD_E2AMSG_LEN_MAX (IPC_E2A_MSG_PARAM_SIZE * 4)
+
+static inline void put_u16(u8 *buf, u16 data)
+{
+	buf[0] = (u8)(data & 0x00ff);
+	buf[1] = (u8)((data >> 8) & 0x00ff);
+}
+
+enum rwnx_cmd_mgr_state {
+	RWNX_CMD_MGR_STATE_DEINIT,
+	RWNX_CMD_MGR_STATE_INITED,
+	RWNX_CMD_MGR_STATE_CRASHED,
+};
+
+struct rwnx_cmd {
+	struct list_head list;
+	lmac_msg_id_t id;
+	lmac_msg_id_t reqid;
+	struct rwnx_cmd_a2emsg *a2e_msg;
+	char *e2a_msg;
+	u32 tkn;
+	u16 flags;
+	struct completion complete;
+	u32 result;
+};
+
+struct aic_sdio_dev;
+struct rwnx_cmd;
+typedef int (*msg_cb_fct)(struct rwnx_cmd *cmd, struct rwnx_cmd_e2amsg *msg);
+
+struct rwnx_cmd_mgr {
+	enum rwnx_cmd_mgr_state state;
+	/* lock for cmd mgr */
+	spinlock_t lock;
+	u32 next_tkn;
+	u32 queue_sz;
+	u32 max_queue_sz;
+	/* lock for cb */
+	spinlock_t cb_lock;
+	void *sdiodev;
+
+	struct list_head cmds;
+
+	int (*queue)(struct rwnx_cmd_mgr *cmd_mgr, struct rwnx_cmd *cmd);
+	int (*llind)(struct rwnx_cmd_mgr *cmd_mgr, struct rwnx_cmd *cmd);
+	int (*msgind)(struct rwnx_cmd_mgr *cmd_mgr,
+		      struct rwnx_cmd_e2amsg *cmd_e2amsg,
+		      msg_cb_fct cb_fct);
+	void (*print)(struct rwnx_cmd_mgr *cmd_mgr);
+	void (*drain)(struct rwnx_cmd_mgr *cmd_mgr);
+
+	struct work_struct cmd_work;
+	struct workqueue_struct *cmd_wq;
+};
+
+void rwnx_cmd_mgr_init(struct rwnx_cmd_mgr *cmd_mgr);
+void rwnx_cmd_mgr_deinit(struct rwnx_cmd_mgr *cmd_mgr);
+int cmd_mgr_queue_force_defer(struct rwnx_cmd_mgr *cmd_mgr,
+			      struct rwnx_cmd *cmd);
+void rwnx_set_cmd_tx(void *dev, struct lmac_msg *msg, uint len);
+
+enum {
+	TASK_NONE = (u8)-1,
+
+	// MAC Management task.
+	TASK_MM = 0,
+	// DEBUG task
+	TASK_DBG,
+	/// SCAN task
+	TASK_SCAN,
+	/// TDLS task
+	TASK_TDLS,
+	/// SCANU task
+	TASK_SCANU,
+	/// ME task
+	TASK_ME,
+	/// SM task
+	TASK_SM,
+	/// APM task
+	TASK_APM,
+	/// BAM task
+	TASK_BAM,
+	/// MESH task
+	TASK_MESH,
+	/// RXU task
+	TASK_RXU,
+	// This is used to define the last task that is running on the EMB processor
+	TASK_LAST_EMB = TASK_RXU,
+
+	// nX API task
+	TASK_API,
+	TASK_MAX,
+};
+
+#define LMAC_FIRST_MSG(task) ((lmac_msg_id_t)((task) << 10))
+#define DRV_TASK_ID          100
+#define MSG_I(msg)           ((msg) & ((1 << 10) - 1))
+#define MSG_T(msg)           ((lmac_task_id_t)((msg) >> 10))
+
+enum dbg_msg_tag {
+	/// Memory read request
+	DBG_MEM_READ_REQ = LMAC_FIRST_MSG(TASK_DBG),
+	/// Memory read confirm
+	DBG_MEM_READ_CFM,
+	/// Memory write request
+	DBG_MEM_WRITE_REQ,
+	/// Memory write confirm
+	DBG_MEM_WRITE_CFM,
+	/// Module filter request
+	DBG_SET_MOD_FILTER_REQ,
+	/// Module filter confirm
+	DBG_SET_MOD_FILTER_CFM,
+	/// Severity filter request
+	DBG_SET_SEV_FILTER_REQ,
+	/// Severity filter confirm
+	DBG_SET_SEV_FILTER_CFM,
+	/// LMAC/MAC HW fatal error indication
+	DBG_ERROR_IND,
+	/// Request to get system statistics
+	DBG_GET_SYS_STAT_REQ,
+	/// COnfirmation of system statistics
+	DBG_GET_SYS_STAT_CFM,
+	/// Memory block write request
+	DBG_MEM_BLOCK_WRITE_REQ,
+	/// Memory block write confirm
+	DBG_MEM_BLOCK_WRITE_CFM,
+	/// Start app request
+	DBG_START_APP_REQ,
+	/// Start app confirm
+	DBG_START_APP_CFM,
+	/// Start npc request
+	DBG_START_NPC_REQ,
+	/// Start npc confirm
+	DBG_START_NPC_CFM,
+	/// Memory mask write request
+	DBG_MEM_MASK_WRITE_REQ,
+	/// Memory mask write confirm
+	DBG_MEM_MASK_WRITE_CFM,
+
+	DBG_RFTEST_CMD_REQ,
+	DBG_RFTEST_CMD_CFM,
+	DBG_BINDING_REQ,
+	DBG_BINDING_CFM,
+	DBG_BINDING_IND,
+
+	DBG_CUSTOM_MSG_REQ,
+	DBG_CUSTOM_MSG_CFM,
+	DBG_CUSTOM_MSG_IND,
+
+	DBG_GPIO_WRITE_REQ,
+	DBG_GPIO_WRITE_CFM,
+
+	/// Max number of Debug messages
+	DBG_MAX,
+};
+
+#if !defined(CONFIG_M2D_OTA_LZMA_SUPPORT)
+#define FW_M2D_OTA_NAME "m2d_ota.bin"
+#else
+#define FW_M2D_OTA_NAME "m2d_ota_lzma.bin"
+#endif
+
+enum {
+	HOST_START_APP_AUTO = 1,
+	HOST_START_APP_CUSTOM,
+	HOST_START_APP_FNCALL = 4,
+	HOST_START_APP_DUMMY = 5,
+};
+
+struct dbg_mem_block_write_req {
+	u32 memaddr;
+	u32 memsize;
+	u32 memdata[1024 / sizeof(u32)];
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_BLOCK_WRITE_CFM
+/// message.
+struct dbg_mem_block_write_cfm {
+	u32 wstatus;
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_WRITE_REQ message.
+struct dbg_mem_write_req {
+	u32 memaddr;
+	u32 memdata;
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_WRITE_CFM message.
+struct dbg_mem_write_cfm {
+	u32 memaddr;
+	u32 memdata;
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_READ_REQ message.
+struct dbg_mem_read_req {
+	u32 memaddr;
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_READ_CFM message.
+struct dbg_mem_read_cfm {
+	u32 memaddr;
+	u32 memdata;
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_MASK_WRITE_REQ
+/// message.
+struct dbg_mem_mask_write_req {
+	u32 memaddr;
+	u32 memmask;
+	u32 memdata;
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_MASK_WRITE_CFM
+/// message.
+struct dbg_mem_mask_write_cfm {
+	u32 memaddr;
+	u32 memdata;
+};
+
+/// Structure containing the parameters of the @ref DBG_START_APP_REQ message.
+struct dbg_start_app_req {
+	u32 bootaddr;
+	u32 boottype;
+};
+
+/// Structure containing the parameters of the @ref DBG_START_APP_CFM message.
+struct dbg_start_app_cfm {
+	u32 bootstatus;
+};
+
+int aicwf_plat_patch_load_8800dc(struct aic_sdio_dev *sdiodev);
+int aicwf_plat_rftest_load_8800dc(struct aic_sdio_dev *sdiodev);
+#ifdef CONFIG_DPD
+int aicwf_misc_ram_valid_check_8800dc(struct aic_sdio_dev *sdiodev,
+				      int *valid_out);
+int aicwf_plat_calib_load_8800dc(struct aic_sdio_dev *sdiodev);
+#endif
+
+int rwnx_load_firmware(u32 **fw_buf, const char *name, struct device *device);
+int aicwf_patch_table_load(struct aic_sdio_dev *rwnx_hw, char *filename);
+
+int rwnx_send_dbg_mem_read_req(struct aic_sdio_dev *sdiodev, u32 mem_addr,
+			       struct dbg_mem_read_cfm *cfm);
+int rwnx_send_dbg_mem_block_write_req(struct aic_sdio_dev *sdiodev,
+				      u32 mem_addr, u32 mem_size,
+				      u32 *mem_data);
+int rwnx_send_dbg_mem_write_req(struct aic_sdio_dev *sdiodev, u32 mem_addr,
+				u32 mem_data);
+int rwnx_send_dbg_mem_mask_write_req(struct aic_sdio_dev *sdiodev, u32 mem_addr,
+				     u32 mem_mask, u32 mem_data);
+int rwnx_send_dbg_start_app_req(struct aic_sdio_dev *sdiodev, u32 boot_addr,
+				u32 boot_type,
+				struct dbg_start_app_cfm *start_app_cfm);
+
+int rwnx_plat_bin_fw_upload(struct aic_sdio_dev *sdiodev, u32 fw_addr,
+			    const char *filename);
+#ifdef CONFIG_M2D_OTA_AUTO_SUPPORT
+int rwnx_plat_m2d_flash_ota(struct aic_sdio_dev *sdiodev, char *filename);
+int rwnx_plat_m2d_flash_ota_check(struct aic_sdio_dev *sdiodev,
+				  char *filename);
+#endif
+
+void rwnx_rx_handle_msg(struct aic_sdio_dev *sdiodev, struct ipc_e2a_msg *msg);
+int aicbsp_platform_init(struct aic_sdio_dev *sdiodev);
+void aicbsp_platform_deinit(struct aic_sdio_dev *sdiodev);
+int aicbsp_driver_fw_init(struct aic_sdio_dev *sdiodev);
+#if (defined(CONFIG_DPD) && !defined(CONFIG_FORCE_DPD_CALIB))
+#endif
+int aicbsp_resv_mem_init(void);
+int aicbsp_resv_mem_deinit(void);
+
+#define RAM_FMAC_FW_ADDR                    0x00120000
+#define RAM_FMAC_FW_PATCH_ADDR              0x00190000
+#define FW_RAM_ADID_BASE_ADDR               0x00161928
+#define FW_RAM_ADID_BASE_ADDR_U03           0x00161928
+#define FW_RAM_PATCH_BASE_ADDR              0x00100000
+#define RAM_8800DC_U01_ADID_ADDR            0x00101788
+#define RAM_8800DC_U02_ADID_ADDR            0x001017d8
+#define RAM_8800DC_FW_PATCH_ADDR            0x00184000
+#define FW_RESET_START_ADDR                 0x40500128
+#define FW_RESET_START_VAL                  0x40
+#define FW_ADID_FLAG_ADDR                   0x40500150
+#define FW_ADID_FLAG_VAL                    0x01
+#define FW_RAM_ADID_BASE_ADDR_8800D80       0x002017E0
+#define FW_RAM_PATCH_BASE_ADDR_8800D80      0x0020B2B0
+#define FW_RAM_ADID_BASE_ADDR_8800D80_U02   0x00201940
+#define FW_RAM_PATCH_BASE_ADDR_8800D80_U02  0x0020b43c
+
+#define AICBT_PT_TAG "AICBT_PT_TAG"
+
+/*****************************************************************************
+ * Addresses within RWNX_ADDR_CPU
+ *****************************************************************************/
+#define RAM_LMAC_FW_ADDR 0x00150000
+
+#define ROM_FMAC_FW_ADDR    0x00010000
+#define ROM_FMAC_PATCH_ADDR 0x00180000
+
+#define RAM_FMAC_FW_PATCH_NAME			"fmacfw_patch.bin"
+#define RWNX_MAC_CALIB_BASE_NAME_8800DC         "fmacfw_calib_8800dc"
+#define RWNX_MAC_CALIB_NAME_8800DC_U02          RWNX_MAC_CALIB_BASE_NAME_8800DC "_u02.bin"
+#ifdef CONFIG_SDIO_BT
+#define RWNX_MAC_CALIB_NAME_8800DC_H_U02                                       \
+	RWNX_MAC_CALIB_BASE_NAME_8800DC "_hbt_u02.bin"
+#else
+#define RWNX_MAC_CALIB_NAME_8800DC_H_U02                                       \
+	RWNX_MAC_CALIB_BASE_NAME_8800DC "_h_u02.bin"
+#endif
+
+#ifdef CONFIG_DPD
+#define ROM_FMAC_CALIB_ADDR 0x00130000
+#ifndef CONFIG_FORCE_DPD_CALIB
+#define FW_DPDRESULT_NAME_8800DC "aic_dpdresult_lite_8800dc.bin"
+#endif
+#endif
+
+#define RWNX_MAC_FW_RF_BASE_NAME_8800DC "lmacfw_rf_8800dc.bin"
+
+#ifdef CONFIG_FOR_IPCOM
+#define RWNX_MAC_PATCH_BASE_NAME_8800DC "fmacfw_patch_8800dc_ipc"
+#define RWNX_MAC_PATCH_NAME2_8800DC     RWNX_MAC_PATCH_BASE_NAME_8800DC ".bin"
+#else
+#define RWNX_MAC_PATCH_BASE_NAME_8800DC "fmacfw_patch_8800dc"
+#define RWNX_MAC_PATCH_NAME2_8800DC     RWNX_MAC_PATCH_BASE_NAME_8800DC ".bin"
+#define RWNX_MAC_PATCH_NAME2_8800DC_U02                                        \
+	RWNX_MAC_PATCH_BASE_NAME_8800DC "_u02.bin"
+#ifdef CONFIG_SDIO_BT
+#define RWNX_MAC_PATCH_NAME2_8800DC_H_U02                                      \
+	RWNX_MAC_PATCH_BASE_NAME_8800DC "_hbt_u02.bin"
+#else
+#define RWNX_MAC_PATCH_NAME2_8800DC_H_U02                                      \
+	RWNX_MAC_PATCH_BASE_NAME_8800DC "_h_u02.bin"
+#endif
+#endif
+
+#define RWNX_MAC_PATCH_TABLE_NAME_8800DC "fmacfw_patch_tbl_8800dc"
+#define RWNX_MAC_PATCH_TABLE_8800DC      RWNX_MAC_PATCH_TABLE_NAME_8800DC ".bin"
+#define RWNX_MAC_PATCH_TABLE_8800DC_U02                                        \
+	RWNX_MAC_PATCH_TABLE_NAME_8800DC "_u02.bin"
+#ifdef CONFIG_SDIO_BT
+#define RWNX_MAC_PATCH_TABLE_8800DC_H_U02                                      \
+	RWNX_MAC_PATCH_TABLE_NAME_8800DC "_hbt_u02.bin"
+#else
+#define RWNX_MAC_PATCH_TABLE_8800DC_H_U02                                      \
+	RWNX_MAC_PATCH_TABLE_NAME_8800DC "_h_u02.bin"
+#endif
+
+#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"
+
+enum {
+	FW_NORMAL_MODE = 0,
+	FW_RFTEST_MODE = 1,
+	FW_BLE_SCAN_WAKEUP_MODE = 2,
+	FW_M2D_OTA_MODE = 3,
+	FW_DPDCALIB_MODE = 4,
+	FW_BLE_SCAN_AD_FILTER_MODE = 5,
+};
+
+enum aicbt_patch_table_type {
+	AICBT_PT_INF = 0x00,
+	AICBT_PT_TRAP = 0x1,
+	AICBT_PT_B4,
+	AICBT_PT_BTMODE,
+	AICBT_PT_PWRON,
+	AICBT_PT_AF,
+	AICBT_PT_VER,
+};
+
+enum aicbt_btport_type {
+	AICBT_BTPORT_NULL,
+	AICBT_BTPORT_MB,
+	AICBT_BTPORT_UART,
+};
+
+/*  btmode
+ * used for force bt mode,if not AICBSP_MODE_NULL
+ * efuse valid and vendor_info will be invalid, even has beed set valid
+ */
+enum aicbt_btmode_type {
+	AICBT_BTMODE_BT_ONLY_SW = 0x0,   // bt only mode with switch
+	AICBT_BTMODE_BT_WIFI_COMBO,      // wifi/bt combo mode
+	AICBT_BTMODE_BT_ONLY,            // bt only mode without switch
+	AICBT_BTMODE_BT_ONLY_TEST,       // bt only test mode
+	AICBT_BTMODE_BT_WIFI_COMBO_TEST, // wifi/bt combo test mode
+	AICBT_BTMODE_BT_ONLY_COANT,      // bt only mode with no external switch
+	AICBT_MODE_NULL = 0xFF,          // invalid value
+};
+
+/*  uart_baud
+ * used for config uart baud when btport set to uart,
+ * otherwise meaningless
+ */
+enum aicbt_uart_baud_type {
+	AICBT_UART_BAUD_115200 = 115200,
+	AICBT_UART_BAUD_921600 = 921600,
+	AICBT_UART_BAUD_1_5M = 1500000,
+	AICBT_UART_BAUD_3_25M = 3250000,
+};
+
+enum aicbt_uart_flowctrl_type {
+	AICBT_UART_FLOWCTRL_DISABLE = 0x0, // uart without flow ctrl
+	AICBT_UART_FLOWCTRL_ENABLE,        // uart with flow ctrl
+};
+
+enum aicbsp_cpmode_type {
+	AICBSP_CPMODE_WORK,
+	AICBSP_CPMODE_TEST,
+	AICBSP_CPMODE_MAX,
+};
+
+enum chip_rev {
+	CHIP_REV_U01 = 1,
+	CHIP_REV_U02 = 3,
+	CHIP_REV_U03 = 7,
+	CHIP_REV_U04 = 7,
+};
+
+#define AIC_M2D_OTA_INFO_ADDR 0x88000020
+#define AIC_M2D_OTA_DATA_ADDR 0x88000040
+#if !defined(CONFIG_M2D_OTA_LZMA_SUPPORT)
+#define AIC_M2D_OTA_FLASH_ADDR      0x08004000
+#define AIC_M2D_OTA_CODE_START_ADDR (AIC_M2D_OTA_FLASH_ADDR + 0x0188)
+#define AIC_M2D_OTA_VER_ADDR        (AIC_M2D_OTA_FLASH_ADDR + 0x018C)
+#else
+#define AIC_M2D_OTA_FLASH_ADDR      0x08005000
+#define AIC_M2D_OTA_CODE_START_ADDR (AIC_M2D_OTA_FLASH_ADDR + 0x1188)
+#define AIC_M2D_OTA_VER_ADDR        (AIC_M2D_OTA_FLASH_ADDR + 0x0010)
+#endif
+/// aic bt tx pwr lvl :lsb->msb: first byte, min pwr lvl; second byte, max pwr
+/// lvl; pwr lvl:20(min), 30 , 40 , 50 , 60(max)
+#define AICBT_TXPWR_LVL         0x00006020
+#define AICBT_TXPWR_LVL_8800dc  0x00006f2f
+#define AICBT_TXPWR_LVL_8800d80 0x00006f2f
+
+#define AICBSP_HWINFO_DEFAULT   (-1)
+#define AICBSP_CPMODE_DEFAULT   AICBSP_CPMODE_WORK
+#define AICBSP_FWLOG_EN_DEFAULT 0
+
+#define AICBT_BTMODE_DEFAULT_8800d80 AICBT_BTMODE_BT_ONLY_COANT
+#define AICBT_BTMODE_DEFAULT         AICBT_BTMODE_BT_ONLY_SW
+#ifdef CONFIG_SDIO_BT
+#define AICBT_BTPORT_DEFAULT AICBT_BTPORT_MB
+#else
+#define AICBT_BTPORT_DEFAULT AICBT_BTPORT_UART
+#endif
+#define AICBT_UART_BAUD_DEFAULT         AICBT_UART_BAUD_1_5M
+#define AICBT_UART_FC_DEFAULT           AICBT_UART_FLOWCTRL_ENABLE
+#define AICBT_LPM_ENABLE_DEFAULT        1
+#define AICBT_TXPWR_LVL_DEFAULT         AICBT_TXPWR_LVL
+#define AICBT_TXPWR_LVL_DEFAULT_8800dc  AICBT_TXPWR_LVL_8800dc
+#define AICBT_TXPWR_LVL_DEFAULT_8800d80 AICBT_TXPWR_LVL_8800d80
+
+#define FEATURE_SDIO_CLOCK    50000000  // 0: default, other: target clock rate
+#define FEATURE_SDIO_CLOCK_V3 150000000 // 0: default, other: target clock rate
+#define FEATURE_SDIO_PHASE    2         // 0: default, 2: 180°
+
+struct aicbt_patch_table {
+	char *name;
+	u32 type;
+	u32 *data;
+	u32 len;
+	struct aicbt_patch_table *next;
+};
+
+struct aicbt_info_t {
+	u32 btmode;
+	u32 btport;
+	u32 uart_baud;
+	u32 uart_flowctrl;
+	u32 lpm_enable;
+	u32 txpwr_lvl;
+};
+
+struct aicbt_patch_info_t {
+	u32 info_len;
+	u32 adid_addrinf;
+	u32 addr_adid;
+	u32 patch_addrinf;
+	u32 addr_patch;
+	u32 reset_addr;
+	u32 reset_val;
+	u32 adid_flag_addr;
+	u32 adid_flag;
+};
+
+struct aicbsp_firmware {
+	const char *desc;
+	const char *bt_adid;
+	const char *bt_patch;
+	const char *bt_table;
+	const char *wl_fw;
+};
+
+struct aicbsp_info_t {
+	int hwinfo;
+	int hwinfo_r;
+	u32 cpmode;
+	u32 adap_test;
+	u32 chip_rev;
+	bool fwlog_en;
+	u8 irqf;
+};
+
+extern int adap_test;
+
+extern struct aicbsp_info_t aicbsp_info;
+extern struct mutex aicbsp_power_lock;
+extern const struct aicbsp_firmware *aicbsp_firmware_list;
+extern const struct aicbsp_firmware fw_u02[];
+extern const struct aicbsp_firmware fw_u03[];
+extern const struct aicbsp_firmware fw_8800dc_u01[];
+extern const struct aicbsp_firmware fw_8800dc_u02[];
+extern const struct aicbsp_firmware fw_8800dc_h_u02[];
+extern const struct aicbsp_firmware fw_8800d80_u01[];
+extern const struct aicbsp_firmware fw_8800d80_u02[];
+extern const struct aicbsp_firmware fw_8800d80_h_u02[];
+
+int aicbsp_system_config(struct aic_sdio_dev *sdiodev);
+int aicwifi_patch_config(struct aic_sdio_dev *sdiodev);
+int aicwifi_sys_config(struct aic_sdio_dev *sdiodev);
+int aicbt_patch_table_free(struct aicbt_patch_table **head);
+struct aicbt_patch_table *aicbt_patch_table_alloc(const char *filename);
+int aicbt_patch_info_unpack(struct aicbt_patch_info_t *patch_info,
+			    struct aicbt_patch_table *head_t);
+int aicbt_patch_trap_data_load(struct aic_sdio_dev *sdiodev,
+			       struct aicbt_patch_table *head);
+int aicbt_patch_table_load(struct aic_sdio_dev *sdiodev,
+			   struct aicbt_patch_table *head);
+int aicbt_init(struct aic_sdio_dev *sdiodev);
+int aicwifi_init(struct aic_sdio_dev *sdiodev);
+int aicwifi_start_from_bootrom(struct aic_sdio_dev *sdiodev);
+
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aic_bsp_export.h b/drivers/net/wireless/aic/aic8800_bsp/aic_bsp_export.h
new file mode 100644
index 0000000000000..5d2a5c38c95e6
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aic_bsp_export.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __AIC_BSP_EXPORT_H
+#define __AIC_BSP_EXPORT_H
+
+#include <linux/types.h>
+
+enum aicbsp_subsys {
+	AIC_BLUETOOTH,
+	AIC_WIFI,
+};
+
+enum aicbsp_pwr_state {
+	AIC_PWR_OFF,
+	AIC_PWR_ON,
+};
+
+enum skb_buff_id {
+	AIC_RESV_MEM_TXDATA,
+};
+
+struct skb_buff_pool {
+	u32 id;
+	u32 size;
+	const char *name;
+	u8 used;
+	struct sk_buff *skb;
+};
+
+struct aicbsp_feature_t {
+	int hwinfo;
+	u32 sdio_clock;
+	u8 sdio_phase;
+	bool fwlog_en;
+	u8 irqf;
+	u8 cpmode;
+	int adapt;
+};
+
+#ifdef CONFIG_DPD
+struct rf_misc_ram_t {
+	u32 bit_mask[3];
+	u32 reserved;
+	u32 dpd_high[96];
+	u32 dpd_11b[96];
+	u32 dpd_low[96];
+	u32 idac_11b[48];
+	u32 idac_high[48];
+	u32 idac_low[48];
+	u32 loft_res[18];
+	u32 rx_iqim_res[16];
+};
+
+struct rf_misc_ram_lite_t {
+	u32 bit_mask[4];
+	u32 dpd_high[96];
+	u32 loft_res[18];
+};
+
+#define MEMBER_SIZE(type, member) sizeof_field(type, member)
+#define DPD_RESULT_SIZE_8800DC    sizeof(struct rf_misc_ram_lite_t)
+
+extern struct rf_misc_ram_lite_t dpd_res;
+#endif
+
+int aicbsp_set_subsys(int, int);
+int aicbsp_get_feature(struct aicbsp_feature_t *feature);
+struct sk_buff *aicbsp_resv_mem_alloc_skb(unsigned int length, uint32_t id);
+void aicbsp_resv_mem_kfree_skb(struct sk_buff *skb, uint32_t id);
+
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aic_bsp_main.c b/drivers/net/wireless/aic/aic8800_bsp/aic_bsp_main.c
new file mode 100644
index 0000000000000..98ba39c8b0fc1
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aic_bsp_main.c
@@ -0,0 +1,556 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief bsp sdio main function
+ *
+ ******************************************************************************
+ */
+
+#include "aic_bsp_driver.h"
+#include "aicwf_txq_prealloc.h"
+#include "rwnx_version_gen.h"
+#include <linux/errno.h>
+#include <linux/inetdevice.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/version.h>
+
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_platform.h>
+
+#define DRV_DESCRIPTION "AIC BSP"
+#define DRV_COPYRIGHT   "Copyright(c) 2015-2020 AICSemi"
+#define DRV_AUTHOR      "AICSemi"
+#define DRV_VERS_MOD    "1.0"
+
+int aicwf_dbg_level_bsp = LOGERROR; // | LOGINFO | LOGDEBUG | LOGTRACE;
+module_param(aicwf_dbg_level_bsp, int, 0660);
+
+static struct platform_device *aicbsp_pdev;
+
+const struct aicbsp_firmware *aicbsp_firmware_list = fw_u02;
+
+const struct aicbsp_firmware fw_u02[] = {
+	[AICBSP_CPMODE_WORK] = {.desc = "normal work mode(sdio u02)",
+							.bt_adid = "fw_adid.bin",
+							.bt_patch = "fw_patch.bin",
+							.bt_table = "fw_patch_table.bin",
+#ifdef CONFIG_SDIO_BT
+							.wl_fw = "fmacfwbt.bin"
+#else
+							.wl_fw = "fmacfw.bin"
+#endif
+	},
+	[AICBSP_CPMODE_TEST] = {.desc = "rf test mode(sdio u02)",
+							.bt_adid = "fw_adid.bin",
+							.bt_patch = "fw_patch.bin",
+							.bt_table = "fw_patch_table.bin",
+							.wl_fw = "fmacfw_rf.bin"},
+};
+
+const struct aicbsp_firmware fw_u03[] = {
+	[AICBSP_CPMODE_WORK] = {.desc = "normal work mode(sdio u03/u04)",
+							.bt_adid = "fw_adid_u03.bin",
+							.bt_patch = "fw_patch_u03.bin",
+							.bt_table = "fw_patch_table_u03.bin",
+#ifdef CONFIG_SDIO_BT
+							.wl_fw = "fmacfwbt.bin"
+#else
+							.wl_fw = "fmacfw.bin"
+#endif
+	},
+
+	[AICBSP_CPMODE_TEST] = {.desc = "rf test mode(sdio u03/u04)",
+							.bt_adid = "fw_adid_u03.bin",
+							.bt_patch = "fw_patch_u03.bin",
+							.bt_table = "fw_patch_table_u03.bin",
+							.wl_fw = "fmacfw_rf.bin"},
+};
+
+const struct aicbsp_firmware fw_8800dc_u01[] = {
+	[AICBSP_CPMODE_WORK] = {.desc = "normal work mode(sdio u01)",
+							.bt_adid = "fw_adid_8800dc.bin",
+							.bt_patch = "fw_patch_8800dc.bin",
+							.bt_table = "fw_patch_table_8800dc.bin",
+							.wl_fw = "fmacfw_8800dc.bin"},
+
+	[AICBSP_CPMODE_TEST] = {.desc = "rf test mode(sdio u01)",
+							.bt_adid = "fw_adid_8800dc.bin",
+							.bt_patch = "fw_patch_8800dc.bin",
+							.bt_table = "fw_patch_table_8800dc.bin",
+							.wl_fw = "fmacfw_rf_8800dc.bin"},
+};
+
+const struct aicbsp_firmware fw_8800dc_u02[] = {
+	[AICBSP_CPMODE_WORK] = {.desc = "normal work mode(8800dc sdio u02)",
+							.bt_adid = "fw_adid_8800dc_u02.bin",
+							.bt_patch = "fw_patch_8800dc_u02.bin",
+							.bt_table = "fw_patch_table_8800dc_u02.bin",
+							.wl_fw = "fmacfw_patch_8800dc_u02.bin"},
+
+	[AICBSP_CPMODE_TEST] = {
+			.desc = "rf test mode(8800dc sdio u02)",
+			.bt_adid = "fw_adid_8800dc_u02.bin",
+			.bt_patch = "fw_patch_8800dc_u02.bin",
+			.bt_table = "fw_patch_table_8800dc_u02.bin",
+			.wl_fw = "lmacfw_rf_8800dc.bin" // u01,u02 lmacfw load same bin
+		},
+};
+
+const struct aicbsp_firmware fw_8800dc_h_u02[] = {
+	[AICBSP_CPMODE_WORK] = {.desc = "normal work mode(8800dc_h sdio u02)",
+				.bt_adid = "fw_adid_8800dc_u02h.bin",
+				.bt_patch = "fw_patch_8800dc_u02h.bin",
+				.bt_table = "fw_patch_table_8800dc_u02h.bin",
+				.wl_fw = "fmacfw_patch_8800dc_h_u02.bin"},
+
+	[AICBSP_CPMODE_TEST] = {
+			.desc = "rf test mode(8800dc_h sdio u02)",
+			.bt_adid = "fw_adid_8800dc_u02h.bin",
+			.bt_patch = "fw_patch_8800dc_u02h.bin",
+			.bt_table = "fw_patch_table_8800dc_u02h.bin",
+			.wl_fw = "lmacfw_rf_8800dc.bin" // u01,u02 lmacfw load same bin
+		},
+};
+
+const struct aicbsp_firmware fw_8800d80_u01[] = {
+	[AICBSP_CPMODE_WORK] = {.desc = "normal work mode(8800d80 sdio u01)",
+							.bt_adid = "fw_adid_8800d80.bin",
+							.bt_patch = "fw_patch_8800d80.bin",
+							.bt_table = "fw_patch_table_8800d80.bin",
+							.wl_fw = "fmacfw_8800d80.bin"},
+
+	[AICBSP_CPMODE_TEST] = {.desc = "rf test mode(8800d80 sdio u01)",
+							.bt_adid = "fw_adid_8800d80.bin",
+							.bt_patch = "fw_patch_8800d80.bin",
+							.bt_table = "fw_patch_table_8800d80.bin",
+							.wl_fw = "lmacfw_rf_8800d80.bin"},
+};
+
+const struct aicbsp_firmware fw_8800d80_u02[] = {
+	[AICBSP_CPMODE_WORK] = {.desc = "normal work mode(8800d80 sdio u02)",
+				.bt_adid = "fw_adid_8800d80_u02.bin",
+				.bt_patch = "fw_patch_8800d80_u02.bin",
+				.bt_table = "fw_patch_table_8800d80_u02.bin",
+#ifdef CONFIG_SDIO_BT
+				.wl_fw = "fmacfwbt_8800d80_u02.bin"
+#else
+				.wl_fw = "fmacfw_8800d80_u02.bin"
+#endif
+	},
+
+	[AICBSP_CPMODE_TEST] = {.desc = "rf test mode(8800d80 sdio u02)",
+				.bt_adid = "fw_adid_8800d80_u02.bin",
+				.bt_patch = "fw_patch_8800d80_u02.bin",
+				.bt_table = "fw_patch_table_8800d80_u02.bin",
+				.wl_fw = "lmacfw_rf_8800d80_u02.bin"},
+};
+
+const struct aicbsp_firmware fw_8800d80_h_u02[] = {
+	[AICBSP_CPMODE_WORK] = {.desc = "normal work mode(8800d80 sdio h_u02)",
+				.bt_adid = "fw_adid_8800d80_u02.bin",
+				.bt_patch = "fw_patch_8800d80_u02.bin",
+				.bt_table = "fw_patch_table_8800d80_u02.bin",
+#ifdef CONFIG_SDIO_BT
+				.wl_fw = "fmacfwbt_8800d80_h_u02.bin"
+#else
+				.wl_fw = "fmacfw_8800d80_h_u02.bin"
+#endif
+	},
+
+	[AICBSP_CPMODE_TEST] = {.desc = "rf test mode(8800d80 sdio u02)",
+				.bt_adid = "fw_adid_8800d80_u02.bin",
+				.bt_patch = "fw_patch_8800d80_u02.bin",
+				.bt_table = "fw_patch_table_8800d80_u02.bin",
+				.wl_fw = "lmacfw_rf_8800d80_u02.bin"},
+};
+
+struct aicbsp_info_t aicbsp_info = {
+	.hwinfo_r = AICBSP_HWINFO_DEFAULT,
+	.hwinfo = AICBSP_HWINFO_DEFAULT,
+	.cpmode = AICBSP_CPMODE_DEFAULT,
+	.fwlog_en = AICBSP_FWLOG_EN_DEFAULT,
+	.adap_test = 0,
+#ifdef CONFIG_IRQ_FALL
+	.irqf = 1,
+#else
+	.irqf = 0,
+#endif
+};
+
+/* mutex for aicbsp power */
+struct mutex aicbsp_power_lock;
+
+static struct platform_driver aicbsp_driver = {
+	.driver = {
+			.owner = THIS_MODULE,
+			.name = "aic_bsp",
+		},
+};
+
+static ssize_t cpmode_show(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	ssize_t count = 0;
+	u8 i = 0;
+
+	count += sprintf(&buf[count], "Support mode value:\n");
+
+	for (i = 0; i < AICBSP_CPMODE_MAX; i++) {
+		if (aicbsp_firmware_list[i].desc)
+			count += sprintf(&buf[count], " %2d: %s\n", i,
+							 aicbsp_firmware_list[i].desc);
+	}
+
+	count += sprintf(&buf[count], "Current: %d, firmware info:\n",
+					 aicbsp_info.cpmode);
+	count += sprintf(&buf[count], "  BT ADID : %s\n",
+					 aicbsp_firmware_list[aicbsp_info.cpmode].bt_adid);
+	count += sprintf(&buf[count], "  BT PATCH: %s\n",
+					 aicbsp_firmware_list[aicbsp_info.cpmode].bt_patch);
+	count += sprintf(&buf[count], "  BT TABLE: %s\n",
+					 aicbsp_firmware_list[aicbsp_info.cpmode].bt_table);
+	/* mod cpmode display */
+	// count += sprintf(&buf[count], "  WIFI FW : %s\n",
+	// aicbsp_firmware_list[aicbsp_info.cpmode].wl_fw);
+	count += sprintf(&buf[count], "  WIFI FW : %s\n",
+					 aicbsp_firmware_list[cur_mode].wl_fw);
+	return count;
+}
+
+static ssize_t cpmode_store(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	unsigned long val;
+	int err = kstrtoul(buf, 0, &val);
+
+	if (err)
+		return err;
+
+	if (val >= AICBSP_CPMODE_MAX) {
+		pr_err("mode value must less than %d\n", AICBSP_CPMODE_MAX);
+		return -EINVAL;
+	}
+
+	aicbsp_info.cpmode = val;
+	pr_info("%s, set mode to: %lu[%s] done\n", __func__, val,
+		aicbsp_firmware_list[val].desc);
+
+	return count;
+}
+
+static ssize_t hwinfo_show(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	ssize_t count = 0;
+
+	count += sprintf(&buf[count], "chip hw rev: ");
+	if (aicbsp_info.hwinfo_r < 0)
+		count += sprintf(&buf[count], "-1(not avalible)\n");
+	else
+		count += sprintf(&buf[count], "0x%02X\n", aicbsp_info.chip_rev);
+
+	count += sprintf(&buf[count], "hwinfo read: ");
+	if (aicbsp_info.hwinfo_r < 0)
+		count +=
+			sprintf(&buf[count], "%d(not avalible), ", aicbsp_info.hwinfo_r);
+	else
+		count += sprintf(&buf[count], "0x%02X, ", aicbsp_info.hwinfo_r);
+
+	if (aicbsp_info.hwinfo < 0)
+		count +=
+			sprintf(&buf[count], "set: %d(not avalible)\n", aicbsp_info.hwinfo);
+	else
+		count += sprintf(&buf[count], "set: 0x%02X\n", aicbsp_info.hwinfo);
+
+	return count;
+}
+
+static ssize_t hwinfo_store(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	long val;
+	int err = kstrtol(buf, 0, &val);
+
+	if (err) {
+		pr_err("invalid input\n");
+		return err;
+	}
+
+	if ((val == -1) || (val >= 0 && val <= 0xFF)) {
+		aicbsp_info.hwinfo = val;
+	} else {
+		pr_err("invalid values\n");
+		return -EINVAL;
+	}
+	return count;
+}
+
+static ssize_t fwdebug_show(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	ssize_t count = 0;
+
+	count += sprintf(&buf[count], "fw log status: %s\n",
+					 aicbsp_info.fwlog_en ? "on" : "off");
+
+	return count;
+}
+
+static ssize_t fwdebug_store(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	long val;
+	int err = kstrtol(buf, 0, &val);
+
+	if (err) {
+		pr_err("invalid input\n");
+		return err;
+	}
+
+	if (val > 1 || val < 0) {
+		pr_err("must be 0 or 1\n");
+		return -EINVAL;
+	}
+
+	aicbsp_info.fwlog_en = val;
+	return count;
+}
+
+static ssize_t adapt_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	ssize_t count = 0;
+
+	count += sprintf(&buf[count], "adapt status: %s\n",
+					 aicbsp_info.adap_test ? "on" : "off");
+
+	return count;
+}
+
+static ssize_t adapt_store(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	long val;
+	int err = kstrtol(buf, 0, &val);
+
+	if (err) {
+		pr_err("invalid input\n");
+		return err;
+	}
+
+	if (val > 1 || val < 0) {
+		pr_err("must be 0 or 1\n");
+		return err;
+	}
+
+	aicbsp_info.adap_test = val;
+	return count;
+}
+
+static DEVICE_ATTR_RW(cpmode);
+
+static DEVICE_ATTR_RW(hwinfo);
+
+static DEVICE_ATTR_RW(fwdebug);
+
+static DEVICE_ATTR_RW(adapt);
+
+static struct attribute *aicbsp_attributes[] = {
+	&dev_attr_cpmode.attr,
+	&dev_attr_hwinfo.attr,
+	&dev_attr_fwdebug.attr,
+	&dev_attr_adapt.attr,
+	NULL,
+};
+
+static struct attribute_group aicbsp_attribute_group = {
+	.name = "aicbsp_info",
+	.attrs = aicbsp_attributes,
+};
+
+int testmode = AICBSP_CPMODE_DEFAULT;
+int adap_test;
+module_param(testmode, int, 0660);
+module_param(adap_test, int, 0660);
+
+#if defined CONFIG_PLATFORM_EXTERNAL && defined CONFIG_CUSTOM_PWF
+int gpio_pwr;
+bool gpio_pwr_assert;
+
+void nxp_pwr_ctrl(int value)
+{
+	pr_info("aic_bsp: %s: %d\n", __func__, value);
+	gpio_set_value(gpio_pwr, value);
+}
+
+static int aicbsp_pwr_node(void)
+{
+	struct device_node *np;
+	int err;
+
+	np = of_find_compatible_node(NULL, NULL, "aic,wifi-power");
+	if (!np) {
+		pr_err("aic_bsp: %s np not found\n", __func__);
+		return -ENODEV;
+	}
+
+	gpio_pwr = of_get_named_gpio(np, "wifi-power", 0);
+	if (!gpio_is_valid(gpio_pwr)) {
+		pr_err("aic_bsp: %s is invalid\n", __func__);
+		return -ENODEV;
+	}
+
+	err = gpio_request(gpio_pwr, "wifi-power");
+	if (err < 0) {
+		pr_err("aic_bsp: %s can't request gpio_pwr gpio %d\n", __func__,
+		       gpio_pwr);
+		return -ENODEV;
+	}
+
+	err = gpio_direction_output(gpio_pwr, 1);
+	if (err < 0) {
+		pr_err("aic_bsp: %s can't request output direction gpio_pwr gpio %d\n",
+		       __func__, gpio_pwr);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+#endif
+
+static int __init aicbsp_init(void)
+{
+	int ret;
+
+	pr_info("AIC_BSP RELEASE VERSION:%s\r\n", RELEASE_VERSION);
+
+	aicbsp_info.cpmode = testmode;
+
+	aicbsp_resv_mem_init();
+	ret = platform_driver_register(&aicbsp_driver);
+	if (ret) {
+		pr_err("register platform driver failed: %d\n", ret);
+		return ret;
+	}
+
+	aicbsp_pdev = platform_device_alloc("aic-bsp", -1);
+	ret = platform_device_add(aicbsp_pdev);
+	if (ret) {
+		pr_err("register platform device failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = sysfs_create_group(&aicbsp_pdev->dev.kobj, &aicbsp_attribute_group);
+	if (ret) {
+		pr_err("register sysfs create group failed!\n");
+		return ret;
+	}
+
+	mutex_init(&aicbsp_power_lock);
+
+#if defined CONFIG_PLATFORM_EXTERNAL && defined CONFIG_CUSTOM_PWF
+	ret = aicbsp_pwr_node();
+	if (ret) {
+		pr_err("aic_bsp: aicbsp_pwr_node fail\n");
+		return ret;
+	}
+#endif
+	return 0;
+}
+
+void aicbsp_sdio_exit(void);
+
+static void __exit aicbsp_exit(void)
+{
+#if defined CONFIG_PLATFORM_EXTERNAL && defined CONFIG_CUSTOM_PWF
+	gpio_free(gpio_pwr);
+#endif
+
+	sysfs_remove_group(&aicbsp_pdev->dev.kobj, &aicbsp_attribute_group);
+	platform_device_del(aicbsp_pdev);
+	platform_driver_unregister(&aicbsp_driver);
+	mutex_destroy(&aicbsp_power_lock);
+	aicbsp_resv_mem_deinit();
+#ifdef CONFIG_PREALLOC_TXQ
+	aicwf_prealloc_txq_free();
+#endif
+}
+
+module_init(aicbsp_init);
+module_exit(aicbsp_exit);
+
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_adid.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_patch.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_patch_table.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_adid_u03.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_patch_u03.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_patch_table_u03.bin");
+
+#ifdef CONFIG_SDIO_BT
+MODULE_FIRMWARE(AIC8800_FW_DIR "fmacfwbt.bin");
+#else
+MODULE_FIRMWARE(AIC8800_FW_DIR "fmacfw.bin");
+#endif
+MODULE_FIRMWARE(AIC8800_FW_DIR "fmacfw_rf.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR RAM_FMAC_FW_PATCH_NAME);
+
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_adid_8800dc.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_patch_8800dc.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_patch_table_8800dc.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fmacfw_8800dc.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fmacfw_rf_8800dc.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_adid_8800dc_u02.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_patch_8800dc_u02.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_patch_table_8800dc_u02.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fmacfw_patch_8800dc_u02.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "lmacfw_rf_8800dc.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_adid_8800dc_u02h.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_patch_8800dc_u02h.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_patch_table_8800dc_u02h.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fmacfw_patch_8800dc_h_u02.bin");
+
+MODULE_FIRMWARE(AIC8800_FW_DIR RWNX_MAC_PATCH_NAME2_8800DC);
+MODULE_FIRMWARE(AIC8800_FW_DIR RWNX_MAC_PATCH_NAME2_8800DC_U02);
+MODULE_FIRMWARE(AIC8800_FW_DIR RWNX_MAC_PATCH_NAME2_8800DC_H_U02);
+MODULE_FIRMWARE(AIC8800_FW_DIR RWNX_MAC_PATCH_TABLE_8800DC_U02);
+MODULE_FIRMWARE(AIC8800_FW_DIR RWNX_MAC_PATCH_TABLE_8800DC_H_U02);
+#ifdef CONFIG_DPD
+MODULE_FIRMWARE(AIC8800_FW_DIR RWNX_MAC_CALIB_NAME_8800DC_U02);
+MODULE_FIRMWARE(AIC8800_FW_DIR RWNX_MAC_CALIB_NAME_8800DC_H_U02);
+#ifndef CONFIG_FORCE_DPD_CALIB
+MODULE_FIRMWARE(AIC8800_FW_DIR FW_DPDRESULT_NAME_8800DC);
+#endif
+#endif
+
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_adid_8800d80.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_patch_8800d80.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_patch_table_8800d80.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fmacfw_8800d80.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "lmacfw_rf_8800d80.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_adid_8800d80_u02.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_patch_8800d80_u02.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fw_patch_table_8800d80_u02.bin");
+#ifdef CONFIG_SDIO_BT
+MODULE_FIRMWARE(AIC8800_FW_DIR "fmacfwbt_8800d80_u02.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fmacfwbt_8800d80_h_u02.bin");
+#else
+MODULE_FIRMWARE(AIC8800_FW_DIR "fmacfw_8800d80_u02.bin");
+MODULE_FIRMWARE(AIC8800_FW_DIR "fmacfw_8800d80_h_u02.bin");
+#endif
+MODULE_FIRMWARE(AIC8800_FW_DIR "lmacfw_rf_8800d80_u02.bin");
+
+#ifdef CONFIG_M2D_OTA_AUTO_SUPPORT
+MODULE_FIRMWARE(AIC8800_FW_DIR FW_M2D_OTA_NAME);
+#endif
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_VERSION(DRV_VERS_MOD);
+MODULE_AUTHOR(DRV_COPYRIGHT " " DRV_AUTHOR);
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aic_chip_8800d80.c b/drivers/net/wireless/aic/aic8800_bsp/aic_chip_8800d80.c
new file mode 100644
index 0000000000000..d25afbc6cee7e
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aic_chip_8800d80.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 AIC semiconductor.
+ *
+ * @file aic_chip_8800d80.c
+ * @brief Chip operations for AIC8800D80
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/ieee80211.h>
+#include "aic_chip_ops.h"
+
+#include "aic8800d80_compat.h"
+#include "aic_bsp_driver.h"
+
+static int aic8800d80_wifi_init(struct aic_sdio_dev *sdiodev, int testmode)
+{
+	const char *fw_path = aicbsp_firmware_list[aicbsp_info.cpmode].wl_fw;
+
+	if (rwnx_plat_bin_fw_upload(sdiodev, RAM_FMAC_FW_ADDR,
+				    fw_path)) {
+		pr_err("aicbsp 8800d80 download wifi fw fail\n");
+		return -1;
+	}
+
+	if (aicwifi_patch_config_8800d80(sdiodev)) {
+		pr_err("aicbsp aicwifi_patch_config_8800d80 fail\n");
+		return -1;
+	}
+
+	if (aicwifi_sys_config_8800d80(sdiodev)) {
+		pr_err("aicbsp aicwifi_patch_config_8800d80 fail\n");
+		return -1;
+	}
+
+	if (aicwifi_start_from_bootrom(sdiodev)) {
+		pr_err("aicbsp 8800d80 wifi start fail\n");
+		return -1;
+	}
+	return 0;
+}
+
+static int aic8800d80_set_patch_info(struct aic_sdio_dev *sdiodev,
+				     struct aicbt_patch_info_t *patch_info,
+				     struct aicbsp_info_t *aicbsp_info,
+				     struct aicbt_patch_table *head)
+{
+	if (aicbsp_info->chip_rev == CHIP_REV_U01) {
+		patch_info->addr_adid = FW_RAM_ADID_BASE_ADDR_8800D80;
+		patch_info->addr_patch = FW_RAM_PATCH_BASE_ADDR_8800D80;
+	} else if (aicbsp_info->chip_rev == CHIP_REV_U02 ||
+				aicbsp_info->chip_rev == CHIP_REV_U03) {
+		patch_info->addr_adid = FW_RAM_ADID_BASE_ADDR_8800D80_U02;
+		patch_info->addr_patch = FW_RAM_PATCH_BASE_ADDR_8800D80_U02;
+	}
+	aicbt_patch_info_unpack(patch_info, head);
+	if (patch_info->info_len == 0) {
+		pr_err("aicbsp %s, aicbt_patch_info_unpack fail\n", __func__);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int aic8800d80_driver_fw_init(struct aic_sdio_dev *sdiodev, u32 *btenable,
+				     struct aicbsp_info_t *aicbsp_info,
+				     const struct aicbsp_firmware **aicbsp_firmware_list)
+{
+	u32 mem_addr;
+	struct dbg_mem_read_cfm rd_mem_addr_cfm;
+	u8 is_chip_id_h = 0;
+
+	mem_addr = 0x40500000;
+
+	if (rwnx_send_dbg_mem_read_req(sdiodev, mem_addr, &rd_mem_addr_cfm))
+		return -1;
+
+	aicbsp_info->chip_rev = (u8)((rd_mem_addr_cfm.memdata >> 16) & 0x3F);
+	is_chip_id_h = (u8)(((rd_mem_addr_cfm.memdata >> 16) & 0xC0) == 0xC0);
+	*btenable = 1;
+	if (is_chip_id_h) {
+		AICWFDBG(LOGINFO, "IS_CHIP_ID_H\n");
+		*aicbsp_firmware_list = fw_8800d80_h_u02;
+	} else {
+		if (aicbsp_info->chip_rev == CHIP_REV_U01)
+			*aicbsp_firmware_list = fw_8800d80_u01;
+		if (aicbsp_info->chip_rev == CHIP_REV_U02 ||
+		    aicbsp_info->chip_rev == CHIP_REV_U03)
+			*aicbsp_firmware_list = fw_8800d80_u02;
+	}
+	if (aicbsp_system_config_8800d80(sdiodev))
+		return -1;
+
+	return 0;
+}
+
+/* ========== Ops instances ========== */
+
+const struct aic_chip_ops aic_chip_aic8800d80_ops = {
+	.name						= "AIC8800D80",
+	.use_func_msg				= false,
+	.use_sdiov3_func			= true,
+	.need_flowctrl_mask			= false,
+	.wakeup_reg_val				= 0x11,
+	.use_flowctrl_msg			= true,
+	.use_hdr_checksum			= true,
+	.need_fix_hdr_len			= false,
+	.need_func0_intr			= true,
+	.use_func2					= false,
+	.sdio_clock					= FEATURE_SDIO_CLOCK_V3,
+	.wifi_init					= aic8800d80_wifi_init,
+	.set_patch_info				= aic8800d80_set_patch_info,
+	.driver_fw_init				= aic8800d80_driver_fw_init,
+
+};
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aic_chip_8800dc.c b/drivers/net/wireless/aic/aic8800_bsp/aic_chip_8800dc.c
new file mode 100644
index 0000000000000..b3841261312f9
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aic_chip_8800dc.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 AIC semiconductor.
+ *
+ * @file aic_chip_8800dc.c
+ * @brief Chip operations for AIC8801 / AIC8800DC / AIC8800DW
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/ieee80211.h>
+#include "aic_chip_ops.h"
+
+#include "aic8800dc_compat.h"
+#include "aic_bsp_driver.h"
+
+static int aic8800dc_wifi_init(struct aic_sdio_dev *sdiodev, int testmode)
+{
+	int ret;
+
+	pr_info("aicbsp ############ %s begin\n", __func__);
+
+	system_config_8800dc(sdiodev);
+	pr_info("aicbsp ############ system_config_8800dc done\n");
+
+	ret = rwnx_plat_patch_load_8800dc(sdiodev);
+	if (ret) {
+		pr_err("aicbsp patch load return %d\n", ret);
+		return ret;
+	}
+	pr_info("aicbsp ############ rwnx_plat_patch_load done\n");
+
+	// rwnx_plat_userconfig_load(sdiodev);
+
+	aicwf_patch_config_8800dc(sdiodev);
+	pr_info("aicbsp ############ aicwf_patch_config_8800dc done\n");
+
+	start_from_bootrom_8800DC(sdiodev);
+
+	return 0;
+}
+
+static int aic8800dc_set_patch_info(struct aic_sdio_dev *sdiodev,
+				    struct aicbt_patch_info_t *patch_info,
+				    struct aicbsp_info_t *aicbsp_info,
+				    struct aicbt_patch_table *head)
+{
+	if (aicbsp_info->chip_rev == CHIP_REV_U01)
+		patch_info->addr_adid = RAM_8800DC_U01_ADID_ADDR;
+	else if (aicbsp_info->chip_rev == CHIP_REV_U02)
+		patch_info->addr_adid = RAM_8800DC_U02_ADID_ADDR;
+	patch_info->addr_patch = RAM_8800DC_FW_PATCH_ADDR;
+	aicbt_patch_info_unpack(patch_info, head);
+	if (patch_info->reset_addr == 0) {
+		patch_info->reset_addr = FW_RESET_START_ADDR;
+		patch_info->reset_val = FW_RESET_START_VAL;
+		patch_info->adid_flag_addr = FW_ADID_FLAG_ADDR;
+		patch_info->adid_flag = FW_ADID_FLAG_VAL;
+		if (rwnx_send_dbg_mem_write_req(sdiodev, patch_info->reset_addr,
+						patch_info->reset_val))
+			return -1;
+		if (rwnx_send_dbg_mem_write_req(sdiodev, patch_info->adid_flag_addr,
+						patch_info->adid_flag))
+			return -1;
+	}
+
+	return 0;
+}
+
+static int aic8800dc_driver_fw_init(struct aic_sdio_dev *sdiodev, u32 *btenable,
+				    struct aicbsp_info_t *aicbsp_info,
+				    const struct aicbsp_firmware **aicbsp_firmware_list)
+{
+	u32 mem_addr;
+	struct dbg_mem_read_cfm rd_mem_addr_cfm;
+	u8 is_chip_id_h = 0;
+
+	mem_addr = 0x40500000;
+
+	if (rwnx_send_dbg_mem_read_req(sdiodev, mem_addr, &rd_mem_addr_cfm))
+		return -1;
+
+	aicbsp_info->chip_rev = (u8)((rd_mem_addr_cfm.memdata >> 16) & 0x3F);
+	is_chip_id_h = (u8)(((rd_mem_addr_cfm.memdata >> 16) & 0xC0) == 0xC0);
+
+	*btenable = ((rd_mem_addr_cfm.memdata >> 26) & 0x1);
+	AICWFDBG(LOGINFO, "btenable = %d\n", *btenable);
+
+	if (*btenable == 0) {
+		sdiodev->chipid = PRODUCT_ID_AIC8800DW;
+		AICWFDBG(LOGINFO, "AIC8800DC change to AIC8800DW\n");
+	}
+
+	if (aicbsp_info->chip_rev != CHIP_REV_U01 &&
+	    aicbsp_info->chip_rev != CHIP_REV_U02 &&
+		aicbsp_info->chip_rev != CHIP_REV_U03 &&
+		aicbsp_info->chip_rev != CHIP_REV_U04) {
+		pr_err("aicbsp: %s, unsupport chip rev: %d\n", __func__,
+		       aicbsp_info->chip_rev);
+		return -1;
+	}
+	if (is_chip_id_h) {
+		AICWFDBG(LOGINFO, "IS_CHIP_ID_H\n");
+		*aicbsp_firmware_list = fw_8800dc_h_u02;
+	} else {
+		if (aicbsp_info->chip_rev == CHIP_REV_U01)
+			*aicbsp_firmware_list = fw_8800dc_u01;
+		else
+			*aicbsp_firmware_list = fw_8800dc_u02;
+	}
+
+	return 0;
+}
+
+/* ========== Ops instances ========== */
+
+const struct aic_chip_ops aic_chip_aic8800dc_ops = {
+	.name						= "AIC8800DC",
+	.use_func_msg				= true,
+	.use_sdiov3_func			= false,
+	.need_flowctrl_mask			= true,
+	.wakeup_reg_val				= 1,
+	.use_flowctrl_msg			= false,
+	.use_hdr_checksum			= false,
+	.need_fix_hdr_len			= true,
+	.need_func0_intr			= false,
+	.use_func2					= true,
+	.sdio_clock					= FEATURE_SDIO_CLOCK,
+	.wifi_init					= aic8800dc_wifi_init,
+	.set_patch_info				= aic8800dc_set_patch_info,
+	.driver_fw_init				= aic8800dc_driver_fw_init,
+
+};
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aic_chip_8801.c b/drivers/net/wireless/aic/aic8800_bsp/aic_chip_8801.c
new file mode 100644
index 0000000000000..f28979e25a28c
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aic_chip_8801.c
@@ -0,0 +1,117 @@
+// 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 "aic_chip_ops.h"
+#include "aic_bsp_driver.h"
+
+#include "aic8800dc_compat.h"
+#include "aic8800d80_compat.h"
+
+static int aic8801_wifi_init(struct aic_sdio_dev *sdiodev, int testmode)
+{
+#ifdef CONFIG_M2D_OTA_AUTO_SUPPORT
+	if (testmode == FW_M2D_OTA_MODE)
+		rwnx_plat_m2d_flash_ota(sdiodev, FW_M2D_OTA_NAME);
+	else if (testmode == FW_NORMAL_MODE)
+		rwnx_plat_m2d_flash_ota_check(sdiodev, FW_M2D_OTA_NAME);
+#endif
+	const char *fw_path = aicbsp_firmware_list[aicbsp_info.cpmode].wl_fw;
+
+	if (rwnx_plat_bin_fw_upload(sdiodev, RAM_FMAC_FW_ADDR,
+				    fw_path)) {
+		pr_err("aicbsp download wifi fw fail\n");
+		return -1;
+	}
+	if (testmode == FW_NORMAL_MODE) {
+		if (rwnx_plat_bin_fw_upload(sdiodev, RAM_FMAC_FW_PATCH_ADDR,
+					    RAM_FMAC_FW_PATCH_NAME)) {
+			pr_err("aicbsp download wifi fw patch fail\n");
+			return -1;
+		}
+	}
+
+	if (aicwifi_patch_config(sdiodev)) {
+		pr_err("aicbsp aicwifi_patch_config fail\n");
+		return -1;
+	}
+
+	if (aicwifi_sys_config(sdiodev)) {
+		pr_err("aicbsp aicwifi_sys_config fail\n");
+		return -1;
+	}
+
+	if (aicwifi_start_from_bootrom(sdiodev)) {
+		pr_err("aicbsp wifi start fail\n");
+		return -1;
+	}
+	return 0;
+}
+
+static int aic8801_set_patch_info(struct aic_sdio_dev *sdiodev,
+				  struct aicbt_patch_info_t *patch_info,
+				  struct aicbsp_info_t *aicbsp_info,
+				  struct aicbt_patch_table *head)
+{
+	patch_info->addr_adid = FW_RAM_ADID_BASE_ADDR;
+	patch_info->addr_patch = FW_RAM_PATCH_BASE_ADDR;
+	return 0;
+}
+
+static int aic8801_driver_fw_init(struct aic_sdio_dev *sdiodev, u32 *btenable,
+				  struct aicbsp_info_t *aicbsp_info,
+				  const struct aicbsp_firmware **aicbsp_firmware_list)
+{
+	u32 mem_addr;
+	struct dbg_mem_read_cfm rd_mem_addr_cfm;
+
+	mem_addr = 0x40500000;
+
+	if (rwnx_send_dbg_mem_read_req(sdiodev, mem_addr, &rd_mem_addr_cfm))
+		return -1;
+
+	aicbsp_info->chip_rev = (u8)(rd_mem_addr_cfm.memdata >> 16);
+	*btenable = 1;
+
+	if (aicbsp_info->chip_rev != CHIP_REV_U02 &&
+	    aicbsp_info->chip_rev != CHIP_REV_U03 &&
+		aicbsp_info->chip_rev != CHIP_REV_U04) {
+		pr_err("aicbsp: %s, unsupport chip rev: %d\n", __func__,
+		       aicbsp_info->chip_rev);
+		return -1;
+	}
+
+	if (aicbsp_info->chip_rev != CHIP_REV_U02)
+		*aicbsp_firmware_list = fw_u03;
+
+	if (aicbsp_system_config(sdiodev))
+		return -1;
+
+	return 0;
+}
+
+/* ========== Ops instances ========== */
+
+const struct aic_chip_ops aic_chip_aic8801_ops = {
+	.name						= "AIC8801",
+	.use_func_msg				= false,
+	.use_sdiov3_func			= false,
+	.need_flowctrl_mask			= true,
+	.wakeup_reg_val				= 1,
+	.use_flowctrl_msg			= true,
+	.use_hdr_checksum			= false,
+	.need_fix_hdr_len			= true,
+	.need_func0_intr			= false,
+	.use_func2					= true,
+	.sdio_clock					= FEATURE_SDIO_CLOCK,
+	.wifi_init					= aic8801_wifi_init,
+	.set_patch_info				= aic8801_set_patch_info,
+	.driver_fw_init				= aic8801_driver_fw_init,
+};
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aic_chip_ops.c b/drivers/net/wireless/aic/aic8800_bsp/aic_chip_ops.c
new file mode 100644
index 0000000000000..9bbdff4521767
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aic_chip_ops.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 AIC semiconductor.
+ *
+ * @file aic_chip_ops.c
+ * @brief Wrapper function implementations for chip-specific operations.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include "aic_chip_ops.h"
+#include "aicsdio.h"
+
+int aicwf_sdio_chipmatch(u16 vid, u16 did, u16 *chipid)
+{
+	if (vid == SDIO_VENDOR_ID_AIC8801 && did == SDIO_DEVICE_ID_AIC8801) {
+		*chipid = PRODUCT_ID_AIC8801;
+		AICWFDBG(LOGINFO, "%s USE AIC8801\r\n", __func__);
+		return 0;
+	} else if (vid == SDIO_VENDOR_ID_AIC8800DC &&
+		   did == SDIO_DEVICE_ID_AIC8800DC) {
+		*chipid = PRODUCT_ID_AIC8800DC;
+		AICWFDBG(LOGINFO, "%s USE AIC8800DC\r\n", __func__);
+		return 0;
+	} else if (vid == SDIO_VENDOR_ID_AIC8800D80 &&
+		   did == SDIO_DEVICE_ID_AIC8800D80) {
+		*chipid = PRODUCT_ID_AIC8800D80;
+		AICWFDBG(LOGINFO, "%s USE AIC8800D80\r\n", __func__);
+		return 0;
+	}
+	return -ENODEV;
+}
+
+/*========== Chip ops wrappers ========== */
+int aic_chip_wifi_init(struct aic_sdio_dev *sdiodev, int testmode)
+{
+	if (sdiodev->chip_ops && sdiodev->chip_ops->wifi_init)
+		return sdiodev->chip_ops->wifi_init(sdiodev, testmode);
+	return 0;
+}
+
+int aic_chip_set_patch_info(struct aic_sdio_dev *sdiodev,
+			    struct aicbt_patch_info_t *patch_info,
+			    struct aicbsp_info_t *aicbsp_info,
+			    struct aicbt_patch_table *head)
+{
+	if (sdiodev->chip_ops && sdiodev->chip_ops->set_patch_info)
+		return sdiodev->chip_ops->set_patch_info(sdiodev, patch_info,
+							 aicbsp_info, head);
+	return 0;
+}
+
+int aic_chip_driver_fw_init(struct aic_sdio_dev *sdiodev, u32 *btenable,
+			    struct aicbsp_info_t *aicbsp_info,
+			    const struct aicbsp_firmware **aicbsp_firmware_list)
+{
+	if (sdiodev->chip_ops && sdiodev->chip_ops->driver_fw_init)
+		return sdiodev->chip_ops->driver_fw_init(sdiodev, btenable,
+							 aicbsp_info,
+							 aicbsp_firmware_list);
+	return 0;
+}
+
+const struct aic_chip_ops *aic_chip_ops_select(u16 chipid)
+{
+	switch (chipid) {
+	case PRODUCT_ID_AIC8801:
+		return &aic_chip_aic8801_ops;
+	case PRODUCT_ID_AIC8800DC:
+	case PRODUCT_ID_AIC8800DW:
+		return &aic_chip_aic8800dc_ops;
+	case PRODUCT_ID_AIC8800D80:
+		return &aic_chip_aic8800d80_ops;
+	default:
+		return NULL;
+	}
+}
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aic_chip_ops.h b/drivers/net/wireless/aic/aic8800_bsp/aic_chip_ops.h
new file mode 100644
index 0000000000000..9927fc740fba1
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aic_chip_ops.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2024 AIC semiconductor.
+ *
+ * @file aic_chip_ops.h
+ * @brief Chip-specific operation interfaces for AIC8800 series.
+ */
+#ifndef _AIC_BSP_CHIP_OPS_H_
+#define _AIC_BSP_CHIP_OPS_H_
+
+#include <linux/types.h>
+
+struct aic_sdio_dev;
+struct aicbsp_info_t;
+struct aicbt_patch_info_t;
+struct aicbt_patch_table;
+struct aicbsp_firmware;
+
+/* Match SDIO vendor/device IDs to a chip product ID */
+int aicwf_sdio_chipmatch(u16 vendor, u16 device, 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 use_func_msg;
+	const bool use_sdiov3_func;
+	const bool need_flowctrl_mask;
+	const u8 wakeup_reg_val;
+	const bool use_flowctrl_msg;
+	const bool use_hdr_checksum;
+	const bool need_fix_hdr_len;
+	const bool need_func0_intr;
+	const bool use_func2;
+	const u32  sdio_clock;
+
+	int (*wifi_init)(struct aic_sdio_dev *sdiodev, int testmode);
+
+	int (*set_patch_info)(struct aic_sdio_dev *sdiodev,
+			      struct aicbt_patch_info_t *patch_info,
+			      struct aicbsp_info_t *aicbsp_info,
+			      struct aicbt_patch_table *head);
+
+	int (*driver_fw_init)(struct aic_sdio_dev *sdiodev, u32 *btenable,
+			      struct aicbsp_info_t *aicbsp_info,
+			      const struct aicbsp_firmware **aicbsp_firmware_list);
+};
+
+extern const struct aic_chip_ops aic_chip_aic8801_ops;
+extern const struct aic_chip_ops aic_chip_aic8800dc_ops;
+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_wifi_init(struct aic_sdio_dev *sdiodev, int testmode);
+int aic_chip_set_patch_info(struct aic_sdio_dev *sdiodev,
+			    struct aicbt_patch_info_t *patch_info,
+			    struct aicbsp_info_t *aicbsp_info,
+			    struct aicbt_patch_table *head);
+int aic_chip_driver_fw_init(struct aic_sdio_dev *sdiodev, u32 *btenable,
+			    struct aicbsp_info_t *aicbsp_info,
+			    const struct aicbsp_firmware **aicbsp_firmware_list);
+
+#endif /* _AIC_BSP_CHIP_OPS_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aicsdio.c b/drivers/net/wireless/aic/aic8800_bsp/aicsdio.c
new file mode 100644
index 0000000000000..0be61f195b074
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aicsdio.c
@@ -0,0 +1,1999 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief bsp sdio config
+ *
+ ******************************************************************************
+ */
+#include "aicsdio.h"
+#include "aic_bsp_driver.h"
+#include "aicsdio_txrxif.h"
+#include "aic_bsp_export.h"
+#include "aic_chip_ops.h"
+
+#include <linux/completion.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.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/semaphore.h>
+#include <linux/version.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_platform.h>
+
+static int aicbsp_platform_power_on(void);
+static void aicbsp_platform_power_off(void);
+
+struct aic_sdio_dev *aicbsp_sdiodev;
+static struct completion *aicbsp_notify_completion;
+static struct completion *aicbsp_probe_completion;
+
+static const struct sdio_device_id aicbsp_sdmmc_ids[];
+static int bsp_init;
+static int bsp_func_init;
+
+#ifdef CONFIG_M2D_OTA_AUTO_SUPPORT
+char saved_sdk_ver[64];
+module_param_string(saved_sdk_ver, saved_sdk_ver, 64, 0660);
+#endif
+
+extern int testmode;
+
+static int aicbsp_dummy_probe(struct sdio_func *func,
+			      const struct sdio_device_id *id)
+{
+	if (func && func->num != 2)
+		return 0;
+
+	if (aicbsp_notify_completion)
+		complete(aicbsp_notify_completion);
+	return 0;
+}
+
+static void aicbsp_dummy_remove(struct sdio_func *func)
+{
+}
+
+static struct sdio_driver aicbsp_dummy_sdmmc_driver = {
+	.probe = aicbsp_dummy_probe,
+	.remove = aicbsp_dummy_remove,
+	.name = "aicbsp_dummy_sdmmc",
+	.id_table = aicbsp_sdmmc_ids,
+};
+
+static int aicbsp_reg_sdio_notify(void *semaphore)
+{
+	aicbsp_notify_completion = semaphore;
+	return sdio_register_driver(&aicbsp_dummy_sdmmc_driver);
+}
+
+static void aicbsp_unreg_sdio_notify(void)
+{
+	mdelay(15);
+	sdio_unregister_driver(&aicbsp_dummy_sdmmc_driver);
+}
+
+static const char *aicbsp_subsys_name(int subsys)
+{
+	switch (subsys) {
+	case AIC_BLUETOOTH:
+		return "AIC_BLUETOOTH";
+	case AIC_WIFI:
+		return "AIC_WIFI";
+	default:
+		return "unknown subsys";
+	}
+}
+
+int aicbsp_set_subsys(int subsys, int state)
+{
+	static int pre_power_map;
+	int cur_power_map;
+	int pre_power_state;
+	int cur_power_state;
+
+	mutex_lock(&aicbsp_power_lock);
+	cur_power_map = pre_power_map;
+	if (state)
+		cur_power_map |= (1 << subsys);
+	else
+		cur_power_map &= ~(1 << subsys);
+
+	pre_power_state = pre_power_map > 0;
+	cur_power_state = cur_power_map > 0;
+
+	sdio_dbg("%s, subsys: %s, state to: %d\n", __func__,
+		 aicbsp_subsys_name(subsys), state);
+
+	if (cur_power_state != pre_power_state) {
+		sdio_dbg("%s, power state change to %d dure to %s\n", __func__,
+			 cur_power_state, aicbsp_subsys_name(subsys));
+		if (cur_power_state) {
+			if (aicbsp_platform_power_on() < 0)
+				goto err0;
+			if (aicbsp_sdio_init())
+				goto err1;
+			if (aicbsp_driver_fw_init(aicbsp_sdiodev))
+				goto err2;
+#ifndef CONFIG_FDRV_NO_REG_SDIO
+			aicbsp_sdio_release(aicbsp_sdiodev);
+#endif
+		} else {
+			aicbsp_sdio_exit();
+			aicbsp_platform_power_off();
+		}
+	} else {
+		sdio_dbg("%s, power state no need to change, current: %d\n", __func__,
+			 cur_power_state);
+	}
+	pre_power_map = cur_power_map;
+	mutex_unlock(&aicbsp_power_lock);
+
+	return cur_power_state;
+
+err2:
+	aicbsp_sdio_release(aicbsp_sdiodev);
+	aicbsp_sdio_exit();
+
+err1:
+	aicbsp_platform_power_off();
+
+err0:
+	sdio_dbg("%s, fail to set %s power state to %d\n", __func__,
+		 aicbsp_subsys_name(subsys), state);
+	mutex_unlock(&aicbsp_power_lock);
+	return -1;
+}
+EXPORT_SYMBOL_GPL(aicbsp_set_subsys);
+
+void *aicbsp_get_drvdata(void *args)
+{
+	(void)args;
+	if (aicbsp_sdiodev)
+		return aicbsp_sdiodev->bus_if;
+	return dev_get_drvdata((const struct device *)args);
+}
+
+#if defined CONFIG_PLATFORM_EXTERNAL && defined CONFIG_CUSTOM_PWF
+extern void nxp_pwr_ctrl(int value);
+struct mmc_host *g_host;
+#endif
+
+static int aicbsp_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;
+
+	bsp_init = 1;
+	bsp_func_init = 1;
+
+	if (!func) {
+		sdio_err("%s func is null\n", __func__);
+		return err;
+	}
+
+	if (!aicbsp_probe_completion) {
+		sdio_err("%s bsp_probe_semaphore is null\n", __func__);
+		return err;
+	}
+
+	sdio_dbg("%s:%d vid:0x%04X  did:0x%04X\n", __func__, func->num,
+		 func->vendor, func->device);
+
+	if (func->num != 2) {
+		sdio_dbg("%s func num != 2\n", __func__);
+		return err;
+	}
+
+	host = func->card->host;
+	host->caps |= MMC_CAP_NONREMOVABLE;
+
+	func = func->card->sdio_func[1 - 1]; // replace 2 with 1
+
+	sdio_dbg("%s after replace:%d\n", __func__, func->num);
+
+	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;
+	}
+	aicbsp_sdiodev = sdiodev;
+
+	err = aicwf_sdio_chipmatch(func->vendor, func->device, &chipid);
+	sdiodev->chipid = chipid;
+	sdiodev->chip_ops = aic_chip_ops_select(sdiodev->chipid);
+
+	sdiodev->func = func;
+	if (sdiodev->chip_ops->use_func_msg)
+		sdiodev->func_msg = func->card->sdio_func[1];
+	sdiodev->bus_if = bus_if;
+	bus_if->bus_priv.sdio = sdiodev;
+
+	if (sdiodev->chip_ops->use_func_msg) {
+		dev_set_drvdata(&sdiodev->func_msg->dev, bus_if);
+		pr_info("aicbsp the device is PRODUCT_ID_AIC8800DC\n");
+	}
+	dev_set_drvdata(&func->dev, bus_if);
+	sdiodev->dev = &func->dev;
+
+	if (!sdiodev->chip_ops->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;
+	}
+
+	if (!aicwf_sdio_bus_init(sdiodev)) {
+		sdio_err("sdio bus init err\r\n");
+		goto fail;
+	}
+
+#if defined CONFIG_PLATFORM_EXTERNAL && defined CONFIG_CUSTOM_PWF
+	g_host = host;
+#endif
+
+	aicbsp_platform_init(sdiodev);
+
+	complete(aicbsp_probe_completion);
+
+	return 0;
+fail:
+	aicwf_sdio_func_deinit(sdiodev);
+	dev_set_drvdata(&func->dev, NULL);
+	kfree(sdiodev);
+	kfree(bus_if);
+	return err;
+}
+
+static void aicbsp_sdio_remove(struct sdio_func *func)
+{
+	struct mmc_host *host;
+	struct aicwf_bus *bus_if = NULL;
+	struct aic_sdio_dev *sdiodev = NULL;
+
+	pr_info("aicbsp %s\n", __func__);
+
+	if (!aicbsp_sdiodev) {
+		AICWFDBG(LOGERROR, "%s: allready unregister\n", __func__);
+		goto done;
+	}
+	if (!func) {
+		AICWFDBG(LOGERROR, "%s, sdio func is null\n", __func__);
+		goto done;
+	}
+
+	host = func->card->host;
+	host->caps &= ~MMC_CAP_NONREMOVABLE;
+
+	bus_if = aicbsp_get_drvdata(&func->dev);
+
+	if (!bus_if) {
+		AICWFDBG(LOGERROR, "%s bus_if is NULL \r\n", __func__);
+		goto done;
+	}
+
+	sdiodev = bus_if->bus_priv.sdio;
+	if (!sdiodev) {
+		AICWFDBG(LOGERROR, "%s sdiodev is NULL \r\n", __func__);
+		goto done;
+	}
+
+	aicwf_sdio_release(sdiodev);
+	aicwf_sdio_func_deinit(sdiodev);
+
+	if (bsp_init != 0 && bsp_func_init != 0) {
+		dev_set_drvdata(&sdiodev->func->dev, NULL);
+		kfree(sdiodev);
+	}
+
+done:
+	if (bsp_init != 0 && bsp_func_init != 0 && bus_if)
+		kfree(bus_if);
+	aicbsp_sdiodev = NULL;
+	aicbsp_probe_completion = NULL;
+}
+
+static int aicbsp_sdio_suspend(struct device *dev)
+{
+	struct sdio_func *func = dev_to_sdio_func(dev);
+	int err;
+	mmc_pm_flag_t sdio_flags;
+
+	//sdio_dbg("%s, func->num = %d\n", __func__, func->num);
+	if (func->num != 2)
+		return 0;
+
+	sdio_flags = sdio_get_host_pm_caps(func);
+	if (!(sdio_flags & MMC_PM_KEEP_POWER)) {
+		sdio_dbg("%s: can't keep power while host is suspended\n", __func__);
+		return -EINVAL;
+	}
+
+	/* keep power while host suspended */
+	err = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
+	if (err) {
+		sdio_dbg("%s: error while trying to keep power\n", __func__);
+		return err;
+	}
+
+	return 0;
+}
+
+static int aicbsp_sdio_resume(struct device *dev)
+{
+	return 0;
+}
+
+static const struct sdio_device_id aicbsp_sdmmc_ids[] = {
+	{ SDIO_DEVICE(SDIO_VENDOR_ID_AIC8801,
+		      SDIO_DEVICE_ID_AIC8801_FUNC2) },
+	{ SDIO_DEVICE(SDIO_VENDOR_ID_AIC8800DC,
+		      SDIO_DEVICE_ID_AIC8800DC) },
+	{ SDIO_DEVICE(SDIO_VENDOR_ID_AIC8800D80,
+		      SDIO_DEVICE_ID_AIC8800D80_FUNC2) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(sdio, aicbsp_sdmmc_ids);
+
+static const struct dev_pm_ops aicbsp_sdio_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(aicbsp_sdio_suspend, aicbsp_sdio_resume)};
+
+static struct sdio_driver aicbsp_sdio_driver = {
+	.probe = aicbsp_sdio_probe,
+	.remove = aicbsp_sdio_remove,
+	.name = AICBSP_SDIO_NAME,
+	.id_table = aicbsp_sdmmc_ids,
+	.drv = {
+			.pm = &aicbsp_sdio_pm_ops,
+		},
+};
+
+static int aicbsp_platform_power_on(void)
+{
+	int ret = 0;
+	struct completion aic_chipup_comp;
+
+#if defined CONFIG_PLATFORM_EXTERNAL && defined CONFIG_CUSTOM_PWF
+	if (g_host) {
+		nxp_pwr_ctrl(0);
+		mdelay(10);
+		nxp_pwr_ctrl(1);
+		mdelay(50);
+		sdio_dbg("mmc detect change\n");
+		mmc_detect_change(g_host, 0);
+	}
+#endif
+
+	init_completion(&aic_chipup_comp);
+	ret = aicbsp_reg_sdio_notify(&aic_chipup_comp);
+	if (ret) {
+		sdio_dbg("%s aicbsp_reg_sdio_notify fail(%d)\n", __func__, ret);
+		return ret;
+	}
+
+	if (wait_for_completion_timeout(&aic_chipup_comp, msecs_to_jiffies(2000))) {
+		aicbsp_unreg_sdio_notify();
+		return 0;
+	}
+
+	aicbsp_unreg_sdio_notify();
+
+	return -1;
+}
+
+static void aicbsp_platform_power_off(void)
+{
+// TODO wifi disable and sdio card detection
+
+#if defined CONFIG_PLATFORM_EXTERNAL && defined CONFIG_CUSTOM_PWF
+	nxp_pwr_ctrl(0);
+#endif
+}
+
+int aicbsp_sdio_init(void)
+{
+	struct completion aic_chipup_comp;
+
+	init_completion(&aic_chipup_comp);
+	aicbsp_probe_completion = &aic_chipup_comp;
+
+	if (sdio_register_driver(&aicbsp_sdio_driver))
+		return -1;
+
+	if (wait_for_completion_timeout(aicbsp_probe_completion, msecs_to_jiffies(2000)) == 0) {
+		pr_err("aicbsp %s aicbsp_sdio_probe fail\r\n", __func__);
+		return -1;
+	}
+
+	return 0;
+}
+
+void aicbsp_sdio_exit(void)
+{
+	sdio_unregister_driver(&aicbsp_sdio_driver);
+}
+
+void aicbsp_sdio_release(struct aic_sdio_dev *sdiodev)
+{
+	if (!sdiodev->func) {
+		pr_warn("aicbsp %s, NULL sdio func\n", __func__);
+		return;
+	}
+
+	sdiodev->bus_if->state = BUS_DOWN_ST;
+	sdio_claim_host(sdiodev->func);
+	sdio_release_irq(sdiodev->func);
+	sdio_release_host(sdiodev->func);
+	if (sdiodev->chip_ops->use_func_msg) {
+		sdio_claim_host(sdiodev->func_msg);
+		sdio_release_irq(sdiodev->func_msg);
+		sdio_release_host(sdiodev->func_msg);
+	}
+}
+
+int aicwf_sdio_readb(struct aic_sdio_dev *sdiodev, uint regaddr, u8 *val)
+{
+	int ret;
+
+	if (!sdiodev->func) {
+		pr_warn("aicbsp %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_readb_func2(struct aic_sdio_dev *sdiodev, uint regaddr, u8 *val)
+{
+	int ret;
+
+	if (!sdiodev->func_msg) {
+		pr_warn("aicbsp %s, NULL sdio func\n", __func__);
+		return 0;
+	}
+
+	sdio_claim_host(sdiodev->func_msg);
+	*val = sdio_readb(sdiodev->func_msg, regaddr, &ret);
+	sdio_release_host(sdiodev->func_msg);
+	return ret;
+}
+
+int aicwf_sdio_writeb(struct aic_sdio_dev *sdiodev, uint regaddr, u8 val)
+{
+	int ret;
+
+	if (!sdiodev->func) {
+		pr_warn("aicbsp %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_writeb_func2(struct aic_sdio_dev *sdiodev, uint regaddr, u8 val)
+{
+	int ret;
+
+	if (!sdiodev->func_msg) {
+		pr_warn("aicbsp %s, NULL sdio func\n", __func__);
+		return 0;
+	}
+	sdio_claim_host(sdiodev->func_msg);
+	sdio_writeb(sdiodev->func_msg, val, regaddr, &ret);
+	sdio_release_host(sdiodev->func_msg);
+	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 (sdiodev->chip_ops->need_flowctrl_mask)
+			fc_reg &= SDIOWIFI_FLOWCTRL_MASK_REG;
+
+		if (fc_reg != 0) {
+			ret = fc_reg;
+			return ret;
+		}
+		if (count >= FLOW_CTRL_RETRY_COUNT) {
+			ret = -fc_reg;
+			break;
+		}
+		count++;
+		if (count < 30)
+			usleep_range(30, 50);
+		else if (count < 40)
+			mdelay(1);
+		else
+			mdelay(10);
+	}
+
+	return ret;
+}
+
+int aicwf_sdio_send_msg(struct aic_sdio_dev *sdiodev, u8 *buf, uint count)
+{
+	int ret = 0;
+
+	if (!sdiodev->func_msg) {
+		pr_warn("aicbsp %s, NULL sdio func\n", __func__);
+		return 0;
+	}
+	sdio_claim_host(sdiodev->func_msg);
+	ret = sdio_writesb(sdiodev->func_msg, 7, buf, count);
+	sdio_release_host(sdiodev->func_msg);
+
+	return ret;
+}
+
+int aicwf_sdio_send_pkt(struct aic_sdio_dev *sdiodev, u8 *buf, uint count)
+{
+	int ret = 0;
+
+	if (!sdiodev->func) {
+		pr_warn("aicbsp %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;
+}
+
+int aicwf_sdio_recv_pkt(struct aic_sdio_dev *sdiodev, struct sk_buff *skbbuf,
+			u32 size, u8 msg)
+{
+	int ret;
+
+	if (!skbbuf || !size)
+		return -EINVAL;
+
+	if (!msg) {
+		if (!sdiodev->func) {
+			pr_warn("aicbsp %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);
+	} else {
+		if (!sdiodev->func_msg) {
+			pr_warn("aicbsp %s, NULL sdio func\n", __func__);
+			return 0;
+		}
+		sdio_claim_host(sdiodev->func_msg);
+		ret = sdio_readsb(sdiodev->func_msg, skbbuf->data,
+				  sdiodev->sdio_reg.rd_fifo_addr, size);
+		sdio_release_host(sdiodev->func_msg);
+	}
+
+	if (ret < 0)
+		return ret;
+
+	skbbuf->len = size;
+
+	return ret;
+}
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+int aicwf_sdio_wakeup(struct aic_sdio_dev *sdiodev)
+{
+	int ret = 0;
+	int read_retry;
+	int write_retry = 20;
+	int wakeup_reg_val = 0;
+
+	wakeup_reg_val = sdiodev->chip_ops->wakeup_reg_val;
+
+	if (sdiodev->state == SDIO_SLEEP_ST) {
+		down(&sdiodev->pwrctl_wakeup_sema);
+		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 = 10;
+				while (read_retry) {
+					u8 val;
+
+					ret = aicwf_sdio_readb(sdiodev, sdiodev->sdio_reg.sleep_reg,
+							       &val);
+					if (ret == 0 && val & 0x10)
+						break;
+
+					read_retry--;
+					usleep_range(30, 50);
+				}
+				if (read_retry != 0)
+					break;
+			}
+			sdio_dbg("write retry:  %d\n", write_retry);
+			write_retry--;
+			usleep_range(30, 50);
+		}
+		up(&sdiodev->pwrctl_wakeup_sema);
+	}
+
+	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;
+
+	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;
+	}
+
+	if (sdiodev->state == SDIO_ACTIVE_ST) {
+		{
+			sdio_dbg("s\n");
+			ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.sleep_reg, 0x10);
+			if (ret)
+				sdio_err("Write sleep fail!\n");
+		}
+		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;
+
+	if (sdiodev->state == target) {
+		if (target == SDIO_ACTIVE_ST)
+			aicwf_sdio_pwrctl_timer(sdiodev, sdiodev->active_duration);
+
+		return ret;
+	}
+
+	switch (target) {
+	case SDIO_ACTIVE_ST:
+		aicwf_sdio_wakeup(sdiodev);
+		break;
+	case SDIO_SLEEP_ST:
+		aicwf_sdio_sleep_allow(sdiodev);
+		break;
+	}
+
+	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 = aicbsp_get_drvdata(dev);
+	struct aic_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+	int ret;
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+	aicwf_sdio_pwrctl_timer(sdiodev, 0);
+	if (sdiodev->pwrctl_tsk) {
+		complete_all(&sdiodev->pwrctrl_trgg);
+		kthread_stop(sdiodev->pwrctl_tsk);
+		sdiodev->pwrctl_tsk = NULL;
+	}
+#endif
+	bus_if->state = BUS_DOWN_ST;
+	ret = down_interruptible(&sdiodev->tx_priv->txctl_sema);
+	if (ret)
+		sdio_err("down txctl_sema fail\n");
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+	aicwf_sdio_pwr_stctl(sdiodev, SDIO_SLEEP_ST);
+#endif
+	if (!ret)
+		up(&sdiodev->tx_priv->txctl_sema);
+	aicwf_frame_queue_flush(&sdiodev->tx_priv->txq);
+}
+
+struct sk_buff *aicwf_sdio_readframes(struct aic_sdio_dev *sdiodev, u8 msg)
+{
+	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, msg);
+	if (ret) {
+		dev_kfree_skb(skb);
+		skb = NULL;
+	}
+
+	return skb;
+}
+
+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 (sdiodev->chip_ops->use_flowctrl_msg) {
+		buffer_cnt = aicwf_sdio_flow_ctrl(sdiodev);
+		while ((buffer_cnt <= 0 ||
+			(buffer_cnt > 0 && len > (buffer_cnt * BUFFER_SIZE))) &&
+			   retry < 10) {
+			retry++;
+			buffer_cnt = aicwf_sdio_flow_ctrl(sdiodev);
+			pr_info("aicbsp buffer_cnt = %d\n", buffer_cnt);
+		}
+	}
+	down(&sdiodev->tx_priv->cmd_txsema);
+
+	if (sdiodev->chip_ops->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_msg(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)
+{
+	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);
+		/* to prevent race conditions with wake-up operations */
+		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)) {
+		aicwf_sdio_send(sdiodev->tx_priv);
+		if (sdiodev->tx_priv->cmd_txstate)
+			break;
+	}
+
+	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 aicwf_bus *bus_if = dev_get_drvdata(dev);
+	struct aic_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+
+	prio = (pkt->priority & 0x7);
+	spin_lock_bh(&sdiodev->tx_priv->txqlock);
+	if (!aicwf_frame_enq(sdiodev->dev, &sdiodev->tx_priv->txq, pkt, prio)) {
+		spin_unlock_bh(&sdiodev->tx_priv->txqlock);
+		return -ENOSR;
+	}
+	ret = 0;
+
+	if (bus_if->state != BUS_UP_ST) {
+		sdio_err("bus_if stopped\n");
+		spin_unlock_bh(&sdiodev->tx_priv->txqlock);
+		return -1;
+	}
+
+	atomic_inc(&sdiodev->tx_priv->tx_pktcnt);
+	spin_unlock_bh(&sdiodev->tx_priv->txqlock);
+	complete(&bus_if->bustx_trgg);
+
+	return ret;
+}
+
+static int aicwf_sdio_bus_txmsg(struct device *dev, u8 *msg, uint msglen)
+{
+	int ret = -1;
+	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);
+
+	if (sdiodev->tx_priv->cmd_txstate) {
+		int timeout = msecs_to_jiffies(CMD_TX_TIMEOUT);
+
+		ret = wait_event_timeout(sdiodev->tx_priv->cmd_txdone_wait,
+					 !(sdiodev->tx_priv->cmd_txstate), timeout);
+	}
+
+	if (!sdiodev->tx_priv->cmd_txstate && sdiodev->tx_priv->cmd_tx_succ) {
+		ret = 0;
+	} else {
+		sdio_err("send failed:%d, %d,%x\n", sdiodev->tx_priv->cmd_txstate,
+			 sdiodev->tx_priv->cmd_tx_succ, ret);
+		ret = -EIO;
+	}
+
+	return ret;
+}
+
+int aicwf_sdio_send(struct aicwf_tx_priv *tx_priv)
+{
+	struct sk_buff *pkt;
+	struct aic_sdio_dev *sdiodev = tx_priv->sdiodev;
+	u16 aggr_len = 0;
+	int retry_times = 0;
+	int max_retry_times = 5;
+
+	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);
+		goto done;
+	}
+
+	if (tx_priv->fw_avail_bufcnt <= 0) { // flow control failed
+		tx_priv->fw_avail_bufcnt = aicwf_sdio_flow_ctrl(sdiodev);
+		while (tx_priv->fw_avail_bufcnt <= 0 && retry_times < max_retry_times) {
+			retry_times++;
+			tx_priv->fw_avail_bufcnt = aicwf_sdio_flow_ctrl(sdiodev);
+		}
+		if (tx_priv->fw_avail_bufcnt <= 0) {
+			sdio_err("fc retry %d fail\n", tx_priv->fw_avail_bufcnt);
+			goto done;
+		}
+	}
+
+	if (atomic_read(&tx_priv->aggr_count) == tx_priv->fw_avail_bufcnt) {
+		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;
+		}
+	} else {
+		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);
+			goto done;
+		}
+		atomic_dec(&sdiodev->tx_priv->tx_pktcnt);
+		spin_unlock_bh(&sdiodev->tx_priv->txqlock);
+
+		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");
+			goto done;
+		}
+
+		// 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) == 0 ||
+		    sdiodev->tx_priv->cmd_txstate) { // no more pkt send it!
+			tx_priv->fw_avail_bufcnt -= atomic_read(&tx_priv->aggr_count);
+			aicwf_sdio_aggr_send(tx_priv);
+		} else {
+			goto done;
+		}
+	}
+
+done:
+	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};
+	u16 curr_len = 0;
+	int allign_len = 0;
+
+	sdio_header[2] = 0x01; // data
+	if (aicbsp_sdiodev->chip_ops->use_hdr_checksum)
+		sdio_header[3] = crc8_ponl_107(&sdio_header[0], 3); // crc8
+	else
+		sdio_header[3] = 0; // reserved
+
+	memcpy(tx_priv->tail, (u8 *)&sdio_header, sizeof(sdio_header));
+	tx_priv->tail += sizeof(sdio_header);
+
+	// 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 (aicbsp_sdiodev->chip_ops->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;
+
+	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);
+}
+
+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("aicbsp %s, NULL sdio func\n", __func__);
+		return 0;
+	}
+
+	if (sdiodev->chip_ops->use_func_msg) {
+		sdio_claim_host(sdiodev->func);
+
+		// since we have func2 we don't register irq handler
+		sdio_claim_irq(sdiodev->func, NULL);
+		sdio_claim_irq(sdiodev->func_msg, NULL);
+
+		sdiodev->func->irq_handler =
+			(sdio_irq_handler_t *)aicwf_sdio_hal_irqhandler;
+		sdiodev->func_msg->irq_handler =
+			(sdio_irq_handler_t *)aicwf_sdio_hal_irqhandler_func2;
+		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);
+
+		// enable sdio interrupt
+		ret = aicwf_sdio_writeb_func2(sdiodev,
+					      sdiodev->sdio_reg.intr_config_reg, 0x07);
+
+		if (ret != 0)
+			sdio_err("func2 intr register failed:%d\n", ret);
+	} else {
+		sdio_claim_host(sdiodev->func);
+		sdio_claim_irq(sdiodev->func, aicwf_sdio_hal_irqhandler);
+
+		if (sdiodev->chip_ops->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;
+}
+
+int aicwf_sdio_bustx_thread(void *data)
+{
+	struct aicwf_bus *bus = (struct aicwf_bus *)data;
+	struct aic_sdio_dev *sdiodev = bus->bus_priv.sdio;
+
+	while (1) {
+		if (kthread_should_stop()) {
+			sdio_dbg("sdio bustx thread stop\n");
+			break;
+		}
+		if (!wait_for_completion_interruptible(&bus->bustx_trgg)) {
+			if ((int)(atomic_read(&sdiodev->tx_priv->tx_pktcnt) > 0) ||
+			    sdiodev->tx_priv->cmd_txstate)
+				aicwf_sdio_tx_process(sdiodev);
+		}
+	}
+
+	return 0;
+}
+
+int aicwf_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;
+
+	while (1) {
+		if (kthread_should_stop()) {
+			sdio_dbg("sdio busrx thread stop\n");
+			break;
+		}
+		if (!wait_for_completion_interruptible(&bus_if->busrx_trgg))
+			aicwf_process_rxframes(rx_priv);
+	}
+
+	return 0;
+}
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+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_info("bus down\n");
+		return;
+	}
+
+	if (sdiodev->pwrctl_tsk)
+		complete(&sdiodev->pwrctrl_trgg);
+}
+#endif
+
+static void aicwf_sdio_enq_rxpkt(struct aic_sdio_dev *sdiodev,
+				 struct sk_buff *pkt)
+{
+	struct aicwf_rx_priv *rx_priv = sdiodev->rx_priv;
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(&rx_priv->rxqlock, flags);
+	if (!aicwf_rxframe_enqueue(sdiodev->dev, &rx_priv->rxq, pkt)) {
+		spin_unlock_irqrestore(&rx_priv->rxqlock, flags);
+		aicwf_dev_skb_free(pkt);
+		return;
+	}
+	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;
+	struct sk_buff *pkt = NULL;
+	int ret;
+
+	if (!func) {
+		pr_warn("aicbsp %s, NULL sdio func\n", __func__);
+		return;
+	}
+	bus_if = dev_get_drvdata(&func->dev);
+	sdiodev = bus_if->bus_priv.sdio;
+
+	if (aicbsp_sdiodev->sdio_hal_irqhandler) {
+		aicbsp_sdiodev->sdio_hal_irqhandler(func);
+		return;
+	}
+
+	if (!bus_if || bus_if->state == BUS_DOWN_ST) {
+		sdio_err("bus err\n");
+		return;
+	}
+
+	if (aicbsp_sdiodev->chip_ops->use_func2) {
+		ret = aicwf_sdio_readb(sdiodev, sdiodev->sdio_reg.block_cnt_reg,
+				       &intstatus);
+
+		while (intstatus) {
+			sdiodev->rx_priv->data_len = intstatus * SDIOWIFI_FUNC_BLOCKSIZE;
+			if (intstatus > 0) {
+				if (intstatus < 64) {
+					pkt = aicwf_sdio_readframes(sdiodev, 0);
+				} 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, 0);
+				}
+			} else {
+				sdio_err("Interrupt but no data\n");
+			}
+
+			if (pkt)
+				aicwf_sdio_enq_rxpkt(sdiodev, pkt);
+
+			ret = aicwf_sdio_readb(sdiodev, sdiodev->sdio_reg.block_cnt_reg,
+					       &intstatus);
+		}
+	} 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
+					// 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);
+				} else { // block mode
+					sdiodev->rx_priv->data_len =
+						(intstatus & 0x7U) * SDIOWIFI_FUNC_BLOCKSIZE;
+					pkt = aicwf_sdio_readframes(sdiodev, 1);
+				}
+			} else {                     // func1
+				if (intstatus == 120U) { // byte mode
+					// 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);
+				} else { // block mode
+					sdiodev->rx_priv->data_len =
+						(intstatus & 0x7FU) * SDIOWIFI_FUNC_BLOCKSIZE;
+					pkt = aicwf_sdio_readframes(sdiodev, 0);
+				}
+			}
+		} else {
+			sdio_err("Interrupt but no data\n");
+		}
+
+		if (pkt)
+			aicwf_sdio_enq_rxpkt(sdiodev, pkt);
+	}
+
+	complete(&bus_if->busrx_trgg);
+}
+
+void aicwf_sdio_hal_irqhandler_func2(struct sdio_func *func)
+{
+	struct aicwf_bus *bus_if = dev_get_drvdata(&func->dev);
+	struct aic_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+	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 (!bus_if || bus_if->state == BUS_DOWN_ST) {
+		sdio_err("bus err\n");
+		return;
+	}
+
+#ifdef CONFIG_PREALLOC_RX_SKB
+	if (list_empty(&aic_rx_buff_list.rxbuff_list)) {
+		pr_warn("aicbsp %s %d, rxbuff list is empty\n", __func__, __LINE__);
+		return;
+	}
+#endif
+
+	ret = aicwf_sdio_readb_func2(sdiodev, sdiodev->sdio_reg.block_cnt_reg,
+				     &intstatus);
+	if (ret) {
+		sdio_err("failed to read interrupt status: %d\n", ret);
+		return;
+	}
+
+	while (intstatus) {
+		sdiodev->rx_priv->data_len = intstatus * SDIOWIFI_FUNC_BLOCKSIZE;
+		if (intstatus > 0) {
+			if (intstatus < 64) {
+				pkt = aicwf_sdio_readframes(sdiodev, 1);
+			} else {
+				sdio_info("byte mode len=%d\r\n", byte_len);
+				// byte_len must<= 128
+				aicwf_sdio_intr_get_len_bytemode(sdiodev, &byte_len);
+				pkt = aicwf_sdio_readframes(sdiodev, 1);
+			}
+		} else {
+			sdio_err("Interrupt but no data\n");
+		}
+
+		if (pkt)
+			aicwf_sdio_enq_rxpkt(sdiodev, pkt);
+
+		ret = aicwf_sdio_readb_func2(sdiodev, sdiodev->sdio_reg.block_cnt_reg,
+					     &intstatus);
+		if (ret) {
+			sdio_err("failed to read interrupt status: %d\n", ret);
+			break;
+		}
+	}
+
+	complete(&bus_if->busrx_trgg);
+}
+
+#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 new bus_ops callbacks */
+static int aicwf_bsp_bus_read_reg(struct device *dev, u32 regaddr, u8 *val);
+static int aicwf_bsp_bus_write_reg(struct device *dev, u32 regaddr, u8 val);
+static int aicwf_bsp_bus_send_pkt(struct device *dev, u8 *buf, uint count);
+static int aicwf_bsp_bus_recv_pkt(struct device *dev, u8 *buf, u32 size);
+static int aicwf_bsp_bus_enable_irq(struct device *dev);
+static void aicwf_bsp_bus_disable_irq(struct device *dev);
+static bool aicwf_bsp_bus_flow_ctrl(struct device *dev);
+static int aicwf_bsp_bus_sleep_allow(struct device *dev);
+static int aicwf_bsp_bus_wakeup(struct device *dev);
+static const void *aicwf_bsp_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_bsp_bus_read_reg,
+	.write_reg      = aicwf_bsp_bus_write_reg,
+	.send_pkt       = aicwf_bsp_bus_send_pkt,
+	.recv_pkt       = aicwf_bsp_bus_recv_pkt,
+	.enable_irq     = aicwf_bsp_bus_enable_irq,
+	.disable_irq    = aicwf_bsp_bus_disable_irq,
+	.flow_ctrl      = aicwf_bsp_bus_flow_ctrl,
+	.sleep_allow    = aicwf_bsp_bus_sleep_allow,
+	.wakeup         = aicwf_bsp_bus_wakeup,
+	.get_hw_props   = aicwf_bsp_bus_get_hw_props,
+};
+
+void aicwf_sdio_release_func2(struct aic_sdio_dev *sdiodev)
+{
+	int ret = 0;
+
+	if (!sdiodev->func_msg) {
+		pr_warn("aicbsp %s, NULL sdio func\n", __func__);
+		return;
+	}
+	sdio_claim_host(sdiodev->func_msg);
+	// disable sdio interrupt
+	ret = aicwf_sdio_writeb_func2(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_msg);
+	sdio_release_host(sdiodev->func_msg);
+}
+
+void aicwf_sdio_release(struct aic_sdio_dev *sdiodev)
+{
+	struct aicwf_bus *bus_if = NULL;
+
+	if (bsp_init == 0) {
+		pr_err("aicbsp already sdio_release\n");
+		return;
+	}
+
+	bsp_init = 0;
+
+	if (!sdiodev->func) {
+		pr_warn("aicbsp %s, NULL sdio func\n", __func__);
+		return;
+	}
+
+	bus_if = aicbsp_get_drvdata(sdiodev->dev);
+	if (bus_if)
+		bus_if->state = BUS_DOWN_ST;
+
+	if (aicbsp_sdiodev->chip_ops->use_func_msg)
+		aicwf_sdio_release_func2(sdiodev);
+
+	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 (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 (aicbsp_sdiodev->chip_ops->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;
+
+	aicbsp_get_feature(&feature);
+	aicwf_sdio_reg_init(sdiodev);
+
+	if (!sdiodev->func) {
+		pr_warn("aicbsp %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) {
+		sdio_err("set blocksize fail %d\n", ret);
+		sdio_release_host(sdiodev->func);
+		return ret;
+	}
+	ret = sdio_enable_func(sdiodev->func);
+	if (ret < 0) {
+		sdio_err("enable func fail %d.\n", ret);
+		sdio_release_host(sdiodev->func);
+		return ret;
+	}
+	usleep_range(20, 40);
+	// SDIO CLOCK SETTING
+	if (feature.sdio_clock > 0) {
+		host->ios.clock = feature.sdio_clock;
+		host->ops->set_ios(host, &host->ios);
+		sdio_dbg("Set SDIO Clock %d MHz\n", host->ios.clock / 1000000);
+	}
+	sdio_release_host(sdiodev->func);
+
+	if (aicbsp_sdiodev->chip_ops->use_func_msg) {
+		sdio_claim_host(sdiodev->func_msg);
+
+		// set sdio blocksize
+		ret = sdio_set_block_size(sdiodev->func_msg, SDIOWIFI_FUNC_BLOCKSIZE);
+		if (ret < 0) {
+			AICWFDBG(LOGERROR, "set func2 blocksize fail %d\n", ret);
+			sdio_release_host(sdiodev->func_msg);
+			return ret;
+		}
+
+		// set sdio enable func
+		ret = sdio_enable_func(sdiodev->func_msg);
+		if (ret < 0)
+			AICWFDBG(LOGERROR, "enable func2 fail %d.\n", ret);
+
+		sdio_release_host(sdiodev->func_msg);
+
+		ret = aicwf_sdio_writeb_func2(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_func2(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) {
+		sdio_err("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) {
+		sdio_err("reg:%d write failed!\n",
+			 sdiodev->sdio_reg.bytemode_enable_reg);
+		return ret;
+	}
+
+	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;
+	int val;
+
+	aicbsp_get_feature(&feature);
+	aicwf_sdio_reg_init(sdiodev);
+
+	if (!sdiodev->func) {
+		pr_warn("aicbsp %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) {
+		sdio_err("set blocksize fail %d\n", ret);
+		sdio_release_host(sdiodev->func);
+		return ret;
+	}
+	ret = sdio_enable_func(sdiodev->func);
+	if (ret < 0) {
+		sdio_err("enable func fail %d.\n", ret);
+		sdio_release_host(sdiodev->func);
+		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;
+	}
+	// mod sdio phase
+	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, 0x00, 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, 2000);
+	sdio_dbg("Set SDIO Clock %d MHz\n", host->ios.clock / 1000000);
+	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) {
+		sdio_err("reg:%d write failed!\n",
+			 sdiodev->sdio_reg.bytemode_enable_reg);
+		return ret;
+	}
+
+	return ret;
+}
+
+void aicwf_sdio_func_deinit(struct aic_sdio_dev *sdiodev)
+{
+	struct aicwf_bus *bus_if = NULL;
+
+	if (bsp_func_init == 0) {
+		pr_err("aicbsp already func_deinit\n");
+		return;
+	}
+
+	bsp_func_init = 0;
+
+	if (!sdiodev->func) {
+		sdio_info("%s, NULL sdio func\n", __func__);
+		return;
+	}
+
+	bus_if = dev_get_drvdata(sdiodev->dev);
+	if (!bus_if) {
+		sdio_info("%s, bus_if is null\n", __func__);
+		return;
+	}
+
+	if (sdiodev->bus_if == bus_if) {
+		sdio_dbg("%s bsp disable\n", __func__);
+		sdio_claim_host(sdiodev->func);
+		sdio_disable_func(sdiodev->func);
+		sdio_release_host(sdiodev->func);
+	}
+
+	if (aicbsp_sdiodev->chip_ops->use_func_msg) {
+		sdio_claim_host(sdiodev->func_msg);
+		sdio_disable_func(sdiodev->func_msg);
+		sdio_release_host(sdiodev->func_msg);
+	}
+}
+
+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);
+#endif
+	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;
+}
+
+int get_testmode(void)
+{
+	return testmode;
+}
+EXPORT_SYMBOL_GPL(get_testmode);
+
+struct sdio_func *get_sdio_func(void)
+{
+	return aicbsp_sdiodev->func;
+}
+EXPORT_SYMBOL_GPL(get_sdio_func);
+
+void set_irq_handler(void *fn)
+{
+	aicbsp_sdiodev->sdio_hal_irqhandler = (sdio_irq_handler_t *)fn;
+}
+EXPORT_SYMBOL_GPL(set_irq_handler);
+
+u8 crc8_ponl_107(u8 *p_buffer, u16 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 – BSP SDIO bus_ops callbacks
+ * ================================================================
+ */
+
+static inline struct aic_sdio_dev *aicwf_bsp_bus_to_sdiodev(struct device *dev)
+{
+	struct aicwf_bus *bus_if = dev_get_drvdata(dev);
+
+	return bus_if->bus_priv.sdio;
+}
+
+static int aicwf_bsp_bus_read_reg(struct device *dev, u32 regaddr, u8 *val)
+{
+	return aicwf_sdio_readb(aicwf_bsp_bus_to_sdiodev(dev), regaddr, val);
+}
+
+static int aicwf_bsp_bus_write_reg(struct device *dev, u32 regaddr, u8 val)
+{
+	return aicwf_sdio_writeb(aicwf_bsp_bus_to_sdiodev(dev), regaddr, val);
+}
+
+static int aicwf_bsp_bus_send_pkt(struct device *dev, u8 *buf, uint count)
+{
+	return aicwf_sdio_send_pkt(aicwf_bsp_bus_to_sdiodev(dev), buf, count);
+}
+
+static int aicwf_bsp_bus_recv_pkt(struct device *dev, u8 *buf, u32 size)
+{
+	struct aic_sdio_dev *sdiodev = aicwf_bsp_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_bsp_bus_enable_irq(struct device *dev)
+{
+	struct aic_sdio_dev *sdiodev = aicwf_bsp_bus_to_sdiodev(dev);
+	int ret;
+
+	sdio_claim_host(sdiodev->func);
+	sdio_claim_irq(sdiodev->func, sdiodev->sdio_hal_irqhandler);
+	sdio_release_host(sdiodev->func);
+
+	ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.intr_config_reg, 0x07);
+	if (ret)
+		sdio_err("intr register failed:%d\n", ret);
+
+	return ret;
+}
+
+static void aicwf_bsp_bus_disable_irq(struct device *dev)
+{
+	struct aic_sdio_dev *sdiodev = aicwf_bsp_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_bsp_bus_flow_ctrl(struct device *dev)
+{
+	int ret;
+
+	ret = aicwf_sdio_flow_ctrl(aicwf_bsp_bus_to_sdiodev(dev));
+	return (ret > 0);
+}
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+static int aicwf_bsp_bus_sleep_allow(struct device *dev)
+{
+	return aicwf_sdio_sleep_allow(aicwf_bsp_bus_to_sdiodev(dev));
+}
+
+static int aicwf_bsp_bus_wakeup(struct device *dev)
+{
+	return aicwf_sdio_wakeup(aicwf_bsp_bus_to_sdiodev(dev));
+}
+#else
+static int aicwf_bsp_bus_sleep_allow(struct device *dev)
+{
+	return 0;
+}
+
+static int aicwf_bsp_bus_wakeup(struct device *dev)
+{
+	return 0;
+}
+#endif /* CONFIG_SDIO_PWRCTRL */
+
+static const void *aicwf_bsp_bus_get_hw_props(struct device *dev)
+{
+	return NULL; /* BSP layer does not expose chip HW props via bus_ops */
+}
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aicsdio.h b/drivers/net/wireless/aic/aic8800_bsp/aicsdio.h
new file mode 100644
index 0000000000000..c0c2df8fbf8d4
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aicsdio.h
@@ -0,0 +1,176 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ * aicwf_sdio.h
+ *
+ * SDIO function declarations
+ *
+ * Copyright (C) AICSemi 2018-2020
+ */
+
+#ifndef _AICWF_SDMMC_H_
+#define _AICWF_SDMMC_H_
+
+#ifdef AICWF_SDIO_SUPPORT
+#include "aic_bsp_driver.h"
+#include <linux/ieee80211.h>
+#include <linux/if_ether.h>
+#include <linux/semaphore.h>
+#include <linux/skbuff.h>
+
+#define AICBSP_SDIO_NAME        "aicbsp_sdio"
+#define SDIOWIFI_FUNC_BLOCKSIZE 512
+
+#define SDIO_VENDOR_ID_AIC           0x8800
+#define SDIO_DEVICE_ID_AIC           0x0001
+#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
+
+/* 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
+#define SDIO_DEVICE_ID_AIC8801_FUNC2    0x0146
+#define SDIO_DEVICE_ID_AIC8800D80_FUNC2 0x0182
+
+enum sdio_type {
+	SDIO_TYPE_DATA = 0X00,
+	SDIO_TYPE_CFG = 0X10,
+	SDIO_TYPE_CFG_CMD_RSP = 0X11,
+	SDIO_TYPE_CFG_DATA_CFM = 0X12
+};
+
+enum AICWF_IC {
+	PRODUCT_ID_AIC8801 = 0,
+	PRODUCT_ID_AIC8800DC,
+	PRODUCT_ID_AIC8800DW,
+	PRODUCT_ID_AIC8800D80
+};
+
+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_cmd_mgr cmd_mgr;
+	struct sdio_func *func;
+	struct sdio_func *func_msg;
+	struct device *dev;
+	struct aicwf_bus *bus_if;
+
+	struct aicwf_rx_priv *rx_priv;
+	struct aicwf_tx_priv *tx_priv;
+	u32 state;
+
+#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;
+	const struct aic_chip_ops *chip_ops;
+	u32 fw_version_uint;
+	struct aic_sdio_reg sdio_reg;
+	void (*sdio_hal_irqhandler)(struct sdio_func *func);
+};
+
+int aicwf_sdio_readb(struct aic_sdio_dev *sdiodev, uint regaddr, u8 *val);
+int aicwf_sdio_readb_func2(struct aic_sdio_dev *sdiodev, uint regaddr, u8 *val);
+int aicwf_sdio_writeb_func2(struct aic_sdio_dev *sdiodev, uint regaddr, u8 val);
+int aicwf_sdio_send_msg(struct aic_sdio_dev *sdiodev, u8 *buf, uint count);
+struct sk_buff *aicwf_sdio_readframes(struct aic_sdio_dev *sdiodev, u8 msg);
+void aicwf_sdio_release_func2(struct aic_sdio_dev *sdiodev);
+int get_testmode(void);
+struct sdio_func *get_sdio_func(void);
+void set_irq_handler(void *fn);
+
+void *aicbsp_get_drvdata(void *args);
+int aicwf_sdio_writeb(struct aic_sdio_dev *sdiodev, uint regaddr, u8 val);
+void aicwf_sdio_hal_irqhandler(struct sdio_func *func);
+void aicwf_sdio_hal_irqhandler_func2(struct sdio_func *func);
+#if defined(CONFIG_SDIO_PWRCTRL)
+int aicwf_sdio_wakeup(struct aic_sdio_dev *sdiodev);
+int aicwf_sdio_sleep_allow(struct aic_sdio_dev *sdiodev);
+
+void aicwf_sdio_pwrctl_timer(struct aic_sdio_dev *sdiodev, uint duration);
+int aicwf_sdio_pwr_stctl(struct aic_sdio_dev *sdiodev, uint target);
+#endif
+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);
+int aicwf_sdio_flow_ctrl(struct aic_sdio_dev *sdiodev);
+int aicwf_sdio_recv_pkt(struct aic_sdio_dev *sdiodev, struct sk_buff *skbbuf,
+			u32 size, u8 msg);
+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 aicbsp_sdio_exit(void);
+int aicbsp_sdio_init(void);
+void aicbsp_sdio_release(struct aic_sdio_dev *sdiodev);
+int aicwf_sdio_txpkt(struct aic_sdio_dev *sdiodev, struct sk_buff *pkt);
+int aicwf_sdio_bustx_thread(void *data);
+int aicwf_sdio_busrx_thread(void *data);
+int aicwf_sdio_aggr(struct aicwf_tx_priv *tx_priv, struct sk_buff *pkt);
+int aicwf_sdio_send(struct aicwf_tx_priv *tx_priv);
+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);
+int aicwf_process_rxframes(struct aicwf_rx_priv *rx_priv);
+u8 crc8_ponl_107(u8 *p_buffer, u16 cal_size);
+#endif /* AICWF_SDIO_SUPPORT */
+
+#endif /*_AICWF_SDMMC_H_*/
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aicsdio_txrxif.c b/drivers/net/wireless/aic/aic8800_bsp/aicsdio_txrxif.c
new file mode 100644
index 0000000000000..f712edb102b16
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aicsdio_txrxif.c
@@ -0,0 +1,465 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief bsp tx/rx interface
+ *
+ ******************************************************************************
+ */
+
+#include "aicsdio_txrxif.h"
+#include "aic_bsp_driver.h"
+#include <linux/atomic.h>
+#include <linux/completion.h>
+#include <linux/debugfs.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/netdevice.h>
+#include <linux/printk.h>
+#include <linux/sched.h>
+#include <linux/semaphore.h>
+#include <linux/vmalloc.h>
+
+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);
+#ifdef AICWF_SDIO_SUPPORT
+	bus_if->bustx_thread = kthread_run(aicwf_sdio_bustx_thread, (void *)bus_if,
+					   "aicwf_bustx_thread");
+	bus_if->busrx_thread = kthread_run(aicwf_sdio_busrx_thread,
+					   (void *)bus_if->bus_priv.sdio->rx_priv,
+									   "aicwf_busrx_thread");
+#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;
+
+	if (!dev) {
+		txrx_err("device not found\n");
+		return;
+	}
+	bus_if = aicbsp_get_drvdata(dev);
+	aicwf_bus_stop(bus_if);
+
+	kfree(bus_if->cmd_buf);
+	if (bus_if->cmd_buf)
+		bus_if->cmd_buf = NULL;
+
+	if (bus_if->bustx_thread) {
+		complete_all(&bus_if->bustx_trgg);
+		kthread_stop(bus_if->bustx_thread);
+		bus_if->bustx_thread = NULL;
+	}
+}
+
+void aicwf_frame_tx(void *dev, struct sk_buff *skb)
+{
+	struct aic_sdio_dev *sdiodev = (struct aic_sdio_dev *)dev;
+
+	aicwf_bus_txdata(sdiodev->bus_if, skb);
+}
+
+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;
+
+	tx_priv->sdiodev = (struct aic_sdio_dev *)arg;
+
+	atomic_set(&tx_priv->aggr_count, 0);
+	tx_priv->aggr_buf = dev_alloc_skb(MAX_AGGR_TXPKT_LEN);
+	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)
+		dev_kfree_skb(tx_priv->aggr_buf);
+
+	kfree(tx_priv);
+}
+
+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;
+}
+
+int aicwf_process_rxframes(struct aicwf_rx_priv *rx_priv)
+{
+	int ret = 0;
+	unsigned long flags = 0;
+	struct sk_buff *skb = NULL;
+	u16 pkt_len = 0;
+	struct sk_buff *skb_inblock = NULL;
+	u16 aggr_len = 0, adjust_len = 0;
+	u8 *data = NULL;
+
+	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); // 8 is for ccmp mic or wep icv
+				if (!skb_inblock) {
+					txrx_err("no more space!\n");
+					aicwf_dev_skb_free(skb);
+					return -EBADE;
+				}
+
+				skb_put(skb_inblock, aggr_len);
+				memcpy(skb_inblock->data, data, aggr_len);
+				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;
+
+				skb_inblock =
+					__dev_alloc_skb(aggr_len + 4, GFP_KERNEL);
+				if (!skb_inblock) {
+					txrx_err("no more space!\n");
+					aicwf_dev_skb_free(skb);
+					return -EBADE;
+				}
+
+				skb_put(skb_inblock, aggr_len + 4);
+				memcpy(skb_inblock->data, data, aggr_len + 4);
+				if ((*(skb_inblock->data + 2) & 0x7f) == SDIO_TYPE_CFG_CMD_RSP)
+					rwnx_rx_handle_msg(rx_priv->sdiodev,
+							   (struct ipc_e2a_msg *)(skb_inblock->data
+							   + 4));
+				skb_pull(skb, adjust_len + 4);
+			}
+		}
+
+		/* skb_inblock no used currently, just free it! */
+		dev_kfree_skb(skb_inblock);
+		dev_kfree_skb(skb);
+		atomic_dec(&rx_priv->rx_cnt);
+	}
+
+#if defined(CONFIG_SDIO_PWRCTRL)
+	aicwf_sdio_pwr_stctl(rx_priv->sdiodev, SDIO_ACTIVE_ST);
+#endif
+
+	return ret;
+}
+
+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->len = 0;
+		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;
+
+	rx_priv->sdiodev = (struct aic_sdio_dev *)arg;
+	aicwf_frame_queue_init(&rx_priv->rxq, 1, MAX_RXQLEN);
+	spin_lock_init(&rx_priv->rxqlock);
+	atomic_set(&rx_priv->rx_cnt, 0);
+
+	INIT_LIST_HEAD(&rx_priv->rxframes_freequeue);
+	spin_lock_init(&rx_priv->freeq_lock);
+	rx_priv->recv_frames = aicwf_rxframe_queue_init(&rx_priv->rxframes_freequeue,
+							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);
+
+	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)
+{
+	if (rx_priv->sdiodev->bus_if->busrx_thread) {
+		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;
+	}
+
+	aicwf_frame_queue_flush(&rx_priv->rxq);
+	aicwf_recvframe_queue_deinit(&rx_priv->rxframes_freequeue);
+	if (rx_priv->recv_frames)
+		vfree(rx_priv->recv_frames);
+
+	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;
+}
+
+static struct sk_buff *aicwf_skb_dequeue_tail(struct frame_queue *pq, int prio)
+{
+	struct sk_buff_head *q = &pq->queuelist[prio];
+	struct sk_buff *p = skb_dequeue_tail(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)
+{
+	struct sk_buff *p = NULL;
+	int prio_modified = -1;
+
+	if (q->queuelist[prio].qlen < q->qmax && q->qcnt < q->qmax) {
+		aicwf_frame_queue_penq(q, prio, pkt);
+		return true;
+	}
+	if (q->queuelist[prio].qlen >= q->qmax) {
+		prio_modified = prio;
+	} else if (q->qcnt >= q->qmax) {
+		p = aicwf_frame_queue_peek_tail(q, &prio_modified);
+		if (prio_modified > prio)
+			return false;
+	}
+
+	if (prio_modified >= 0) {
+		if (prio_modified == prio)
+			return false;
+
+		p = aicwf_skb_dequeue_tail(q, prio_modified);
+		aicwf_dev_skb_free(p);
+
+		p = aicwf_frame_queue_penq(q, prio_modified, pkt);
+		if (!p)
+			txrx_err("failed\n");
+	}
+
+	return p;
+}
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aicsdio_txrxif.h b/drivers/net/wireless/aic/aic8800_bsp/aicsdio_txrxif.h
new file mode 100644
index 0000000000000..dedcaffc57c94
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aicsdio_txrxif.h
@@ -0,0 +1,180 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ * aicwf_txrxif.h
+ *
+ * bus function declarations
+ *
+ * Copyright (C) AICSemi 2018-2020
+ */
+
+#ifndef _AICWF_TXRXIF_H_
+#define _AICWF_TXRXIF_H_
+
+#include "aicsdio.h"
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include "aic_bus.h"
+
+#define CMD_BUF_MAX        1536
+#define TXPKT_BLOCKSIZE    512
+#define MAX_AGGR_TXPKT_LEN (1536 * 4)
+#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_DEBUG_LEVEL
+
+#define txrx_err(fmt, ...)                                                     \
+	pr_err("aicbsp: txrx_err:<%s,%d>: " fmt, __func__, __LINE__, ##__VA_ARGS__)
+#define sdio_err(fmt, ...)                                                     \
+	pr_err("aicbsp: sdio_err:<%s,%d>: " fmt, __func__, __LINE__, ##__VA_ARGS__)
+#define usb_err(fmt, ...)                                                      \
+	pr_err("aicbsp: usb_err:<%s,%d>: " fmt, __func__, __LINE__, ##__VA_ARGS__)
+#if DBG_LEVEL >= DEBUG_DEBUG_LEVEL
+#define sdio_dbg(fmt, ...) pr_debug("aicbsp: " fmt, ##__VA_ARGS__)
+#define usb_dbg(fmt, ...)  pr_debug("aicbsp: " fmt, ##__VA_ARGS__)
+#else
+#define sdio_dbg(fmt, ...)
+#define usb_dbg(fmt, ...)
+#endif
+#if DBG_LEVEL >= DEBUG_INFO_LEVEL
+#define sdio_info(fmt, ...) pr_info("aicbsp: " fmt, ##__VA_ARGS__)
+#define usb_info(fmt, ...)  pr_info("aicbsp: " fmt, ##__VA_ARGS__)
+#else
+#define sdio_info(fmt, ...)
+#define usb_info(fmt, ...)
+#endif
+
+/* aicwf_bus_state and aicwf_bus_ops are 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];
+};
+
+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_priv */
+	spinlock_t txqlock;
+	struct semaphore txctl_sema;
+#endif
+	struct sk_buff *aggr_buf;
+	atomic_t aggr_count;
+	u8 *head;
+	u8 *tail;
+};
+
+#define MAX_REORD_RXFRAME     250
+#define REORDER_UPDATE_TIME   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;
+	uint len;
+	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;
+};
+
+struct aicwf_rx_priv {
+	struct aic_sdio_dev *sdiodev;
+	void *rwnx_vif;
+	atomic_t rx_cnt;
+	u32 data_len;
+	/* lock for rx priv */
+	spinlock_t rxqlock;
+	struct frame_queue rxq;
+
+	/* lock for freeq */
+	spinlock_t freeq_lock;
+	struct list_head rxframes_freequeue;
+	struct list_head stas_reord_list;
+	/* lock for reord */
+	spinlock_t stas_reord_lock;
+	struct recv_msdu *recv_frames;
+};
+
+/* Inline bus wrappers and aicwf_bus_init/deinit are now 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);
+
+#endif /* _AICWF_TXRXIF_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aicwf_txq_prealloc.c b/drivers/net/wireless/aic/aic8800_bsp/aicwf_txq_prealloc.c
new file mode 100644
index 0000000000000..bc40c820d4573
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aicwf_txq_prealloc.c
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief bsp txq prealloc mem
+ *
+ ******************************************************************************
+ */
+#include "aic_bsp_driver.h"
+#include "aicsdio_txrxif.h"
+#include "aicwf_txq_prealloc.h"
+#include <linux/slab.h>
+
+struct prealloc_txq {
+	int prealloced;
+	void *txq;
+	size_t size;
+};
+
+struct prealloc_txq prealloc_txq;
+#define MAX_TXQ_SIZE (100 * 1024)
+
+void *aicwf_prealloc_txq_alloc(size_t size)
+{
+	WARN_ON_ONCE(size > MAX_TXQ_SIZE);
+
+	// check prealloc_txq.size
+	if ((int)prealloc_txq.size != (int)size) {
+		AICWFDBG(LOGINFO, "%s size is diff will to be kzalloc \r\n", __func__);
+
+		if (prealloc_txq.txq) {
+			AICWFDBG(LOGINFO, "%s txq to kfree \r\n", __func__);
+			kfree(prealloc_txq.txq);
+			prealloc_txq.txq = NULL;
+		}
+
+		prealloc_txq.size = size;
+		prealloc_txq.prealloced = 0;
+	}
+	// check prealloc or not
+	if (!prealloc_txq.prealloced) {
+		prealloc_txq.txq = kzalloc(size, GFP_KERNEL);
+		if (!prealloc_txq.txq) {
+			AICWFDBG(LOGERROR, "%s txq kzalloc fail \r\n", __func__);
+		} else {
+			AICWFDBG(LOGINFO, "%s txq kzalloc successful \r\n", __func__);
+			prealloc_txq.prealloced = 1;
+		}
+	} else {
+		AICWFDBG(LOGINFO, "%s txq not need to kzalloc \r\n", __func__);
+	}
+
+	return prealloc_txq.txq;
+}
+
+void aicwf_prealloc_txq_free(void)
+{
+	if (prealloc_txq.txq) {
+		AICWFDBG(LOGINFO, "%s txq to kfree \r\n", __func__);
+		kfree(prealloc_txq.txq);
+		prealloc_txq.txq = NULL;
+	}
+}
+EXPORT_SYMBOL_GPL(aicwf_prealloc_txq_alloc);
diff --git a/drivers/net/wireless/aic/aic8800_bsp/aicwf_txq_prealloc.h b/drivers/net/wireless/aic/aic8800_bsp/aicwf_txq_prealloc.h
new file mode 100644
index 0000000000000..680d37ffca931
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/aicwf_txq_prealloc.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AICWF_TXQ_PREALLOC_H_
+#define _AICWF_TXQ_PREALLOC_H_
+
+void *aicwf_prealloc_txq_alloc(size_t size);
+void aicwf_prealloc_txq_free(void);
+
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_bsp/md5.c b/drivers/net/wireless/aic/aic8800_bsp/md5.c
new file mode 100644
index 0000000000000..38e7e062c2fc8
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/md5.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "md5.h"
+#include <linux/memory.h>
+
+unsigned char PADDING[] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+			   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+			   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+			   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+void md5_init(struct MD5_CTX *context)
+{
+	context->count[0] = 0;
+	context->count[1] = 0;
+	context->state[0] = 0x67452301;
+	context->state[1] = 0xEFCDAB89;
+	context->state[2] = 0x98BADCFE;
+	context->state[3] = 0x10325476;
+}
+
+void md5_update(struct MD5_CTX *context, unsigned char *input, unsigned int inputlen)
+{
+	unsigned int i = 0, index = 0, partlen = 0;
+
+	index = (context->count[0] >> 3) & 0x3F;
+	partlen = 64 - index;
+	context->count[0] += inputlen << 3;
+	if (context->count[0] < (inputlen << 3))
+		context->count[1]++;
+	context->count[1] += inputlen >> 29;
+
+	if (inputlen >= partlen) {
+		memcpy(&context->buffer[index], input, partlen);
+		md5_transform(context->state, context->buffer);
+		for (i = partlen; i + 64 <= inputlen; i += 64)
+			md5_transform(context->state, &input[i]);
+		index = 0;
+	} else {
+		i = 0;
+	}
+	memcpy(&context->buffer[index], &input[i], inputlen - i);
+}
+
+void md5_final(struct MD5_CTX *context, unsigned char digest[16])
+{
+	unsigned int index = 0, padlen = 0;
+	unsigned char bits[8];
+
+	index = (context->count[0] >> 3) & 0x3F;
+	padlen = (index < 56) ? (56 - index) : (120 - index);
+	md5_encode(bits, context->count, 8);
+	md5_update(context, PADDING, padlen);
+	md5_update(context, bits, 8);
+	md5_encode(digest, context->state, 16);
+}
+
+void md5_encode(unsigned char *output, unsigned int *input, unsigned int len)
+{
+	unsigned int i = 0, j = 0;
+
+	while (j < len) {
+		output[j] = input[i] & 0xFF;
+		output[j + 1] = (input[i] >> 8) & 0xFF;
+		output[j + 2] = (input[i] >> 16) & 0xFF;
+		output[j + 3] = (input[i] >> 24) & 0xFF;
+		i++;
+		j += 4;
+	}
+}
+
+void md5_decode(unsigned int *output, unsigned char *input, unsigned int len)
+{
+	unsigned int i = 0, j = 0;
+
+	while (j < len) {
+		output[i] = (input[j]) | (input[j + 1] << 8) | (input[j + 2] << 16) |
+					(input[j + 3] << 24);
+		i++;
+		j += 4;
+	}
+}
+
+void md5_transform(unsigned int state[4], unsigned char block[64])
+{
+	unsigned int a = state[0];
+	unsigned int b = state[1];
+	unsigned int c = state[2];
+	unsigned int d = state[3];
+	unsigned int x[64];
+
+	md5_decode(x, block, 64);
+	FF(&a, b, c, d, x[0], 7, 0xd76aa478);   /* 1 */
+	FF(&d, a, b, c, x[1], 12, 0xe8c7b756);  /* 2 */
+	FF(&c, d, a, b, x[2], 17, 0x242070db);  /* 3 */
+	FF(&b, c, d, a, x[3], 22, 0xc1bdceee);  /* 4 */
+	FF(&a, b, c, d, x[4], 7, 0xf57c0faf);   /* 5 */
+	FF(&d, a, b, c, x[5], 12, 0x4787c62a);  /* 6 */
+	FF(&c, d, a, b, x[6], 17, 0xa8304613);  /* 7 */
+	FF(&b, c, d, a, x[7], 22, 0xfd469501);  /* 8 */
+	FF(&a, b, c, d, x[8], 7, 0x698098d8);   /* 9 */
+	FF(&d, a, b, c, x[9], 12, 0x8b44f7af);  /* 10 */
+	FF(&c, d, a, b, x[10], 17, 0xffff5bb1); /* 11 */
+	FF(&b, c, d, a, x[11], 22, 0x895cd7be); /* 12 */
+	FF(&a, b, c, d, x[12], 7, 0x6b901122);  /* 13 */
+	FF(&d, a, b, c, x[13], 12, 0xfd987193); /* 14 */
+	FF(&c, d, a, b, x[14], 17, 0xa679438e); /* 15 */
+	FF(&b, c, d, a, x[15], 22, 0x49b40821); /* 16 */
+
+	/* Round 2 */
+	GG(&a, b, c, d, x[1], 5, 0xf61e2562);   /* 17 */
+	GG(&d, a, b, c, x[6], 9, 0xc040b340);   /* 18 */
+	GG(&c, d, a, b, x[11], 14, 0x265e5a51); /* 19 */
+	GG(&b, c, d, a, x[0], 20, 0xe9b6c7aa);  /* 20 */
+	GG(&a, b, c, d, x[5], 5, 0xd62f105d);   /* 21 */
+	GG(&d, a, b, c, x[10], 9, 0x2441453);   /* 22 */
+	GG(&c, d, a, b, x[15], 14, 0xd8a1e681); /* 23 */
+	GG(&b, c, d, a, x[4], 20, 0xe7d3fbc8);  /* 24 */
+	GG(&a, b, c, d, x[9], 5, 0x21e1cde6);   /* 25 */
+	GG(&d, a, b, c, x[14], 9, 0xc33707d6);  /* 26 */
+	GG(&c, d, a, b, x[3], 14, 0xf4d50d87);  /* 27 */
+	GG(&b, c, d, a, x[8], 20, 0x455a14ed);  /* 28 */
+	GG(&a, b, c, d, x[13], 5, 0xa9e3e905);  /* 29 */
+	GG(&d, a, b, c, x[2], 9, 0xfcefa3f8);   /* 30 */
+	GG(&c, d, a, b, x[7], 14, 0x676f02d9);  /* 31 */
+	GG(&b, c, d, a, x[12], 20, 0x8d2a4c8a); /* 32 */
+
+	/* Round 3 */
+	HH(&a, b, c, d, x[5], 4, 0xfffa3942);   /* 33 */
+	HH(&d, a, b, c, x[8], 11, 0x8771f681);  /* 34 */
+	HH(&c, d, a, b, x[11], 16, 0x6d9d6122); /* 35 */
+	HH(&b, c, d, a, x[14], 23, 0xfde5380c); /* 36 */
+	HH(&a, b, c, d, x[1], 4, 0xa4beea44);   /* 37 */
+	HH(&d, a, b, c, x[4], 11, 0x4bdecfa9);  /* 38 */
+	HH(&c, d, a, b, x[7], 16, 0xf6bb4b60);  /* 39 */
+	HH(&b, c, d, a, x[10], 23, 0xbebfbc70); /* 40 */
+	HH(&a, b, c, d, x[13], 4, 0x289b7ec6);  /* 41 */
+	HH(&d, a, b, c, x[0], 11, 0xeaa127fa);  /* 42 */
+	HH(&c, d, a, b, x[3], 16, 0xd4ef3085);  /* 43 */
+	HH(&b, c, d, a, x[6], 23, 0x4881d05);   /* 44 */
+	HH(&a, b, c, d, x[9], 4, 0xd9d4d039);   /* 45 */
+	HH(&d, a, b, c, x[12], 11, 0xe6db99e5); /* 46 */
+	HH(&c, d, a, b, x[15], 16, 0x1fa27cf8); /* 47 */
+	HH(&b, c, d, a, x[2], 23, 0xc4ac5665);  /* 48 */
+
+	/* Round 4 */
+	II(&a, b, c, d, x[0], 6, 0xf4292244);   /* 49 */
+	II(&d, a, b, c, x[7], 10, 0x432aff97);  /* 50 */
+	II(&c, d, a, b, x[14], 15, 0xab9423a7); /* 51 */
+	II(&b, c, d, a, x[5], 21, 0xfc93a039);  /* 52 */
+	II(&a, b, c, d, x[12], 6, 0x655b59c3);  /* 53 */
+	II(&d, a, b, c, x[3], 10, 0x8f0ccc92);  /* 54 */
+	II(&c, d, a, b, x[10], 15, 0xffeff47d); /* 55 */
+	II(&b, c, d, a, x[1], 21, 0x85845dd1);  /* 56 */
+	II(&a, b, c, d, x[8], 6, 0x6fa87e4f);   /* 57 */
+	II(&d, a, b, c, x[15], 10, 0xfe2ce6e0); /* 58 */
+	II(&c, d, a, b, x[6], 15, 0xa3014314);  /* 59 */
+	II(&b, c, d, a, x[13], 21, 0x4e0811a1); /* 60 */
+	II(&a, b, c, d, x[4], 6, 0xf7537e82);   /* 61 */
+	II(&d, a, b, c, x[11], 10, 0xbd3af235); /* 62 */
+	II(&c, d, a, b, x[2], 15, 0x2ad7d2bb);  /* 63 */
+	II(&b, c, d, a, x[9], 21, 0xeb86d391);  /* 64 */
+	state[0] += a;
+	state[1] += b;
+	state[2] += c;
+	state[3] += d;
+}
diff --git a/drivers/net/wireless/aic/aic8800_bsp/md5.h b/drivers/net/wireless/aic/aic8800_bsp/md5.h
new file mode 100644
index 0000000000000..146c0f6d9430c
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/md5.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef MD5_H
+#define MD5_H
+
+#include <linux/types.h>
+
+struct MD5_CTX {
+	unsigned int count[2];
+	unsigned int state[4];
+	unsigned char buffer[64];
+};
+
+static inline u32 F(u32 x, u32 y, u32 z)
+{
+	return (x & y) | (~x & z);
+}
+
+static inline u32 G(u32 x, u32 y, u32 z)
+{
+	return (x & z) | (y & ~z);
+}
+
+static inline u32 H(u32 x, u32 y, u32 z)
+{
+	return x ^ y ^ z;
+}
+
+static inline u32 I(u32 x, u32 y, u32 z)
+{
+	return y ^ (x | ~z);
+}
+
+static inline u32 ROTATE_LEFT(u32 x, u32 n)
+{
+	return (x << n) | (x >> (32 - n));
+}
+
+static inline void FF(u32 *a, u32 b, u32 c, u32 d,
+		      u32 x, u32 s, u32 ac)
+{
+	*a += F(b, c, d) + x + ac;
+	*a = ROTATE_LEFT(*a, s);
+	*a += b;
+}
+
+static inline void GG(u32 *a, u32 b, u32 c, u32 d,
+		      u32 x, u32 s, u32 ac)
+{
+	*a += G(b, c, d) + x + ac;
+	*a = ROTATE_LEFT(*a, s);
+	*a += b;
+}
+
+static inline void HH(u32 *a, u32 b, u32 c, u32 d,
+		      u32 x, u32 s, u32 ac)
+{
+	*a += H(b, c, d) + x + ac;
+	*a = ROTATE_LEFT(*a, s);
+	*a += b;
+}
+
+static inline void II(u32 *a, u32 b, u32 c, u32 d,
+		      u32 x, u32 s, u32 ac)
+{
+	*a += I(b, c, d) + x + ac;
+	*a = ROTATE_LEFT(*a, s);
+	*a += b;
+}
+
+void md5_init(struct MD5_CTX *context);
+void md5_update(struct MD5_CTX *context, unsigned char *input, unsigned int inputlen);
+void md5_final(struct MD5_CTX *context, unsigned char digest[16]);
+void md5_transform(unsigned int state[4], unsigned char block[64]);
+void md5_encode(unsigned char *output, unsigned int *input, unsigned int len);
+void md5_decode(unsigned int *output, unsigned char *input, unsigned int len);
+
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_bsp/rwnx_version_gen.h b/drivers/net/wireless/aic/aic8800_bsp/rwnx_version_gen.h
new file mode 100644
index 0000000000000..8768db424ce78
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_bsp/rwnx_version_gen.h
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#define RWNX_VERS_REV    "241c091M (master)"
+#define RWNX_VERS_MOD    "6.4.3.0"
+#define RWNX_VERS_BANNER "rwnx v6.4.3.0 - - 241c091M (master)"
+#define RELEASE_VERSION "2.0.14"
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/Kconfig b/drivers/net/wireless/aic/aic8800_fdrv/Kconfig
new file mode 100644
index 0000000000000..037bb16a9dddd
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/Kconfig
@@ -0,0 +1,539 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+menuconfig AIC8800_WLAN_SUPPORT
+	tristate "AIC8800 FullMAC WLAN support"
+	depends on AIC8800_BSP_SUPPORT
+	depends on CFG80211
+	help
+	  Enable the AIC8800 FullMAC Wi-Fi driver.
+	  This layer registers the cfg80211 wireless device and handles
+	  station, AP, P2P, and management frame operation for AIC8800
+	  chipsets.
+
+	  If unsure, say N.
+
+if AIC8800_WLAN_SUPPORT
+
+config RWNX_FULLMAC
+	def_bool y
+
+menu "Firmware interface compatibility"
+
+config RWNX_TL4
+	bool "RWNX TL4 support"
+	depends on BROKEN
+	default n
+	help
+	  Enable support for the TL4 transport layout used by some older
+	  firmware builds. This option changes host and firmware message
+	  handling paths and should match the firmware image in use.
+
+	  If unsure, say N.
+
+config RWNX_OLD_IPC
+	bool "RWNX old IPC format"
+	depends on BROKEN
+	default n
+	help
+	  Enable compatibility with the older firmware IPC format.
+	  Select this only when the target firmware expects the legacy IPC
+	  message layout and cannot communicate with the default format.
+
+	  If unsure, say N.
+
+endmenu
+
+menu "Platform integration and legacy compatibility"
+
+config AIC8800_WPA3_FOR_OLD_KERNEL
+	bool "WPA3 compatibility for old kernels"
+	depends on BROKEN
+	help
+	  Enable downstream WPA3 compatibility code intended for kernels
+	  that predate the required cfg80211 interfaces. Current mainline
+	  already supplies the interfaces used by the normal driver path.
+	  The retained legacy implementation is therefore kept disabled.
+
+config AIC8800_VHT_FOR_OLD_KERNEL
+	bool "VHT compatibility for old kernels"
+	depends on BROKEN
+	help
+	  Enable the legacy VHT capability implementation. Keep this
+	  disabled on current mainline kernels. The normal cfg80211 path
+	  advertises VHT capabilities through the current kernel API. This
+	  option remains only for downstream source compatibility.
+
+config AIC8800_HE_FOR_OLD_KERNEL
+	bool "HE compatibility for old kernels"
+	depends on BROKEN
+	help
+	  Enable the legacy 802.11ax HE capability implementation. Keep
+	  this disabled on current mainline kernels. The default driver path
+	  uses the current cfg80211 HE capability representation. This code
+	  is retained only for old downstream kernel integrations.
+
+config AIC8800_WIFI_SUSPEND_FOR_LINUX
+	bool "Legacy procfs Wi-Fi suspend control"
+	depends on BROKEN
+	help
+	  Expose the downstream procfs control used by legacy user space
+	  to request Wi-Fi suspend. Normal systems should use cfg80211
+	  power management and system suspend instead. The legacy procfs
+	  ABI remains disabled because it is not an upstream interface.
+
+config AIC8800_GKI
+	bool "Android GKI compatibility"
+	depends on BROKEN
+	help
+	  Enable compatibility wrappers needed by Android Generic Kernel
+	  Image based platforms. These wrappers target downstream Android
+	  kernel interfaces rather than a current mainline kernel. The code
+	  is retained for reference but cannot be selected upstream.
+
+config AIC8800_GENL
+	bool "Generic netlink control interface"
+	default n
+	help
+	  Build the private AIC8800 generic-netlink command interface used
+	  for manufacturing and driver debugging. Commands require
+	  CAP_NET_ADMIN and this interface is not a stable userspace ABI.
+	  Normal systems should use cfg80211 and nl80211 instead.
+
+	  If unsure, say N.
+
+config AIC8800_USE_WIRELESS_EXT
+	bool "Legacy Wireless Extensions support"
+	depends on CFG80211_WEXT && BROKEN
+	help
+	  Build the AIC-specific Wireless Extensions compatibility layer.
+	  New user space should use nl80211 instead. The retained code uses
+	  downstream compatibility interfaces and is not enabled on current
+	  mainline kernels. This option remains for source history only.
+
+endmenu
+
+menu "Wireless and data path features"
+
+config RWNX_SPLIT_TX_BUF
+	bool "Split TX buffer for A-MSDU"
+	depends on BROKEN
+	default n
+	help
+	  Enable split TX buffer handling for A-MSDU capable firmware.
+	  This option should be enabled only when the firmware advertises
+	  matching aggregation support and expects split host buffers.
+
+	  If unsure, say N.
+
+config RWNX_AMSDUS_TX
+	bool "Enable A-MSDU TX"
+	default n
+	depends on RWNX_SPLIT_TX_BUF
+	help
+	  Enable transmit-side A-MSDU aggregation support in the FullMAC
+	  driver. This depends on split TX buffer support and matching
+	  firmware capabilities for aggregated frame transmission.
+
+	  If unsure, say N.
+
+config RWNX_BFMER
+	bool "VHT beamformer support"
+	default n
+	help
+	  Enable VHT beamformer support for chip and firmware builds that
+	  implement explicit transmit beamforming. This adds beamforming
+	  station bookkeeping and firmware command handling.
+
+	  If unsure, say N.
+
+config RWNX_MUMIMO_TX
+	bool "MU-MIMO TX support"
+	default n
+	depends on !RWNX_OLD_IPC
+	help
+	  Enable MU-MIMO transmit support for firmware builds that expose
+	  multi-user transmit scheduling. This option adds MU group state
+	  tracking and related firmware message handling.
+
+	  If unsure, say N.
+
+config AIC8800_RX_REORDER
+	bool "RX reorder support"
+	default y
+	help
+	  Enable receive reorder handling for aggregated 802.11 traffic.
+	  The driver keeps per-station reorder state so packets are passed
+	  to the network stack in sequence when block-ack is active.
+
+	  If unsure, say Y.
+
+config AIC8800_ARP_OFFLOAD
+	bool "ARP offload support"
+	default y
+	help
+	  Enable ARP offload support in the Wi-Fi firmware path.
+	  When supported by firmware, ARP handling may continue while the
+	  host is in a low-power state.
+
+	  If unsure, say Y.
+
+config AIC8800_RADAR_OR_IR_DETECT
+	bool "Radar/IR detect support"
+	default n
+	help
+	  Enable radar or interference detection event handling.
+	  This is used by DFS-capable firmware to report channel events
+	  that may require channel changes or regulatory action.
+
+	  If unsure, say N.
+
+config AIC8800_USE_5G
+	bool "Enable 5 GHz band"
+	default y
+	help
+	  Enable 5 GHz band support in the driver capability setup.
+	  Disable this only for hardware or regulatory configurations that
+	  must restrict operation to the 2.4 GHz band.
+
+	  If unsure, say Y.
+
+config AIC8800_CREATE_TRACE_POINTS
+	bool "Create trace points"
+	depends on EVENT_TRACING
+	default n
+	help
+	  Enable tracepoint definitions for selected AIC8800 driver paths.
+	  This is useful for driver development and timing analysis but is
+	  not required for normal runtime operation.
+
+	  If unsure, say N.
+
+config AIC8800_TXRX_THREAD_PRIO
+	bool "TX/RX thread priority"
+	default y
+	help
+	  Enable priority tuning for the driver transmit and receive
+	  worker threads. This can reduce latency under load on platforms
+	  where the SDIO transport shares CPU time with other work.
+	  The selected priority is applied when the transport threads start.
+	  Leave this enabled for the standard SDIO configuration.
+
+	  If unsure, say Y.
+
+config AIC8800_COEX
+	bool "Wi-Fi/Bluetooth coexistence"
+	default y
+	help
+	  Enable coexistence support for combo Wi-Fi and Bluetooth chips.
+	  The driver exchanges coexistence information with firmware so
+	  shared-radio operation can be coordinated. This requires
+	  coexistence support in the selected firmware image.
+	  Disable it only for Wi-Fi-only modules.
+
+	  If unsure, say Y.
+
+config AIC8800_RX_NETIF_RECV_SKB
+	bool "Use netif_receive_skb for RX"
+	default y
+	help
+	  Pass received packets to the networking stack with
+	  netif_receive_skb(). This is the normal receive path for this
+	  driver and should match the platform networking integration.
+
+	  If unsure, say Y.
+
+config AIC8800_SET_VENDOR_EXTENSION_IE
+	bool "Vendor extension IE support"
+	depends on BROKEN
+	default n
+	help
+	  Enable support for setting vendor extension information elements
+	  in management frames. This is intended for product-specific
+	  interoperability requirements and test configurations.
+
+	  If unsure, say N.
+
+config AIC8800_SUPPORT_REALTIME_CHANGE_MAC
+	bool "Runtime MAC address change"
+	default y
+	help
+	  Allow the driver to update the interface MAC address at runtime.
+	  This is used by platforms that need to apply a board-specific or
+	  user-provided MAC address after probe.
+
+	  If unsure, say Y.
+
+config AIC8800_USE_P2P0
+	bool "Use P2P0 interface"
+	default n
+	help
+	  Enable use of a dedicated P2P0 interface for Wi-Fi Direct.
+	  This should match the firmware and user space expectation for
+	  P2P device interface creation.
+
+	  If unsure, say N.
+
+config AIC8800_TX_NETIF_FLOWCTRL
+	bool "TX netdev flow control"
+	default n
+	help
+	  Enable netdev queue flow control from the transmit path.
+	  The driver may stop and wake netdev queues based on firmware and
+	  SDIO buffer pressure to avoid excessive packet buildup.
+
+	  If unsure, say N.
+
+config AIC8800_ONE_TXQ
+	bool "Single TX queue"
+	default n
+	help
+	  Use a single transmit queue model for platforms that cannot use
+	  the normal per-access-category queue layout. This may simplify
+	  integration at the cost of scheduling flexibility.
+
+	  If unsure, say N.
+
+config AIC8800_PREALLOC_RX_SKB
+	bool "Preallocate RX SKBs"
+	help
+	  Preallocate receive socket buffers during initialization. This
+	  may improve allocation latency at the cost of reserved memory.
+	  The buffers remain available to the SDIO receive path while the
+	  driver is active. Enable this only when that memory tradeoff fits.
+
+config AIC8800_FDRV_NO_REG_SDIO
+	bool "Avoid direct SDIO register access"
+	help
+	  Disable the FullMAC driver's direct SDIO register access path.
+	  Firmware-mediated access is used instead for affected operations.
+	  This is intended for platforms that restrict direct bus access.
+	  Enable it only when the platform integration requires that mode.
+
+config AIC8800_BR_SUPPORT
+	bool "Bridge support"
+	depends on BROKEN
+	default n
+	help
+	  Enable bridge helper code used by some downstream networking
+	  setups. This can adjust packet handling when the wireless
+	  interface is attached to a Linux bridge.
+
+	  If unsure, say N.
+
+config AIC8800_SCHED_SCAN
+	bool "Scheduled scan support"
+	default n
+	help
+	  Enable scheduled scan handling in the driver. When supported by
+	  firmware, cfg80211 can request periodic scans without waking
+	  user space for every scan cycle.
+
+	  If unsure, say N.
+
+config AIC8800_USE_CUSTOMER_MAC
+	bool "Use customer MAC address"
+	default n
+	help
+	  Enable product-specific MAC address selection logic.
+	  This option is intended for platforms that store the Wi-Fi MAC
+	  address outside the normal firmware or NVMEM path.
+
+	  If unsure, say N.
+
+endmenu
+
+menu "Performance, regulatory and power management"
+
+config AIC8800_FILTER_TCP_ACK
+	bool "Filter TCP ACK packets"
+	default y
+	help
+	  Enable TCP ACK filtering in the SDIO data path.
+	  This can reduce transmit traffic and improve throughput on some
+	  platforms by suppressing redundant pure ACK packets.
+
+	  If unsure, say Y.
+
+config AIC8800_TEMP_COMP
+	bool "Temperature compensation"
+	default y
+	help
+	  Enable temperature compensation support for RF calibration.
+	  The driver can request temperature data from firmware or chip
+	  logic and apply compensation where supported.
+
+	  If unsure, say Y.
+
+config AIC8800_AUTO_CUSTREG
+	bool "Automatic customer regulatory setup"
+	default y
+	help
+	  Enable automatic customer regulatory configuration handling.
+	  The driver may apply board or product regulatory parameters
+	  during initialization when firmware support is present.
+
+	  If unsure, say Y.
+
+config AIC8800_TEMP_CONTROL
+	bool "Temperature control"
+	default y
+	help
+	  Enable temperature control handling for supported chips.
+	  This allows the driver to exchange thermal control commands with
+	  firmware and react to temperature-related state changes.
+
+	  If unsure, say Y.
+
+config AIC8800_WOWLAN_PM
+	bool "WoWLAN power management"
+	default y
+	help
+	  Enable Wake-on-WLAN power management support.
+	  When firmware supports it, selected wireless events can wake the
+	  host from suspend while the device remains in a low-power mode.
+
+	  If unsure, say Y.
+
+config AIC8800_POWER_LIMIT
+	bool "Power limit support"
+	default y
+	help
+	  Enable power limit handling for supported board configurations.
+	  The driver can pass configured transmit power restrictions to
+	  firmware when product data requires it.
+
+	  If unsure, say Y.
+
+config AIC8800_REGION_PW
+	bool "Regional power support"
+	default y
+	help
+	  Enable regional transmit power handling.
+	  This option allows board or firmware data to select power tables
+	  that correspond to the configured regulatory region.
+
+	  If unsure, say Y.
+
+config AIC8800_MCC
+	bool "Multi-channel concurrency support"
+	default y
+	help
+	  Enable multi-channel concurrency support for station and P2P
+	  operation on different channels. This should match firmware
+	  capability and concurrent mode requirements.
+	  The firmware remains responsible for channel time scheduling.
+	  Disable this when using firmware without MCC support.
+
+	  If unsure, say Y.
+
+config AICWF_LATENCY_MODE
+	bool "Low latency mode"
+	default n
+	help
+	  Enable low latency tuning in the AIC8800 data path.
+	  This may reduce packet latency at the cost of higher CPU usage
+	  or lower batching efficiency on some systems.
+
+	  If unsure, say N.
+
+config AIC8800_AUTO_POWERSAVE
+	bool "Automatic power save"
+	default y
+	help
+	  Enable automatic power save handling in the SDIO and firmware
+	  path. The driver may enter low-power states when traffic is idle
+	  and wake the device when new work arrives.
+
+	  If unsure, say Y.
+
+config AIC8800_NO_WAKE_COUNT
+	bool "Disable wake count tracking"
+	depends on !AIC8800_AUTO_POWERSAVE
+	default n
+	help
+	  Disable the driver's wake count tracking logic.
+	  This is mutually exclusive with the automatic power save flow and
+	  is intended only for platforms with different wake accounting.
+	  Normal mainline systems should keep wake count tracking enabled.
+	  This option exists for firmware-specific power management flows.
+
+	  If unsure, say N.
+
+endmenu
+
+menu "Radar, monitoring and diagnostics"
+
+config RWNX_RADAR
+	bool "Radar event handling"
+	default y
+	help
+	  Enable radar event processing from firmware.
+	  This is used with DFS-capable firmware so the driver can report
+	  radar detection and channel availability events to cfg80211.
+
+	  If unsure, say Y.
+
+config RWNX_BCMC
+	bool "Broadcast/multicast station support"
+	default y
+	help
+	  Enable broadcast and multicast station bookkeeping in AP mode.
+	  The driver uses this state for buffered multicast traffic and
+	  firmware station table management.
+
+	  If unsure, say Y.
+
+config RWNX_MON_DATA
+	bool "Monitor data interface support"
+	default y
+	help
+	  Enable monitor plus data interface support.
+	  This allows monitor-mode receive paths to coexist with normal
+	  data interfaces when firmware and hardware support it.
+
+	  If unsure, say Y.
+
+config RWNX_SW_PROFILING
+	bool "Software profiling"
+	default n
+	help
+	  Enable driver software profiling hooks.
+	  This option is intended for development builds that need timing
+	  or performance instrumentation in the FullMAC driver.
+
+	  If unsure, say N.
+
+config RWNX_DBG
+	bool "Debug log support"
+	default y
+	help
+	  Enable debug logging support in the FullMAC driver.
+	  The resulting log output can help diagnose firmware and driver
+	  interactions during bring-up and validation.
+
+	  If unsure, say Y.
+
+config RWNX_DEBUGFS
+	bool "DebugFS support"
+	depends on DEBUG_FS && BROKEN
+	default n
+	help
+	  Enable debugfs entries for the AIC8800 FullMAC driver.
+	  These files expose runtime state for debugging and are not
+	  required for normal end-user operation. The retained implementation
+	  still needs conversion to current debugfs and cfg80211 interfaces.
+
+	  If unsure, say N.
+
+config RWNX_P2P_DEBUGFS
+	bool "P2P debugfs support"
+	depends on RWNX_DEBUGFS
+	help
+	  Enable debugfs controls for P2P notice-of-absence and OppPS
+	  settings. These entries are intended for driver development and
+	  expose firmware scheduling state. They are not needed for normal
+	  P2P operation and depend on the main driver debugfs support.
+
+endmenu
+
+endif # AIC8800_WLAN_SUPPORT
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/Makefile b/drivers/net/wireless/aic/aic8800_fdrv/Makefile
new file mode 100644
index 0000000000000..6bfaaa574934e
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/Makefile
@@ -0,0 +1,117 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_AIC8800_WLAN_SUPPORT) += aic8800_fdrv.o
+
+aic8800_fdrv-y := \
+	rwnx_msg_tx.o          \
+	rwnx_msg_rx.o          \
+	rwnx_utils.o           \
+	rwnx_cmds.o            \
+	rwnx_irqs.o            \
+	rwnx_cfgfile.o         \
+	rwnx_strs.o            \
+	rwnx_rx.o              \
+	rwnx_tx.o              \
+	rwnx_txq.o             \
+	rwnx_main.o            \
+	rwnx_mod_params.o      \
+	rwnx_mesh.o            \
+	rwnx_platform.o        \
+	aicwf_chip_8801.o          \
+	aicwf_chip_8800dc.o          \
+	aicwf_chip_8800d80.o          \
+	aicwf_chip_ops.o            \
+	rwnx_v7.o              \
+	ipc_host.o             \
+	rwnx_tdls.o            \
+	aic_vendor.o	       \
+	md5.o                  \
+	aicwf_compat_8800dc.o  \
+	aicwf_compat_8800d80.o \
+	rwnx_wakelock.o		\
+	regdb.o				   \
+	aic_priv_cmd.o		\
+	aicwf_rx_prealloc.o
+
+aic8800_fdrv-$(CONFIG_AIC8800_GENL)             		+= aicwf_genl.o
+aic8800_fdrv-$(CONFIG_AIC8800_BR_SUPPORT)       		+= aic_br_ext.o
+aic8800_fdrv-$(CONFIG_RWNX_RADAR)       		+= rwnx_radar.o
+aic8800_fdrv-$(CONFIG_RWNX_DEBUGFS)         	+= rwnx_debugfs.o
+aic8800_fdrv-$(CONFIG_RWNX_DEBUGFS)         	+= rwnx_fw_trace.o
+aic8800_fdrv-$(CONFIG_NL80211_TESTMODE) 		+= rwnx_testmode.o
+aic8800_fdrv-$(CONFIG_RWNX_BFMER)       		+= rwnx_bfmer.o
+aic8800_fdrv-$(CONFIG_RWNX_MUMIMO_TX)   		+= rwnx_mu_group.o
+aic8800_fdrv-$(CONFIG_AIC8800_SDIO_SUPPORT)     		+= sdio_host.o
+aic8800_fdrv-$(CONFIG_AIC8800_SDIO_SUPPORT)     		+= aicwf_txrxif.o
+aic8800_fdrv-$(CONFIG_AIC8800_SDIO_SUPPORT)     		+= aicwf_sdio.o
+aic8800_fdrv-$(CONFIG_AIC8800_FILTER_TCP_ACK)   		+= aicwf_tcp_ack.o
+aic8800_fdrv-$(CONFIG_AIC8800_SDIO_BT)   			+= aic_btsdio.o
+aic8800_fdrv-$(CONFIG_AIC8800_GKI)     				+= rwnx_gki.o
+aic8800_fdrv-$(CONFIG_AIC8800_USE_WIRELESS_EXT)  	+= aicwf_wext_linux.o
+aic8800_fdrv-$(CONFIG_AIC8800_CREATE_TRACE_POINTS) 	+= rwnx_trace.o
+
+ccflags-y += -I$(srctree)
+ccflags-y += -I$(src)
+ccflags-y += -I$(src)/../aic8800_bsp
+ccflags-y += -DAIC_TRACE_INCLUDE_PATH=$(src)
+
+ccflags-$(CONFIG_RWNX_DEBUGFS) 					+= -DCONFIG_RWNX_DEBUGFS
+ccflags-$(CONFIG_RWNX_P2P_DEBUGFS) 				+= -DCONFIG_RWNX_P2P_DEBUGFS
+ccflags-$(CONFIG_RWNX_TL4) 						+= -DCONFIG_RWNX_TL4
+ccflags-$(CONFIG_RWNX_OLD_IPC) 					+= -DCONFIG_RWNX_OLD_IPC
+ccflags-$(CONFIG_AIC8800_BSP_PLATFORM_EXTERNAL) 			+= -DCONFIG_PLATFORM_EXTERNAL
+ccflags-$(CONFIG_AIC8800_VRF_DCDC_MODE) 				+= -DCONFIG_VRF_DCDC_MODE
+ccflags-$(CONFIG_AIC8800_HE_FOR_OLD_KERNEL) 			+= -DCONFIG_HE_FOR_OLD_KERNEL
+ccflags-$(CONFIG_AIC8800_COEX) 							+= -DCONFIG_COEX
+ccflags-$(CONFIG_RWNX_FULLMAC) 					+= -DCONFIG_RWNX_FULLMAC
+ccflags-$(CONFIG_RWNX_RADAR) 					+= -DCONFIG_RWNX_RADAR
+ccflags-$(CONFIG_RWNX_MON_DATA) 				+= -DCONFIG_RWNX_MON_DATA
+ccflags-$(CONFIG_RWNX_BFMER) 					+= -DCONFIG_RWNX_BFMER
+ccflags-$(CONFIG_RWNX_SPLIT_TX_BUF) 			+= -DCONFIG_RWNX_SPLIT_TX_BUF
+ccflags-$(CONFIG_RWNX_AMSDUS_TX) 				+= -DCONFIG_RWNX_AMSDUS_TX
+ccflags-$(CONFIG_RWNX_DBG) 						+= -DCONFIG_RWNX_DBG
+ccflags-$(CONFIG_RWNX_SW_PROFILING) 			+= -DCONFIG_RWNX_SW_PROFILING
+ccflags-$(CONFIG_RWNX_MUMIMO_TX) 				+= -DCONFIG_RWNX_MUMIMO_TX
+ccflags-$(CONFIG_AIC8800_USE_5G) 						+= -DUSE_5G
+ccflags-$(CONFIG_AIC8800_CREATE_TRACE_POINTS) 			+= -DAIC8800_CREATE_TRACE_POINTS
+ccflags-$(CONFIG_AIC8800_TXRX_THREAD_PRIO) 				+= -DCONFIG_TXRX_THREAD_PRIO
+ccflags-$(CONFIG_AIC8800_GPIO_WAKEUP) 					+= -DCONFIG_GPIO_WAKEUP
+ccflags-$(CONFIG_AIC8800_SET_VENDOR_EXTENSION_IE) 		+= -DCONFIG_SET_VENDOR_EXTENSION_IE
+ccflags-$(CONFIG_AIC8800_SUPPORT_REALTIME_CHANGE_MAC) 	+= -DCONFIG_SUPPORT_REALTIME_CHANGE_MAC
+ccflags-$(CONFIG_AIC8800_WPA3_FOR_OLD_KERNEL) 			+= -DCONFIG_WPA3_FOR_OLD_KERNEL
+ccflags-$(CONFIG_AIC8800_VHT_FOR_OLD_KERNEL) 			+= -DCONFIG_VHT_FOR_OLD_KERNEL
+ccflags-$(CONFIG_AIC8800_PREALLOC_RX_SKB) 				+= -DCONFIG_PREALLOC_RX_SKB
+ccflags-$(CONFIG_AIC8800_WIFI_SUSPEND_FOR_LINUX) 		+= -DCONFIG_WIFI_SUSPEND_FOR_LINUX
+ccflags-$(CONFIG_AIC8800_USE_P2P0) 						+= -DCONFIG_USE_P2P0
+ccflags-$(CONFIG_AIC8800_FDRV_NO_REG_SDIO) 				+= -DCONFIG_FDRV_NO_REG_SDIO
+ccflags-$(CONFIG_AIC8800_SCHED_SCAN) 					+= -DCONFIG_SCHED_SCAN
+ccflags-$(CONFIG_AIC8800_OOB) 							+= -DCONFIG_OOB
+ccflags-$(CONFIG_AIC8800_USE_CUSTOMER_MAC) 				+= -DCONFIG_USE_CUSTOMER_MAC
+ccflags-$(CONFIG_AIC8800_PREALLOC_TXQ) 					+= -DCONFIG_PREALLOC_TXQ
+ccflags-$(CONFIG_AIC8800_DPD) 							+= -DCONFIG_DPD
+ccflags-$(CONFIG_AIC8800_FORCE_DPD_CALIB) 			+= -DCONFIG_FORCE_DPD_CALIB
+ccflags-$(CONFIG_AIC8800_FILTER_TCP_ACK) 				+= -DCONFIG_FILTER_TCP_ACK
+ccflags-$(CONFIG_AIC8800_SDIO_BT) 						+= -DCONFIG_SDIO_BT
+ccflags-$(CONFIG_AIC8800_RESV_MEM_SUPPORT) 				+= -DCONFIG_RESV_MEM_SUPPORT
+ccflags-$(CONFIG_AIC8800_GKI) 							+= -DCONFIG_GKI
+ccflags-$(CONFIG_AIC8800_TEMP_COMP) 					+= -DCONFIG_TEMP_COMP
+ccflags-$(CONFIG_AIC8800_AUTO_CUSTREG) 					+= -DCONFIG_AUTO_CUSTREG
+ccflags-$(CONFIG_AIC8800_TEMP_CONTROL) 					+= -DCONFIG_TEMP_CONTROL
+ccflags-$(CONFIG_AIC8800_WOWLAN_PM) 					+= -DCONFIG_WOWLAN_PM
+ccflags-$(CONFIG_AIC8800_GENL) 							+= -DCONFIG_GENL
+ccflags-$(CONFIG_AIC8800_POWER_LIMIT) 					+= -DCONFIG_POWER_LIMIT
+ccflags-$(CONFIG_AIC8800_REGION_PW) 					+= -DCONFIG_REGION_PW
+ccflags-$(CONFIG_AIC8800_MCC) 							+= -DCONFIG_MCC
+ccflags-$(CONFIG_AICWF_LATENCY_MODE) 			+= -DAICWF_LATENCY_MODE
+ccflags-$(CONFIG_AIC8800_USE_WIRELESS_EXT) 				+= -DCONFIG_USE_WIRELESS_EXT
+ccflags-$(CONFIG_AIC8800_AUTO_POWERSAVE) 				+= -DCONFIG_AUTO_POWERSAVE
+ccflags-$(CONFIG_AIC8800_NO_WAKE_COUNT) 				+= -DCONFIG_NO_WAKE_COUNT
+ccflags-$(CONFIG_AIC8800_SDIO_SUPPORT) 					+= -DAICWF_SDIO_SUPPORT
+ccflags-$(CONFIG_AIC8800_SDIO_PWRCTRL) 					+= -DCONFIG_SDIO_PWRCTRL
+ccflags-$(CONFIG_AIC8800_BR_SUPPORT) 					+= -DCONFIG_BR_SUPPORT
+ccflags-$(CONFIG_AIC8800_RX_REORDER) 					+= -DAICWF_RX_REORDER
+ccflags-$(CONFIG_AIC8800_ARP_OFFLOAD) 					+= -DAICWF_ARP_OFFLOAD
+ccflags-$(CONFIG_AIC8800_RADAR_OR_IR_DETECT) 				+= -DRADAR_OR_IR_DETECT
+ccflags-$(CONFIG_AIC8800_RX_NETIF_RECV_SKB) 			+= -DCONFIG_RX_NETIF_RECV_SKB
+ccflags-$(CONFIG_AIC8800_ONE_TXQ)  						+= -DCONFIG_ONE_TXQ
+ccflags-$(CONFIG_AIC8800_TX_NETIF_FLOWCTRL) 			+= -DCONFIG_TX_NETIF_FLOWCTRL

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [RFC PATCH wireless-next 2/4] wifi: aic: add FullMAC firmware and platform interface
  2026-07-21 10:20 [RFC PATCH wireless-next 0/4] wifi: aic: add AIC8800 SDIO FullMAC driver Yanli Yang
  2026-07-21 10:21 ` [RFC PATCH wireless-next 1/4] wifi: aic: add SDIO BSP and build configuration Yanli Yang
@ 2026-07-21 10:21 ` Yanli Yang
  2026-07-21 10:21 ` [RFC PATCH wireless-next 3/4] wifi: aic: add FullMAC WLAN driver Yanli Yang
  2026-07-21 10:21 ` [RFC PATCH wireless-next 4/4] wifi: aic: integrate AIC8800 driver Yanli Yang
  3 siblings, 0 replies; 5+ messages in thread
From: Yanli Yang @ 2026-07-21 10:21 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg, Zhirun Liu, dijiaxu, chunqiuliu, liheng.wei

From: Zhirun Liu <zhirunliu@aicsemi.com>

Add the FullMAC IPC and LMAC protocol definitions, command
serialization, firmware request and confirmation handling, platform
bring-up, interrupt helpers and BSP integration.

Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
 .../aic/aic8800_fdrv/aic_bsp_export.h         |   62 +
 .../net/wireless/aic/aic8800_fdrv/aic_bus.h   |  191 +
 .../wireless/aic/aic8800_fdrv/aicwf_debug.h   |   37 +
 .../net/wireless/aic/aic8800_fdrv/hal_desc.h  |  362 ++
 .../wireless/aic/aic8800_fdrv/ipc_compat.h    |   25 +
 .../net/wireless/aic/aic8800_fdrv/ipc_host.c  |   42 +
 .../net/wireless/aic/aic8800_fdrv/ipc_host.h  |  169 +
 .../wireless/aic/aic8800_fdrv/ipc_shared.h    |  781 ++++
 .../net/wireless/aic/aic8800_fdrv/lmac_mac.h  |  470 ++
 .../net/wireless/aic/aic8800_fdrv/lmac_msg.h  | 3124 ++++++++++++++
 .../wireless/aic/aic8800_fdrv/lmac_types.h    |   37 +
 drivers/net/wireless/aic/aic8800_fdrv/md5.c   |  175 +
 drivers/net/wireless/aic/aic8800_fdrv/md5.h   |   82 +
 .../wireless/aic/aic8800_fdrv/reg_access.h    |  146 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_cmds.c |  471 ++
 .../net/wireless/aic/aic8800_fdrv/rwnx_cmds.h |   99 +
 .../wireless/aic/aic8800_fdrv/rwnx_compat.h   |   93 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_defs.h |  744 ++++
 .../wireless/aic/aic8800_fdrv/rwnx_events.h   |  938 ++++
 .../net/wireless/aic/aic8800_fdrv/rwnx_irqs.c |   44 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_irqs.h |   12 +
 .../wireless/aic/aic8800_fdrv/rwnx_msg_rx.c   | 1742 ++++++++
 .../wireless/aic/aic8800_fdrv/rwnx_msg_rx.h   |    8 +
 .../wireless/aic/aic8800_fdrv/rwnx_msg_tx.c   | 3815 +++++++++++++++++
 .../wireless/aic/aic8800_fdrv/rwnx_msg_tx.h   |  285 ++
 .../wireless/aic/aic8800_fdrv/rwnx_platform.c | 2946 +++++++++++++
 .../wireless/aic/aic8800_fdrv/rwnx_platform.h |  186 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_prof.h |  142 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_strs.c |  268 ++
 .../net/wireless/aic/aic8800_fdrv/rwnx_strs.h |   31 +
 .../wireless/aic/aic8800_fdrv/rwnx_utils.c    |   36 +
 .../wireless/aic/aic8800_fdrv/rwnx_utils.h    |  131 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_v7.c   |  182 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_v7.h   |   21 +
 .../wireless/aic/aic8800_fdrv/rwnx_version.h  |   14 +
 .../aic/aic8800_fdrv/rwnx_version_gen.h       |    5 +
 .../wireless/aic/aic8800_fdrv/rwnx_wakelock.c |  109 +
 .../wireless/aic/aic8800_fdrv/rwnx_wakelock.h |   24 +
 38 files changed, 18049 insertions(+)
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_bsp_export.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_bus.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_debug.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/hal_desc.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/ipc_compat.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/ipc_host.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/ipc_host.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/ipc_shared.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/lmac_mac.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/lmac_msg.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/lmac_types.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/md5.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/md5.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/reg_access.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_cmds.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_cmds.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_compat.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_defs.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_events.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_irqs.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_irqs.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_rx.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_rx.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_tx.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_tx.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_platform.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_platform.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_prof.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_strs.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_strs.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_utils.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_utils.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_v7.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_v7.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_version.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_version_gen.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_wakelock.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_wakelock.h

diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aic_bsp_export.h b/drivers/net/wireless/aic/aic8800_fdrv/aic_bsp_export.h
new file mode 100644
index 0000000000000..11b414df42ce3
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_bsp_export.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __AIC_BSP_EXPORT_H
+#define __AIC_BSP_EXPORT_H
+
+#include <linux/types.h>
+
+enum aicbsp_subsys {
+	AIC_BLUETOOTH,
+	AIC_WIFI,
+};
+
+enum aicbsp_pwr_state {
+	AIC_PWR_OFF,
+	AIC_PWR_ON,
+};
+
+struct aicbsp_feature_t {
+	int hwinfo;
+	u32 sdio_clock;
+	u8 sdio_phase;
+	int fwlog_en;
+	u8 irqf;
+	u8 cpmode;
+	int adapt;
+};
+
+enum skb_buff_id {
+	AIC_RESV_MEM_TXDATA,
+};
+
+#ifdef CONFIG_DPD
+struct rf_misc_ram_t {
+	u32 bit_mask[3];
+	u32 reserved;
+	u32 dpd_high[96];
+	u32 dpd_11b[96];
+	u32 dpd_low[96];
+	u32 idac_11b[48];
+	u32 idac_high[48];
+	u32 idac_low[48];
+	u32 loft_res[18];
+	u32 rx_iqim_res[16];
+};
+
+struct rf_misc_ram_lite_t {
+	u32 bit_mask[4];
+	u32 dpd_high[96];
+	u32 loft_res[18];
+};
+
+#define MEMBER_SIZE(type, member) sizeof_field(type, member)
+#define DPD_RESULT_SIZE_8800DC    sizeof(struct rf_misc_ram_lite_t)
+
+extern struct rf_misc_ram_lite_t dpd_res;
+#endif
+
+int aicbsp_set_subsys(int, int);
+int aicbsp_get_feature(struct aicbsp_feature_t *feature);
+struct sk_buff *aicbsp_resv_mem_alloc_skb(unsigned int length, uint32_t id);
+void aicbsp_resv_mem_kfree_skb(struct sk_buff *skb, uint32_t id);
+
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aic_bus.h b/drivers/net/wireless/aic/aic8800_fdrv/aic_bus.h
new file mode 100644
index 0000000000000..2498d3d592b42
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_bus.h
@@ -0,0 +1,191 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2024 AIC semiconductor.
+ *
+ * @file aic_bus.h
+ * @brief Common bus abstraction layer for AIC8800 series.
+ *
+ * This header defines the bus-level operations that abstract the
+ * underlying transport (SDIO, USB, etc.) from the upper driver layers.
+ * The design follows the pattern used by silabs/wfx and broadcom/b43.
+ *
+ * Key principles:
+ *  - struct aicwf_bus_ops provides a complete set of low-level bus
+ *    primitives (read/write registers, send/recv packets, flow control,
+ *    power management, interrupt handling).
+ *  - Upper-layer code accesses the bus only through the ops table and
+ *    inline wrappers defined here.  It MUST NOT dereference the
+ *    bus-private structure (aic_sdio_dev etc.) directly.
+ *  - When a new transport (e.g. USB) is added, only the bus_ops
+ *    implementation needs to change; upper layers are unaffected.
+ */
+
+#ifndef _AIC_BUS_H_
+#define _AIC_BUS_H_
+
+#include <linux/device.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+
+/* Bus operational state */
+enum aicwf_bus_state { BUS_DOWN_ST, BUS_UP_ST };
+
+struct aicwf_bus {
+	union {
+		struct aic_sdio_dev *sdio;
+		struct aic_usb_dev *usb;
+	} bus_priv;
+	struct device *dev;
+	struct aicwf_bus_ops *ops;
+	enum aicwf_bus_state state;
+	u8 *cmd_buf;
+	struct completion bustx_trgg;
+	struct completion busrx_trgg;
+	struct completion busirq_trgg; // new oob feature
+	struct task_struct *bustx_thread;
+	struct task_struct *busrx_thread;
+	struct task_struct *busirq_thread; // new oob feature
+};
+
+/* ================================================================
+ *  Bus-level operation vectors
+ *
+ *  Every transport (SDIO, USB, ...) provides one instance of this
+ *  struct.  The bus-private data pointer is carried inside
+ *  struct aicwf_bus so that ops callbacks can retrieve it via
+ *  bus->bus_priv.
+ * ================================================================
+ */
+
+struct aicwf_bus_ops {
+	/* ----- Lifecycle ----- */
+	int  (*start)(struct device *dev);
+	void (*stop)(struct device *dev);
+
+	/* ----- Data / message transmission ----- */
+	int (*txdata)(struct device *dev, struct sk_buff *skb);
+	int (*txmsg)(struct device *dev, u8 *msg, uint len);
+
+	/* ----- Low-level register access -----
+	 * These replace the direct aicwf_sdio_readb/writeb calls.
+	 */
+	int (*read_reg)(struct device *dev, u32 regaddr, u8 *val);
+	int (*write_reg)(struct device *dev, u32 regaddr, u8 val);
+
+	/* ----- Bulk data transfer -----
+	 * send_pkt  – write a buffer to the chip's TX FIFO
+	 * recv_pkt  – read  a buffer from the chip's RX FIFO
+	 */
+	int (*send_pkt)(struct device *dev, u8 *buf, uint count);
+	int (*recv_pkt)(struct device *dev, u8 *buf, u32 size);
+
+	/* ----- Interrupt handling ----- */
+	int  (*enable_irq)(struct device *dev);
+	void (*disable_irq)(struct device *dev);
+
+	/* ----- Flow control -----
+	 * Returns true if the device is ready to accept more data.
+	 */
+	bool (*flow_ctrl)(struct device *dev);
+
+	/* ----- Power management ----- */
+	int (*sleep_allow)(struct device *dev);
+	int (*wakeup)(struct device *dev);
+
+	/* ----- Chip-specific hardware info -----
+	 * Returns a pointer to chip hardware properties (SDIO register
+	 * layout, feature flags, etc.).  The caller treats this as
+	 * opaque; only the bus layer interprets it.
+	 */
+	const void *(*get_hw_props)(struct device *dev);
+};
+
+/* ================================================================
+ *  Inline wrappers – upper layers call these instead of touching
+ *  bus_priv internals.
+ * ================================================================
+ */
+
+static inline int aicwf_bus_start(struct aicwf_bus *bus)
+{
+	return bus->ops->start(bus->dev);
+}
+
+static inline void aicwf_bus_stop(struct aicwf_bus *bus)
+{
+	bus->ops->stop(bus->dev);
+}
+
+static inline int aicwf_bus_txdata(struct aicwf_bus *bus, struct sk_buff *skb)
+{
+	return bus->ops->txdata(bus->dev, skb);
+}
+
+static inline int aicwf_bus_txmsg(struct aicwf_bus *bus, u8 *msg, uint len)
+{
+	return bus->ops->txmsg(bus->dev, msg, len);
+}
+
+static inline int aicwf_bus_read_reg(struct aicwf_bus *bus,
+				     u32 regaddr, u8 *val)
+{
+	return bus->ops->read_reg(bus->dev, regaddr, val);
+}
+
+static inline int aicwf_bus_write_reg(struct aicwf_bus *bus,
+				      u32 regaddr, u8 val)
+{
+	return bus->ops->write_reg(bus->dev, regaddr, val);
+}
+
+static inline int aicwf_bus_send_pkt(struct aicwf_bus *bus,
+				     u8 *buf, uint count)
+{
+	return bus->ops->send_pkt(bus->dev, buf, count);
+}
+
+static inline int aicwf_bus_recv_pkt(struct aicwf_bus *bus,
+				     u8 *buf, u32 size)
+{
+	return bus->ops->recv_pkt(bus->dev, buf, size);
+}
+
+static inline int aicwf_bus_enable_irq(struct aicwf_bus *bus)
+{
+	return bus->ops->enable_irq(bus->dev);
+}
+
+static inline void aicwf_bus_disable_irq(struct aicwf_bus *bus)
+{
+	bus->ops->disable_irq(bus->dev);
+}
+
+static inline bool aicwf_bus_flow_ctrl(struct aicwf_bus *bus)
+{
+	return bus->ops->flow_ctrl(bus->dev);
+}
+
+static inline int aicwf_bus_sleep_allow(struct aicwf_bus *bus)
+{
+	return bus->ops->sleep_allow(bus->dev);
+}
+
+static inline int aicwf_bus_wakeup(struct aicwf_bus *bus)
+{
+	return bus->ops->wakeup(bus->dev);
+}
+
+static inline const void *aicwf_bus_get_hw_props(struct aicwf_bus *bus)
+{
+	return bus->ops->get_hw_props(bus->dev);
+}
+
+/* ================================================================
+ *  Bus-level init / deinit (shared across transports)
+ * ================================================================
+ */
+
+int aicwf_bus_init(uint bus_hdrlen, struct device *dev);
+void aicwf_bus_deinit(struct device *dev);
+
+#endif /* _AIC_BUS_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_debug.h b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_debug.h
new file mode 100644
index 0000000000000..4a49c16f15a31
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_debug.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AICWF_DEBUG_H_
+#define _AICWF_DEBUG_H_
+
+#define RWNX_FN_ENTRY_STR ">>> %s()\n", __func__
+
+/* message levels */
+#define LOGERROR    0x0001
+#define LOGINFO     0x0002
+#define LOGTRACE    0x0004
+#define LOGDEBUG    0x0008
+#define LOGDATA     0x0010
+#define LOGIRQ      0x0020
+#define LOGSDPWRC   0x0040
+#define LOGWAKELOCK 0x0080
+#define LOGRXPOLL   0x0100
+
+extern int aicwf_dbg_level;
+void rwnx_data_dump(char *tag, void *data, unsigned long len);
+
+#define AICWF_LOG "AICWFDBG("
+
+#define AICWFDBG(level, args, arg...)                                          \
+	do {                                                                       \
+		if (aicwf_dbg_level & (level)) {                                         \
+			pr_debug(AICWF_LOG #level ")\t" args, ##arg);                        \
+		}                                                                      \
+	} while (0)
+
+#define RWNX_DBG(fmt, ...)                                                     \
+	do {                                                                       \
+		if (aicwf_dbg_level & LOGTRACE) {                                      \
+			pr_debug(AICWF_LOG "LOGTRACE)\t" fmt, ##__VA_ARGS__);                \
+		}                                                                      \
+	} while (0)
+
+#endif /* _AICWF_DEBUG_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/hal_desc.h b/drivers/net/wireless/aic/aic8800_fdrv/hal_desc.h
new file mode 100644
index 0000000000000..95cba70c2a7ab
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/hal_desc.h
@@ -0,0 +1,362 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file hal_desc.c
+ *
+ * @brief File containing the definition of HW descriptors
+ *
+ ******************************************************************************
+ */
+
+#ifndef _HAL_DESC_H_
+#define _HAL_DESC_H_
+
+#include "lmac_types.h"
+#include "rwnx_compat.h"
+
+/* Rate and policy table */
+
+#define N_CCK   8
+#define N_OFDM  8
+#define N_HT    (8 * 2 * 2 * 4)
+#define N_VHT   (10 * 4 * 2 * 8)
+#define N_HE_SU (12 * 4 * 3 * 8)
+#define N_HE_MU (12 * 6 * 3 * 8)
+#define N_HE_ER (3 * 3 + 3) // RU242 + RU106
+
+/* conversion table from NL80211 to MACHW enum */
+extern const int chnl2bw[];
+
+/* conversion table from MACHW to NL80211 enum */
+extern const int bw2chnl[];
+
+/* Values for formatModTx */
+#define FORMATMOD_NON_HT          0
+#define FORMATMOD_NON_HT_DUP_OFDM 1
+#define FORMATMOD_HT_MF           2
+#define FORMATMOD_HT_GF           3
+#define FORMATMOD_VHT             4
+#define FORMATMOD_HE_SU           5
+#define FORMATMOD_HE_MU           6
+#define FORMATMOD_HE_ER           7
+#define FORMATMOD_HE_TB           8
+
+/* Values for navProtFrmEx */
+#define NAV_PROT_NO_PROT_BIT          0
+#define NAV_PROT_SELF_CTS_BIT         1
+#define NAV_PROT_RTS_CTS_BIT          2
+#define NAV_PROT_RTS_CTS_WITH_QAP_BIT 3
+#define NAV_PROT_STBC_BIT             4
+
+/* THD MACCTRLINFO2 fields, used in  struct umacdesc umac.flags */
+/// WhichDescriptor definition - contains aMPDU bit and position value
+/// Offset of WhichDescriptor field in the MAC CONTROL INFO 2 word
+#define WHICHDESC_OFT                   19
+/// Mask of the WhichDescriptor field
+#define WHICHDESC_MSK                   (0x07 << WHICHDESC_OFT)
+/// Only 1 THD possible, describing an unfragmented MSDU
+#define WHICHDESC_UNFRAGMENTED_MSDU     (0x00 << WHICHDESC_OFT)
+/// THD describing the first MPDU of a fragmented MSDU
+#define WHICHDESC_FRAGMENTED_MSDU_FIRST (0x01 << WHICHDESC_OFT)
+/// THD describing intermediate MPDUs of a fragmented MSDU
+#define WHICHDESC_FRAGMENTED_MSDU_INT   (0x02 << WHICHDESC_OFT)
+/// THD describing the last MPDU of a fragmented MSDU
+#define WHICHDESC_FRAGMENTED_MSDU_LAST  (0x03 << WHICHDESC_OFT)
+/// THD for extra descriptor starting an AMPDU
+#define WHICHDESC_AMPDU_EXTRA           (0x04 << WHICHDESC_OFT)
+/// THD describing the first MPDU of an A-MPDU
+#define WHICHDESC_AMPDU_FIRST           (0x05 << WHICHDESC_OFT)
+/// THD describing intermediate MPDUs of an A-MPDU
+#define WHICHDESC_AMPDU_INT             (0x06 << WHICHDESC_OFT)
+/// THD describing the last MPDU of an A-MPDU
+#define WHICHDESC_AMPDU_LAST            (0x07 << WHICHDESC_OFT)
+
+/// aMPDU bit offset
+#define AMPDU_OFT 21
+/// aMPDU bit
+#define AMPDU_BIT CO_BIT(AMPDU_OFT)
+
+union rwnx_mcs_index {
+	struct {
+		u32 mcs : 3;
+		u32 nss : 2;
+	} ht;
+	struct {
+		u32 mcs : 4;
+		u32 nss : 3;
+	} vht;
+	struct {
+		u32 mcs : 4;
+		u32 nss : 3;
+	} he;
+	u32 legacy : 7;
+};
+
+/* c.f RW-WLAN-nX-MAC-HW-UM */
+union rwnx_rate_ctrl_info {
+	struct {
+		u32 mcs_index_tx : 7;
+		u32 bw_tx : 2;
+		u32 gi_and_pre_type_tx : 2;
+		u32 format_mod_tx : 3;
+		u32 nav_prot_frm_ex : 3;
+		u32 mcs_index_prot_tx : 7;
+		u32 bw_prot_tx : 2;
+		u32 format_mod_prot_tx : 3;
+		u32 n_retry : 3;
+	};
+	u32 value;
+};
+
+/* c.f RW-WLAN-nX-MAC-HW-UM */
+struct rwnx_power_ctrl_info {
+	u32 tx_pwr_level_pt : 8;
+	u32 tx_pwr_level_prot_pt : 8;
+	u32 reserved : 16;
+};
+
+/* c.f RW-WLAN-nX-MAC-HW-UM */
+union rwnx_pol_phy_ctrl_info_1 {
+	struct {
+		u32 rsvd1 : 3;
+		u32 bf_frm_ex : 1;
+		u32 num_extn_ss : 2;
+		u32 fec_coding : 1;
+		u32 stbc : 2;
+		u32 rsvd2 : 5;
+		u32 n_tx : 3;
+		u32 n_tx_prot : 3;
+	};
+	u32 value;
+};
+
+/* c.f RW-WLAN-nX-MAC-HW-UM */
+union rwnx_pol_phy_ctrl_info_2 {
+	struct {
+		u32 antenna_set : 8;
+		u32 smm_index : 8;
+		u32 beam_formed : 1;
+	};
+	u32 value;
+};
+
+/* c.f RW-WLAN-nX-MAC-HW-UM */
+union rwnx_pol_mac_ctrl_info_1 {
+	struct {
+		u32 key_sram_index : 10;
+		u32 key_sram_index_ra : 10;
+	};
+	u32 value;
+};
+
+/* c.f RW-WLAN-nX-MAC-HW-UM */
+union rwnx_pol_mac_ctrl_info_2 {
+	struct {
+		u32 long_retry_limit : 8;
+		u32 short_retry_limit : 8;
+		u32 rts_threshold : 12;
+	};
+	u32 value;
+};
+
+#define POLICY_TABLE_PATTERN 0xBADCAB1E
+
+struct tx_policy_tbl {
+	/* Unique Pattern at the start of Policy Table */
+	u32 upatterntx;
+	/* PHY Control 1 Information used by MAC HW */
+	union rwnx_pol_phy_ctrl_info_1 phyctrlinfo_1;
+	/* PHY Control 2 Information used by MAC HW */
+	union rwnx_pol_phy_ctrl_info_2 phyctrlinfo_2;
+	/* MAC Control 1 Information used by MAC HW */
+	union rwnx_pol_mac_ctrl_info_1 macctrlinfo_1;
+	/* MAC Control 2 Information used by MAC HW */
+	union rwnx_pol_mac_ctrl_info_2 macctrlinfo_2;
+
+	union rwnx_rate_ctrl_info ratectrlinfos[NX_TX_MAX_RATES];
+	struct rwnx_power_ctrl_info powerctrlinfos[NX_TX_MAX_RATES];
+};
+
+#ifdef CONFIG_RWNX_FULLMAC
+
+/**
+ * struct rwnx_hw_txstatus - Bitfield of confirmation status
+ *
+ * @tx_done: packet has been processed by the firmware.
+ * @retry_required: packet has been transmitted but not acknoledged.
+ * Driver must repush it.
+ * @sw_retry_required: packet has not been transmitted (FW wasn't able to push
+ * it when it received it: not active channel ...). Driver must repush it.
+ * @acknowledged: packet has been acknowledged by peer
+ */
+union rwnx_hw_txstatus {
+	struct {
+		u32 tx_done : 1;
+		u32 retry_required : 1;
+		u32 sw_retry_required : 1;
+		u32 acknowledged : 1;
+		u32 reserved : 28;
+	};
+	u32 value;
+};
+
+/**
+ * struct tx_cfm_tag - Structure indicating the status and other
+ * information about the transmission
+ *
+ * @pn: PN that was used for the transmission
+ * @sn: Sequence number of the packet
+ * @timestamp: Timestamp of first transmission of this MPDU
+ * @credits: Number of credits to be reallocated for the txq that push this
+ * buffer (can be 0 or 1)
+ * @ampdu_size: Size of the ampdu in which the frame has been transmitted if
+ * this was the last frame of the a-mpdu, and 0 if the frame is not the last
+ * frame on a a-mdpu.
+ * 1 means that the frame has been transmitted as a singleton.
+ * @amsdu_size: Size, in bytes, allowed to create a-msdu.
+ * @status: transmission status
+ */
+struct tx_cfm_tag {
+	/*
+	 *	u16_l pn[4];
+	 *	u16_l sn;
+	 *	u16_l timestamp;
+	 */
+	s8_l credits;
+	u8_l ampdu_size;
+#ifdef CONFIG_RWNX_SPLIT_TX_BUF
+	u16_l amsdu_size;
+#endif
+	union rwnx_hw_txstatus status;
+	u32_l hostid;
+};
+
+/**
+ * struct rwnx_hw_txhdr - Hardware part of tx header
+ *
+ * @cfm: Information updated by fw/hardware after sending a frame
+ */
+struct rwnx_hw_txhdr {
+	struct tx_cfm_tag cfm;
+};
+
+#endif /* CONFIG_RWNX_FULLMAC */
+
+/* Modem */
+
+#define MDM_PHY_CONFIG_TRIDENT 0
+#define MDM_PHY_CONFIG_ELMA    1
+#define MDM_PHY_CONFIG_KARST   2
+
+// MODEM features (from reg_mdm_stat.h)
+/// MUMIMOTX field bit
+#define MDM_MUMIMOTX_BIT ((u32)0x80000000)
+/// MUMIMOTX field position
+#define MDM_MUMIMOTX_POS 31
+/// MUMIMORX field bit
+#define MDM_MUMIMORX_BIT ((u32)0x40000000)
+/// MUMIMORX field position
+#define MDM_MUMIMORX_POS 30
+/// BFMER field bit
+#define MDM_BFMER_BIT    ((u32)0x20000000)
+/// BFMER field position
+#define MDM_BFMER_POS    29
+/// BFMEE field bit
+#define MDM_BFMEE_BIT    ((u32)0x10000000)
+/// BFMEE field position
+#define MDM_BFMEE_POS    28
+/// LDPCDEC field bit
+#define MDM_LDPCDEC_BIT  ((u32)0x08000000)
+/// LDPCDEC field position
+#define MDM_LDPCDEC_POS  27
+/// LDPCENC field bit
+#define MDM_LDPCENC_BIT  ((u32)0x04000000)
+/// LDPCENC field position
+#define MDM_LDPCENC_POS  26
+/// CHBW field mask
+#define MDM_CHBW_MASK    ((u32)0x03000000)
+/// CHBW field LSB position
+#define MDM_CHBW_LSB     24
+/// CHBW field width
+#define MDM_CHBW_WIDTH   ((u32)0x00000002)
+/// DSSSCCK field bit
+#define MDM_DSSSCCK_BIT  ((u32)0x00800000)
+/// DSSSCCK field position
+#define MDM_DSSSCCK_POS  23
+/// VHT field bit
+#define MDM_VHT_BIT      ((u32)0x00400000)
+/// VHT field position
+#define MDM_VHT_POS      22
+/// HE field bit
+#define MDM_HE_BIT       ((u32)0x00200000)
+/// HE field position
+#define MDM_HE_POS       21
+/// ESS field bit
+#define MDM_ESS_BIT      ((u32)0x00100000)
+/// ESS field position
+#define MDM_ESS_POS      20
+/// RFMODE field mask
+#define MDM_RFMODE_MASK  ((u32)0x000F0000)
+/// RFMODE field LSB position
+#define MDM_RFMODE_LSB   16
+/// RFMODE field width
+#define MDM_RFMODE_WIDTH ((u32)0x00000004)
+/// NSTS field mask
+#define MDM_NSTS_MASK    ((u32)0x0000F000)
+/// NSTS field LSB position
+#define MDM_NSTS_LSB     12
+/// NSTS field width
+#define MDM_NSTS_WIDTH   ((u32)0x00000004)
+/// NSS field mask
+#define MDM_NSS_MASK     ((u32)0x00000F00)
+/// NSS field LSB position
+#define MDM_NSS_LSB      8
+/// NSS field width
+#define MDM_NSS_WIDTH    ((u32)0x00000004)
+/// NTX field mask
+#define MDM_NTX_MASK     ((u32)0x000000F0)
+/// NTX field LSB position
+#define MDM_NTX_LSB      4
+/// NTX field width
+#define MDM_NTX_WIDTH    ((u32)0x00000004)
+/// NRX field mask
+#define MDM_NRX_MASK     ((u32)0x0000000F)
+/// NRX field LSB position
+#define MDM_NRX_LSB      0
+/// NRX field width
+#define MDM_NRX_WIDTH    ((u32)0x00000004)
+
+#define __MDM_PHYCFG_FROM_VERS(v) (((v) & MDM_RFMODE_MASK) >> MDM_RFMODE_LSB)
+
+#define RIU_FCU_PRESENT_MASK ((u32)0xFF000000)
+#define RIU_FCU_PRESENT_LSB  24
+
+#define __RIU_FCU_PRESENT(v)                                                   \
+	(((v) & RIU_FCU_PRESENT_MASK) >> RIU_FCU_PRESENT_LSB == 5)
+
+/// AGC load version field mask
+#define RIU_AGC_LOAD_MASK ((u32)0x00C00000)
+/// AGC load version field LSB position
+#define RIU_AGC_LOAD_LSB  22
+
+#define __RIU_AGCLOAD_FROM_VERS(v)                                             \
+	(((v) & RIU_AGC_LOAD_MASK) >> RIU_AGC_LOAD_LSB)
+
+#define __FPGA_TYPE(v) (((v) & 0xFFFF0000) >> 16)
+
+#define __MDM_MAJOR_VERSION(v) (((v) & 0xFF000000) >> 24)
+#define __MDM_MINOR_VERSION(v) (((v) & 0x00FF0000) >> 16)
+/*
+ * #define __MDM_VERSION(v)                                                       \
+ *	((__MDM_MAJOR_VERSION(v) + 2) * 10 + __MDM_MINOR_VERSION(v))
+ */
+#define __MDM_VERSION(v)	\
+({								\
+	u32 __v = (v);						\
+	(__MDM_MAJOR_VERSION(__v) + 2) * 10 + __MDM_MINOR_VERSION(__v);	\
+})
+
+#endif // _HAL_DESC_H_
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/ipc_compat.h b/drivers/net/wireless/aic/aic8800_fdrv/ipc_compat.h
new file mode 100644
index 0000000000000..8c5b22ce74d0a
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/ipc_compat.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ ****************************************************************************************
+ */
+
+#ifndef _IPC_H_
+#define _IPC_H_
+
+#define __INLINE inline
+
+#define __ALIGN4 __aligned(4)
+
+#define ASSERT_ERR(condition)                                                  \
+	do {                                                                       \
+		if (unlikely(!(condition))) {                                          \
+			pr_err("%s:%d:ASSERT_ERR(" #condition ")\n", __FILE__,    \
+				   __LINE__);                                                  \
+		}                                                                      \
+	} while (0)
+
+#endif /* _IPC_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/ipc_host.c b/drivers/net/wireless/aic/aic8800_fdrv/ipc_host.c
new file mode 100644
index 0000000000000..6768a6e4a34f2
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/ipc_host.c
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief IPC module
+ *
+ ******************************************************************************
+ */
+
+#ifndef __KERNEL__
+#include <stdio.h>
+#else
+#include "rwnx_defs.h"
+#include "rwnx_prof.h"
+#include <linux/spinlock.h>
+#endif
+
+#include "ipc_host.h"
+
+/*
+ * TYPES DEFINITION
+ ******************************************************************************
+ */
+
+const int nx_txdesc_cnt[] = {
+	NX_TXDESC_CNT0, NX_TXDESC_CNT1, NX_TXDESC_CNT2, NX_TXDESC_CNT3,
+#if NX_TXQ_CNT == 5
+	NX_TXDESC_CNT4,
+#endif
+};
+
+const int nx_txuser_cnt[] = {
+	CONFIG_USER_MAX,
+	CONFIG_USER_MAX,
+	CONFIG_USER_MAX,
+	CONFIG_USER_MAX,
+#if NX_TXQ_CNT == 5
+	1,
+#endif
+};
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/ipc_host.h b/drivers/net/wireless/aic/aic8800_fdrv/ipc_host.h
new file mode 100644
index 0000000000000..69efdedc8ec86
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/ipc_host.h
@@ -0,0 +1,169 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief IPC module
+ *
+ ******************************************************************************
+ */
+
+#ifndef _IPC_HOST_H_
+#define _IPC_HOST_H_
+
+/*
+ * INCLUDE FILES
+ ******************************************************************************
+ */
+#include "ipc_shared.h"
+#ifndef __KERNEL__
+#include "arch.h"
+#else
+#include "ipc_compat.h"
+#endif
+
+/**
+ ******************************************************************************
+ * @brief This structure is used to initialize the MAC SW
+ *
+ * The WLAN device driver provides functions call-back with this structure
+ ******************************************************************************
+ */
+struct ipc_host_cb_tag {
+	/// WLAN driver call-back function: send_data_cfm
+	int (*send_data_cfm)(void *pthis, void *host_id);
+
+	/// WLAN driver call-back function: recv_data_ind
+	uint8_t (*recv_data_ind)(void *pthis, void *host_id);
+
+	/// WLAN driver call-back function: recv_radar_ind
+	uint8_t (*recv_radar_ind)(void *pthis, void *host_id);
+
+	/// WLAN driver call-back function: recv_unsup_rx_vec_ind
+	uint8_t (*recv_unsup_rx_vec_ind)(void *pthis, void *host_id);
+
+	/// WLAN driver call-back function: recv_msg_ind
+	uint8_t (*recv_msg_ind)(void *pthis, void *host_id);
+
+	/// WLAN driver call-back function: recv_msgack_ind
+	uint8_t (*recv_msgack_ind)(void *pthis, void *host_id);
+
+	/// WLAN driver call-back function: recv_dbg_ind
+	uint8_t (*recv_dbg_ind)(void *pthis, void *host_id);
+
+	/// WLAN driver call-back function: prim_tbtt_ind
+	void (*prim_tbtt_ind)(void *pthis);
+
+	/// WLAN driver call-back function: sec_tbtt_ind
+	void (*sec_tbtt_ind)(void *pthis);
+};
+
+/*
+ * Struct used to store information about host buffers (DMA Address and local
+ * pointer)
+ */
+struct ipc_hostbuf {
+	void *hostid;      ///< ptr to hostbuf client (ipc_host client) structure
+	u32 dma_addr; ///< ptr to real hostbuf dma address
+};
+
+/// Definition of the IPC Host environment structure.
+struct ipc_host_env_tag {
+	/// Structure containing the callback pointers
+	struct ipc_host_cb_tag cb;
+
+	/// Pointer to the shared environment
+	struct ipc_shared_env_tag *shared;
+
+#ifdef CONFIG_RWNX_FULLMAC
+	// Array used to store the descriptor addresses
+	struct ipc_hostbuf ipc_host_rxdesc_array[IPC_RXDESC_CNT];
+	// Index of the host RX descriptor array (ipc_shared environment)
+	u8 ipc_host_rxdesc_idx;
+	/// Store the number of RX Descriptors
+	u8 rxdesc_nb;
+#endif //(CONFIG_RWNX_FULLMAC)
+
+	/// Fields for Data Rx handling
+	// Index used for ipc_host_rxbuf_array to point to current buffer
+	u8 ipc_host_rxbuf_idx;
+	// Store the number of Rx Data buffers
+	u32 rx_bufnb;
+	// Store the size of the Rx Data buffers
+	u32 rx_bufsz;
+
+	/// Fields for Radar events handling
+	// Global array used to store the hostid and hostbuf addresses
+	struct ipc_hostbuf ipc_host_radarbuf_array[IPC_RADARBUF_CNT];
+	// Index used for ipc_host_rxbuf_array to point to current buffer
+	u8 ipc_host_radarbuf_idx;
+	// Store the number of radar event buffers
+	u32 radar_bufnb;
+	// Store the size of the radar event buffers
+	u32 radar_bufsz;
+
+	/// Fields for Unsupported frame handling
+	// Global array used to store the hostid and hostbuf addresses
+	struct ipc_hostbuf ipc_host_unsuprxvecbuf_array[IPC_UNSUPRXVECBUF_CNT];
+	// Index used for ipc_host_unsuprxvecbuf_array to point to current buffer
+	u8 ipc_host_unsuprxvecbuf_idx;
+	// Store the number of unsupported rx vector buffers
+	u32 unsuprxvec_bufnb;
+	// Store the size of unsupported rx vector buffers
+	u32 unsuprxvec_bufsz;
+
+	// Index used that points to the first free TX desc
+	u32 txdesc_free_idx[IPC_TXQUEUE_CNT][CONFIG_USER_MAX];
+	// Index used that points to the first used TX desc
+	u32 txdesc_used_idx[IPC_TXQUEUE_CNT][CONFIG_USER_MAX];
+	// Array storing the currently pushed host ids for the BK queue
+	void *tx_host_id0[CONFIG_USER_MAX][NX_TXDESC_CNT0];
+	// Array storing the currently pushed host ids for the BE queue
+	void *tx_host_id1[CONFIG_USER_MAX][NX_TXDESC_CNT1];
+	// Array storing the currently pushed host ids for the VI queue
+	void *tx_host_id2[CONFIG_USER_MAX][NX_TXDESC_CNT2];
+	// Array storing the currently pushed host ids for the VO queue
+	void *tx_host_id3[CONFIG_USER_MAX][NX_TXDESC_CNT3];
+#if NX_TXQ_CNT == 5
+	// Array storing the currently pushed host ids for the BCN queue
+	void *tx_host_id4[1][NX_TXDESC_CNT4];
+#endif
+	// Pointer to the different host ids arrays, per IPC queue
+	void **tx_host_id[IPC_TXQUEUE_CNT][CONFIG_USER_MAX];
+	// Pointer to the different TX descriptor arrays, per IPC queue
+	struct txdesc_host *txdesc[IPC_TXQUEUE_CNT][CONFIG_USER_MAX];
+
+	/// Fields for Emb->App MSGs handling
+	// Global array used to store the hostid and hostbuf addresses for msg/ind
+	struct ipc_hostbuf ipc_host_msgbuf_array[IPC_MSGE2A_BUF_CNT];
+	// Index of the MSG E2A buffers array to point to current buffer
+	u8 ipc_host_msge2a_idx;
+	// Store the number of E2A MSG buffers
+	u32 ipc_e2amsg_bufnb;
+	// Store the size of the E2A MSG buffers
+	u32 ipc_e2amsg_bufsz;
+
+	/// E2A ACKs of A2E MSGs
+	u8 msga2e_cnt;
+	void *msga2e_hostid;
+
+	/// Fields for Debug MSGs handling
+	// Global array used to store the hostid and hostbuf addresses for Debug
+	// messages
+	struct ipc_hostbuf ipc_host_dbgbuf_array[IPC_DBGBUF_CNT];
+	// Index of the Debug messages buffers array to point to current buffer
+	u8 ipc_host_dbg_idx;
+	// Store the number of Debug messages buffers
+	u32 ipc_dbg_bufnb;
+	// Store the size of the Debug messages buffers
+	u32 ipc_dbg_bufsz;
+
+	/// Pointer to the attached object (used in callbacks and register accesses)
+	void *pthis;
+};
+
+extern const int nx_txdesc_cnt[];
+extern const int nx_txuser_cnt[];
+
+#endif // _IPC_HOST_H_
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/ipc_shared.h b/drivers/net/wireless/aic/aic8800_fdrv/ipc_shared.h
new file mode 100644
index 0000000000000..c71b74bd69f75
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/ipc_shared.h
@@ -0,0 +1,781 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief Shared data between both IPC modules
+ *
+ ******************************************************************************
+ */
+
+#ifndef _IPC_SHARED_H_
+#define _IPC_SHARED_H_
+
+/*
+ * INCLUDE FILES
+ ****************************************************************************************
+ */
+#include "ipc_compat.h"
+#include "lmac_mac.h"
+#include "rwnx_compat.h"
+
+/*
+ * DEFINES AND MACROS
+ ****************************************************************************************
+ */
+#define CO_BIT(pos) (1U << (pos))
+
+#define IPC_TXQUEUE_CNT NX_TXQ_CNT
+#define NX_TXDESC_CNT0  8
+#define NX_TXDESC_CNT1  64
+#define NX_TXDESC_CNT2  64
+#define NX_TXDESC_CNT3  32
+#if NX_TXQ_CNT == 5
+#define NX_TXDESC_CNT4 8
+#endif
+
+/*
+ * Number of Host buffers available for Data Rx handling (through DMA)
+ */
+#define IPC_RXBUF_CNT 128
+
+/*
+ * Number of shared descriptors available for Data RX handling
+ */
+#define IPC_RXDESC_CNT 128
+
+/*
+ * Number of Host buffers available for Radar events handling (through DMA)
+ */
+#define IPC_RADARBUF_CNT 16
+
+/*
+ * Number of Host buffers available for unsupported Rx vectors handling (through
+ * DMA)
+ */
+#define IPC_UNSUPRXVECBUF_CNT 8
+
+/*
+ *  Size of RxVector
+ */
+#define IPC_RXVEC_SIZE 16
+
+/*
+ * Number of Host buffers available for Emb->App MSGs sending (through DMA)
+ */
+#ifdef CONFIG_RWNX_FULLMAC
+#define IPC_MSGE2A_BUF_CNT 64
+#endif
+/*
+ * Number of Host buffers available for Debug Messages sending (through DMA)
+ */
+#define IPC_DBGBUF_CNT 32
+
+/*
+ * Length used in MSGs structures
+ */
+#define IPC_A2E_MSG_BUF_SIZE 127 // size in 4-byte words
+#ifdef CONFIG_RWNX_FULLMAC
+#define IPC_E2A_MSG_SIZE_BASE 256 // size in 4-byte words
+#endif
+
+#ifdef CONFIG_RWNX_TL4
+#define IPC_E2A_MSG_PARAM_SIZE                                                 \
+	(IPC_E2A_MSG_SIZE_BASE + (IPC_E2A_MSG_SIZE_BASE / 2))
+#else
+#define IPC_E2A_MSG_PARAM_SIZE IPC_E2A_MSG_SIZE_BASE
+#endif
+
+/*
+ * Debug messages buffers size (in bytes)
+ */
+#define IPC_DBG_PARAM_SIZE 256
+
+/*
+ * Define used for Rx hostbuf validity.
+ * This value should appear only when hostbuf was used for a Reception.
+ */
+#define RX_DMA_OVER_PATTERN 0xAAAAAA00
+
+/*
+ * Define used for MSG buffers validity.
+ * This value will be written only when a MSG buffer is used for sending from
+ * Emb to App.
+ */
+#define IPC_MSGE2A_VALID_PATTERN 0xADDEDE2A
+
+/*
+ * Define used for Debug messages buffers validity.
+ * This value will be written only when a DBG buffer is used for sending from
+ * Emb to App.
+ */
+#define IPC_DBG_VALID_PATTERN 0x000CACA0
+
+/*
+ *  Length of the receive vectors, in bytes
+ */
+#define DMA_HDR_PHYVECT_LEN 36
+
+/*
+ * Maximum number of payload addresses and lengths present in the descriptor
+ */
+#ifdef CONFIG_RWNX_SPLIT_TX_BUF
+#define NX_TX_PAYLOAD_MAX 6
+#else
+#define NX_TX_PAYLOAD_MAX 1
+#endif
+
+/*
+ * Message struct/ID API version
+ */
+#define MSG_API_VER 33
+
+/*
+ ****************************************************************************************
+ */
+// c.f LMAC/src/tx/tx_swdesc.h
+/// Descriptor filled by the Host
+struct hostdesc {
+	/// Pointer to packet payload
+	// u32_l packet_addr;
+	/// Size of the payload
+	u16_l packet_len;
+	u16_l flags_ext;
+
+	u32_l hostid;
+#ifdef CONFIG_RWNX_FULLMAC
+	/// Address of the status descriptor in host memory (used for confirmation
+	/// upload)
+	// u32_l status_desc_addr;
+	/// Destination Address
+	struct mac_addr eth_dest_addr;
+	/// Source Address
+	struct mac_addr eth_src_addr;
+	/// Ethernet Type
+	u16_l ethertype;
+#else /* ! CONFIG_RWNX_FULLMAC */
+#ifdef CONFIG_RWNX_AGG_TX
+	/// Sequence Number for AMPDU MPDUs - for quick check if it's allowed within
+	/// window
+	u16_l sn;
+#endif /* CONFIG_RWNX_AGG_TX */
+	/// Padding between the buffer control structure and the MPDU in host memory
+	u8_l padding;
+#endif /* CONFIG_RWNX_FULLMAC */
+	u8_l ac;
+	/// Packet TID (0xFF if not a QoS frame)
+	u8_l tid;
+	/// Interface Id
+	u8_l vif_idx;
+	/// Station Id (0xFF if station is unknown)
+	u8_l staid;
+#ifdef CONFIG_RWNX_MUMIMO_TX
+	/// MU-MIMO information (GroupId and User Position in the group) - The
+	/// GroupId is located on bits 0-5 and the User Position on bits 6-7. The
+	/// GroupId value is set to 63 if MU-MIMO shall not be used
+	u8_l mumimo_info;
+#endif /* CONFIG_RWNX_MUMIMO_TX */
+#ifdef CONFIG_RWNX_FULLMAC
+	/// TX flags
+	u16_l flags;
+#endif /* CONFIG_RWNX_FULLMAC */
+};
+
+/// Descriptor filled by the UMAC
+struct umacdesc {
+#ifdef CONFIG_RWNX_AGG_TX
+	/// First Sequence Number of the BlockAck window
+	u16_l sn_win;
+	/// Flags from UMAC (match tx_hd.macctrlinfo2 format)
+	u32_l flags;
+	/// PHY related flags field - rate, GI type, BW type - filled by driver
+	u32_l phy_flags;
+#endif //(CONFIG_RWNX_AGG_TX)
+};
+
+struct txdesc_api {
+	/// Information provided by Host
+	struct hostdesc host;
+};
+
+struct txdesc_host {
+	u32_l ready;
+
+	/// API of the embedded part
+	struct txdesc_api api;
+};
+
+/// Comes from ipc_dma.h
+/// Element in the pool of TX DMA bridge descriptors.
+struct dma_desc {
+	/* Application subsystem address which is used as source address for DMA
+	 * payload transfer
+	 */
+	u32_l src;
+	/** Points to the start of the embedded data buffer associated with this
+	 * descriptor. This address acts as the destination address for the DMA
+	 * payload transfer
+	 */
+	u32_l dest;
+	/// Complete length of the buffer in memory
+	u16_l length;
+	/// Control word for the DMA engine (e.g. for interrupt generation)
+	u16_l ctrl;
+	/// Pointer to the next element of the chained list
+	u32_l next;
+};
+
+// Comes from la.h
+/// Length of the configuration data of a logic analyzer
+#define LA_CONF_LEN 10
+
+/// Structure containing the configuration data of a logic analyzer
+struct la_conf_tag {
+	u32_l conf[LA_CONF_LEN];
+	u32_l trace_len;
+	u32_l diag_conf;
+};
+
+/// Size of a logic analyzer memory
+#define LA_MEM_LEN (1024 * 1024)
+
+/// Type of errors
+enum {
+	/// Recoverable error, not requiring any action from Upper MAC
+	DBG_ERROR_RECOVERABLE = 0,
+	/// Fatal error, requiring Upper MAC to reset Lower MAC and HW and restart
+	/// operation
+	DBG_ERROR_FATAL
+};
+
+/// Maximum length of the SW diag trace
+#define DBG_SW_DIAG_MAX_LEN 1024
+
+/// Maximum length of the error trace
+#define DBG_ERROR_TRACE_SIZE 256
+
+/// Number of MAC diagnostic port banks
+#define DBG_DIAGS_MAC_MAX 48
+
+/// Number of PHY diagnostic port banks
+#define DBG_DIAGS_PHY_MAX 32
+
+/// Maximum size of the RX header descriptor information in the debug dump
+#define DBG_RHD_MEM_LEN (5 * 1024)
+
+/// Maximum size of the RX buffer descriptor information in the debug dump
+#define DBG_RBD_MEM_LEN (5 * 1024)
+
+/// Maximum size of the TX header descriptor information in the debug dump
+#define DBG_THD_MEM_LEN (10 * 1024)
+
+/// Structure containing the information about the PHY channel that is used
+struct phy_channel_info {
+	/// PHY channel information 1
+	u32_l info1;
+	/// PHY channel information 2
+	u32_l info2;
+};
+
+/// Debug information forwarded to host when an error occurs
+struct dbg_debug_info_tag {
+	/// Type of error (0: recoverable, 1: fatal)
+	u32_l error_type;
+	/// Pointer to the first RX Header Descriptor chained to the MAC HW
+	u32_l rhd;
+	/// Size of the RX header descriptor buffer
+	u32_l rhd_len;
+	/// Pointer to the first RX Buffer Descriptor chained to the MAC HW
+	u32_l rbd;
+	/// Size of the RX buffer descriptor buffer
+	u32_l rbd_len;
+	/// Pointer to the first TX Header Descriptors chained to the MAC HW
+	u32_l thd[NX_TXQ_CNT];
+	/// Size of the TX header descriptor buffer
+	u32_l thd_len[NX_TXQ_CNT];
+	/// MAC HW diag configuration
+	u32_l hw_diag;
+	/// Error message
+	u32_l error[DBG_ERROR_TRACE_SIZE / 4];
+	/// SW diag configuration length
+	u32_l sw_diag_len;
+	/// SW diag configuration
+	u32_l sw_diag[DBG_SW_DIAG_MAX_LEN / 4];
+	/// PHY channel information
+	struct phy_channel_info chan_info;
+	/// Embedded LA configuration
+	struct la_conf_tag la_conf;
+	/// MAC diagnostic port state
+	u16_l diags_mac[DBG_DIAGS_MAC_MAX];
+	/// PHY diagnostic port state
+	u16_l diags_phy[DBG_DIAGS_PHY_MAX];
+	/// MAC HW RX Header descriptor pointer
+	u32_l rhd_hw_ptr;
+	/// MAC HW RX Buffer descriptor pointer
+	u32_l rbd_hw_ptr;
+};
+
+/// Full debug dump that is forwarded to host in case of error
+struct dbg_debug_dump_tag {
+	/// Debug information
+	struct dbg_debug_info_tag dbg_info;
+
+	/// RX header descriptor memory
+	u32_l rhd_mem[DBG_RHD_MEM_LEN / 4];
+
+	/// RX buffer descriptor memory
+	u32_l rbd_mem[DBG_RBD_MEM_LEN / 4];
+
+	/// TX header descriptor memory
+	u32_l thd_mem[NX_TXQ_CNT][DBG_THD_MEM_LEN / 4];
+
+	/// Logic analyzer memory
+	u32_l la_mem[LA_MEM_LEN / 4];
+};
+
+/// Number of pulses in a radar event structure
+#define RADAR_PULSE_MAX 4
+
+/// Definition of an array of radar pulses
+struct radar_pulse_array_desc {
+	/// Buffer containing the radar pulses
+	u32_l pulse[RADAR_PULSE_MAX];
+	/// Index of the radar detection chain that detected those pulses
+	u32_l idx;
+	/// Number of valid pulses in the buffer
+	u32_l cnt;
+};
+
+/// Bit mapping inside a radar pulse element
+struct radar_pulse {
+	s32_l
+		freq : 6;  /** Freq (resolution is 2Mhz range is [-Fadc/4 .. Fadc/4]) */
+	u32_l fom : 4; /** Figure of Merit */
+	u32_l len : 6; /** Length of the current radar pulse (resolution is 2us) */
+	u32_l rep : 16; /* Time interval between the previous radar event
+			 * and the current one (in us)
+			 */
+};
+
+/// Definition of a RX vector descriptor
+struct rx_vector_desc {
+	/// PHY channel information
+	struct phy_channel_info phy_info;
+
+	/// RX vector 1
+	u32_l rx_vect1[IPC_RXVEC_SIZE / 4];
+
+	/// Used to print a valid rx vector
+	u32_l pattern;
+};
+
+///
+struct rxdesc_tag {
+	/// Host Buffer Address
+	u32_l host_id;
+	/// Length
+	u32_l frame_len;
+	/// Status
+	u16_l status;
+};
+
+/**
+ ****************************************************************************************
+ * @addtogroup IPC_TX IPC Tx path
+ *  @ingroup IPC
+ *  @brief IPC Tx path structures and functions
+ *
+ * A typical use case of the IPC Tx path API:
+ * @msc
+ * hscale = "2";
+ *
+ * a [label=Driver],
+ * b [label="IPC host"],
+ * c [label="IPC emb"],
+ * d [label=Firmware];
+ *
+ * ---   [label="Tx descriptor queue example"];
+ * a=>a  [label="Driver receives a Tx packet from OS"];
+ * a=>b  [label="ipc_host_txdesc_get()"];
+ * a<<b  [label="struct txdesc_host *"];
+ * a=>a  [label="Driver fill the descriptor"];
+ * a=>b  [label="ipc_host_txdesc_push()"];
+ * ...   [label="(several Tx desc can be pushed)"];
+ * b:>c  [label="Tx desc queue filled IRQ"];
+ * c=>>d [label="EDCA sub-scheduler callback"];
+ * c<<d  [label="Tx desc queue to pop"];
+ * c=>>d [label="UMAC Tx desc callback"];
+ * ...   [label="(several Tx desc can be popped)"];
+ * d=>d  [label="Packets are sent or discarded"];
+ * ---   [label="Tx confirm queue example"];
+ * c<=d  [label="ipc_emb_txcfm_push()"];
+ * c>>d  [label="Request accepted"];
+ * ...   [label="(several Tx cfm can be pushed)"];
+ * b<:c  [label="Tx cfm queue filled IRQ"];
+ * a<<=b [label="Driver's Tx Confirm callback"];
+ * a=>b  [label="ipc_host_txcfm_pop()"];
+ * a<<b  [label="struct ipc_txcfm"];
+ * a<=a  [label="Packets are freed by the driver"];
+ * @endmsc
+ *
+ * @{
+ ****************************************************************************************
+ */
+
+/// @} IPC_TX
+
+/**
+ ****************************************************************************************
+ *  @defgroup IPC_RX IPC Rx path
+ *  @ingroup IPC
+ *  @brief IPC Rx path functions and structures
+ *
+ * A typical use case of the IPC Rx path API:
+ * @msc
+ * hscale = "2";
+ *
+ * a [label=Firmware],
+ * b [label="IPC emb"],
+ * c [label="IPC host"],
+ * d [label=Driver];
+ *
+ * ---   [label="Rx buffer and desc queues usage example"];
+ * d=>c  [label="ipc_host_rxbuf_push()"];
+ * d=>c  [label="ipc_host_rxbuf_push()"];
+ * d=>c  [label="ipc_host_rxbuf_push()"];
+ * ...   [label="(several Rx buffer are pushed)"];
+ * a=>a  [label=" Frame is received\n from the medium"];
+ * a<<b  [label="struct ipc_rxbuf"];
+ * a=>a  [label=" Firmware fill the buffer\n with received frame"];
+ * a<<b  [label="Push accepted"];
+ * ...   [label="(several Rx desc can be pushed)"];
+ * b:>c  [label="Rx desc queue filled IRQ"];
+ * c=>>d [label="Driver Rx packet callback"];
+ * c<=d  [label="ipc_host_rxdesc_pop()"];
+ * d=>d  [label="Rx packet is handed\nover to the OS "];
+ * ...   [label="(several Rx desc can be poped)"];
+ * ---   [label="Rx buffer request exemple"];
+ * b:>c  [label="Low Rx buffer count IRQ"];
+ * a<<b  [label="struct ipc_rxbuf"];
+ * c=>>d [label="Driver Rx buffer callback"];
+ * d=>c  [label="ipc_host_rxbuf_push()"];
+ * d=>c  [label="ipc_host_rxbuf_push()"];
+ * d=>c  [label="ipc_host_rxbuf_push()"];
+ * ...   [label="(several Rx buffer are pushed)"];
+ * @endmsc
+ *
+ * @addtogroup IPC_RX
+ * @{
+ ****************************************************************************************
+ */
+
+/// @} IPC_RX
+
+/**
+ ****************************************************************************************
+ *  @defgroup IPC_MISC IPC Misc
+ *  @ingroup IPC
+ *  @brief IPC miscellaneous functions
+ ****************************************************************************************
+ */
+/** IPC header structure.  This structure is stored at the beginning of every
+ * IPC message.
+ * @warning This structure's size must NOT exceed 4 bytes in length.
+ */
+struct ipc_header {
+	/// IPC message type.
+	u16_l type;
+	/// IPC message size in number of bytes.
+	u16_l size;
+};
+
+struct ipc_msg_elt {
+	/// Message header (alignment forced on word size, see allocation in shared
+	/// env).
+	struct ipc_header header __ALIGN4;
+};
+
+/// Message structure for MSGs from Emb to App
+struct ipc_e2a_msg {
+	u16_l id; ///< Message id.
+	u16_l dummy_dest_id;
+	u16_l dummy_src_id;
+	u16_l param_len;                     ///< Parameter embedded struct length.
+	u32_l pattern;                       ///< Used to stamp a valid MSG buffer
+	u32_l param[IPC_E2A_MSG_PARAM_SIZE]; ///< Parameter embedded struct. Must be
+										 ///< word-aligned.
+};
+
+/// Message structure for Debug messages from Emb to App
+struct ipc_dbg_msg {
+	u32_l string[IPC_DBG_PARAM_SIZE / 4]; ///< Debug string
+	u32_l pattern;                        ///< Used to stamp a valid buffer
+};
+
+/// Message structure for MSGs from App to Emb.
+/// Actually a sub-structure will be used when filling the messages.
+struct ipc_a2e_msg {
+	u32_l dummy_word; // used to cope with kernel message structure
+	u32_l msg[IPC_A2E_MSG_BUF_SIZE]; // body of the msg
+};
+
+struct ipc_shared_rx_buf {
+	/// < ptr to hostbuf client (ipc_host client) structure
+	u32_l hostid;
+	/// < ptr to real hostbuf dma address
+	u32_l dma_addr;
+};
+
+struct ipc_shared_rx_desc {
+	/// DMA Address
+	u32_l dma_addr;
+};
+
+/// Structure containing FW characteristics for compatibility checking
+struct compatibility_tag {
+	/// Size of IPC shared memory
+	u16_l ipc_shared_size;
+	/// Message struct/ID API version
+	u16_l msg_api;
+	/// Version of IPC shared
+	u8_l ipc_shared_version;
+	/// Number of host buffers available for Emb->App MSGs sending
+	u8_l msge2a_buf_cnt;
+	/// Number of host buffers available for Debug Messages sending
+	u8_l dbgbuf_cnt;
+	/// Number of host buffers available for Radar events handling
+	u8_l radarbuf_cnt;
+	/// Number of host buffers available for unsupported Rx vectors handling
+	u8_l unsuprxvecbuf_cnt;
+	/// Number of shared descriptors available for Data RX handling
+	u8_l rxdesc_cnt;
+	/// Number of host buffers available for Data Rx handling
+	u8_l rxbuf_cnt;
+	/// Number of descriptors in BK TX queue (power of 2, min 4, max 64)
+	u8_l bk_txq;
+	/// Number of descriptors in BE TX queue (power of 2, min 4, max 64)
+	u8_l be_txq;
+	/// Number of descriptors in VI TX queue (power of 2, min 4, max 64)
+	u8_l vi_txq;
+	/// Number of descriptors in VO TX queue (power of 2, min 4, max 64)
+	u8_l vo_txq;
+	/// Number of descriptors in BCN TX queue (power of 2, min 4, max 64)
+	u8_l bcn_txq;
+};
+
+/*
+ * TYPE and STRUCT DEFINITIONS
+ ****************************************************************************************
+ */
+
+// Indexes are defined in the MIB shared structure
+struct ipc_shared_env_tag {
+	struct compatibility_tag comp_info; // FW characteristics
+
+	struct ipc_a2e_msg
+		msg_a2e_buf; // room for MSG to be sent from App to Emb
+
+	// Fields for MSGs sending from Emb to App
+	struct ipc_e2a_msg
+		msg_e2a_buf; // room to build the MSG to be DMA Xferred
+	struct dma_desc
+		msg_dma_desc; // DMA descriptor for Emb->App MSGs Xfers
+	u32_l msg_e2a_hostbuf_addr[IPC_MSGE2A_BUF_CNT]; // buffers @ for DMA Xfers
+
+	// Fields for Debug MSGs sending from Emb to App
+	struct ipc_dbg_msg
+		dbg_buf; // room to build the MSG to be DMA Xferred
+	struct dma_desc
+		dbg_dma_desc; // DMA descriptor for Emb->App MSGs Xfers
+	u32_l dbg_hostbuf_addr[IPC_DBGBUF_CNT]; // buffers @ for MSGs DMA Xfers
+	u32_l la_dbginfo_addr; // Host buffer address for the debug information
+	u32_l pattern_addr;
+
+	u32_l radarbuf_hostbuf[IPC_RADARBUF_CNT]; // buffers @ for Radar Events
+	u32_l unsuprxvecbuf_hostbuf[IPC_UNSUPRXVECBUF_CNT]; // buffers @ for
+	// unsupported Rx vectors
+	struct txdesc_host txdesc0[CONFIG_USER_MAX][NX_TXDESC_CNT0];
+	struct txdesc_host txdesc1[CONFIG_USER_MAX][NX_TXDESC_CNT1];
+	struct txdesc_host txdesc2[CONFIG_USER_MAX][NX_TXDESC_CNT2];
+	struct txdesc_host txdesc3[CONFIG_USER_MAX][NX_TXDESC_CNT3];
+#if NX_TXQ_CNT == 5
+	struct txdesc_host txdesc4[1][NX_TXDESC_CNT4];
+#endif
+#ifdef CONFIG_RWNX_FULLMAC
+	// RX Descriptors Array
+	struct ipc_shared_rx_desc host_rxdesc[IPC_RXDESC_CNT];
+	// RX Buffers Array
+	struct ipc_shared_rx_buf host_rxbuf[IPC_RXBUF_CNT];
+#else
+	// buffers @ for Data Rx
+	u32_l host_rxbuf[IPC_RXBUF_CNT];
+#endif /* CONFIG_RWNX_FULLMAC */
+
+	u32_l buffered[NX_REMOTE_STA_MAX][TID_MAX];
+
+	u16 trace_pattern;
+	u32 trace_start;
+	u32 trace_end;
+	u32 trace_size;
+	u32 trace_offset;
+	u32 trace_nb_compo;
+	u32 trace_offset_compo;
+};
+
+extern struct ipc_shared_env_tag ipc_shared_env;
+
+/*
+ * TYPE and STRUCT DEFINITIONS
+ ****************************************************************************************
+ */
+
+// IRQs from app to emb
+/// Interrupts bits used for the TX descriptors of the AC queues
+#ifdef CONFIG_RWNX_MUMIMO_TX
+#ifdef CONFIG_RWNX_OLD_IPC
+#error "MU-MIMO cannot be compiled for old IPC"
+#endif
+/// Interrupts bits used
+#if CONFIG_USER_MAX > 3
+#define IPC_IRQ_A2E_USER_MSK 0xF
+#elif CONFIG_USER_MAX > 2
+#define IPC_IRQ_A2E_USER_MSK 0x7
+#else
+#define IPC_IRQ_A2E_USER_MSK 0x3
+#endif
+
+/// Offset of the interrupts for AC0
+#define IPC_IRQ_A2E_AC0_OFT 8
+/// Mask of the interrupts for AC0
+#define IPC_IRQ_A2E_AC0_MSK (IPC_IRQ_A2E_USER_MSK << IPC_IRQ_A2E_AC0_OFT)
+/// Offset of the interrupts for AC1
+#define IPC_IRQ_A2E_AC1_OFT (IPC_IRQ_A2E_AC0_OFT + CONFIG_USER_MAX)
+/// Mask of the interrupts for AC1
+#define IPC_IRQ_A2E_AC1_MSK (IPC_IRQ_A2E_USER_MSK << IPC_IRQ_A2E_AC1_OFT)
+/// Offset of the interrupts for AC2
+#define IPC_IRQ_A2E_AC2_OFT (IPC_IRQ_A2E_AC1_OFT + CONFIG_USER_MAX)
+/// Mask of the interrupts for AC2
+#define IPC_IRQ_A2E_AC2_MSK (IPC_IRQ_A2E_USER_MSK << IPC_IRQ_A2E_AC2_OFT)
+/// Offset of the interrupts for AC3
+#define IPC_IRQ_A2E_AC3_OFT (IPC_IRQ_A2E_AC2_OFT + CONFIG_USER_MAX)
+/// Mask of the interrupts for AC3
+#define IPC_IRQ_A2E_AC3_MSK (IPC_IRQ_A2E_USER_MSK << IPC_IRQ_A2E_AC3_OFT)
+/// Offset of the interrupts for BCN
+#define IPC_IRQ_A2E_BCN_OFT (IPC_IRQ_A2E_AC3_OFT + CONFIG_USER_MAX)
+/// Mask of the interrupts for BCN
+#define IPC_IRQ_A2E_BCN_MSK CO_BIT(IPC_IRQ_A2E_BCN_OFT)
+
+#define IPC_IRQ_A2E_AC_TXDESC                                                  \
+	(IPC_IRQ_A2E_AC0_MSK | IPC_IRQ_A2E_AC1_MSK | IPC_IRQ_A2E_AC2_MSK |         \
+	 IPC_IRQ_A2E_AC3_MSK)
+
+/// Interrupts bits used for the TX descriptors of the BCN queue
+#if NX_TXQ_CNT < 5
+#define IPC_IRQ_A2E_BCN_TXDESC 0
+#else
+#define IPC_IRQ_A2E_BCN_TXDESC (0x01 << IPC_IRQ_A2E_BCN_OFT)
+#endif
+
+/// IPC TX descriptor interrupt mask
+#define IPC_IRQ_A2E_TXDESC (IPC_IRQ_A2E_AC_TXDESC | IPC_IRQ_A2E_BCN_TXDESC)
+#else
+/// IPC TX descriptor interrupt mask
+#define IPC_IRQ_A2E_TXDESC 0xFF00
+#endif
+
+#define IPC_IRQ_A2E_TXDESC_FIRSTBIT (8)
+#define IPC_IRQ_A2E_RXBUF_BACK      CO_BIT(5)
+#define IPC_IRQ_A2E_RXDESC_BACK     CO_BIT(4)
+
+#define IPC_IRQ_A2E_MSG CO_BIT(1)
+#define IPC_IRQ_A2E_DBG CO_BIT(0)
+
+#define IPC_IRQ_A2E_ALL (IPC_IRQ_A2E_TXDESC | IPC_IRQ_A2E_MSG | IPC_IRQ_A2E_DBG)
+
+// IRQs from emb to app
+#define IPC_IRQ_E2A_TXCFM_POS 7
+
+#ifdef CONFIG_RWNX_MUMIMO_TX
+#ifdef CONFIG_RWNX_OLD_IPC
+#error "MU-MIMO cannot be compiled for old IPC"
+#endif
+/// Interrupts bits used
+#if CONFIG_USER_MAX > 3
+#define IPC_IRQ_E2A_USER_MSK 0xF
+#elif CONFIG_USER_MAX > 2
+#define IPC_IRQ_E2A_USER_MSK 0x7
+#else
+#define IPC_IRQ_E2A_USER_MSK 0x3
+#endif
+
+/// Offset of the interrupts for AC0
+#define IPC_IRQ_E2A_AC0_OFT IPC_IRQ_E2A_TXCFM_POS
+/// Mask of the interrupts for AC0
+#define IPC_IRQ_E2A_AC0_MSK (IPC_IRQ_E2A_USER_MSK << IPC_IRQ_E2A_AC0_OFT)
+/// Offset of the interrupts for AC1
+#define IPC_IRQ_E2A_AC1_OFT (IPC_IRQ_E2A_AC0_OFT + CONFIG_USER_MAX)
+/// Mask of the interrupts for AC1
+#define IPC_IRQ_E2A_AC1_MSK (IPC_IRQ_E2A_USER_MSK << IPC_IRQ_E2A_AC1_OFT)
+/// Offset of the interrupts for AC2
+#define IPC_IRQ_E2A_AC2_OFT (IPC_IRQ_E2A_AC1_OFT + CONFIG_USER_MAX)
+/// Mask of the interrupts for AC2
+#define IPC_IRQ_E2A_AC2_MSK (IPC_IRQ_E2A_USER_MSK << IPC_IRQ_E2A_AC2_OFT)
+/// Offset of the interrupts for AC3
+#define IPC_IRQ_E2A_AC3_OFT (IPC_IRQ_E2A_AC2_OFT + CONFIG_USER_MAX)
+/// Mask of the interrupts for AC3
+#define IPC_IRQ_E2A_AC3_MSK (IPC_IRQ_E2A_USER_MSK << IPC_IRQ_E2A_AC3_OFT)
+/// Offset of the interrupts for BCN
+#define IPC_IRQ_E2A_BCN_OFT (IPC_IRQ_E2A_AC3_OFT + CONFIG_USER_MAX)
+/// Mask of the interrupts for BCN
+#define IPC_IRQ_E2A_BCN_MSK CO_BIT(IPC_IRQ_E2A_BCN_OFT)
+
+#define IPC_IRQ_E2A_AC_TXCFM                                                   \
+	(IPC_IRQ_E2A_AC0_MSK | IPC_IRQ_E2A_AC1_MSK | IPC_IRQ_E2A_AC2_MSK |         \
+	 IPC_IRQ_E2A_AC3_MSK)
+
+/// Interrupts bits used for the TX descriptors of the BCN queue
+#if NX_TXQ_CNT < 5
+#define IPC_IRQ_E2A_BCN_TXCFM 0
+#else
+#define IPC_IRQ_E2A_BCN_TXCFM (0x01 << IPC_IRQ_E2A_BCN_OFT)
+#endif
+
+/// IPC TX descriptor interrupt mask
+#define IPC_IRQ_E2A_TXCFM (IPC_IRQ_E2A_AC_TXCFM | IPC_IRQ_E2A_BCN_TXCFM)
+
+#else
+
+#define IPC_IRQ_E2A_TXCFM (((1 << NX_TXQ_CNT) - 1) << IPC_IRQ_E2A_TXCFM_POS)
+
+#endif /* CONFIG_RWNX_MUMIMO_TX */
+
+#define IPC_IRQ_E2A_UNSUP_RX_VEC CO_BIT(7)
+#define IPC_IRQ_E2A_RADAR        CO_BIT(6)
+#define IPC_IRQ_E2A_TBTT_SEC     CO_BIT(5)
+#define IPC_IRQ_E2A_TBTT_PRIM    CO_BIT(4)
+#define IPC_IRQ_E2A_RXDESC       CO_BIT(3)
+#define IPC_IRQ_E2A_MSG_ACK      CO_BIT(2)
+#define IPC_IRQ_E2A_MSG          CO_BIT(1)
+#define IPC_IRQ_E2A_DBG          CO_BIT(0)
+
+#define IPC_IRQ_E2A_ALL                                                        \
+	(IPC_IRQ_E2A_TXCFM | IPC_IRQ_E2A_RXDESC | IPC_IRQ_E2A_MSG_ACK |            \
+	 IPC_IRQ_E2A_MSG | IPC_IRQ_E2A_DBG | IPC_IRQ_E2A_TBTT_PRIM |               \
+	 IPC_IRQ_E2A_TBTT_SEC | IPC_IRQ_E2A_RADAR | IPC_IRQ_E2A_UNSUP_RX_VEC)
+
+// FLAGS for RX desc
+#define IPC_RX_FORWARD  CO_BIT(1)
+#define IPC_RX_INTRABSS CO_BIT(0)
+
+// IPC message TYPE
+enum {
+	IPC_MSG_NONE = 0,
+	IPC_MSG_WRAP,
+	IPC_MSG_KMSG,
+
+	IPC_DBG_STRING,
+
+};
+
+#endif // _IPC_SHARED_H_
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/lmac_mac.h b/drivers/net/wireless/aic/aic8800_fdrv/lmac_mac.h
new file mode 100644
index 0000000000000..3f57b5991cfe4
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/lmac_mac.h
@@ -0,0 +1,470 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief MAC related definitions.
+ *
+ * Adapted from mac_types.h to used lmac_types.h instead of standard types
+ * eg: perl -pi -e '$_ =~ s/uint(\d{1,2})_t/u$1_l/g; \
+ *                  $_ =~ s/int(\d{1,2})_t/s$1_l/g; \
+ *                  $_ =~ s/CO_BIT/BIT/g;'
+ *
+ ****************************************************************************************
+ */
+
+#ifndef LMAC_MAC_H_
+#define LMAC_MAC_H_
+
+#include "lmac_types.h"
+
+/// Interface types
+enum mac_vif_type {
+	/// ESS STA interface
+	VIF_STA,
+	/// IBSS STA interface
+	VIF_IBSS,
+	/// AP interface
+	VIF_AP,
+	/// Mesh Point interface
+	VIF_MESH_POINT,
+	/// Monitor interface
+	VIF_MONITOR,
+	/// Unknown type
+	VIF_UNKNOWN
+};
+
+/// MAC address length in bytes.
+#define MAC_ADDR_LEN 6
+
+/// MAC address structure.
+struct mac_addr {
+	/// Array of 16-bit words that make up the MAC address.
+	u16_l array[MAC_ADDR_LEN / 2];
+};
+
+/// SSID maximum length.
+#define MAC_SSID_LEN 32
+
+/// SSID.
+struct mac_ssid {
+	/// Actual length of the SSID.
+	u8_l length;
+	/// Array containing the SSID name.
+	u8_l array[MAC_SSID_LEN];
+};
+
+/// BSS type
+enum mac_bss_type {
+	INFRASTRUCTURE_MODE = 1,
+	INDEPENDENT_BSS_MODE,
+	ANY_BSS_MODE
+};
+
+/// Channel Band
+enum mac_chan_band {
+	/// 2.4GHz Band
+	PHY_BAND_2G4,
+	/// 5GHz band
+	PHY_BAND_5G,
+	/// Number of bands
+	PHY_BAND_MAX,
+};
+
+/// Operating Channel Bandwidth
+enum mac_chan_bandwidth {
+	/// 20MHz BW
+	PHY_CHNL_BW_20,
+	/// 40MHz BW
+	PHY_CHNL_BW_40,
+	/// 80MHz BW
+	PHY_CHNL_BW_80,
+	/// 160MHz BW
+	PHY_CHNL_BW_160,
+	/// 80+80MHz BW
+	PHY_CHNL_BW_80P80,
+	/// Reserved BW
+	PHY_CHNL_BW_OTHER,
+};
+
+/// max number of channels in the 2.4 GHZ band
+#define MAC_DOMAINCHANNEL_24G_MAX 14
+
+/// max number of channels in the 5 GHZ band
+#define MAC_DOMAINCHANNEL_5G_MAX 28
+
+/// Channel Flag
+enum mac_chan_flags {
+	/// Cannot initiate radiation on this channel
+	CHAN_NO_IR = BIT(0),
+	/// Channel is not allowed
+	CHAN_DISABLED = BIT(1),
+	/// Radar detection required on this channel
+	CHAN_RADAR = BIT(2),
+};
+
+/// Primary Channel definition
+struct mac_chan_def {
+	/// Frequency of the channel (in MHz)
+	u16_l freq;
+	/// RF band (@ref mac_chan_band)
+	u8_l band;
+	/// Additional information (@ref mac_chan_flags)
+	u8_l flags;
+	/// Max transmit power allowed on this channel (dBm)
+	s8_l tx_power;
+};
+
+/// Operating Channel
+struct mac_chan_op {
+	/// Band (@ref mac_chan_band)
+	u8_l band;
+	/// Channel type (@ref mac_chan_bandwidth)
+	u8_l type;
+	/// Frequency for Primary 20MHz channel (in MHz)
+	u16_l prim20_freq;
+	/// Frequency center of the contiguous channel or center of Primary 80+80
+	/// (in MHz)
+	u16_l center1_freq;
+	/// Frequency center of the non-contiguous secondary 80+80 (in MHz)
+	u16_l center2_freq;
+	/// Max transmit power allowed on this channel (dBm)
+	s8_l tx_power;
+	/// Additional information (@ref mac_chan_flags)
+	u8_l flags;
+};
+
+/// Cipher suites (order is important as it is used by MACHW)
+enum mac_cipher_suite {
+	/// 00-0F-AC 1
+	MAC_CIPHER_WEP40 = 0,
+	/// 00-0F-AC 2
+	MAC_CIPHER_TKIP = 1,
+	/// 00-0F-AC 4
+	MAC_CIPHER_CCMP = 2,
+	/// 00-0F-AC 5
+	MAC_CIPHER_WEP104 = 3,
+	/// 00-14-72 1
+	MAC_CIPHER_WPI_SMS4 = 4,
+	/// 00-0F-AC 6  (aka AES_CMAC)
+	MAC_CIPHER_BIP_CMAC_128 = 5,
+
+	// following cipher are not supported by MACHW
+	/// 00-0F-AC 08
+	MAC_CIPHER_GCMP_128,
+	/// 00-0F-AC 09
+	MAC_CIPHER_GCMP_256,
+	/// 00-0F-AC 10
+	MAC_CIPHER_CCMP_256,
+	/// 00-0F-AC 11
+	MAC_CIPHER_BIP_GMAC_128,
+	/// 00-0F-AC 12
+	MAC_CIPHER_BIP_GMAC_256,
+	/// 00-0F-AC 13
+	MAC_CIPHER_BIP_CMAC_256,
+
+	MAC_CIPHER_INVALID = 0xFF
+};
+
+/// Authentication and Key Management suite
+enum mac_akm_suite {
+	/// No security
+	MAC_AKM_NONE,
+	/// Pre RSN (WEP or WPA)
+	MAC_AKM_PRE_RSN,
+	/// 00-0F-AC 1
+	MAC_AKM_8021X,
+	/// 00-0F-AC 2
+	MAC_AKM_PSK,
+	/// 00-0F-AC 3
+	MAC_AKM_FT_8021X,
+	/// 00-0F-AC 4
+	MAC_AKM_FT_PSK,
+	/// 00-0F-AC 5
+	MAC_AKM_8021X_SHA256,
+	/// 00-0F-AC 6
+	MAC_AKM_PSK_SHA256,
+	/// 00-0F-AC 7
+	MAC_AKM_TDLS,
+	/// 00-0F-AC 8
+	MAC_AKM_SAE,
+	/// 00-0F-AC 9
+	MAC_AKM_FT_OVER_SAE,
+	/// 00-0F-AC 11
+	MAC_AKM_8021X_SUITE_B,
+	/// 00-0F-AC 12
+	MAC_AKM_8021X_SUITE_B_192,
+	/// 00-0F-AC 14
+	MAC_AKM_FILS_SHA256,
+	/// 00-0F-AC 15
+	MAC_AKM_FILS_SHA384,
+	/// 00-0F-AC 16
+	MAC_AKM_FT_FILS_SHA256,
+	/// 00-0F-AC 17
+	MAC_AKM_FT_FILS_SHA384,
+	/// 00-0F-AC 18
+	MAC_AKM_OWE,
+
+	/// 00-14-72 1
+	MAC_AKM_WAPI_CERT,
+	/// 00-14-72 2
+	MAC_AKM_WAPI_PSK,
+};
+
+/// Scan result element, parsed from beacon or probe response frames.
+struct mac_scan_result {
+	/// Scan result is valid
+	bool valid_flag;
+	/// Network BSSID.
+	struct mac_addr bssid;
+	/// Network name.
+	struct mac_ssid ssid;
+	/// Network type (@ref mac_bss_type).
+	u16_l bsstype;
+	/// Network channel.
+	struct mac_chan_def *chan;
+	/// Network beacon period (in TU).
+	u16_l beacon_period;
+	/// Capability information
+	u16_l cap_info;
+	/// Supported AKM (bit-field of @ref mac_akm_suite)
+	u32_l akm;
+	/// Group cipher (bit-field of @ref mac_cipher_suite)
+	u16_l group_cipher;
+	/// Group cipher (bit-field of @ref mac_cipher_suite)
+	u16_l pairwise_cipher;
+	/// RSSI of the scanned BSS (in dBm)
+	s8_l rssi;
+};
+
+/// Legacy rate 802.11 definitions
+enum mac_legacy_rates {
+	/// DSSS/CCK 1Mbps
+	MAC_RATE_1MBPS = 2,
+	/// DSSS/CCK 2Mbps
+	MAC_RATE_2MBPS = 4,
+	/// DSSS/CCK 5.5Mbps
+	MAC_RATE_5_5MBPS = 11,
+	/// OFDM 6Mbps
+	MAC_RATE_6MBPS = 12,
+	/// OFDM 9Mbps
+	MAC_RATE_9MBPS = 18,
+	/// DSSS/CCK 11Mbps
+	MAC_RATE_11MBPS = 22,
+	/// OFDM 12Mbps
+	MAC_RATE_12MBPS = 24,
+	/// OFDM 18Mbps
+	MAC_RATE_18MBPS = 36,
+	/// OFDM 24Mbps
+	MAC_RATE_24MBPS = 48,
+	/// OFDM 36Mbps
+	MAC_RATE_36MBPS = 72,
+	/// OFDM 48Mbps
+	MAC_RATE_48MBPS = 96,
+	/// OFDM 54Mbps
+	MAC_RATE_54MBPS = 108
+};
+
+/// BSS Membership Selector definitions
+enum mac_bss_membership {
+	/// HT PHY
+	MAC_BSS_MEMBERSHIP_HT_PHY = 127,
+	/// VHT PHY
+	MAC_BSS_MEMBERSHIP_VHT_PHY = 126,
+};
+
+/// MAC rateset maximum length
+#define MAC_RATESET_LEN 12
+
+/// Structure containing the legacy rateset of a station
+struct mac_rateset {
+	/// Number of legacy rates supported
+	u8_l length;
+	/// Array of legacy rates
+	u8_l array[MAC_RATESET_LEN];
+};
+
+/// MAC Security Key maximum length
+#define MAC_SEC_KEY_LEN 32 // TKIP keys 256 bits (max length) with MIC keys
+
+/// Structure defining a security key
+struct mac_sec_key {
+	/// Key material length
+	u8_l length;
+	/// Key material
+	u32_l array[MAC_SEC_KEY_LEN / 4];
+};
+
+/// Access Category enumeration
+enum mac_ac {
+	/// Background
+	AC_BK = 0,
+	/// Best-effort
+	AC_BE,
+	/// Video
+	AC_VI,
+	/// Voice
+	AC_VO,
+	/// Number of access categories
+	AC_MAX
+};
+
+/// Traffic ID enumeration
+enum mac_tid {
+	/// TID_0. Mapped to @ref AC_BE as per 802.11 standard.
+	TID_0,
+	/// TID_1. Mapped to @ref AC_BK as per 802.11 standard.
+	TID_1,
+	/// TID_2. Mapped to @ref AC_BK as per 802.11 standard.
+	TID_2,
+	/// TID_3. Mapped to @ref AC_BE as per 802.11 standard.
+	TID_3,
+	/// TID_4. Mapped to @ref AC_VI as per 802.11 standard.
+	TID_4,
+	/// TID_5. Mapped to @ref AC_VI as per 802.11 standard.
+	TID_5,
+	/// TID_6. Mapped to @ref AC_VO as per 802.11 standard.
+	TID_6,
+	/// TID_7. Mapped to @ref AC_VO as per 802.11 standard.
+	TID_7,
+	/// Non standard Management TID used internally
+	TID_MGT,
+	/// Number of TID supported
+	TID_MAX
+};
+
+/// MCS bitfield maximum size (in bytes)
+#define MAX_MCS_LEN 16 // 16 * 8 = 128
+
+/// MAC HT capability information element
+struct mac_htcapability {
+	/// HT capability information
+	u16_l ht_capa_info;
+	/// A-MPDU parameters
+	u8_l a_mpdu_param;
+	/// Supported MCS
+	u8_l mcs_rate[MAX_MCS_LEN];
+	/// HT extended capability information
+	u16_l ht_extended_capa;
+	/// Beamforming capability information
+	u32_l tx_beamforming_capa;
+	/// Antenna selection capability information
+	u8_l asel_capa;
+};
+
+/// MAC VHT capability information element
+struct mac_vhtcapability {
+	/// VHT capability information
+	u32_l vht_capa_info;
+	/// RX MCS map
+	u16_l rx_mcs_map;
+	/// RX highest data rate
+	u16_l rx_highest;
+	/// TX MCS map
+	u16_l tx_mcs_map;
+	/// TX highest data rate
+	u16_l tx_highest;
+};
+
+/// Length (in bytes) of the MAC HE capability field
+#define MAC_HE_MAC_CAPA_LEN      6
+/// Length (in bytes) of the PHY HE capability field
+#define MAC_HE_PHY_CAPA_LEN      11
+/// Maximum length (in bytes) of the PPE threshold data
+#define MAC_HE_PPE_THRES_MAX_LEN 25
+
+/// Structure listing the per-NSS, per-BW supported MCS combinations
+struct mac_he_mcs_nss_supp {
+	/// per-NSS supported MCS in RX, for BW <= 80MHz
+	u16_l rx_mcs_80;
+	/// per-NSS supported MCS in TX, for BW <= 80MHz
+	u16_l tx_mcs_80;
+	/// per-NSS supported MCS in RX, for BW = 160MHz
+	u16_l rx_mcs_160;
+	/// per-NSS supported MCS in TX, for BW = 160MHz
+	u16_l tx_mcs_160;
+	/// per-NSS supported MCS in RX, for BW = 80+80MHz
+	u16_l rx_mcs_80p80;
+	/// per-NSS supported MCS in TX, for BW = 80+80MHz
+	u16_l tx_mcs_80p80;
+};
+
+/// MAC HE capability information element
+struct mac_hecapability {
+	/// MAC HE capabilities
+	u8_l mac_cap_info[MAC_HE_MAC_CAPA_LEN];
+	/// PHY HE capabilities
+	u8_l phy_cap_info[MAC_HE_PHY_CAPA_LEN];
+	/// Supported MCS combinations
+	struct mac_he_mcs_nss_supp mcs_supp;
+	/// PPE Thresholds data
+	u8_l ppe_thres[MAC_HE_PPE_THRES_MAX_LEN];
+};
+
+/// Station flags
+enum mac_sta_flags {
+	/// Bit indicating that a STA has QoS (WMM) capability
+	STA_QOS_CAPA = BIT(0),
+	/// Bit indicating that a STA has HT capability
+	STA_HT_CAPA = BIT(1),
+	/// Bit indicating that a STA has VHT capability
+	STA_VHT_CAPA = BIT(2),
+	/// Bit indicating that a STA has MFP capability
+	STA_MFP_CAPA = BIT(3),
+	/// Bit indicating that the STA included the Operation Notification IE
+	STA_OPMOD_NOTIF = BIT(4),
+	/// Bit indicating that a STA has HE capability
+	STA_HE_CAPA = BIT(5),
+};
+
+/// Connection flags
+enum mac_connection_flags {
+	/// Flag indicating whether the control port is controlled by host or not
+	CONTROL_PORT_HOST = BIT(0),
+	/// Flag indicating whether the control port frame shall be sent unencrypted
+	CONTROL_PORT_NO_ENC = BIT(1),
+	/// Flag indicating whether HT and VHT shall be disabled or not
+	DISABLE_HT = BIT(2),
+	/// Flag indicating whether WPA or WPA2 authentication is in use
+	WPA_WPA2_IN_USE = BIT(3),
+	/// Flag indicating whether MFP is in use
+	MFP_IN_USE = BIT(4),
+	//  Flag indicating Roam
+	REASSOCIATION = BIT(5),
+};
+
+#ifdef CONFIG_AIC8800_HE_FOR_OLD_KERNEL
+#define IEEE80211_HE_MAC_CAP2_ALL_ACK                                   0x02
+#define IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G             0x02
+#define IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G       0x04
+#define IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD                    0x20
+#define IEEE80211_HE_PHY_CAP1_HE_LTF_AND_GI_FOR_HE_PPDUS_0_8US          0x40
+#define IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS                   0x80
+#define IEEE80211_HE_PHY_CAP2_MIDAMBLE_RX_TX_MAX_NSTS                   0x01
+#define IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US                      0x02
+#define IEEE80211_HE_PHY_CAP2_DOPPLER_RX                                0x20
+#define IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ                       0x08
+#define IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_16_QAM                   0x18
+#define IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1                          0x00
+#define IEEE80211_HE_PHY_CAP3_RX_HE_MU_PPDU_FROM_NON_AP_STA             0x40
+#define IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE                             0x01
+#define IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_4          0x0c
+#define IEEE80211_HE_PHY_CAP5_NG16_SU_FEEDBACK                          0x40
+#define IEEE80211_HE_PHY_CAP5_NG16_MU_FEEDBACK                          0x80
+#define IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_42_SU                       0x01
+#define IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_75_MU                       0x02
+#define IEEE80211_HE_PHY_CAP6_TRIG_SU_BEAMFORMER_FB                     0x04
+#define IEEE80211_HE_PHY_CAP6_TRIG_MU_BEAMFORMER_FB                     0x08
+#define IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT                     0x80
+#define IEEE80211_HE_PHY_CAP6_PARTIAL_BANDWIDTH_DL_MUMIMO               0x40
+#define IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI          0x04
+#define IEEE80211_HE_PHY_CAP8_20MHZ_IN_40MHZ_HE_PPDU_IN_2G              0x02
+#define IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_COMP_SIGB     0x10
+#define IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_NON_COMP_SIGB 0x20
+
+#define IEEE80211_HE_PPE_THRES_MAX_LEN 25
+
+#include <linux/ieee80211.h>
+#endif
+#endif // LMAC_MAC_H_
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/lmac_msg.h b/drivers/net/wireless/aic/aic8800_fdrv/lmac_msg.h
new file mode 100644
index 0000000000000..6013e78448a91
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/lmac_msg.h
@@ -0,0 +1,3124 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief Main definitions for message exchanges with LMAC
+ *
+ ****************************************************************************************
+ */
+
+#ifndef LMAC_MSG_H_
+#define LMAC_MSG_H_
+
+#include "lmac_mac.h"
+
+/* Task identifiers for communication between LMAC and DRIVER */
+enum {
+	TASK_NONE = (u8_l)-1,
+
+	// MAC Management task.
+	TASK_MM = 0,
+	// DEBUG task
+	TASK_DBG,
+	/// SCAN task
+	TASK_SCAN,
+	/// TDLS task
+	TASK_TDLS,
+	/// SCANU task
+	TASK_SCANU,
+	/// ME task
+	TASK_ME,
+	/// SM task
+	TASK_SM,
+	/// APM task
+	TASK_APM,
+	/// BAM task
+	TASK_BAM,
+	/// MESH task
+	TASK_MESH,
+	/// RXU task
+	TASK_RXU,
+	/// RM_task
+	TASK_RM,
+	/// TWT task
+	TASK_TWT,
+#if defined CONFIG_RWNX_FULLMAC
+	// This is used to define the last task that is running on the EMB processor
+	TASK_LAST_EMB = TASK_TWT,
+#else
+#error "Need to define SOFTMAC or FULLMAC"
+#endif
+	// nX API task
+	TASK_API,
+	TASK_MAX,
+};
+
+/// For MAC HW States copied from "hal_machw.h"
+enum {
+	/// MAC HW IDLE State.
+	HW_IDLE = 0,
+	/// MAC HW RESERVED State.
+	HW_RESERVED,
+	/// MAC HW DOZE State.
+	HW_DOZE,
+	/// MAC HW ACTIVE State.
+	HW_ACTIVE
+};
+
+/// Power Save mode setting
+enum mm_ps_mode_state {
+	MM_PS_MODE_OFF,
+	MM_PS_MODE_ON,
+	MM_PS_MODE_ON_DYN,
+};
+
+/// Status/error codes used in the MAC software.
+enum {
+	CO_OK,
+	CO_FAIL,
+	CO_EMPTY,
+	CO_FULL,
+	CO_BAD_PARAM,
+	CO_NOT_FOUND,
+	CO_NO_MORE_ELT_AVAILABLE,
+	CO_NO_ELT_IN_USE,
+	CO_BUSY,
+	CO_OP_IN_PROGRESS,
+};
+
+/// Remain on channel operation codes
+enum mm_remain_on_channel_op {
+	MM_ROC_OP_START = 0,
+	MM_ROC_OP_CANCEL,
+};
+
+#define DRV_TASK_ID 100
+
+/// Message Identifier. The number of messages is limited to 0xFFFF.
+/// The message ID is divided in two parts:
+/// - bits[15..10] : task index (no more than 64 tasks supported).
+/// - bits[9..0] : message index (no more that 1024 messages per task).
+typedef u16 lmac_msg_id_t;
+
+typedef u16 lmac_task_id_t;
+
+/// Build the first message ID of a task.
+#define LMAC_FIRST_MSG(task) ((lmac_msg_id_t)((task) << 10))
+
+#define MSG_T(msg) ((lmac_task_id_t)((msg) >> 10))
+#define MSG_I(msg) ((msg) & ((1 << 10) - 1))
+
+/// Message structure.
+struct lmac_msg {
+	lmac_msg_id_t id;       ///< Message id.
+	lmac_task_id_t dest_id; ///< Destination kernel identifier.
+	lmac_task_id_t src_id;  ///< Source kernel identifier.
+	u16 param_len;          ///< Parameter embedded struct length.
+	u32 param[]; ///< Parameter embedded struct. Must be word-aligned.
+};
+
+/// List of messages related to the task.
+enum mm_msg_tag {
+	/// RESET Request.
+	MM_RESET_REQ = LMAC_FIRST_MSG(TASK_MM),
+	/// RESET Confirmation.
+	MM_RESET_CFM,
+	/// START Request.
+	MM_START_REQ,
+	/// START Confirmation.
+	MM_START_CFM,
+	/// Read Version Request.
+	MM_VERSION_REQ,
+	/// Read Version Confirmation.
+	MM_VERSION_CFM,
+	/// ADD INTERFACE Request.
+	MM_ADD_IF_REQ,
+	/// ADD INTERFACE Confirmation.
+	MM_ADD_IF_CFM,
+	/// REMOVE INTERFACE Request.
+	MM_REMOVE_IF_REQ,
+	/// REMOVE INTERFACE Confirmation.
+	MM_REMOVE_IF_CFM,
+	/// STA ADD Request.
+	MM_STA_ADD_REQ,
+	/// STA ADD Confirm.
+	MM_STA_ADD_CFM,
+	/// STA DEL Request.
+	MM_STA_DEL_REQ,
+	/// STA DEL Confirm.
+	MM_STA_DEL_CFM,
+	/// RX FILTER CONFIGURATION Request.
+	MM_SET_FILTER_REQ,
+	/// RX FILTER CONFIGURATION Confirmation.
+	MM_SET_FILTER_CFM,
+	/// CHANNEL CONFIGURATION Request.
+	MM_SET_CHANNEL_REQ,
+	/// CHANNEL CONFIGURATION Confirmation.
+	MM_SET_CHANNEL_CFM,
+	/// DTIM PERIOD CONFIGURATION Request.
+	MM_SET_DTIM_REQ,
+	/// DTIM PERIOD CONFIGURATION Confirmation.
+	MM_SET_DTIM_CFM,
+	/// BEACON INTERVAL CONFIGURATION Request.
+	MM_SET_BEACON_INT_REQ,
+	/// BEACON INTERVAL CONFIGURATION Confirmation.
+	MM_SET_BEACON_INT_CFM,
+	/// BASIC RATES CONFIGURATION Request.
+	MM_SET_BASIC_RATES_REQ,
+	/// BASIC RATES CONFIGURATION Confirmation.
+	MM_SET_BASIC_RATES_CFM,
+	/// BSSID CONFIGURATION Request.
+	MM_SET_BSSID_REQ,
+	/// BSSID CONFIGURATION Confirmation.
+	MM_SET_BSSID_CFM,
+	/// EDCA PARAMETERS CONFIGURATION Request.
+	MM_SET_EDCA_REQ,
+	/// EDCA PARAMETERS CONFIGURATION Confirmation.
+	MM_SET_EDCA_CFM,
+	/// ABGN MODE CONFIGURATION Request.
+	MM_SET_MODE_REQ,
+	/// ABGN MODE CONFIGURATION Confirmation.
+	MM_SET_MODE_CFM,
+	/// Request setting the VIF active state (i.e associated or AP started)
+	MM_SET_VIF_STATE_REQ,
+	/// Confirmation of the @ref MM_SET_VIF_STATE_REQ message.
+	MM_SET_VIF_STATE_CFM,
+	/// SLOT TIME PARAMETERS CONFIGURATION Request.
+	MM_SET_SLOTTIME_REQ,
+	/// SLOT TIME PARAMETERS CONFIGURATION Confirmation.
+	MM_SET_SLOTTIME_CFM,
+	/// Power Mode Change Request.
+	MM_SET_IDLE_REQ,
+	/// Power Mode Change Confirm.
+	MM_SET_IDLE_CFM,
+	/// KEY ADD Request.
+	MM_KEY_ADD_REQ,
+	/// KEY ADD Confirm.
+	MM_KEY_ADD_CFM,
+	/// KEY DEL Request.
+	MM_KEY_DEL_REQ,
+	/// KEY DEL Confirm.
+	MM_KEY_DEL_CFM,
+	/// Block Ack agreement info addition
+	MM_BA_ADD_REQ,
+	/// Block Ack agreement info addition confirmation
+	MM_BA_ADD_CFM,
+	/// Block Ack agreement info deletion
+	MM_BA_DEL_REQ,
+	/// Block Ack agreement info deletion confirmation
+	MM_BA_DEL_CFM,
+	/// Indication of the primary TBTT to the upper MAC. Upon the reception of
+	/// this
+	// message the upper MAC has to push the beacon(s) to the beacon
+	// transmission queue.
+	MM_PRIMARY_TBTT_IND,
+	/// Indication of the secondary TBTT to the upper MAC. Upon the reception of
+	/// this
+	// message the upper MAC has to push the beacon(s) to the beacon
+	// transmission queue.
+	MM_SECONDARY_TBTT_IND,
+	/// Request for changing the TX power
+	MM_SET_POWER_REQ,
+	/// Confirmation of the TX power change
+	MM_SET_POWER_CFM,
+	/// Request to the LMAC to trigger the embedded logic analyzer and forward
+	/// the debug
+	/// dump.
+	MM_DBG_TRIGGER_REQ,
+	/// Set Power Save mode
+	MM_SET_PS_MODE_REQ,
+	/// Set Power Save mode confirmation
+	MM_SET_PS_MODE_CFM,
+	/// Request to add a channel context
+	MM_CHAN_CTXT_ADD_REQ,
+	/// Confirmation of the channel context addition
+	MM_CHAN_CTXT_ADD_CFM,
+	/// Request to delete a channel context
+	MM_CHAN_CTXT_DEL_REQ,
+	/// Confirmation of the channel context deletion
+	MM_CHAN_CTXT_DEL_CFM,
+	/// Request to link a channel context to a VIF
+	MM_CHAN_CTXT_LINK_REQ,
+	/// Confirmation of the channel context link
+	MM_CHAN_CTXT_LINK_CFM,
+	/// Request to unlink a channel context from a VIF
+	MM_CHAN_CTXT_UNLINK_REQ,
+	/// Confirmation of the channel context unlink
+	MM_CHAN_CTXT_UNLINK_CFM,
+	/// Request to update a channel context
+	MM_CHAN_CTXT_UPDATE_REQ,
+	/// Confirmation of the channel context update
+	MM_CHAN_CTXT_UPDATE_CFM,
+	/// Request to schedule a channel context
+	MM_CHAN_CTXT_SCHED_REQ,
+	/// Confirmation of the channel context scheduling
+	MM_CHAN_CTXT_SCHED_CFM,
+	/// Request to change the beacon template in LMAC
+	MM_BCN_CHANGE_REQ,
+	/// Confirmation of the beacon change
+	MM_BCN_CHANGE_CFM,
+	/// Request to update the TIM in the beacon (i.e to indicate traffic
+	/// bufferized at AP)
+	MM_TIM_UPDATE_REQ,
+	/// Confirmation of the TIM update
+	MM_TIM_UPDATE_CFM,
+	/// Connection loss indication
+	MM_CONNECTION_LOSS_IND,
+	/// Channel context switch indication to the upper layers
+	MM_CHANNEL_SWITCH_IND,
+	/// Channel context pre-switch indication to the upper layers
+	MM_CHANNEL_PRE_SWITCH_IND,
+	/// Request to remain on channel or cancel remain on channel
+	MM_REMAIN_ON_CHANNEL_REQ,
+	/// Confirmation of the (cancel) remain on channel request
+	MM_REMAIN_ON_CHANNEL_CFM,
+	/// Remain on channel expired indication
+	MM_REMAIN_ON_CHANNEL_EXP_IND,
+	/// Indication of a PS state change of a peer device
+	MM_PS_CHANGE_IND,
+	/// Indication that some buffered traffic should be sent to the peer device
+	MM_TRAFFIC_REQ_IND,
+	/// Request to modify the STA Power-save mode options
+	MM_SET_PS_OPTIONS_REQ,
+	/// Confirmation of the PS options setting
+	MM_SET_PS_OPTIONS_CFM,
+	/// Indication of PS state change for a P2P VIF
+	MM_P2P_VIF_PS_CHANGE_IND,
+	/// Indication that CSA counter has been updated
+	MM_CSA_COUNTER_IND,
+	/// Channel occupation report indication
+	MM_CHANNEL_SURVEY_IND,
+	/// Message containing Beamformer Information
+	MM_BFMER_ENABLE_REQ,
+	/// Request to Start/Stop/Update NOA - GO Only
+	MM_SET_P2P_NOA_REQ,
+	/// Request to Start/Stop/Update Opportunistic PS - GO Only
+	MM_SET_P2P_OPPPS_REQ,
+	/// Start/Stop/Update NOA Confirmation
+	MM_SET_P2P_NOA_CFM,
+	/// Start/Stop/Update Opportunistic PS Confirmation
+	MM_SET_P2P_OPPPS_CFM,
+	/// P2P NoA Update Indication - GO Only
+	MM_P2P_NOA_UPD_IND,
+	/// Request to set RSSI threshold and RSSI hysteresis
+	MM_CFG_RSSI_REQ,
+	/// Indication that RSSI level is below or above the threshold
+	MM_RSSI_STATUS_IND,
+	/// Indication that CSA is done
+	MM_CSA_FINISH_IND,
+	/// Indication that CSA is in prorgess (resp. done) and traffic must be
+	/// stopped (resp. restarted)
+	MM_CSA_TRAFFIC_IND,
+	/// Request to update the group information of a station
+	MM_MU_GROUP_UPDATE_REQ,
+	/// Confirmation of the @ref MM_MU_GROUP_UPDATE_REQ message
+	MM_MU_GROUP_UPDATE_CFM,
+	/// Request to initialize the antenna diversity algorithm
+	MM_ANT_DIV_INIT_REQ,
+	/// Request to stop the antenna diversity algorithm
+	MM_ANT_DIV_STOP_REQ,
+	/// Request to update the antenna switch status
+	MM_ANT_DIV_UPDATE_REQ,
+	/// Request to switch the antenna connected to path_0
+	MM_SWITCH_ANTENNA_REQ,
+	/// Indication that a packet loss has occurred
+	MM_PKTLOSS_IND,
+
+	MM_SET_ARPOFFLOAD_REQ,
+	MM_SET_ARPOFFLOAD_CFM,
+	MM_SET_AGG_DISABLE_REQ,
+	MM_SET_AGG_DISABLE_CFM,
+	MM_SET_COEX_REQ,
+	MM_SET_COEX_CFM,
+	MM_SET_RF_CONFIG_REQ,
+	MM_SET_RF_CONFIG_CFM,
+	MM_SET_RF_CALIB_REQ,
+	MM_SET_RF_CALIB_CFM,
+
+	/// MU EDCA PARAMETERS Configuration Request.
+	MM_SET_MU_EDCA_REQ,
+	/// MU EDCA PARAMETERS Configuration Confirmation.
+	MM_SET_MU_EDCA_CFM,
+	/// UORA PARAMETERS Configuration Request.
+	MM_SET_UORA_REQ,
+	/// UORA PARAMETERS Configuration Confirmation.
+	MM_SET_UORA_CFM,
+	/// TXOP RTS THRESHOLD Configuration Request.
+	MM_SET_TXOP_RTS_THRES_REQ,
+	/// TXOP RTS THRESHOLD Configuration Confirmation.
+	MM_SET_TXOP_RTS_THRES_CFM,
+	/// HE BSS Color Configuration Request.
+	MM_SET_BSS_COLOR_REQ,
+	/// HE BSS Color Configuration Confirmation.
+	MM_SET_BSS_COLOR_CFM,
+
+	MM_GET_MAC_ADDR_REQ,
+	MM_GET_MAC_ADDR_CFM,
+
+	MM_GET_STA_INFO_REQ,
+	MM_GET_STA_INFO_CFM,
+
+	MM_SET_TXPWR_IDX_LVL_REQ,
+	MM_SET_TXPWR_IDX_LVL_CFM,
+
+	MM_SET_TXPWR_OFST_REQ,
+	MM_SET_TXPWR_OFST_CFM,
+
+	MM_SET_STACK_START_REQ,
+	MM_SET_STACK_START_CFM,
+
+	MM_APM_STALOSS_IND,
+
+	MM_SET_VENDOR_HWCONFIG_REQ,
+	MM_SET_VENDOR_HWCONFIG_CFM,
+
+	MM_GET_FW_VERSION_REQ,
+	MM_GET_FW_VERSION_CFM,
+
+	MM_SET_RESUME_RESTORE_REQ,
+	MM_SET_RESUME_RESTORE_CFM,
+
+	MM_GET_WIFI_DISABLE_REQ,
+	MM_GET_WIFI_DISABLE_CFM,
+
+	MM_CFG_RSSI_CFM,
+
+	MM_SET_VENDOR_SWCONFIG_REQ,
+	MM_SET_VENDOR_SWCONFIG_CFM,
+
+	MM_SET_TXPWR_LVL_ADJ_REQ,
+	MM_SET_TXPWR_LVL_ADJ_CFM,
+
+	MM_RADAR_DETECT_IND,
+
+	MM_SET_APF_PROG_REQ,
+	MM_SET_APF_PROG_CFM,
+
+	MM_GET_APF_PROG_REQ,
+	MM_GET_APF_PROG_CFM,
+
+	/// arp announcement frame reception indication
+	MM_ARP_ANNOUNCEMENT_IND,
+
+	/// MAX number of messages
+	MM_MAX,
+};
+
+/// Interface types
+enum {
+	/// ESS STA interface
+	MM_STA,
+	/// IBSS STA interface
+	MM_IBSS,
+	/// AP interface
+	MM_AP,
+	// Mesh Point interface
+	MM_MESH_POINT,
+	// Monitor interface
+	MM_MONITOR,
+};
+
+/// BA agreement types
+enum {
+	/// BlockAck agreement for TX
+	BA_AGMT_TX,
+	/// BlockAck agreement for RX
+	BA_AGMT_RX,
+};
+
+/// BA agreement related status
+enum {
+	/// Correct BA agreement establishment
+	BA_AGMT_ESTABLISHED,
+	/// BA agreement already exists for STA+TID requested, cannot override it
+	/// (should have been deleted first)
+	BA_AGMT_ALREADY_EXISTS,
+	/// Correct BA agreement deletion
+	BA_AGMT_DELETED,
+	/// BA agreement for the (STA, TID) doesn't exist so nothing to delete
+	BA_AGMT_DOESNT_EXIST,
+};
+
+/// Features supported by LMAC - Positions
+enum mm_features {
+	/// Beaconing
+	MM_FEAT_BCN_BIT = 0,
+	/*
+	 *	/// Autonomous Beacon Transmission
+	 *	MM_FEAT_AUTOBCN_BIT,
+	 *	/// Scan in LMAC
+	 *	MM_FEAT_HWSCAN_BIT,
+	 *	/// Connection Monitoring
+	 *	MM_FEAT_CMON_BIT,
+	 *	/// Multi Role
+	 *	MM_FEAT_MROLE_BIT,
+	 */
+	/// Radar Detection
+	MM_FEAT_RADAR_BIT,
+	/// Power Save
+	MM_FEAT_PS_BIT,
+	/// UAPSD
+	MM_FEAT_UAPSD_BIT,
+	/// DPSM
+	//  MM_FEAT_DPSM_BIT,
+	/// A-MPDU
+	MM_FEAT_AMPDU_BIT,
+	/// A-MSDU
+	MM_FEAT_AMSDU_BIT,
+	/// Channel Context
+	//  MM_FEAT_CHNL_CTXT_BIT,
+	/// Packet reordering
+	//  MM_FEAT_REORD_BIT,
+	/// P2P
+	MM_FEAT_P2P_BIT,
+	/// P2P Go
+	MM_FEAT_P2P_GO_BIT,
+	/// UMAC Present
+	MM_FEAT_UMAC_BIT,
+	/// VHT support
+	MM_FEAT_VHT_BIT,
+	/// Beamformee
+	MM_FEAT_BFMEE_BIT,
+	/// Beamformer
+	MM_FEAT_BFMER_BIT,
+	/// WAPI
+	MM_FEAT_WAPI_BIT,
+	/// MFP
+	MM_FEAT_MFP_BIT,
+	/// Mu-MIMO RX support
+	MM_FEAT_MU_MIMO_RX_BIT,
+	/// Mu-MIMO TX support
+	MM_FEAT_MU_MIMO_TX_BIT,
+	/// Wireless Mesh Networking
+	MM_FEAT_MESH_BIT,
+	/// TDLS support
+	MM_FEAT_TDLS_BIT,
+	/// Antenna Diversity support
+	MM_FEAT_ANT_DIV_BIT,
+	/// UF support
+	MM_FEAT_UF_BIT,
+	/// A-MSDU maximum size (bit0)
+	MM_AMSDU_MAX_SIZE_BIT0,
+	/// A-MSDU maximum size (bit1)
+	MM_AMSDU_MAX_SIZE_BIT1,
+	/// MON_DATA support
+	MM_FEAT_MON_DATA_BIT,
+	/// HE (802.11ax) support
+	MM_FEAT_HE_BIT,
+	/// TWT support
+	MM_FEAT_TWT_BIT,
+};
+
+/// Maximum number of words in the configuration buffer
+#define PHY_CFG_BUF_SIZE 16
+
+/// Structure containing the parameters of the PHY configuration
+struct phy_cfg_tag {
+	/// Buffer containing the parameters specific for the PHY used
+	u32_l parameters[PHY_CFG_BUF_SIZE];
+};
+
+/// Structure containing the parameters of the Trident PHY configuration
+struct phy_trd_cfg_tag {
+	/// MDM type(nxm)(upper nibble) and MDM2RF path mapping(lower nibble)
+	u8_l path_mapping;
+	/// TX DC offset compensation
+	u32_l tx_dc_off_comp;
+};
+
+/// Structure containing the parameters of the Karst PHY configuration
+struct phy_karst_cfg_tag {
+	/// TX IQ mismatch compensation in 2.4GHz
+	u32_l tx_iq_comp_2_4G[2];
+	/// RX IQ mismatch compensation in 2.4GHz
+	u32_l rx_iq_comp_2_4G[2];
+	/// TX IQ mismatch compensation in 5GHz
+	u32_l tx_iq_comp_5G[2];
+	/// RX IQ mismatch compensation in 5GHz
+	u32_l rx_iq_comp_5G[2];
+	/// RF path used by default (0 or 1)
+	u8_l path_used;
+};
+
+/// Structure containing the parameters of the @ref MM_START_REQ message
+struct mm_start_req {
+	/// PHY configuration
+	struct phy_cfg_tag phy_cfg;
+	/// UAPSD timeout
+	u32_l uapsd_timeout;
+	/// Local LP clock accuracy (in ppm)
+	u16_l lp_clk_accuracy;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_CHANNEL_REQ message
+struct mm_set_channel_req {
+	/// Channel information
+	struct mac_chan_op chan;
+	/// Index of the RF for which the channel has to be set (0: operating
+	/// (primary), 1: secondary RF (used for additional radar detection). This
+	/// parameter is reserved if no secondary RF is available in the system
+	u8_l index;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_CHANNEL_CFM message
+struct mm_set_channel_cfm {
+	/// Radio index to be used in policy table
+	u8_l radio_idx;
+	/// TX power configured (in dBm)
+	s8_l power;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_DTIM_REQ message
+struct mm_set_dtim_req {
+	/// DTIM period
+	u8_l dtim_period;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_POWER_REQ message
+struct mm_set_power_req {
+	/// Index of the interface for which the parameter is configured
+	u8_l inst_nbr;
+	/// TX power (in dBm)
+	s8_l power;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_POWER_CFM message
+struct mm_set_power_cfm {
+	/// Radio index to be used in policy table
+	u8_l radio_idx;
+	/// TX power configured (in dBm)
+	s8_l power;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_BEACON_INT_REQ
+/// message
+struct mm_set_beacon_int_req {
+	/// Beacon interval
+	u16_l beacon_int;
+	/// Index of the interface for which the parameter is configured
+	u8_l inst_nbr;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_BASIC_RATES_REQ
+/// message
+struct mm_set_basic_rates_req {
+	/// Basic rate set (as expected by bssBasicRateSet field of Rates MAC HW
+	/// register)
+	u32_l rates;
+	/// Index of the interface for which the parameter is configured
+	u8_l inst_nbr;
+	/// Band on which the interface will operate
+	u8_l band;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_BSSID_REQ message
+struct mm_set_bssid_req {
+	/// BSSID to be configured in HW
+	struct mac_addr bssid;
+	/// Index of the interface for which the parameter is configured
+	u8_l inst_nbr;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_FILTER_REQ message
+struct mm_set_filter_req {
+	/// RX filter to be put into rxCntrlReg HW register
+	u32_l filter;
+};
+
+/// Structure containing the parameters of the @ref MM_ADD_IF_REQ message.
+struct mm_add_if_req {
+	/// Type of the interface (AP, STA, ADHOC, ...)
+	u8_l type;
+	/// MAC ADDR of the interface to start
+	struct mac_addr addr;
+	/// P2P Interface
+	bool p2p;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_EDCA_REQ message
+struct mm_set_edca_req {
+	/// EDCA parameters of the queue (as expected by edcaACxReg HW register)
+	u32_l ac_param;
+	/// Flag indicating if UAPSD can be used on this queue
+	bool uapsd;
+	/// HW queue for which the parameters are configured
+	u8_l hw_queue;
+	/// Index of the interface for which the parameters are configured
+	u8_l inst_nbr;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_MU_EDCA_REQ message
+struct mm_set_mu_edca_req {
+	/// MU EDCA parameters of the different HE queues
+	u32_l param[AC_MAX];
+};
+
+/// Structure containing the parameters of the @ref MM_SET_UORA_REQ message
+struct mm_set_uora_req {
+	/// Minimum exponent of OFDMA Contention Window.
+	u8_l eocw_min;
+	/// Maximum exponent of OFDMA Contention Window.
+	u8_l eocw_max;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_TXOP_RTS_THRES_REQ
+/// message
+struct mm_set_txop_rts_thres_req {
+	/// TXOP RTS threshold
+	u16_l txop_dur_rts_thres;
+	/// Index of the interface for which the parameter is configured
+	u8_l inst_nbr;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_BSS_COLOR_REQ message
+struct mm_set_bss_color_req {
+	/// HE BSS color, formatted as per BSS_COLOR MAC HW register
+	u32_l bss_color;
+};
+
+struct mm_set_idle_req {
+	u8_l hw_idle;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_SLOTTIME_REQ message
+struct mm_set_slottime_req {
+	/// Slot time expressed in us
+	u8_l slottime;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_MODE_REQ message
+struct mm_set_mode_req {
+	/// abgnMode field of macCntrl1Reg register
+	u8_l abgnmode;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_VIF_STATE_REQ message
+struct mm_set_vif_state_req {
+	/// Association Id received from the AP (valid only if the VIF is of STA
+	/// type)
+	u16_l aid;
+	/// Flag indicating if the VIF is active or not
+	bool active;
+	/// Interface index
+	u8_l inst_nbr;
+};
+
+/// Structure containing the parameters of the @ref MM_ADD_IF_CFM message.
+struct mm_add_if_cfm {
+	/// Status of operation (different from 0 if unsuccessful)
+	u8_l status;
+	/// Interface index assigned by the LMAC
+	u8_l inst_nbr;
+};
+
+/// Structure containing the parameters of the @ref MM_REMOVE_IF_REQ message.
+struct mm_remove_if_req {
+	/// Interface index assigned by the LMAC
+	u8_l inst_nbr;
+};
+
+/// Structure containing the parameters of the @ref MM_VERSION_CFM message.
+struct mm_version_cfm {
+	/// Version of the LMAC FW
+	u32_l version_lmac;
+	/// Version1 of the MAC HW (as encoded in version1Reg MAC HW register)
+	u32_l version_machw_1;
+	/// Version2 of the MAC HW (as encoded in version2Reg MAC HW register)
+	u32_l version_machw_2;
+	/// Version1 of the PHY (depends on actual PHY)
+	u32_l version_phy_1;
+	/// Version2 of the PHY (depends on actual PHY)
+	u32_l version_phy_2;
+	/// Supported Features
+	u32_l features;
+	/// Maximum number of supported stations
+	u16_l max_sta_nb;
+	/// Maximum number of supported virtual interfaces
+	u8_l max_vif_nb;
+};
+
+/// Structure containing the parameters of the @ref MM_STA_ADD_REQ message.
+struct mm_sta_add_req {
+	/// Bitfield showing some capabilities of the STA (@ref enum mac_sta_flags)
+	u32_l capa_flags;
+	/// Maximum A-MPDU size, in bytes, for HE frames
+	u32_l ampdu_size_max_he;
+	/// Maximum A-MPDU size, in bytes, for VHT frames
+	u32_l ampdu_size_max_vht;
+	/// PAID/GID
+	u32_l paid_gid;
+	/// Maximum A-MPDU size, in bytes, for HT frames
+	u16_l ampdu_size_max_ht;
+	/// MAC address of the station to be added
+	struct mac_addr mac_addr;
+	/// A-MPDU spacing, in us
+	u8_l ampdu_spacing_min;
+	/// Interface index
+	u8_l inst_nbr;
+	/// TDLS station
+	bool tdls_sta;
+	/// Indicate if the station is TDLS link initiator station
+	bool tdls_sta_initiator;
+	/// Indicate if the TDLS Channel Switch is allowed
+	bool tdls_chsw_allowed;
+	/// nonTransmitted BSSID index, set to the BSSID index in case the STA added
+	/// is an AP that is a nonTransmitted BSSID. Should be set to 0 otherwise
+	u8_l bssid_index;
+	/// Maximum BSSID indicator, valid if the STA added is an AP that is a
+	/// nonTransmitted BSSID
+	u8_l max_bssid_ind;
+};
+
+/// Structure containing the parameters of the @ref MM_STA_ADD_CFM message.
+struct mm_sta_add_cfm {
+	/// Status of the operation (different from 0 if unsuccessful)
+	u8_l status;
+	/// Index assigned by the LMAC to the newly added station
+	u8_l sta_idx;
+	/// MAC HW index of the newly added station
+	u8_l hw_sta_idx;
+};
+
+/// Structure containing the parameters of the @ref MM_STA_DEL_REQ message.
+struct mm_sta_del_req {
+	/// Index of the station to be deleted
+	u8_l sta_idx;
+};
+
+/// Structure containing the parameters of the @ref MM_STA_DEL_CFM message.
+struct mm_sta_del_cfm {
+	/// Status of the operation (different from 0 if unsuccessful)
+	u8_l status;
+};
+
+/// Structure containing the parameters of the SET_POWER_MODE REQ message.
+struct mm_setpowermode_req {
+	u8_l mode;
+	u8_l sta_idx;
+};
+
+/// Structure containing the parameters of the SET_POWER_MODE CFM message.
+struct mm_setpowermode_cfm {
+	u8_l status;
+};
+
+/// Structure containing the parameters of the @ref MM_KEY_ADD REQ message.
+struct mm_key_add_req {
+	/// Key index (valid only for default keys)
+	u8_l key_idx;
+	/// STA index (valid only for pairwise or mesh group keys)
+	u8_l sta_idx;
+	/// Key material
+	struct mac_sec_key key;
+	/// Cipher suite (WEP64, WEP128, TKIP, CCMP)
+	u8_l cipher_suite;
+	/// Index of the interface for which the key is set (valid only for default
+	/// keys or mesh group keys)
+	u8_l inst_nbr;
+	/// A-MSDU SPP parameter
+	u8_l spp;
+	/// Indicate if provided key is a pairwise key or not
+	bool pairwise;
+};
+
+/// Structure containing the parameters of the @ref MM_KEY_ADD_CFM message.
+struct mm_key_add_cfm {
+	/// Status of the operation (different from 0 if unsuccessful)
+	u8_l status;
+	/// HW index of the key just added
+	u8_l hw_key_idx;
+};
+
+/// Structure containing the parameters of the @ref MM_KEY_DEL_REQ message.
+struct mm_key_del_req {
+	/// HW index of the key to be deleted
+	u8_l hw_key_idx;
+};
+
+/// Structure containing the parameters of the @ref MM_BA_ADD_REQ message.
+struct mm_ba_add_req {
+	/// Type of agreement (0: TX, 1: RX)
+	u8_l type;
+	/// Index of peer station with which the agreement is made
+	u8_l sta_idx;
+	/// TID for which the agreement is made with peer station
+	u8_l tid;
+	/// Buffer size - number of MPDUs that can be held in its buffer per TID
+	u8_l bufsz;
+	/// Start sequence number negotiated during BA setup - the one in first
+	/// aggregated MPDU counts more
+	u16_l ssn;
+};
+
+/// Structure containing the parameters of the @ref MM_BA_ADD_CFM message.
+struct mm_ba_add_cfm {
+	/// Index of peer station for which the agreement is being confirmed
+	u8_l sta_idx;
+	/// TID for which the agreement is being confirmed
+	u8_l tid;
+	/// Status of ba establishment
+	u8_l status;
+};
+
+/// Structure containing the parameters of the @ref MM_BA_DEL_REQ message.
+struct mm_ba_del_req {
+	/// Type of agreement (0: TX, 1: RX)
+	u8_l type;
+	/// Index of peer station for which the agreement is being deleted
+	u8_l sta_idx;
+	/// TID for which the agreement is being deleted
+	u8_l tid;
+};
+
+/// Structure containing the parameters of the @ref MM_BA_DEL_CFM message.
+struct mm_ba_del_cfm {
+	/// Index of peer station for which the agreement deletion is being
+	/// confirmed
+	u8_l sta_idx;
+	/// TID for which the agreement deletion is being confirmed
+	u8_l tid;
+	/// Status of ba deletion
+	u8_l status;
+};
+
+/// Structure containing the parameters of the @ref MM_CHAN_CTXT_ADD_REQ message
+struct mm_chan_ctxt_add_req {
+	/// Operating channel
+	struct mac_chan_op chan;
+};
+
+/// Structure containing the parameters of the @ref MM_CHAN_CTXT_ADD_REQ message
+struct mm_chan_ctxt_add_cfm {
+	/// Status of the addition
+	u8_l status;
+	/// Index of the new channel context
+	u8_l index;
+};
+
+/// Structure containing the parameters of the @ref MM_CHAN_CTXT_DEL_REQ message
+struct mm_chan_ctxt_del_req {
+	/// Index of the new channel context to be deleted
+	u8_l index;
+};
+
+/// Structure containing the parameters of the @ref MM_CHAN_CTXT_LINK_REQ
+/// message
+struct mm_chan_ctxt_link_req {
+	/// VIF index
+	u8_l vif_index;
+	/// Channel context index
+	u8_l chan_index;
+	/// Indicate if this is a channel switch (unlink current ctx first if true)
+	u8_l chan_switch;
+};
+
+/// Structure containing the parameters of the @ref MM_CHAN_CTXT_UNLINK_REQ
+/// message
+struct mm_chan_ctxt_unlink_req {
+	/// VIF index
+	u8_l vif_index;
+};
+
+/// Structure containing the parameters of the @ref MM_CHAN_CTXT_UPDATE_REQ
+/// message
+struct mm_chan_ctxt_update_req {
+	/// Channel context index
+	u8_l chan_index;
+	/// New channel information
+	struct mac_chan_op chan;
+};
+
+/// Structure containing the parameters of the @ref MM_CHAN_CTXT_SCHED_REQ
+/// message
+struct mm_chan_ctxt_sched_req {
+	/// VIF index
+	u8_l vif_index;
+	/// Channel context index
+	u8_l chan_index;
+	/// Type of the scheduling request (0: normal scheduling, 1: derogatory
+	/// scheduling)
+	u8_l type;
+};
+
+/// Structure containing the parameters of the @ref MM_CHANNEL_SWITCH_IND
+/// message
+struct mm_channel_switch_ind {
+	/// Index of the channel context we will switch to
+	u8_l chan_index;
+	/// Indicate if the switch has been triggered by a Remain on channel request
+	bool roc;
+	/// VIF on which remain on channel operation has been started (if roc == 1)
+	u8_l vif_index;
+	/// Indicate if the switch has been triggered by a TDLS Remain on channel
+	/// request
+	bool roc_tdls;
+};
+
+/// Structure containing the parameters of the @ref MM_CHANNEL_PRE_SWITCH_IND
+/// message
+struct mm_channel_pre_switch_ind {
+	/// Index of the channel context we will switch to
+	u8_l chan_index;
+};
+
+/// Structure containing the parameters of the @ref MM_CONNECTION_LOSS_IND
+/// message.
+struct mm_connection_loss_ind {
+	/// VIF instance number
+	u8_l inst_nbr;
+};
+
+/// Structure containing the parameters of the @ref MM_DBG_TRIGGER_REQ message.
+struct mm_dbg_trigger_req {
+	/// Error trace to be reported by the LMAC
+	char error[64];
+};
+
+/// Structure containing the parameters of the @ref MM_SET_PS_MODE_REQ message.
+struct mm_set_ps_mode_req {
+	/// Power Save is activated or deactivated
+	u8_l new_state;
+};
+
+/// Structure containing the parameters of the @ref MM_BCN_CHANGE_REQ message.
+#define BCN_MAX_CSA_CPT 2
+struct mm_bcn_change_req {
+	/// Pointer, in host memory, to the new beacon template
+	u32_l bcn_ptr;
+	/// Length of the beacon template
+	u16_l bcn_len;
+	/// Offset of the TIM IE in the beacon
+	u16_l tim_oft;
+	/// Length of the TIM IE
+	u8_l tim_len;
+	/// Index of the VIF for which the beacon is updated
+	u8_l inst_nbr;
+	/// Offset of CSA (channel switch announcement) counters (0 means no
+	/// counter)
+	u8_l csa_oft[BCN_MAX_CSA_CPT];
+};
+
+/// Structure containing the parameters of the @ref MM_TIM_UPDATE_REQ message.
+struct mm_tim_update_req {
+	/// Association ID of the STA the bit of which has to be updated (0 for
+	/// BC/MC traffic)
+	u16_l aid;
+	/// Flag indicating the availability of data packets for the given STA
+	u8_l tx_avail;
+	/// Index of the VIF for which the TIM is updated
+	u8_l inst_nbr;
+};
+
+/// Structure containing the parameters of the @ref MM_REMAIN_ON_CHANNEL_REQ
+/// message.
+struct mm_remain_on_channel_req {
+	/// Operation Code
+	u8_l op_code;
+	/// VIF Index
+	u8_l vif_index;
+	/// Band (2.4GHz or 5GHz)
+	u8_l band;
+	/// Channel type: 20,40,80,160 or 80+80 MHz
+	u8_l type;
+	/// Frequency for Primary 20MHz channel (in MHz)
+	u16_l prim20_freq;
+	/// Frequency for Center of the contiguous channel or center of Primary
+	/// 80+80
+	u16_l center1_freq;
+	/// Frequency for Center of the non-contiguous secondary 80+80
+	u16_l center2_freq;
+	/// Duration (in ms)
+	u32_l duration_ms;
+	/// TX power (in dBm)
+	s8_l tx_power;
+};
+
+/// Structure containing the parameters of the @ref MM_REMAIN_ON_CHANNEL_CFM
+/// message
+struct mm_remain_on_channel_cfm {
+	/// Operation Code
+	u8_l op_code;
+	/// Status of the operation
+	u8_l status;
+	/// Channel Context index
+	u8_l chan_ctxt_index;
+};
+
+/// Structure containing the parameters of the @ref MM_REMAIN_ON_CHANNEL_EXP_IND
+/// message
+struct mm_remain_on_channel_exp_ind {
+	/// VIF Index
+	u8_l vif_index;
+	/// Channel Context index
+	u8_l chan_ctxt_index;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_UAPSD_TMR_REQ
+/// message.
+struct mm_set_uapsd_tmr_req {
+	/// action: Start or Stop the timer
+	u8_l action;
+	/// timeout value, in milliseconds
+	u32_l timeout;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_UAPSD_TMR_CFM
+/// message.
+struct mm_set_uapsd_tmr_cfm {
+	/// Status of the operation (different from 0 if unsuccessful)
+	u8_l status;
+};
+
+/// Structure containing the parameters of the @ref MM_PS_CHANGE_IND message
+struct mm_ps_change_ind {
+	/// Index of the peer device that is switching its PS state
+	u8_l sta_idx;
+	/// New PS state of the peer device (0: active, 1: sleeping)
+	u8_l ps_state;
+};
+
+/// Structure containing the parameters of the @ref MM_P2P_VIF_PS_CHANGE_IND
+/// message
+struct mm_p2p_vif_ps_change_ind {
+	/// Index of the P2P VIF that is switching its PS state
+	u8_l vif_index;
+	/// New PS state of the P2P VIF interface (0: active, 1: sleeping)
+	u8_l ps_state;
+};
+
+/// Structure containing the parameters of the @ref MM_TRAFFIC_REQ_IND message
+struct mm_traffic_req_ind {
+	/// Index of the peer device that needs traffic
+	u8_l sta_idx;
+	/// Number of packets that need to be sent (if 0, all buffered traffic shall
+	/// be sent and if set to @ref PS_SP_INTERRUPTED, it means that current
+	/// service period has been interrupted)
+	u8_l pkt_cnt;
+	/// Flag indicating if the traffic request concerns U-APSD queues or not
+	bool uapsd;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_PS_OPTIONS_REQ
+/// message.
+struct mm_set_ps_options_req {
+	/// VIF Index
+	u8_l vif_index;
+	/// Listen interval (0 if wake up shall be based on DTIM period)
+	u16_l listen_interval;
+	/// Flag indicating if we shall listen the BC/MC traffic or not
+	bool dont_listen_bc_mc;
+};
+
+/// Structure containing the parameters of the @ref MM_CSA_COUNTER_IND message
+struct mm_csa_counter_ind {
+	/// Index of the VIF
+	u8_l vif_index;
+	/// Updated CSA counter value
+	u8_l csa_count;
+};
+
+/// Structure containing the parameters of the @ref MM_CHANNEL_SURVEY_IND
+/// message
+struct mm_channel_survey_ind {
+	/// Frequency of the channel
+	u16_l freq;
+	/// Noise in dbm
+	s8_l noise_dbm;
+	/// Amount of time spent of the channel (in ms)
+	u32_l chan_time_ms;
+	/// Amount of time the primary channel was sensed busy
+	u32_l chan_time_busy_ms;
+};
+
+/// Structure containing the parameters of the @ref MM_BFMER_ENABLE_REQ message.
+struct mm_bfmer_enable_req {
+	/**
+	 * Address of the beamforming report space allocated in host memory
+	 * (Valid only if vht_su_bfmee is true)
+	 */
+	u32_l host_bfr_addr;
+	/**
+	 * Size of the beamforming report space allocated in host memory. This space
+	 * should be twice the maximum size of the expected beamforming reports as
+	 * the FW will divide it in two in order to be able to upload a new report
+	 * while another one is used in transmission
+	 */
+	u16_l host_bfr_size;
+	/// AID
+	u16_l aid;
+	/// Station Index
+	u8_l sta_idx;
+	/// Maximum number of spatial streams the station can receive
+	u8_l rx_nss;
+	/**
+	 * Indicate if peer STA is MU Beamformee (VHT) capable
+	 * (Valid only if vht_su_bfmee is true)
+	 */
+	bool vht_mu_bfmee;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_P2P_NOA_REQ message.
+struct mm_set_p2p_noa_req {
+	/// VIF Index
+	u8_l vif_index;
+	/// Allocated NOA Instance Number - Valid only if count = 0
+	u8_l noa_inst_nb;
+	/// Count
+	u8_l count;
+	/// Indicate if NoA can be paused for traffic reason
+	bool dyn_noa;
+	/// Duration (in us)
+	u32_l duration_us;
+	/// Interval (in us)
+	u32_l interval_us;
+	/// Start Time offset from next TBTT (in us)
+	u32_l start_offset;
+};
+
+#ifdef AICWF_ARP_OFFLOAD
+struct mm_set_arpoffload_en_req {
+	u32_l ipaddr;
+	u8_l enable;
+	u8_l vif_idx;
+};
+
+struct mm_set_arpoffload_en_cfm {
+	u8_l status;
+};
+#endif
+
+struct mm_set_agg_disable_req {
+	u8_l disable;
+	u8_l staidx;
+	u8_l disable_rx;
+};
+
+struct mm_set_coex_req {
+	u8_l bt_on;
+	u8_l disable_coexnull;
+	u8_l enable_nullcts;
+	u8_l enable_periodic_timer;
+	u8_l coex_timeslot_set;
+	u32_l coex_timeslot[2];
+};
+
+struct mm_set_rf_config_req {
+	u8_l table_sel;
+	u8_l table_ofst;
+	u8_l table_num;
+	u8_l deft_page;
+	u32_l data[64];
+};
+
+struct mm_set_rf_calib_req {
+	u32_l cal_cfg_24g;
+	u32_l cal_cfg_5g;
+	u32_l param_alpha;
+	u32_l bt_calib_en;
+	u32_l bt_calib_param;
+	u8_l xtal_cap;
+	u8_l xtal_cap_fine;
+};
+
+struct mm_set_rf_calib_cfm {
+	u32_l rxgain_24g_addr;
+	u32_l rxgain_5g_addr;
+	u32_l txgain_24g_addr;
+	u32_l txgain_5g_addr;
+};
+
+struct mm_get_mac_addr_req {
+	u32_l get;
+};
+
+struct mm_get_mac_addr_cfm {
+	u8_l mac_addr[6];
+};
+
+struct mm_get_sta_info_req {
+	u8_l sta_idx;
+};
+
+struct mm_get_sta_info_cfm {
+	u32_l rate_info;
+	u32_l txfailed;
+	u8 rssi;
+};
+
+#define REGION_NUM_MAX 5
+
+struct txpwr_lvl_conf {
+	u8_l enable;
+	u8_l dsss;
+	u8_l ofdmlowrate_2g4;
+	u8_l ofdm64qam_2g4;
+	u8_l ofdm256qam_2g4;
+	u8_l ofdm1024qam_2g4;
+	u8_l ofdmlowrate_5g;
+	u8_l ofdm64qam_5g;
+	u8_l ofdm256qam_5g;
+	u8_l ofdm1024qam_5g;
+};
+
+struct txpwr_lvl_conf_v2 {
+	u8_l enable;
+	s8_l pwrlvl_11b_11ag_2g4[12];
+	s8_l pwrlvl_11n_11ac_2g4[10];
+	s8_l pwrlvl_11ax_2g4[12];
+};
+
+struct txpwr_lvl_conf_v3 {
+	u8_l enable;
+	s8_l pwrlvl_11b_11ag_2g4[12];
+	s8_l pwrlvl_11n_11ac_2g4[10];
+	s8_l pwrlvl_11ax_2g4[12];
+	s8_l pwrlvl_11a_5g[12];
+	s8_l pwrlvl_11n_11ac_5g[10];
+	s8_l pwrlvl_11ax_5g[12];
+};
+
+struct txpwr_lvl_adj_conf {
+	u8_l enable;
+	s8_l pwrlvl_adj_tbl_2g4[3];
+	s8_l pwrlvl_adj_tbl_5g[6];
+};
+
+struct txpwr_loss_conf {
+	u8_l loss_enable_2g4;
+	s8_l loss_value_2g4;
+	u8_l loss_enable_5g;
+	s8_l loss_value_5g;
+};
+
+struct mm_set_txpwr_lvl_req {
+	union {
+		struct txpwr_lvl_conf txpwr_lvl;
+		struct txpwr_lvl_conf_v2 txpwr_lvl_v2;
+		struct txpwr_lvl_conf_v3 txpwr_lvl_v3;
+	};
+};
+
+struct mm_set_txpwr_lvl_adj_req {
+	struct txpwr_lvl_adj_conf txpwr_lvl_adj;
+};
+
+struct txpwr_idx_conf {
+	u8_l enable;
+	u8_l dsss;
+	u8_l ofdmlowrate_2g4;
+	u8_l ofdm64qam_2g4;
+	u8_l ofdm256qam_2g4;
+	u8_l ofdm1024qam_2g4;
+	u8_l ofdmlowrate_5g;
+	u8_l ofdm64qam_5g;
+	u8_l ofdm256qam_5g;
+	u8_l ofdm1024qam_5g;
+};
+
+struct mm_set_txpwr_idx_req {
+	struct txpwr_idx_conf txpwr_idx;
+};
+
+struct txpwr_ofst_conf {
+	u8_l enable;
+	s8_l chan_1_4;
+	s8_l chan_5_9;
+	s8_l chan_10_13;
+	s8_l chan_36_64;
+	s8_l chan_100_120;
+	s8_l chan_122_140;
+	s8_l chan_142_165;
+};
+
+/*
+ * pwrofst2x_tbl_2g4[3][3]:
+ * +---------------+----------+----------+----------+
+ * | RateTyp\ChGrp |  CH_1_4  |  CH_5_9  | CH_10_13 |
+ * +---------------+----------+----------+----------+
+ * | DSSS          |  [0][0]  |  [0][1]  |  [0][2]  |
+ * +---------------+----------+----------+----------+
+ * | OFDM_HIGHRATE |  [1][0]  |  [1][1]  |  [1][2]  |
+ * +---------------+----------+----------+----------+
+ * | OFDM_LOWRATE  |  [2][0]  |  [2][1]  |  [2][2]  |
+ * +---------------+----------+----------+----------+
+ * pwrofst2x_tbl_5g[3][6]:
+ * +---------------+--------------+--------------+----------------+----------------+--+--+
+ * | RateTyp\ChGrp | CH_42(36~50) | CH_58(51~64) | CH_106(98~114) |
+ * CH_122(115~130)| CH_138(131~146)| CH_155(147~166)|
+ * +---------------+--------------+--------------+----------------+----------------+--+--+
+ * | OFDM_LOWRATE  |    [0][0]    |    [0][1]    |     [0][2]     |     [0][3]
+ * |     [0][4]     |     [0][5]     |
+ * +---------------+--------------+--------------+----------------+----------------+--+--+
+ * | OFDM_HIGHRATE |    [1][0]    |    [1][1]    |     [1][2]     |     [1][3]
+ * |     [1][4]     |     [1][5]     |
+ * +---------------+--------------+--------------+----------------+----------------+--+--+
+ * | OFDM_MIDRATE  |    [2][0]    |    [2][1]    |     [2][2]     |     [2][3]
+ * |     [2][4]     |     [2][5]     |
+ * +---------------+--------------+--------------+----------------+----------------+--+--+
+ */
+
+struct txpwr_ofst2x_conf {
+	s8 enable;
+	s8 pwrofst2x_tbl_2g4[3][3];
+	s8 pwrofst2x_tbl_5g[3][6];
+};
+
+struct xtal_cap_conf {
+	u8_l enable;
+	u8_l xtal_cap;
+	u8_l xtal_cap_fine;
+};
+
+struct mm_set_txpwr_ofst_req {
+	union {
+		struct txpwr_ofst_conf txpwr_ofst;
+		struct txpwr_ofst2x_conf txpwr_ofst2x;
+	};
+};
+
+struct mm_set_stack_start_req {
+	u8_l is_stack_start;
+	u8_l efuse_valid;
+	u8_l set_vendor_info;
+	u8_l fwtrace_redir;
+};
+
+struct mm_set_stack_start_cfm {
+	u8_l is_5g_support;
+	u8_l vendor_info;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_P2P_OPPPS_REQ
+/// message.
+struct mm_set_p2p_oppps_req {
+	/// VIF Index
+	u8_l vif_index;
+	/// CTWindow
+	u8_l ctwindow;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_P2P_NOA_CFM message.
+struct mm_set_p2p_noa_cfm {
+	/// Request status
+	u8_l status;
+};
+
+/// Structure containing the parameters of the @ref MM_SET_P2P_OPPPS_CFM
+/// message.
+struct mm_set_p2p_oppps_cfm {
+	/// Request status
+	u8_l status;
+};
+
+/// Structure containing the parameters of the @ref MM_P2P_NOA_UPD_IND message.
+struct mm_p2p_noa_upd_ind {
+	/// VIF Index
+	u8_l vif_index;
+	/// NOA Instance Number
+	u8_l noa_inst_nb;
+	/// NoA Type
+	u8_l noa_type;
+	/// Count
+	u8_l count;
+	/// Duration (in us)
+	u32_l duration_us;
+	/// Interval (in us)
+	u32_l interval_us;
+	/// Start Time
+	u32_l start_time;
+};
+
+/// Structure containing the parameters of the @ref MM_CFG_RSSI_REQ message
+struct mm_cfg_rssi_req {
+	/// Index of the VIF
+	u8_l vif_index;
+	/// RSSI threshold
+	s8_l rssi_thold;
+	/// RSSI hysteresis
+	u8_l rssi_hyst;
+};
+
+/// Structure containing the parameters of the @ref MM_RSSI_STATUS_IND message
+struct mm_rssi_status_ind {
+	/// Index of the VIF
+	u8_l vif_index;
+	/// Status of the RSSI
+	bool rssi_status;
+	/// Current RSSI
+	s8_l rssi;
+};
+
+/// Structure containing the parameters of the @ref MM_PKTLOSS_IND message
+struct mm_pktloss_ind {
+	/// Index of the VIF
+	u8_l vif_index;
+	/// Address of the STA for which there is a packet loss
+	struct mac_addr mac_addr;
+	/// Number of packets lost
+	u32 num_packets;
+};
+
+/// Structure containing the parameters of the @ref MM_CSA_FINISH_IND message
+struct mm_csa_finish_ind {
+	/// Index of the VIF
+	u8_l vif_index;
+	/// Status of the operation
+	u8_l status;
+	/// New channel ctx index
+	u8_l chan_idx;
+};
+
+/// Structure containing the parameters of the @ref MM_CSA_TRAFFIC_IND message
+struct mm_csa_traffic_ind {
+	/// Index of the VIF
+	u8_l vif_index;
+	/// Is tx traffic enable or disable
+	bool enable;
+};
+
+/// Structure containing the parameters of the @ref MM_MU_GROUP_UPDATE_REQ
+/// message. Size allocated for the structure depends of the number of group
+struct mm_mu_group_update_req {
+	/// Station index
+	u8_l sta_idx;
+	/// Number of groups the STA belongs to
+	u8_l group_cnt;
+	/// Group information
+	struct {
+		/// Group Id
+		u8_l group_id;
+		/// User position
+		u8_l user_pos;
+	} groups[];
+};
+
+///////////////////////////////////////////////////////////////////////////////
+/////////// For Scan messages
+///////////////////////////////////////////////////////////////////////////////
+enum scan_msg_tag {
+	/// Scanning start Request.
+	SCAN_START_REQ = LMAC_FIRST_MSG(TASK_SCAN),
+	/// Scanning start Confirmation.
+	SCAN_START_CFM,
+	/// End of scanning indication.
+	SCAN_DONE_IND,
+	/// Cancel scan request
+	SCAN_CANCEL_REQ,
+	/// Cancel scan confirmation
+	SCAN_CANCEL_CFM,
+
+	/// MAX number of messages
+	SCAN_MAX,
+};
+
+/// Maximum number of SSIDs in a scan request
+#define SCAN_SSID_MAX 3
+
+/// Maximum number of channels in a scan request
+#define SCAN_CHANNEL_MAX (MAC_DOMAINCHANNEL_24G_MAX + MAC_DOMAINCHANNEL_5G_MAX)
+
+/// Maximum length of the ProbeReq IEs (SoftMAC mode)
+#define SCAN_MAX_IE_LEN 300
+
+/// Maximum number of PHY bands supported
+#define SCAN_BAND_MAX 2
+
+/// Structure containing the parameters of the @ref SCAN_START_REQ message
+struct scan_start_req {
+	/// List of channel to be scanned
+	struct mac_chan_def chan[SCAN_CHANNEL_MAX];
+	/// List of SSIDs to be scanned
+	struct mac_ssid ssid[SCAN_SSID_MAX];
+	/// BSSID to be scanned
+	struct mac_addr bssid;
+	/// Pointer (in host memory) to the additional IEs that need to be added to
+	/// the ProbeReq (following the SSID element)
+	u32_l add_ies;
+	/// Length of the additional IEs
+	u16_l add_ie_len;
+	/// Index of the VIF that is scanning
+	u8_l vif_idx;
+	/// Number of channels to scan
+	u8_l chan_cnt;
+	/// Number of SSIDs to scan for
+	u8_l ssid_cnt;
+	/// no CCK - For P2P frames not being sent at CCK rate in 2GHz band.
+	bool no_cck;
+	/// Scan duration, in us
+	u32_l duration;
+};
+
+/// Structure containing the parameters of the @ref SCAN_START_CFM message
+struct scan_start_cfm {
+	/// Status of the request
+	u8_l status;
+};
+
+/// Structure containing the parameters of the @ref SCAN_CANCEL_REQ message
+struct scan_cancel_req {
+};
+
+/// Structure containing the parameters of the @ref SCAN_START_CFM message
+struct scan_cancel_cfm {
+	/// Status of the request
+	u8_l status;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+/////////// For Scanu messages
+///////////////////////////////////////////////////////////////////////////////
+/// Messages that are logically related to the task.
+enum {
+	/// Scan request from host.
+	SCANU_START_REQ = LMAC_FIRST_MSG(TASK_SCANU),
+	/// Scanning start Confirmation.
+	SCANU_START_CFM,
+	/// Join request
+	SCANU_JOIN_REQ,
+	/// Join confirmation.
+	SCANU_JOIN_CFM,
+	/// Scan result indication.
+	SCANU_RESULT_IND,
+	/// Fast scan request from any other module.
+	SCANU_FAST_REQ,
+	/// Confirmation of fast scan request.
+	SCANU_FAST_CFM,
+
+	SCANU_VENDOR_IE_REQ,
+	SCANU_VENDOR_IE_CFM,
+	SCANU_START_CFM_ADDTIONAL,
+	SCANU_CANCEL_REQ,
+	SCANU_CANCEL_CFM,
+
+	/// MAX number of messages
+	SCANU_MAX,
+};
+
+/// Maximum length of the additional ProbeReq IEs (FullMAC mode)
+#define SCANU_MAX_IE_LEN 200
+
+/// Structure containing the parameters of the @ref SCANU_START_REQ message
+struct scanu_start_req {
+	/// List of channel to be scanned
+	struct mac_chan_def chan[SCAN_CHANNEL_MAX];
+	/// List of SSIDs to be scanned
+	struct mac_ssid ssid[SCAN_SSID_MAX];
+	/// BSSID to be scanned (or WILDCARD BSSID if no BSSID is searched in
+	/// particular)
+	struct mac_addr bssid;
+	/// Address (in host memory) of the additional IEs that need to be added to
+	/// the ProbeReq (following the SSID element)
+	u32_l add_ies;
+	/// Length of the additional IEs
+	u16_l add_ie_len;
+	/// Index of the VIF that is scanning
+	u8_l vif_idx;
+	/// Number of channels to scan
+	u8_l chan_cnt;
+	/// Number of SSIDs to scan for
+	u8_l ssid_cnt;
+	/// no CCK - For P2P frames not being sent at CCK rate in 2GHz band.
+	bool no_cck;
+	/// Scan duration, in us
+	u32_l duration;
+};
+
+struct scanu_vendor_ie_req {
+	u16_l add_ie_len;
+	u8_l vif_idx;
+	u8_l ie[256];
+};
+
+/// Structure containing the parameters of the @ref SCANU_START_CFM message
+struct scanu_start_cfm {
+	/// Index of the VIF that was scanning
+	u8_l vif_idx;
+	/// Status of the request
+	u8_l status;
+	/// Number of scan results available
+	u8_l result_cnt;
+};
+
+/// Parameters of the @SCANU_RESULT_IND message
+struct scanu_result_ind {
+	/// Length of the frame
+	u16_l length;
+	/// Frame control field of the frame.
+	u16_l framectrl;
+	/// Center frequency on which we received the packet
+	u16_l center_freq;
+	/// PHY band
+	u8_l band;
+	/// Index of the station that sent the frame. 0xFF if unknown.
+	u8_l sta_idx;
+	/// Index of the VIF that received the frame. 0xFF if unknown.
+	u8_l inst_nbr;
+	/// RSSI of the received frame.
+	s8_l rssi;
+	/// Frame payload.
+	u32_l payload[];
+};
+
+struct rxu_arp_announcement_ind {
+	/// index of the vif
+	u8_l vif_index;
+	u8_l offset;
+	/// length of the frame
+	u16_l length;
+	u32_l rx_status;
+	/// frame payload
+	u8_l payload[];
+};
+
+/// Structure containing the parameters of the message.
+struct scanu_fast_req {
+	/// The SSID to scan in the channel.
+	struct mac_ssid ssid;
+	/// BSSID.
+	struct mac_addr bssid;
+	/// Probe delay.
+	u16_l probe_delay;
+	/// Minimum channel time.
+	u16_l minch_time;
+	/// Maximum channel time.
+	u16_l maxch_time;
+	/// The channel number to scan.
+	u16_l ch_nbr;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+/////////// For ME messages
+///////////////////////////////////////////////////////////////////////////////
+/// Messages that are logically related to the task.
+enum {
+	/// Configuration request from host.
+	ME_CONFIG_REQ = LMAC_FIRST_MSG(TASK_ME),
+	/// Configuration confirmation.
+	ME_CONFIG_CFM,
+	/// Configuration request from host.
+	ME_CHAN_CONFIG_REQ,
+	/// Configuration confirmation.
+	ME_CHAN_CONFIG_CFM,
+	/// Set control port state for a station.
+	ME_SET_CONTROL_PORT_REQ,
+	/// Control port setting confirmation.
+	ME_SET_CONTROL_PORT_CFM,
+	/// TKIP MIC failure indication.
+	ME_TKIP_MIC_FAILURE_IND,
+	/// Add a station to the FW (AP mode)
+	ME_STA_ADD_REQ,
+	/// Confirmation of the STA addition
+	ME_STA_ADD_CFM,
+	/// Delete a station from the FW (AP mode)
+	ME_STA_DEL_REQ,
+	/// Confirmation of the STA deletion
+	ME_STA_DEL_CFM,
+	/// Indication of a TX RA/TID queue credit update
+	ME_TX_CREDITS_UPDATE_IND,
+	/// Request indicating to the FW that there is traffic buffered on host
+	ME_TRAFFIC_IND_REQ,
+	/// Confirmation that the @ref ME_TRAFFIC_IND_REQ has been executed
+	ME_TRAFFIC_IND_CFM,
+	/// Request of RC statistics to a station
+	ME_RC_STATS_REQ,
+	/// RC statistics confirmation
+	ME_RC_STATS_CFM,
+	/// RC fixed rate request
+	ME_RC_SET_RATE_REQ,
+	/// Configure monitor interface
+	ME_CONFIG_MONITOR_REQ,
+	/// Configure monitor interface response
+	ME_CONFIG_MONITOR_CFM,
+	/// Setting power Save mode request from host
+	ME_SET_PS_MODE_REQ,
+	/// Set power Save mode confirmation
+	ME_SET_PS_MODE_CFM,
+	/// Setting Low Power level request from host
+	ME_SET_LP_LEVEL_REQ,
+	/// Set Low Power level confirmation
+	ME_SET_LP_LEVEL_CFM,
+	/// MAX number of messages
+	ME_MAX,
+};
+
+/// Structure containing the parameters of the @ref ME_START_REQ message
+struct me_config_req {
+	/// HT Capabilities
+	struct mac_htcapability ht_cap;
+	/// VHT Capabilities
+	struct mac_vhtcapability vht_cap;
+	/// HE capabilities
+	struct mac_hecapability he_cap;
+	/// Lifetime of packets sent under a BlockAck agreement (expressed in TUs)
+	u16_l tx_lft;
+	/// Maximum supported BW
+	u8_l phy_bw_max;
+	/// Boolean indicating if HT is supported or not
+	bool ht_supp;
+	/// Boolean indicating if VHT is supported or not
+	bool vht_supp;
+	/// Boolean indicating if HE is supported or not
+	bool he_supp;
+	/// Boolean indicating if HE OFDMA UL is enabled or not
+	bool he_ul_on;
+	/// Boolean indicating if PS mode shall be enabled or not
+	bool ps_on;
+	/// Boolean indicating if Antenna Diversity shall be enabled or not
+	bool ant_div_on;
+	/// Boolean indicating if Dynamic PS mode shall be used or not
+	bool dpsm;
+};
+
+/// Structure containing the parameters of the @ref ME_CHAN_CONFIG_REQ message
+struct me_chan_config_req {
+	/// List of 2.4GHz supported channels
+	struct mac_chan_def chan2G4[MAC_DOMAINCHANNEL_24G_MAX];
+	/// List of 5GHz supported channels
+	struct mac_chan_def chan5G[MAC_DOMAINCHANNEL_5G_MAX];
+	/// Number of 2.4GHz channels in the list
+	u8_l chan2G4_cnt;
+	/// Number of 5GHz channels in the list
+	u8_l chan5G_cnt;
+};
+
+/// Structure containing the parameters of the @ref ME_SET_CONTROL_PORT_REQ
+/// message
+struct me_set_control_port_req {
+	/// Index of the station for which the control port is opened
+	u8_l sta_idx;
+	/// Control port state
+	bool control_port_open;
+};
+
+/// Structure containing the parameters of the @ref ME_TKIP_MIC_FAILURE_IND
+/// message
+struct me_tkip_mic_failure_ind {
+	/// Address of the sending STA
+	struct mac_addr addr;
+	/// TSC value
+	u64_l tsc;
+	/// Boolean indicating if the packet was a group or unicast one (true if
+	/// group)
+	bool ga;
+	/// Key Id
+	u8_l keyid;
+	/// VIF index
+	u8_l vif_idx;
+};
+
+/// Structure containing the parameters of the @ref ME_STA_ADD_REQ message
+struct me_sta_add_req {
+	/// MAC address of the station to be added
+	struct mac_addr mac_addr;
+	/// Supported legacy rates
+	struct mac_rateset rate_set;
+	/// HT Capabilities
+	struct mac_htcapability ht_cap;
+	/// VHT Capabilities
+	struct mac_vhtcapability vht_cap;
+	/// HE capabilities
+	struct mac_hecapability he_cap;
+	/// Flags giving additional information about the station (@ref
+	/// mac_sta_flags)
+	u32_l flags;
+	/// Association ID of the station
+	u16_l aid;
+	/// Bit field indicating which queues have U-APSD enabled
+	u8_l uapsd_queues;
+	/// Maximum size, in frames, of a APSD service period
+	u8_l max_sp_len;
+	/// Operation mode information (valid if bit @ref STA_OPMOD_NOTIF is
+	/// set in the flags)
+	u8_l opmode;
+	/// Index of the VIF the station is attached to
+	u8_l vif_idx;
+	/// Whether the station is TDLS station
+	bool tdls_sta;
+	/// Indicate if the station is TDLS link initiator station
+	bool tdls_sta_initiator;
+	/// Indicate if the TDLS Channel Switch is allowed
+	bool tdls_chsw_allowed;
+};
+
+/// Structure containing the parameters of the @ref ME_STA_ADD_CFM message
+struct me_sta_add_cfm {
+	/// Station index
+	u8_l sta_idx;
+	/// Status of the station addition
+	u8_l status;
+	/// PM state of the station
+	u8_l pm_state;
+};
+
+/// Structure containing the parameters of the @ref ME_STA_DEL_REQ message.
+struct me_sta_del_req {
+	/// Index of the station to be deleted
+	u8_l sta_idx;
+	/// Whether the station is TDLS station
+	bool tdls_sta;
+};
+
+/// Structure containing the parameters of the @ref ME_TX_CREDITS_UPDATE_IND
+/// message.
+struct me_tx_credits_update_ind {
+	/// Index of the station for which the credits are updated
+	u8_l sta_idx;
+	/// TID for which the credits are updated
+	u8_l tid;
+	/// Offset to be applied on the credit count
+	s8_l credits;
+};
+
+/// Structure containing the parameters of the @ref ME_TRAFFIC_IND_REQ message.
+struct me_traffic_ind_req {
+	/// Index of the station for which UAPSD traffic is available on host
+	u8_l sta_idx;
+	/// Flag indicating the availability of UAPSD packets for the given STA
+	u8_l tx_avail;
+	/// Indicate if traffic is on uapsd-enabled queues
+	bool uapsd;
+};
+
+struct mm_apm_staloss_ind {
+	u8_l sta_idx;
+	u8_l vif_idx;
+	u8_l mac_addr[6];
+};
+
+#ifdef CONFIG_SDIO_BT
+struct mm_bt_recv_ind {
+	u32_l data_len;
+	u8_l bt_data[1024];
+};
+#endif
+
+enum vendor_hwconfig_tag {
+	ACS_TXOP_REQ = 0,
+	CHANNEL_ACCESS_REQ,
+	MAC_TIMESCALE_REQ,
+	CCA_THRESHOLD_REQ,
+	BWMODE_REQ,
+	CHIP_TEMP_GET_REQ,
+	AP_PS_LEVEL_SET_REQ,
+	CUSTOMIZED_FREQ_REQ,
+	WAKEUP_INFO_REQ,
+	KEEPALIVE_PKT_REQ,
+};
+
+enum {
+	BWMODE20M = 0,
+	BWMODE10M,
+	BWMODE5M,
+};
+
+struct mm_set_acs_txop_req {
+	u32_l hwconfig_id;
+	u16_l txop_bk;
+	u16_l txop_be;
+	u16_l txop_vi;
+	u16_l txop_vo;
+};
+
+struct mm_set_channel_access_req {
+	u32_l hwconfig_id;
+	u32_l edca[4];
+	u8_l vif_idx;
+	u8_l retry_cnt;
+	u8_l rts_en;
+	u8_l long_nav_en;
+	u8_l cfe_en;
+	u8_l rc_retry_cnt[3];
+	s8_l ccademod_th;
+};
+
+struct mm_set_mac_timescale_req {
+	u32_l hwconfig_id;
+	u8_l sifs_a_time;
+	u8_l sifs_b_time;
+	u8_l slot_time;
+	u8_l rx_startdelay_ofdm;
+	u8_l rx_startdelay_long;
+	u8_l rx_startdelay_short;
+};
+
+struct mm_set_cca_threshold_req {
+	u32_l hwconfig_id;
+	u8_l auto_cca_en;
+	s8_l cca20p_rise_th;
+	s8_l cca20s_rise_th;
+	s8_l cca20p_fall_th;
+	s8_l cca20s_fall_th;
+};
+
+struct mm_set_bwmode_req {
+	u32_l hwconfig_id;
+	u8_l bwmode;
+};
+
+struct mm_get_chip_temp_req {
+	u32_l hwconfig_id;
+};
+
+struct mm_get_chip_temp_cfm {
+	/// Temp degree val
+	s8_l degree;
+};
+
+struct mm_set_ap_ps_level_req {
+	u32_l hwconfig_id;
+	u8 ap_ps_level;
+};
+
+struct mm_set_vendor_hwconfig_cfm {
+	u32_l hwconfig_id;
+	union {
+		struct mm_get_chip_temp_cfm chip_temp_cfm;
+	};
+};
+
+struct mm_set_customized_freq_req {
+	u32_l hwconfig_id;
+	u16_l raw_freq[4];
+	u16_l map_freq[4];
+};
+
+struct mm_set_keepalive_req {
+	u32_l hwconfig_id;
+	u16_l code;
+	u16_l length;
+	u32_l intv;
+	u8_l payload[];
+};
+
+struct mm_set_txop_req {
+	u16_l txop_bk;
+	u16_l txop_be;
+	u16_l txop_vi;
+	u16_l txop_vo;
+	u8_l long_nav_en;
+	u8_l cfe_en;
+};
+
+struct mm_set_wakeup_info_req {
+	u32_l hwconfig_id;
+	u16_l code;
+	u16_l offset;
+	u16_l length;
+	u16_l length_win;
+	u8_l mask_and_pattern[];
+};
+
+struct mm_get_fw_version_cfm {
+	u8_l fw_version_len;
+	u8_l fw_version[63];
+};
+
+struct mm_get_wifi_disable_cfm {
+	u8_l wifi_disable;
+};
+
+enum vendor_swconfig_tag {
+	BCN_CFG_REQ = 0,
+	TEMP_COMP_SET_REQ,
+	TEMP_COMP_GET_REQ,
+	EXT_FLAGS_SET_REQ,
+	EXT_FLAGS_GET_REQ,
+	EXT_FLAGS_MASK_SET_REQ,
+};
+
+struct mm_set_bcn_cfg_req {
+	/// Ignore or not bcn tim bcmc bit
+	bool tim_bcmc_ignored_enable;
+};
+
+struct mm_set_bcn_cfg_cfm {
+	/// Request status
+	bool tim_bcmc_ignored_status;
+};
+
+struct mm_set_temp_comp_req {
+	/// Enable or not temp comp
+	u8_l enable;
+	u8_l reserved[3];
+	u32_l tmr_period_ms;
+};
+
+struct mm_set_temp_comp_cfm {
+	/// Request status
+	u8_l status;
+};
+
+struct mm_get_temp_comp_cfm {
+	/// Request status
+	u8_l status;
+	/// Temp degree val
+	s8_l degree;
+};
+
+struct mm_set_ext_flags_req {
+	u32_l user_flags;
+};
+
+struct mm_set_ext_flags_cfm {
+	u32_l user_flags;
+};
+
+struct mm_get_ext_flags_cfm {
+	u32_l user_flags;
+};
+
+struct mm_mask_set_ext_flags_req {
+	u32_l user_flags_mask;
+	u32_l user_flags_val;
+};
+
+struct mm_mask_set_ext_flags_cfm {
+	u32_l user_flags;
+};
+
+struct mm_set_vendor_swconfig_req {
+	u32_l swconfig_id;
+	union {
+		struct mm_set_bcn_cfg_req bcn_cfg_req;
+		struct mm_set_temp_comp_req temp_comp_set_req;
+		struct mm_set_ext_flags_req ext_flags_set_req;
+		struct mm_mask_set_ext_flags_req ext_flags_mask_set_req;
+	};
+};
+
+struct mm_set_vendor_swconfig_cfm {
+	u32_l swconfig_id;
+	union {
+		struct mm_set_bcn_cfg_cfm bcn_cfg_cfm;
+		struct mm_set_temp_comp_cfm temp_comp_set_cfm;
+		struct mm_get_temp_comp_cfm temp_comp_get_cfm;
+		struct mm_set_ext_flags_cfm ext_flags_set_cfm;
+		struct mm_get_ext_flags_cfm ext_flags_get_cfm;
+		struct mm_mask_set_ext_flags_cfm ext_flags_mask_set_cfm;
+	};
+};
+
+#ifdef CONFIG_SDIO_BT
+struct mm_bt_send_req {
+	u32_l data_len;
+	u8_l bt_data[1024];
+};
+
+struct mm_bt_send_cfm {
+	u8_l status;
+};
+#endif
+
+/// Structure containing the parameters of the @ref ME_RC_STATS_REQ message.
+struct me_rc_stats_req {
+	/// Index of the station for which the RC statistics are requested
+	u8_l sta_idx;
+};
+
+/// Structure containing the rate control statistics
+struct rc_rate_stats {
+	/// Number of attempts (per sampling interval)
+	u16_l attempts;
+	/// Number of success (per sampling interval)
+	u16_l success;
+	/// Estimated probability of success (EWMA)
+	u16_l probability;
+	/// Rate configuration of the sample
+	u16_l rate_config;
+	union {
+		struct {
+			/// Number of times the sample has been skipped (per sampling
+			/// interval)
+			u8_l sample_skipped;
+			/// Whether the old probability is available
+			bool old_prob_available;
+			/// Whether the rate can be used in the retry chain
+			bool rate_allowed;
+		};
+		struct {
+			/// RU size and UL length received in the latest HE trigger frame
+			u16_l ru_and_length;
+		};
+	};
+};
+
+/// Number of RC samples
+#define RC_MAX_N_SAMPLE 10
+/// Index of the HE statistics element in the table
+#define RC_HE_STATS_IDX RC_MAX_N_SAMPLE
+
+/// Structure containing the parameters of the @ref ME_RC_STATS_CFM message.
+struct me_rc_stats_cfm {
+	/// Index of the station for which the RC statistics are provided
+	u8_l sta_idx;
+	/// Number of samples used in the RC algorithm
+	u16_l no_samples;
+	/// Number of MPDUs transmitted (per sampling interval)
+	u16_l ampdu_len;
+	/// Number of AMPDUs transmitted (per sampling interval)
+	u16_l ampdu_packets;
+	/// Average number of MPDUs in each AMPDU frame (EWMA)
+	u32_l avg_ampdu_len;
+	// Current step 0 of the retry chain
+	u8_l sw_retry_step;
+	/// Trial transmission period
+	u8_l sample_wait;
+	/// Retry chain steps
+	u16_l retry_step_idx[4];
+	/// RC statistics - Max number of RC samples, plus one for the HE TB
+	/// statistics
+	struct rc_rate_stats rate_stats[RC_MAX_N_SAMPLE + 1];
+	/// Throughput - Max number of RC samples, plus one for the HE TB statistics
+	u32_l tp[RC_MAX_N_SAMPLE + 1];
+};
+
+/// Structure containing the parameters of the @ref ME_RC_SET_RATE_REQ message.
+struct me_rc_set_rate_req {
+	/// Index of the station for which the fixed rate is set
+	u8_l sta_idx;
+	/// Rate configuration to be set
+	u16_l fixed_rate_cfg;
+};
+
+/// Structure containing the parameters of the @ref ME_CONFIG_MONITOR_REQ
+/// message.
+struct me_config_monitor_req {
+	/// Channel to configure
+	struct mac_chan_op chan;
+	/// Is channel data valid
+	bool chan_set;
+	/// Enable report of unsupported HT frames
+	bool uf;
+	/// Enable auto-reply as the mac_addr matches
+	bool auto_reply;
+};
+
+/// Structure containing the parameters of the @ref ME_CONFIG_MONITOR_CFM
+/// message.
+struct me_config_monitor_cfm {
+	/// Channel context index
+	u8_l chan_index;
+	/// Channel parameters
+	struct mac_chan_op chan;
+};
+
+/// Structure containing the parameters of the @ref ME_SET_PS_MODE_REQ message.
+struct me_set_ps_mode_req {
+	/// Power Save is activated or deactivated
+	u8_l ps_state;
+};
+
+/// Structure containing the parameters of the @ref ME_SET_LP_LEVEL_REQ message.
+struct me_set_lp_level_req {
+	/// Low Power level
+	u8_l lp_level;
+	u8_l disable_filter;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+/////////// For SM messages
+///////////////////////////////////////////////////////////////////////////////
+/// Message API of the SM task
+enum sm_msg_tag {
+	/// Request to connect to an AP
+	SM_CONNECT_REQ = LMAC_FIRST_MSG(TASK_SM),
+	/// Confirmation of connection
+	SM_CONNECT_CFM,
+	/// Indicates that the SM associated to the AP
+	SM_CONNECT_IND,
+	/// Request to disconnect
+	SM_DISCONNECT_REQ,
+	/// Confirmation of disconnection
+	SM_DISCONNECT_CFM,
+	/// Indicates that the SM disassociated the AP
+	SM_DISCONNECT_IND,
+	/// Request to start external authentication
+	SM_EXTERNAL_AUTH_REQUIRED_IND,
+	/// Response to external authentication request
+	SM_EXTERNAL_AUTH_REQUIRED_RSP,
+	/// Request to update assoc elements after FT over the air authentication
+	SM_FT_AUTH_IND,
+	/// Response to FT authentication with updated assoc elements
+	SM_FT_AUTH_RSP,
+
+	SM_RSP_TIMEOUT_IND,
+
+	SM_COEX_TS_TIMEOUT_IND,
+
+	SM_EXTERNAL_AUTH_REQUIRED_RSP_CFM,
+	/// MAX number of messages
+	SM_MAX,
+};
+
+/// Structure containing the parameters of @ref SM_CONNECT_REQ message.
+struct sm_connect_req {
+	/// SSID to connect to
+	struct mac_ssid ssid;
+	/// BSSID to connect to (if not specified, set this field to WILDCARD BSSID)
+	struct mac_addr bssid;
+	/// Channel on which we have to connect (if not specified, set -1 in the
+	/// chan.freq field)
+	struct mac_chan_def chan;
+	/// Connection flags (see @ref mac_connection_flags)
+	u32_l flags;
+	/// Control port Ethertype (in network endianness)
+	u16_l ctrl_port_ethertype;
+	/// Length of the association request IEs
+	u16_l ie_len;
+	/// Listen interval to be used for this connection
+	u16_l listen_interval;
+	/// Flag indicating if the we have to wait for the BC/MC traffic after
+	/// beacon or not
+	bool dont_wait_bcmc;
+	/// Authentication type
+	u8_l auth_type;
+	/// UAPSD queues (bit0: VO, bit1: VI, bit2: BE, bit3: BK)
+	u8_l uapsd_queues;
+	/// VIF index
+	u8_l vif_idx;
+	/// Buffer containing the additional information elements to be put in the
+	/// association request
+	u32_l ie_buf[64];
+};
+
+/// Structure containing the parameters of the @ref SM_CONNECT_CFM message.
+struct sm_connect_cfm {
+	/// Status. If 0, it means that the connection procedure will be performed
+	/// and that a subsequent @ref SM_CONNECT_IND message will be forwarded once
+	/// the procedure is completed
+	u8_l status;
+};
+
+#define SM_ASSOC_IE_LEN 800
+/// Structure containing the parameters of the @ref SM_CONNECT_IND message.
+struct sm_connect_ind {
+	/// Status code of the connection procedure
+	u16_l status_code;
+	/// BSSID
+	struct mac_addr bssid;
+	/// Flag indicating if the indication refers to an internal roaming or from
+	/// a host request
+	bool roamed;
+	/// Index of the VIF for which the association process is complete
+	u8_l vif_idx;
+	/// Index of the STA entry allocated for the AP
+	u8_l ap_idx;
+	/// Index of the LMAC channel context the connection is attached to
+	u8_l ch_idx;
+	/// Flag indicating if the AP is supporting QoS
+	bool qos;
+	/// ACM bits set in the AP WMM parameter element
+	u8_l acm;
+	/// Length of the AssocReq IEs
+	u16_l assoc_req_ie_len;
+	/// Length of the AssocRsp IEs
+	u16_l assoc_rsp_ie_len;
+	/// IE buffer
+	u32_l assoc_ie_buf[SM_ASSOC_IE_LEN / 4];
+
+	u16_l aid;
+	u8_l band;
+	u16_l center_freq;
+	u8_l width;
+	u32_l center_freq1;
+	u32_l center_freq2;
+
+	/// EDCA parameters
+	u32_l ac_param[AC_MAX];
+};
+
+/// Structure containing the parameters of the @ref SM_DISCONNECT_REQ message.
+struct sm_disconnect_req {
+	/// Reason of the deauthentication.
+	u16_l reason_code;
+	/// Index of the VIF.
+	u8_l vif_idx;
+};
+
+/// Structure containing the parameters of SM_ASSOCIATION_IND the message
+struct sm_association_ind {
+	// MAC ADDR of the STA
+	struct mac_addr me_mac_addr;
+};
+
+/// Structure containing the parameters of the @ref SM_DISCONNECT_IND message.
+struct sm_disconnect_ind {
+	/// Reason of the disconnection.
+	u16_l reason_code;
+	/// Index of the VIF.
+	u8_l vif_idx;
+	/// FT over DS is ongoing
+	bool ft_over_ds;
+	u8_l reassoc;
+};
+
+/// Structure containing the parameters of the @ref
+/// SM_EXTERNAL_AUTH_REQUIRED_IND
+struct sm_external_auth_required_ind {
+	/// Index of the VIF.
+	u8_l vif_idx;
+	/// SSID to authenticate to
+	struct mac_ssid ssid;
+	/// BSSID to authenticate to
+	struct mac_addr bssid;
+	/// AKM suite of the respective authentication
+	u32_l akm;
+};
+
+/// Structure containing the parameters of the @ref
+/// SM_EXTERNAL_AUTH_REQUIRED_RSP
+struct sm_external_auth_required_rsp {
+	/// Index of the VIF.
+	u8_l vif_idx;
+	/// Authentication status
+	u16_l status;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+/////////// For APM messages
+///////////////////////////////////////////////////////////////////////////////
+/// Message API of the APM task
+enum apm_msg_tag {
+	/// Request to start the AP.
+	APM_START_REQ = LMAC_FIRST_MSG(TASK_APM),
+	/// Confirmation of the AP start.
+	APM_START_CFM,
+	/// Request to stop the AP.
+	APM_STOP_REQ,
+	/// Confirmation of the AP stop.
+	APM_STOP_CFM,
+	/// Request to start CAC
+	APM_START_CAC_REQ,
+	/// Confirmation of the CAC start
+	APM_START_CAC_CFM,
+	/// Request to stop CAC
+	APM_STOP_CAC_REQ,
+	/// Confirmation of the CAC stop
+	APM_STOP_CAC_CFM,
+
+	APM_SET_BEACON_IE_REQ,
+	APM_SET_BEACON_IE_CFM,
+
+	/// MAX number of messages
+	APM_MAX,
+};
+
+/// Structure containing the parameters of the @ref APM_START_REQ message.
+struct apm_start_req {
+	/// Basic rate set
+	struct mac_rateset basic_rates;
+	/// Control channel on which we have to enable the AP
+	struct mac_chan_def chan;
+	/// Center frequency of the first segment
+	u32_l center_freq1;
+	/// Center frequency of the second segment (only in 80+80 configuration)
+	u32_l center_freq2;
+	/// Width of channel
+	u8_l ch_width;
+	/// Address, in host memory, to the beacon template
+	u32_l bcn_addr;
+	/// Length of the beacon template
+	u16_l bcn_len;
+	/// Offset of the TIM IE in the beacon
+	u16_l tim_oft;
+	/// Beacon interval
+	u16_l bcn_int;
+	/// Flags (@ref mac_connection_flags)
+	u32_l flags;
+	/// Control port Ethertype
+	u16_l ctrl_port_ethertype;
+	/// Length of the TIM IE
+	u8_l tim_len;
+	/// Index of the VIF for which the AP is started
+	u8_l vif_idx;
+};
+
+struct apm_set_bcn_ie_req {
+	u8_l vif_idx;
+	u16_l bcn_ie_len;
+	u8_l bcn_ie[512];
+};
+
+/// Structure containing the parameters of the @ref APM_START_CFM message.
+struct apm_start_cfm {
+	/// Status of the AP starting procedure
+	u8_l status;
+	/// Index of the VIF for which the AP is started
+	u8_l vif_idx;
+	/// Index of the channel context attached to the VIF
+	u8_l ch_idx;
+	/// Index of the STA used for BC/MC traffic
+	u8_l bcmc_idx;
+};
+
+/// Structure containing the parameters of the @ref APM_STOP_REQ message.
+struct apm_stop_req {
+	/// Index of the VIF for which the AP has to be stopped
+	u8_l vif_idx;
+};
+
+/// Structure containing the parameters of the @ref APM_START_CAC_REQ message.
+struct apm_start_cac_req {
+	/// Control channel on which we have to start the CAC
+	struct mac_chan_def chan;
+	/// Center frequency of the first segment
+	u32_l center_freq1;
+	/// Center frequency of the second segment (only in 80+80 configuration)
+	u32_l center_freq2;
+	/// Width of channel
+	u8_l ch_width;
+	/// Index of the VIF for which the CAC is started
+	u8_l vif_idx;
+};
+
+/// Structure containing the parameters of the @ref APM_START_CAC_CFM message.
+struct apm_start_cac_cfm {
+	/// Status of the CAC starting procedure
+	u8_l status;
+	/// Index of the channel context attached to the VIF for CAC
+	u8_l ch_idx;
+};
+
+/// Structure containing the parameters of the @ref APM_STOP_CAC_REQ message.
+struct apm_stop_cac_req {
+	/// Index of the VIF for which the CAC has to be stopped
+	u8_l vif_idx;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+/////////// For MESH messages
+///////////////////////////////////////////////////////////////////////////////
+
+/// Maximum length of the Mesh ID
+#define MESH_MESHID_MAX_LEN (32)
+
+/// Message API of the MESH task
+enum mesh_msg_tag {
+	/// Request to start the MP
+	MESH_START_REQ = LMAC_FIRST_MSG(TASK_MESH),
+	/// Confirmation of the MP start.
+	MESH_START_CFM,
+
+	/// Request to stop the MP.
+	MESH_STOP_REQ,
+	/// Confirmation of the MP stop.
+	MESH_STOP_CFM,
+
+	// Request to update the MP
+	MESH_UPDATE_REQ,
+	/// Confirmation of the MP update
+	MESH_UPDATE_CFM,
+
+	/// Request information about a given link
+	MESH_PEER_INFO_REQ,
+	/// Response to the MESH_PEER_INFO_REQ message
+	MESH_PEER_INFO_CFM,
+
+	/// Request automatic establishment of a path with a given mesh STA
+	MESH_PATH_CREATE_REQ,
+	/// Confirmation to the MESH_PATH_CREATE_REQ message
+	MESH_PATH_CREATE_CFM,
+
+	/// Request a path update (delete path, modify next hop mesh STA)
+	MESH_PATH_UPDATE_REQ,
+	/// Confirmation to the MESH_PATH_UPDATE_REQ message
+	MESH_PATH_UPDATE_CFM,
+
+	/// Indication from Host that the indicated Mesh Interface is a proxy for an
+	/// external STA
+	MESH_PROXY_ADD_REQ,
+
+	/// Indicate that a connection has been established or lost
+	MESH_PEER_UPDATE_IND,
+	/// Notification that a connection has been established or lost (when MPM
+	/// handled by userspace)
+	MESH_PEER_UPDATE_NTF = MESH_PEER_UPDATE_IND,
+
+	/// Indicate that a path is now active or inactive
+	MESH_PATH_UPDATE_IND,
+	/// Indicate that proxy information have been updated
+	MESH_PROXY_UPDATE_IND,
+
+	/// MAX number of messages
+	MESH_MAX,
+};
+
+/// Structure containing the parameters of the @ref MESH_START_REQ message.
+struct mesh_start_req {
+	/// Basic rate set
+	struct mac_rateset basic_rates;
+	/// Control channel on which we have to enable the AP
+	struct mac_chan_def chan;
+	/// Center frequency of the first segment
+	u32_l center_freq1;
+	/// Center frequency of the second segment (only in 80+80 configuration)
+	u32_l center_freq2;
+	/// Width of channel
+	u8_l ch_width;
+	/// DTIM Period
+	u8_l dtim_period;
+	/// Beacon Interval
+	u16_l bcn_int;
+	/// Index of the VIF for which the MP is started
+	u8_l vif_index;
+	/// Length of the Mesh ID
+	u8_l mesh_id_len;
+	/// Mesh ID
+	u8_l mesh_id[MESH_MESHID_MAX_LEN];
+	/// Address of the IEs to download
+	u32_l ie_addr;
+	/// Length of the provided IEs
+	u8_l ie_len;
+	/// Indicate if Mesh Peering Management (MPM) protocol is handled in
+	/// userspace
+	bool user_mpm;
+	/// Indicate if Mesh Point is using authentication
+	bool is_auth;
+	/// Indicate which authentication method is used
+	u8_l auth_id;
+};
+
+/// Structure containing the parameters of the @ref MESH_START_CFM message.
+struct mesh_start_cfm {
+	/// Status of the MP starting procedure
+	u8_l status;
+	/// Index of the VIF for which the MP is started
+	u8_l vif_idx;
+	/// Index of the channel context attached to the VIF
+	u8_l ch_idx;
+	/// Index of the STA used for BC/MC traffic
+	u8_l bcmc_idx;
+};
+
+/// Structure containing the parameters of the @ref MESH_STOP_REQ message.
+struct mesh_stop_req {
+	/// Index of the VIF for which the MP has to be stopped
+	u8_l vif_idx;
+};
+
+/// Structure containing the parameters of the @ref MESH_STOP_CFM message.
+struct mesh_stop_cfm {
+	/// Index of the VIF for which the MP has to be stopped
+	u8_l vif_idx;
+	/// Status
+	u8_l status;
+};
+
+/// Bit fields for mesh_update_req message's flags value
+enum mesh_update_flags_bit {
+	/// Root Mode
+	MESH_UPDATE_FLAGS_ROOT_MODE_BIT = 0,
+	/// Gate Mode
+	MESH_UPDATE_FLAGS_GATE_MODE_BIT,
+	/// Mesh Forwarding
+	MESH_UPDATE_FLAGS_MESH_FWD_BIT,
+	/// Local Power Save Mode
+	MESH_UPDATE_FLAGS_LOCAL_PSM_BIT,
+};
+
+/// Structure containing the parameters of the @ref MESH_UPDATE_REQ message.
+struct mesh_update_req {
+	/// Flags, indicate fields which have been updated
+	u8_l flags;
+	/// VIF Index
+	u8_l vif_idx;
+	/// Root Mode
+	u8_l root_mode;
+	/// Gate Announcement
+	bool gate_announ;
+	/// Mesh Forwarding
+	bool mesh_forward;
+	/// Local PS Mode
+	u8_l local_ps_mode;
+};
+
+/// Structure containing the parameters of the @ref MESH_UPDATE_CFM message.
+struct mesh_update_cfm {
+	/// Status
+	u8_l status;
+};
+
+/// Structure containing the parameters of the @ref MESH_PEER_INFO_REQ message.
+struct mesh_peer_info_req {
+	/// Index of the station allocated for the peer
+	u8_l sta_idx;
+};
+
+/// Structure containing the parameters of the @ref MESH_PEER_INFO_CFM message.
+struct mesh_peer_info_cfm {
+	/// Response status
+	u8_l status;
+	/// Index of the station allocated for the peer
+	u8_l sta_idx;
+	/// Local Link ID
+	u16_l local_link_id;
+	/// Peer Link ID
+	u16_l peer_link_id;
+	/// Local PS Mode
+	u8_l local_ps_mode;
+	/// Peer PS Mode
+	u8_l peer_ps_mode;
+	/// Non-peer PS Mode
+	u8_l non_peer_ps_mode;
+	/// Link State
+	u8_l link_state;
+};
+
+/// Structure containing the parameters of the @ref MESH_PATH_CREATE_REQ
+/// message.
+struct mesh_path_create_req {
+	/// Index of the interface on which path has to be created
+	u8_l vif_idx;
+	/// Indicate if originator MAC Address is provided
+	bool has_orig_addr;
+	/// Path Target MAC Address
+	struct mac_addr tgt_mac_addr;
+	/// Originator MAC Address
+	struct mac_addr orig_mac_addr;
+};
+
+/// Structure containing the parameters of the @ref MESH_PATH_CREATE_CFM
+/// message.
+struct mesh_path_create_cfm {
+	/// Confirmation status
+	u8_l status;
+	/// VIF Index
+	u8_l vif_idx;
+};
+
+/// Structure containing the parameters of the @ref MESH_PATH_UPDATE_REQ
+/// message.
+struct mesh_path_update_req {
+	/// Indicate if path must be deleted
+	bool delete;
+	/// Index of the interface on which path has to be created
+	u8_l vif_idx;
+	/// Path Target MAC Address
+	struct mac_addr tgt_mac_addr;
+	/// Next Hop MAC Address
+	struct mac_addr nhop_mac_addr;
+};
+
+/// Structure containing the parameters of the @ref MESH_PATH_UPDATE_CFM
+/// message.
+struct mesh_path_update_cfm {
+	/// Confirmation status
+	u8_l status;
+	/// VIF Index
+	u8_l vif_idx;
+};
+
+/// Structure containing the parameters of the @ref MESH_PROXY_ADD_REQ message.
+struct mesh_proxy_add_req {
+	/// VIF Index
+	u8_l vif_idx;
+	/// MAC Address of the External STA
+	struct mac_addr ext_sta_addr;
+};
+
+/// Structure containing the parameters of the @ref MESH_PROXY_UPDATE_IND
+struct mesh_proxy_update_ind {
+	/// Indicate if proxy information has been added or deleted
+	bool delete;
+	/// Indicate if we are a proxy for the external STA
+	bool local;
+	/// VIF Index
+	u8_l vif_idx;
+	/// MAC Address of the External STA
+	struct mac_addr ext_sta_addr;
+	/// MAC Address of the proxy (only valid if local is false)
+	struct mac_addr proxy_mac_addr;
+};
+
+/// Structure containing the parameters of the @ref MESH_PEER_UPDATE_IND
+/// message.
+struct mesh_peer_update_ind {
+	/// Indicate if connection has been established or lost
+	bool estab;
+	/// VIF Index
+	u8_l vif_idx;
+	/// STA Index
+	u8_l sta_idx;
+	/// Peer MAC Address
+	struct mac_addr peer_addr;
+};
+
+/// Structure containing the parameters of the @ref MESH_PEER_UPDATE_NTF
+/// message.
+struct mesh_peer_update_ntf {
+	/// VIF Index
+	u8_l vif_idx;
+	/// STA Index
+	u8_l sta_idx;
+	/// Mesh Link State
+	u8_l state;
+};
+
+/// Structure containing the parameters of the @ref MESH_PATH_UPDATE_IND
+/// message.
+struct mesh_path_update_ind {
+	/// Indicate if path is deleted or not
+	bool delete;
+	/// Indicate if path is towards an external STA (not part of MBSS)
+	bool ext_sta;
+	/// VIF Index
+	u8_l vif_idx;
+	/// Path Index
+	u8_l path_idx;
+	/// Target MAC Address
+	struct mac_addr tgt_mac_addr;
+	/// External STA MAC Address (only if ext_sta is true)
+	struct mac_addr ext_sta_mac_addr;
+	/// Next Hop STA Index
+	u8_l nhop_sta_idx;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+/////////// For Debug messages
+///////////////////////////////////////////////////////////////////////////////
+
+/// Messages related to Debug Task
+enum dbg_msg_tag {
+	/// Memory read request
+	DBG_MEM_READ_REQ = LMAC_FIRST_MSG(TASK_DBG),
+	/// Memory read confirm
+	DBG_MEM_READ_CFM,
+	/// Memory write request
+	DBG_MEM_WRITE_REQ,
+	/// Memory write confirm
+	DBG_MEM_WRITE_CFM,
+	/// Module filter request
+	DBG_SET_MOD_FILTER_REQ,
+	/// Module filter confirm
+	DBG_SET_MOD_FILTER_CFM,
+	/// Severity filter request
+	DBG_SET_SEV_FILTER_REQ,
+	/// Severity filter confirm
+	DBG_SET_SEV_FILTER_CFM,
+	/// LMAC/MAC HW fatal error indication
+	DBG_ERROR_IND,
+	/// Request to get system statistics
+	DBG_GET_SYS_STAT_REQ,
+	/// COnfirmation of system statistics
+	DBG_GET_SYS_STAT_CFM,
+	/// Memory block write request
+	DBG_MEM_BLOCK_WRITE_REQ,
+	/// Memory block write confirm
+	DBG_MEM_BLOCK_WRITE_CFM,
+	/// Start app request
+	DBG_START_APP_REQ,
+	/// Start app confirm
+	DBG_START_APP_CFM,
+	/// Start npc request
+	DBG_START_NPC_REQ,
+	/// Start npc confirm
+	DBG_START_NPC_CFM,
+	/// Memory mask write request
+	DBG_MEM_MASK_WRITE_REQ,
+	/// Memory mask write confirm
+	DBG_MEM_MASK_WRITE_CFM,
+
+	DBG_RFTEST_CMD_REQ,
+	DBG_RFTEST_CMD_CFM,
+	DBG_BINDING_REQ,
+	DBG_BINDING_CFM,
+	DBG_BINDING_IND,
+
+	DBG_CUSTOM_MSG_REQ,
+	DBG_CUSTOM_MSG_CFM,
+	DBG_CUSTOM_MSG_IND,
+
+	DBG_GPIO_WRITE_REQ,
+	DBG_GPIO_WRITE_CFM,
+	DBG_GPIO_READ_REQ,
+	DBG_GPIO_READ_CFM,
+	DBG_GPIO_INIT_REQ,
+	DBG_GPIO_INIT_CFM,
+
+	/// EF usrdata read request
+	DBG_EF_USRDATA_READ_REQ,
+	/// EF usrdata read confirm
+	DBG_EF_USRDATA_READ_CFM,
+	/// Memory block read request
+	DBG_MEM_BLOCK_READ_REQ,
+	/// Memory block read confirm
+	DBG_MEM_BLOCK_READ_CFM,
+
+	DBG_PWM_INIT_REQ,
+	DBG_PWM_INIT_CFM,
+	DBG_PWM_DEINIT_REQ,
+	DBG_PWM_DEINIT_CFM,
+
+	/// Max number of Debug messages
+	DBG_MAX,
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_READ_REQ message.
+struct dbg_mem_read_req {
+	u32_l memaddr;
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_READ_CFM message.
+struct dbg_mem_read_cfm {
+	u32_l memaddr;
+	u32_l memdata;
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_WRITE_REQ message.
+struct dbg_mem_write_req {
+	u32_l memaddr;
+	u32_l memdata;
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_WRITE_CFM message.
+struct dbg_mem_write_cfm {
+	u32_l memaddr;
+	u32_l memdata;
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_MASK_WRITE_REQ
+/// message.
+struct dbg_mem_mask_write_req {
+	u32_l memaddr;
+	u32_l memmask;
+	u32_l memdata;
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_MASK_WRITE_CFM
+/// message.
+struct dbg_mem_mask_write_cfm {
+	u32_l memaddr;
+	u32_l memdata;
+};
+
+struct dbg_rftest_cmd_req {
+	u32_l cmd;
+	u32_l argc;
+	u8_l argv[30];
+};
+
+struct dbg_rftest_cmd_cfm {
+	u32_l rftest_result[18];
+};
+
+struct dbg_gpio_write_req {
+	u8 gpio_idx;
+	u8 gpio_val;
+};
+
+struct dbg_gpio_read_req {
+	u8 gpio_idx;
+};
+
+struct dbg_gpio_read_cfm {
+	u8 gpio_idx;
+	u8 gpio_val;
+};
+
+struct dbg_gpio_init_req {
+	u8 gpio_idx;
+	u8 gpio_dir; // 1 output, 0 input;
+	u8 gpio_val; // for output, 1 high, 0 low;
+};
+
+/// Structure containing the parameters of the @ref DBG_SET_MOD_FILTER_REQ
+/// message.
+struct dbg_set_mod_filter_req {
+	/// Bit field indicating for each module if the traces are enabled or not
+	u32_l mod_filter;
+};
+
+/// Structure containing the parameters of the @ref DBG_SEV_MOD_FILTER_REQ
+/// message.
+struct dbg_set_sev_filter_req {
+	/// Bit field indicating the severity threshold for the traces
+	u32_l sev_filter;
+};
+
+/// Structure containing the parameters of the @ref DBG_GET_SYS_STAT_CFM
+/// message.
+struct dbg_get_sys_stat_cfm {
+	/// Time spent in CPU sleep since last reset of the system statistics
+	u32_l cpu_sleep_time;
+	/// Time spent in DOZE since last reset of the system statistics
+	u32_l doze_time;
+	/// Total time spent since last reset of the system statistics
+	u32_l stats_time;
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_BLOCK_WRITE_REQ
+/// message.
+struct dbg_mem_block_write_req {
+	u32_l memaddr;
+	u32_l memsize;
+	u32_l memdata[1024 / sizeof(u32_l)];
+};
+
+/// Structure containing the parameters of the @ref DBG_MEM_BLOCK_WRITE_CFM
+/// message.
+struct dbg_mem_block_write_cfm {
+	u32_l wstatus;
+};
+
+/// Structure containing the parameters of the @ref DBG_START_APP_REQ message.
+struct dbg_start_app_req {
+	u32_l bootaddr;
+	u32_l boottype;
+};
+
+/// Structure containing the parameters of the @ref DBG_START_APP_CFM message.
+struct dbg_start_app_cfm {
+	u32_l bootstatus;
+};
+
+enum {
+	HOST_START_APP_AUTO = 1,
+	HOST_START_APP_CUSTOM,
+	HOST_START_APP_FNCALL = 4,
+	HOST_START_APP_DUMMY = 5,
+};
+
+///////////////////////////////////////////////////////////////////////////////
+/////////// For TDLS messages
+///////////////////////////////////////////////////////////////////////////////
+
+/// List of messages related to the task.
+enum tdls_msg_tag {
+	/// TDLS channel Switch Request.
+	TDLS_CHAN_SWITCH_REQ = LMAC_FIRST_MSG(TASK_TDLS),
+	/// TDLS channel switch confirmation.
+	TDLS_CHAN_SWITCH_CFM,
+	/// TDLS channel switch indication.
+	TDLS_CHAN_SWITCH_IND,
+	/// TDLS channel switch to base channel indication.
+	TDLS_CHAN_SWITCH_BASE_IND,
+	/// TDLS cancel channel switch request.
+	TDLS_CANCEL_CHAN_SWITCH_REQ,
+	/// TDLS cancel channel switch confirmation.
+	TDLS_CANCEL_CHAN_SWITCH_CFM,
+	/// TDLS peer power save indication.
+	TDLS_PEER_PS_IND,
+	/// TDLS peer traffic indication request.
+	TDLS_PEER_TRAFFIC_IND_REQ,
+	/// TDLS peer traffic indication confirmation.
+	TDLS_PEER_TRAFFIC_IND_CFM,
+
+#ifdef CONFIG_SDIO_BT
+	TDLS_SDIO_BT_SEND_REQ = LMAC_FIRST_MSG(TASK_TDLS) + 16,
+	TDLS_SDIO_BT_SEND_CFM,
+	TDLS_SDIO_BT_RECV_IND,
+#endif
+	/// MAX number of messages
+	TDLS_MAX
+};
+
+/// Structure containing the parameters of the @ref TDLS_CHAN_SWITCH_REQ message
+struct tdls_chan_switch_req {
+	/// Index of the VIF
+	u8_l vif_index;
+	/// STA Index
+	u8_l sta_idx;
+	/// MAC address of the TDLS station
+	struct mac_addr peer_mac_addr;
+	bool initiator;
+	/// Band (2.4GHz or 5GHz)
+	u8_l band;
+	/// Channel type: 20,40,80,160 or 80+80 MHz
+	u8_l type;
+	/// Frequency for Primary 20MHz channel (in MHz)
+	u16_l prim20_freq;
+	/// Frequency for Center of the contiguous channel or center of Primary
+	/// 80+80
+	u16_l center1_freq;
+	/// Frequency for Center of the non-contiguous secondary 80+80
+	u16_l center2_freq;
+	/// TX power (in dBm)
+	s8_l tx_power;
+	/// Operating class
+	u8_l op_class;
+};
+
+/// Structure containing the parameters of the @ref TDLS_CANCEL_CHAN_SWITCH_REQ
+/// message
+struct tdls_cancel_chan_switch_req {
+	/// Index of the VIF
+	u8_l vif_index;
+	/// STA Index
+	u8_l sta_idx;
+	/// MAC address of the TDLS station
+	struct mac_addr peer_mac_addr;
+};
+
+/// Structure containing the parameters of the @ref TDLS_CHAN_SWITCH_CFM message
+struct tdls_chan_switch_cfm {
+	/// Status of the operation
+	u8_l status;
+};
+
+/// Structure containing the parameters of the @ref TDLS_CANCEL_CHAN_SWITCH_CFM
+/// message
+struct tdls_cancel_chan_switch_cfm {
+	/// Status of the operation
+	u8_l status;
+};
+
+/// Structure containing the parameters of the @ref TDLS_CHAN_SWITCH_IND message
+struct tdls_chan_switch_ind {
+	/// VIF Index
+	u8_l vif_index;
+	/// Channel Context Index
+	u8_l chan_ctxt_index;
+	/// Status of the operation
+	u8_l status;
+};
+
+/// Structure containing the parameters of the @ref TDLS_CHAN_SWITCH_BASE_IND
+/// message
+struct tdls_chan_switch_base_ind {
+	/// VIF Index
+	u8_l vif_index;
+	/// Channel Context index
+	u8_l chan_ctxt_index;
+};
+
+/// Structure containing the parameters of the @ref TDLS_PEER_PS_IND message
+struct tdls_peer_ps_ind {
+	/// VIF Index
+	u8_l vif_index;
+	/// STA Index
+	u8_l sta_idx;
+	/// MAC ADDR of the TDLS STA
+	struct mac_addr peer_mac_addr;
+	/// Flag to indicate if the TDLS peer is going to sleep
+	bool ps_on;
+};
+
+/// Structure containing the parameters of the @ref TDLS_PEER_TRAFFIC_IND_REQ
+/// message
+struct tdls_peer_traffic_ind_req {
+	/// VIF Index
+	u8_l vif_index;
+	/// STA Index
+	u8_l sta_idx;
+	// MAC ADDR of the TDLS STA
+	struct mac_addr peer_mac_addr;
+	/// Dialog token
+	u8_l dialog_token;
+	/// TID of the latest MPDU transmitted over the TDLS direct link to the TDLS
+	/// STA
+	u8_l last_tid;
+	/// Sequence number of the latest MPDU transmitted over the TDLS direct link
+	/// to the TDLS STA
+	u16_l last_sn;
+};
+
+/// Structure containing the parameters of the @ref TDLS_PEER_TRAFFIC_IND_CFM
+/// message
+struct tdls_peer_traffic_ind_cfm {
+	/// Status of the operation
+	u8_l status;
+};
+
+#endif // LMAC_MSG_H_
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/lmac_types.h b/drivers/net/wireless/aic/aic8800_fdrv/lmac_types.h
new file mode 100644
index 0000000000000..59537b4575ddd
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/lmac_types.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file co_types.h
+ *
+ * @brief This file replaces the need to include stdint or stdbool typical
+ *headers, which may not be available in all toolchains, and adds new types
+ *
+ ****************************************************************************************
+ */
+
+#ifndef _LMAC_INT_H_
+#define _LMAC_INT_H_
+
+#include <linux/types.h>
+#include <linux/version.h>
+#include <linux/bits.h>
+
+#ifdef CONFIG_RWNX_TL4
+typedef u16 u8_l;
+typedef s16 s8_l;
+typedef u16 bool_l;
+#else
+typedef u8 u8_l;
+typedef s8 s8_l;
+#endif
+typedef u16 u16_l;
+typedef s16 s16_l;
+typedef u32 u32_l;
+typedef s32 s32_l;
+typedef u64 u64_l;
+
+/// @} CO_INT
+#endif // _LMAC_INT_H_
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/md5.c b/drivers/net/wireless/aic/aic8800_fdrv/md5.c
new file mode 100644
index 0000000000000..e2ef60d9475c4
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/md5.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief md5 check
+ *
+ ****************************************************************************************
+ */
+
+#include "md5.h"
+#include <linux/memory.h>
+
+unsigned char PADDING[] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+			   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+			   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+			   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+void md5_init(struct MD5_CTX *context)
+{
+	context->count[0] = 0;
+	context->count[1] = 0;
+	context->state[0] = 0x67452301;
+	context->state[1] = 0xEFCDAB89;
+	context->state[2] = 0x98BADCFE;
+	context->state[3] = 0x10325476;
+}
+
+void md5_update(struct MD5_CTX *context, unsigned char *input, unsigned int inputlen)
+{
+	unsigned int i = 0, index = 0, partlen = 0;
+
+	index = (context->count[0] >> 3) & 0x3F;
+	partlen = 64 - index;
+	context->count[0] += inputlen << 3;
+	if (context->count[0] < (inputlen << 3))
+		context->count[1]++;
+	context->count[1] += inputlen >> 29;
+
+	if (inputlen >= partlen) {
+		memcpy(&context->buffer[index], input, partlen);
+		md5_transform(context->state, context->buffer);
+		for (i = partlen; i + 64 <= inputlen; i += 64)
+			md5_transform(context->state, &input[i]);
+		index = 0;
+	} else {
+		i = 0;
+	}
+	memcpy(&context->buffer[index], &input[i], inputlen - i);
+}
+
+void md5_final(struct MD5_CTX *context, unsigned char digest[16])
+{
+	unsigned int index = 0, padlen = 0;
+	unsigned char bits[8];
+
+	index = (context->count[0] >> 3) & 0x3F;
+	padlen = (index < 56) ? (56 - index) : (120 - index);
+	md5_encode(bits, context->count, 8);
+	md5_update(context, PADDING, padlen);
+	md5_update(context, bits, 8);
+	md5_encode(digest, context->state, 16);
+}
+
+void md5_encode(unsigned char *output, unsigned int *input, unsigned int len)
+{
+	unsigned int i = 0, j = 0;
+
+	while (j < len) {
+		output[j] = input[i] & 0xFF;
+		output[j + 1] = (input[i] >> 8) & 0xFF;
+		output[j + 2] = (input[i] >> 16) & 0xFF;
+		output[j + 3] = (input[i] >> 24) & 0xFF;
+		i++;
+		j += 4;
+	}
+}
+
+void md5_decode(unsigned int *output, unsigned char *input, unsigned int len)
+{
+	unsigned int i = 0, j = 0;
+
+	while (j < len) {
+		output[i] = (input[j]) | (input[j + 1] << 8) | (input[j + 2] << 16) |
+					(input[j + 3] << 24);
+		i++;
+		j += 4;
+	}
+}
+
+void md5_transform(unsigned int state[4], unsigned char block[64])
+{
+	unsigned int a = state[0];
+	unsigned int b = state[1];
+	unsigned int c = state[2];
+	unsigned int d = state[3];
+	unsigned int x[64];
+
+	md5_decode(x, block, 64);
+	FF(a, b, c, d, x[0], 7, 0xd76aa478);   /* 1 */
+	FF(d, a, b, c, x[1], 12, 0xe8c7b756);  /* 2 */
+	FF(c, d, a, b, x[2], 17, 0x242070db);  /* 3 */
+	FF(b, c, d, a, x[3], 22, 0xc1bdceee);  /* 4 */
+	FF(a, b, c, d, x[4], 7, 0xf57c0faf);   /* 5 */
+	FF(d, a, b, c, x[5], 12, 0x4787c62a);  /* 6 */
+	FF(c, d, a, b, x[6], 17, 0xa8304613);  /* 7 */
+	FF(b, c, d, a, x[7], 22, 0xfd469501);  /* 8 */
+	FF(a, b, c, d, x[8], 7, 0x698098d8);   /* 9 */
+	FF(d, a, b, c, x[9], 12, 0x8b44f7af);  /* 10 */
+	FF(c, d, a, b, x[10], 17, 0xffff5bb1); /* 11 */
+	FF(b, c, d, a, x[11], 22, 0x895cd7be); /* 12 */
+	FF(a, b, c, d, x[12], 7, 0x6b901122);  /* 13 */
+	FF(d, a, b, c, x[13], 12, 0xfd987193); /* 14 */
+	FF(c, d, a, b, x[14], 17, 0xa679438e); /* 15 */
+	FF(b, c, d, a, x[15], 22, 0x49b40821); /* 16 */
+
+	/* Round 2 */
+	GG(a, b, c, d, x[1], 5, 0xf61e2562);   /* 17 */
+	GG(d, a, b, c, x[6], 9, 0xc040b340);   /* 18 */
+	GG(c, d, a, b, x[11], 14, 0x265e5a51); /* 19 */
+	GG(b, c, d, a, x[0], 20, 0xe9b6c7aa);  /* 20 */
+	GG(a, b, c, d, x[5], 5, 0xd62f105d);   /* 21 */
+	GG(d, a, b, c, x[10], 9, 0x2441453);   /* 22 */
+	GG(c, d, a, b, x[15], 14, 0xd8a1e681); /* 23 */
+	GG(b, c, d, a, x[4], 20, 0xe7d3fbc8);  /* 24 */
+	GG(a, b, c, d, x[9], 5, 0x21e1cde6);   /* 25 */
+	GG(d, a, b, c, x[14], 9, 0xc33707d6);  /* 26 */
+	GG(c, d, a, b, x[3], 14, 0xf4d50d87);  /* 27 */
+	GG(b, c, d, a, x[8], 20, 0x455a14ed);  /* 28 */
+	GG(a, b, c, d, x[13], 5, 0xa9e3e905);  /* 29 */
+	GG(d, a, b, c, x[2], 9, 0xfcefa3f8);   /* 30 */
+	GG(c, d, a, b, x[7], 14, 0x676f02d9);  /* 31 */
+	GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); /* 32 */
+
+	/* Round 3 */
+	HH(a, b, c, d, x[5], 4, 0xfffa3942);   /* 33 */
+	HH(d, a, b, c, x[8], 11, 0x8771f681);  /* 34 */
+	HH(c, d, a, b, x[11], 16, 0x6d9d6122); /* 35 */
+	HH(b, c, d, a, x[14], 23, 0xfde5380c); /* 36 */
+	HH(a, b, c, d, x[1], 4, 0xa4beea44);   /* 37 */
+	HH(d, a, b, c, x[4], 11, 0x4bdecfa9);  /* 38 */
+	HH(c, d, a, b, x[7], 16, 0xf6bb4b60);  /* 39 */
+	HH(b, c, d, a, x[10], 23, 0xbebfbc70); /* 40 */
+	HH(a, b, c, d, x[13], 4, 0x289b7ec6);  /* 41 */
+	HH(d, a, b, c, x[0], 11, 0xeaa127fa);  /* 42 */
+	HH(c, d, a, b, x[3], 16, 0xd4ef3085);  /* 43 */
+	HH(b, c, d, a, x[6], 23, 0x4881d05);   /* 44 */
+	HH(a, b, c, d, x[9], 4, 0xd9d4d039);   /* 45 */
+	HH(d, a, b, c, x[12], 11, 0xe6db99e5); /* 46 */
+	HH(c, d, a, b, x[15], 16, 0x1fa27cf8); /* 47 */
+	HH(b, c, d, a, x[2], 23, 0xc4ac5665);  /* 48 */
+
+	/* Round 4 */
+	II(a, b, c, d, x[0], 6, 0xf4292244);   /* 49 */
+	II(d, a, b, c, x[7], 10, 0x432aff97);  /* 50 */
+	II(c, d, a, b, x[14], 15, 0xab9423a7); /* 51 */
+	II(b, c, d, a, x[5], 21, 0xfc93a039);  /* 52 */
+	II(a, b, c, d, x[12], 6, 0x655b59c3);  /* 53 */
+	II(d, a, b, c, x[3], 10, 0x8f0ccc92);  /* 54 */
+	II(c, d, a, b, x[10], 15, 0xffeff47d); /* 55 */
+	II(b, c, d, a, x[1], 21, 0x85845dd1);  /* 56 */
+	II(a, b, c, d, x[8], 6, 0x6fa87e4f);   /* 57 */
+	II(d, a, b, c, x[15], 10, 0xfe2ce6e0); /* 58 */
+	II(c, d, a, b, x[6], 15, 0xa3014314);  /* 59 */
+	II(b, c, d, a, x[13], 21, 0x4e0811a1); /* 60 */
+	II(a, b, c, d, x[4], 6, 0xf7537e82);   /* 61 */
+	II(d, a, b, c, x[11], 10, 0xbd3af235); /* 62 */
+	II(c, d, a, b, x[2], 15, 0x2ad7d2bb);  /* 63 */
+	II(b, c, d, a, x[9], 21, 0xeb86d391);  /* 64 */
+	state[0] += a;
+	state[1] += b;
+	state[2] += c;
+	state[3] += d;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/md5.h b/drivers/net/wireless/aic/aic8800_fdrv/md5.h
new file mode 100644
index 0000000000000..95ad6e18256d3
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/md5.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef MD5_H
+#define MD5_H
+
+#include <linux/types.h>
+#include <asm/types.h>
+
+struct MD5_CTX {
+	unsigned int count[2];
+	unsigned int state[4];
+	unsigned char buffer[64];
+};
+
+static inline u32 F(u32 x, u32 y, u32 z)
+{
+	return (x & y) | (~x & z);
+}
+
+static inline u32 G(u32 x, u32 y, u32 z)
+{
+	return (x & z) | (y & ~z);
+}
+
+static inline u32 H(u32 x, u32 y, u32 z)
+{
+	return x ^ y ^ z;
+}
+
+static inline u32 I(u32 x, u32 y, u32 z)
+{
+	return y ^ (x | ~z);
+}
+
+static inline u32 ROTATE_LEFT(u32 x, u32 n)
+{
+	return (x << n) | (x >> (32 - n));
+}
+
+static inline u32 FF(u32 a, u32 b, u32 c, u32 d,
+		     u32 x, u32 s, u32 ac)
+{
+	a += F(b, c, d) + x + ac;
+	a = ROTATE_LEFT(a, s);
+	a += b;
+	return a;
+}
+
+static inline u32 GG(u32 a, u32 b, u32 c, u32 d,
+		     u32 x, u32 s, u32 ac)
+{
+	a += G(b, c, d) + x + ac;
+	a = ROTATE_LEFT(a, s);
+	a += b;
+	return a;
+}
+
+static inline u32 HH(u32 a, u32 b, u32 c, u32 d,
+		     u32 x, u32 s, u32 ac)
+{
+	a += H(b, c, d) + x + ac;
+	a = ROTATE_LEFT(a, s);
+	a += b;
+	return a;
+}
+
+static inline u32 II(u32 a, u32 b, u32 c, u32 d,
+		     u32 x, u32 s, u32 ac)
+{
+	a += I(b, c, d) + x + ac;
+	a = ROTATE_LEFT(a, s);
+	a += b;
+	return a;
+}
+
+void md5_init(struct MD5_CTX *context);
+void md5_update(struct MD5_CTX *context, unsigned char *input, unsigned int inputlen);
+void md5_final(struct MD5_CTX *context, unsigned char digest[16]);
+void md5_transform(unsigned int state[4], unsigned char block[64]);
+void md5_encode(unsigned char *output, unsigned int *input, unsigned int len);
+void md5_decode(unsigned int *output, unsigned char *input, unsigned int len);
+
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/reg_access.h b/drivers/net/wireless/aic/aic8800_fdrv/reg_access.h
new file mode 100644
index 0000000000000..fbdb87b44552d
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/reg_access.h
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ******************************************************************************
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief Definitions and macros for MAC HW and platform register accesses
+ *
+ ******************************************************************************
+ */
+
+#ifndef REG_ACCESS_H_
+#define REG_ACCESS_H_
+
+/*****************************************************************************
+ * Addresses within RWNX_ADDR_SYSTEM
+ *****************************************************************************/
+/* Shard RAM */
+#define SHARED_RAM_START_ADDR 0x00000000
+
+/* IPC registers */
+#define IPC_REG_BASE_ADDR 0x00800000
+
+/* System Controller Registers */
+#define SYSCTRL_SIGNATURE_ADDR    0x00900000
+// old diag register name
+#define SYSCTRL_DIAG_CONF_ADDR    0x00900068
+#define SYSCTRL_PHYDIAG_CONF_ADDR 0x00900074
+#define SYSCTRL_RIUDIAG_CONF_ADDR 0x00900078
+// new diag register name
+#define SYSCTRL_DIAG_CONF0        0x00900064
+#define SYSCTRL_DIAG_CONF1        0x00900068
+#define SYSCTRL_DIAG_CONF2        0x00900074
+#define SYSCTRL_DIAG_CONF3        0x00900078
+#define SYSCTRL_MISC_CNTL_ADDR    0x009000E0
+#define BOOTROM_ENABLE            BIT(4)
+#define FPGA_B_RESET              BIT(1)
+#define SOFT_RESET                BIT(0)
+
+/* MAC platform */
+#define NXMAC_VERSION_1_ADDR         0x00B00004
+#define NXMAC_MU_MIMO_TX_BIT         BIT(19)
+#define NXMAC_BFMER_BIT              BIT(18)
+#define NXMAC_BFMEE_BIT              BIT(17)
+#define NXMAC_MAC_80211MH_FORMAT_BIT BIT(16)
+#define NXMAC_COEX_BIT               BIT(14)
+#define NXMAC_WAPI_BIT               BIT(13)
+#define NXMAC_TPC_BIT                BIT(12)
+#define NXMAC_VHT_BIT                BIT(11)
+#define NXMAC_HT_BIT                 BIT(10)
+#define NXMAC_RCE_BIT                BIT(8)
+#define NXMAC_CCMP_BIT               BIT(7)
+#define NXMAC_TKIP_BIT               BIT(6)
+#define NXMAC_WEP_BIT                BIT(5)
+#define NXMAC_SECURITY_BIT           BIT(4)
+#define NXMAC_SME_BIT                BIT(3)
+#define NXMAC_HCCA_BIT               BIT(2)
+#define NXMAC_EDCA_BIT               BIT(1)
+#define NXMAC_QOS_BIT                BIT(0)
+
+#define NXMAC_RX_CNTRL_ADDR                   0x00B00060
+#define NXMAC_EN_DUPLICATE_DETECTION_BIT      BIT(31)
+#define NXMAC_ACCEPT_UNKNOWN_BIT              BIT(30)
+#define NXMAC_ACCEPT_OTHER_DATA_FRAMES_BIT    BIT(29)
+#define NXMAC_ACCEPT_QO_S_NULL_BIT            BIT(28)
+#define NXMAC_ACCEPT_QCFWO_DATA_BIT           BIT(27)
+#define NXMAC_ACCEPT_Q_DATA_BIT               BIT(26)
+#define NXMAC_ACCEPT_CFWO_DATA_BIT            BIT(25)
+#define NXMAC_ACCEPT_DATA_BIT                 BIT(24)
+#define NXMAC_ACCEPT_OTHER_CNTRL_FRAMES_BIT   BIT(23)
+#define NXMAC_ACCEPT_CF_END_BIT               BIT(22)
+#define NXMAC_ACCEPT_ACK_BIT                  BIT(21)
+#define NXMAC_ACCEPT_CTS_BIT                  BIT(20)
+#define NXMAC_ACCEPT_RTS_BIT                  BIT(19)
+#define NXMAC_ACCEPT_PS_POLL_BIT              BIT(18)
+#define NXMAC_ACCEPT_BA_BIT                   BIT(17)
+#define NXMAC_ACCEPT_BAR_BIT                  BIT(16)
+#define NXMAC_ACCEPT_OTHER_MGMT_FRAMES_BIT    BIT(15)
+#define NXMAC_ACCEPT_BFMEE_FRAMES_BIT         BIT(14)
+#define NXMAC_ACCEPT_ALL_BEACON_BIT           BIT(13)
+#define NXMAC_ACCEPT_NOT_EXPECTED_BA_BIT      BIT(12)
+#define NXMAC_ACCEPT_DECRYPT_ERROR_FRAMES_BIT BIT(11)
+#define NXMAC_ACCEPT_BEACON_BIT               BIT(10)
+#define NXMAC_ACCEPT_PROBE_RESP_BIT           BIT(9)
+#define NXMAC_ACCEPT_PROBE_REQ_BIT            BIT(8)
+#define NXMAC_ACCEPT_MY_UNICAST_BIT           BIT(7)
+#define NXMAC_ACCEPT_UNICAST_BIT              BIT(6)
+#define NXMAC_ACCEPT_ERROR_FRAMES_BIT         BIT(5)
+#define NXMAC_ACCEPT_OTHER_BSSID_BIT          BIT(4)
+#define NXMAC_ACCEPT_BROADCAST_BIT            BIT(3)
+#define NXMAC_ACCEPT_MULTICAST_BIT            BIT(2)
+#define NXMAC_DONT_DECRYPT_BIT                BIT(1)
+#define NXMAC_EXC_UNENCRYPTED_BIT             BIT(0)
+
+#define NXMAC_DEBUG_PORT_SEL_ADDR     0x00B00510
+#define NXMAC_SW_SET_PROFILING_ADDR   0x00B08564
+#define NXMAC_SW_CLEAR_PROFILING_ADDR 0x00B08568
+
+/* Modem Status */
+#define MDM_HDMCONFIG_ADDR 0x00C00000
+
+/* Clock gating configuration */
+#define MDM_MEMCLKCTRL0_ADDR   0x00C00848
+#define MDM_CLKGATEFCTRL0_ADDR 0x00C00874
+#define CRM_CLKGATEFCTRL0_ADDR 0x00940010
+
+/* AGC (trident) */
+#define AGC_RWNXAGCCNTL_ADDR 0x00C02060
+
+/* LDPC RAM*/
+#define PHY_LDPC_RAM_ADDR 0x00C09000
+
+/* FCU (elma )*/
+#define FCU_RWNXFCAGCCNTL_ADDR 0x00C09034
+
+/* AGC RAM */
+#define PHY_AGC_UCODE_ADDR 0x00C0A000
+
+/* RIU */
+#define RIU_RWNXVERSION_ADDR         0x00C0B000
+#define RIU_RWNXDYNAMICCONFIG_ADDR   0x00C0B008
+#define RIU_AGCMEMBISTSTAT_ADDR      0x00C0B238
+#define RIU_AGCMEMSIGNATURESTAT_ADDR 0x00C0B23C
+#define RIU_RWNXAGCCNTL_ADDR         0x00C0B390
+
+/* FCU RAM */
+#define PHY_FCU_UCODE_ADDR 0x00C0E000
+
+/* RF ITF */
+#define FPGAB_MPIF_SEL_ADDR       0x00C10030
+#define RF_V6_DIAGPORT_CONF1_ADDR 0x00C10010
+#define RF_v6_PHYDIAG_CONF1_ADDR  0x00C10018
+
+#define RF_V7_DIAGPORT_CONF1_ADDR 0x00F10010
+#define RF_v7_PHYDIAG_CONF1_ADDR  0x00F10018
+
+/*****************************************************************************
+ * Macros for generated register files
+ *****************************************************************************/
+/* Macros for IPC registers access (used in reg_ipc_app.h) */
+#define REG_IPC_APP_RD(env, INDEX)                                             \
+	(*(u32 *)((u8 *)(env) + IPC_REG_BASE_ADDR + 4 * (INDEX)))
+
+#define REG_IPC_APP_WR(env, INDEX, value)                                      \
+	(*(u32 *)((u8 *)(env) + IPC_REG_BASE_ADDR + 4 * (INDEX)) = value)
+
+#endif /* REG_ACCESS_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_cmds.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_cmds.c
new file mode 100644
index 0000000000000..0aabf28a531cb
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_cmds.c
@@ -0,0 +1,471 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief Handles queueing (push to IPC, ack/cfm from IPC) of commands issued to
+ * LMAC FW
+ *
+ ******************************************************************************
+ */
+
+#include "rwnx_cmds.h"
+#include "aicwf_txrxif.h"
+#include "rwnx_defs.h"
+#include "rwnx_events.h"
+#include "rwnx_strs.h"
+#include <linux/list.h>
+#ifdef AICWF_SDIO_SUPPORT
+#include "aicwf_sdio.h"
+#else
+#include "aicwf_usb.h"
+#endif
+
+static void cmd_dump(const struct rwnx_cmd *cmd)
+{
+	if (!cmd) {
+		AICWFDBG(LOGDEBUG, KERN_ERR "AICWF %s cmd is null\n", __func__);
+		return;
+	}
+	AICWFDBG(LOGDEBUG, KERN_CRIT
+		 "tkn[%d]  flags:%04x  result:%3d  cmd:%4d-%-24s - reqcfm(%4d-%-s)\n",
+		 cmd->tkn, cmd->flags, cmd->result, cmd->id, RWNX_ID2STR(cmd->id),
+		 cmd->reqid,
+		 cmd->reqid != (lmac_msg_id_t)-1 ? RWNX_ID2STR(cmd->reqid) : "none");
+}
+
+static void cmd_complete(struct rwnx_cmd_mgr *cmd_mgr, struct rwnx_cmd *cmd)
+{
+	// RWNX_DBG(RWNX_FN_ENTRY_STR); //debug trace
+	lockdep_assert_held(&cmd_mgr->lock);
+
+	list_del(&cmd->list);
+	cmd_mgr->queue_sz--;
+
+	cmd->flags |= RWNX_CMD_FLAG_DONE;
+	if (cmd->flags & RWNX_CMD_FLAG_NONBLOCK) {
+		rwnx_cmd_free(cmd); // kfree(cmd);
+	} else {
+		if (RWNX_CMD_WAIT_COMPLETE(cmd->flags)) {
+			cmd->result = 0;
+			complete(&cmd->complete);
+		}
+	}
+}
+
+int cmd_mgr_queue_force_defer(struct rwnx_cmd_mgr *cmd_mgr,
+			      struct rwnx_cmd *cmd)
+{
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+#ifdef AIC8800_CREATE_TRACE_POINTS
+	trace_msg_send(cmd->id);
+#endif
+	spin_lock_bh(&cmd_mgr->lock);
+
+	if (cmd_mgr->state == RWNX_CMD_MGR_STATE_CRASHED) {
+		AICWFDBG(LOGDEBUG, KERN_CRIT "AICWF cmd queue crashed\n");
+		cmd->result = -EPIPE;
+		spin_unlock_bh(&cmd_mgr->lock);
+		return -EPIPE;
+	}
+
+	if (!list_empty(&cmd_mgr->cmds)) {
+		if (cmd_mgr->queue_sz == cmd_mgr->max_queue_sz) {
+			AICWFDBG(LOGDEBUG, KERN_CRIT "AICWF Too many cmds (%d) already queued\n",
+				 cmd_mgr->max_queue_sz);
+			cmd->result = -ENOMEM;
+			spin_unlock_bh(&cmd_mgr->lock);
+			return -ENOMEM;
+		}
+	}
+
+	cmd->flags |= RWNX_CMD_FLAG_WAIT_PUSH;
+
+	if (cmd->flags & RWNX_CMD_FLAG_REQ_CFM)
+		cmd->flags |= RWNX_CMD_FLAG_WAIT_CFM;
+
+	cmd->tkn = cmd_mgr->next_tkn++;
+	cmd->result = -EINTR;
+
+	if (!(cmd->flags & RWNX_CMD_FLAG_NONBLOCK))
+		init_completion(&cmd->complete);
+
+	list_add_tail(&cmd->list, &cmd_mgr->cmds);
+	cmd_mgr->queue_sz++;
+	spin_unlock_bh(&cmd_mgr->lock);
+
+	WAKE_CMD_WORK(cmd_mgr);
+	return 0;
+}
+
+static int cmd_mgr_queue(struct rwnx_cmd_mgr *cmd_mgr, struct rwnx_cmd *cmd)
+{
+#ifdef AICWF_SDIO_SUPPORT
+	int ret;
+	struct aic_sdio_dev *sdiodev =
+		container_of(cmd_mgr, struct aic_sdio_dev, cmd_mgr);
+#endif
+	bool defer_push = false;
+
+	// RWNX_DBG(RWNX_FN_ENTRY_STR); //debug trace
+#ifdef AIC8800_CREATE_TRACE_POINTS
+	trace_msg_send(cmd->id);
+#endif
+	spin_lock_bh(&cmd_mgr->lock);
+
+	if (cmd_mgr->state == RWNX_CMD_MGR_STATE_CRASHED) {
+		AICWFDBG(LOGDEBUG, KERN_CRIT "AICWF cmd queue crashed\n");
+		cmd->result = -EPIPE;
+		spin_unlock_bh(&cmd_mgr->lock);
+		return -EPIPE;
+	}
+
+	if (!list_empty(&cmd_mgr->cmds)) {
+		struct rwnx_cmd *last;
+
+		if (cmd_mgr->queue_sz == cmd_mgr->max_queue_sz) {
+			AICWFDBG(LOGDEBUG, KERN_CRIT "AICWF Too many cmds (%d) already queued\n",
+				 cmd_mgr->max_queue_sz);
+			cmd->result = -ENOMEM;
+			spin_unlock_bh(&cmd_mgr->lock);
+			return -ENOMEM;
+		}
+		last = list_entry(cmd_mgr->cmds.prev, struct rwnx_cmd, list);
+		if (last->flags & (RWNX_CMD_FLAG_WAIT_ACK | RWNX_CMD_FLAG_WAIT_PUSH |
+				   RWNX_CMD_FLAG_WAIT_CFM)) {
+			cmd->flags |= RWNX_CMD_FLAG_WAIT_PUSH;
+			defer_push = true;
+		}
+	}
+
+	if (cmd->flags & RWNX_CMD_FLAG_REQ_CFM)
+		cmd->flags |= RWNX_CMD_FLAG_WAIT_CFM;
+
+	cmd->tkn = cmd_mgr->next_tkn++;
+	cmd->result = -EINTR;
+
+	if (!(cmd->flags & RWNX_CMD_FLAG_NONBLOCK))
+		init_completion(&cmd->complete);
+
+	list_add_tail(&cmd->list, &cmd_mgr->cmds);
+	cmd_mgr->queue_sz++;
+
+#ifdef AICWF_ARP_OFFLOAD
+	if (cmd->a2e_msg->id == ME_TRAFFIC_IND_REQ ||
+	    cmd->a2e_msg->id == MM_SET_ARPOFFLOAD_REQ) {
+#else
+	if (cmd->a2e_msg->id == ME_TRAFFIC_IND_REQ) {
+#endif
+		defer_push = true;
+		cmd->flags |= RWNX_CMD_FLAG_WAIT_PUSH;
+	}
+
+	spin_unlock_bh(&cmd_mgr->lock);
+	if (!defer_push) {
+#ifdef AICWF_SDIO_SUPPORT
+		aicwf_set_cmd_tx((void *)(sdiodev), cmd->a2e_msg,
+				 sizeof(struct lmac_msg) + cmd->a2e_msg->param_len);
+#else
+		aicwf_set_cmd_tx((void *)(usbdev), cmd->a2e_msg,
+				 sizeof(struct lmac_msg) + cmd->a2e_msg->param_len);
+#endif
+		kfree(cmd->a2e_msg);
+	} else {
+		if (cmd_mgr->queue_sz <= 1)
+			WAKE_CMD_WORK(cmd_mgr);
+		return 0;
+	}
+
+	if (!(cmd->flags & RWNX_CMD_FLAG_NONBLOCK)) {
+		unsigned long tout =
+			msecs_to_jiffies(RWNX_80211_CMD_TIMEOUT_MS * cmd_mgr->queue_sz);
+		if (!wait_for_completion_timeout(&cmd->complete, tout)) {
+			AICWFDBG(LOGDEBUG, KERN_CRIT "AICWF cmd timed-out\n");
+#ifdef AICWF_SDIO_SUPPORT
+			ret = aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.wakeup_reg, 2);
+			if (ret < 0) {
+				sdio_err("reg:%d write failed!\n",
+					 sdiodev->sdio_reg.wakeup_reg);
+			}
+#endif
+
+			cmd_dump(cmd);
+			spin_lock_bh(&cmd_mgr->lock);
+			cmd_mgr->state = RWNX_CMD_MGR_STATE_CRASHED;
+			if (!(cmd->flags & RWNX_CMD_FLAG_DONE)) {
+				cmd->result = -ETIMEDOUT;
+				cmd_complete(cmd_mgr, cmd);
+			}
+			spin_unlock_bh(&cmd_mgr->lock);
+		} else {
+			rwnx_cmd_free(cmd); // kfree(cmd);
+			if (!list_empty(&cmd_mgr->cmds))
+				WAKE_CMD_WORK(cmd_mgr);
+		}
+	} else {
+		cmd->result = 0;
+	}
+
+	return 0;
+}
+
+static int cmd_mgr_llind(struct rwnx_cmd_mgr *cmd_mgr, struct rwnx_cmd *cmd)
+{
+	struct rwnx_cmd *cur, *acked = NULL;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	spin_lock_bh(&cmd_mgr->lock);
+	list_for_each_entry(cur, &cmd_mgr->cmds, list) {
+		if (!acked) {
+			if (cur->tkn == cmd->tkn) {
+				if (WARN_ON_ONCE(cur != cmd))
+					cmd_dump(cmd);
+				acked = cur;
+				continue;
+			}
+		}
+		if (cur->flags & RWNX_CMD_FLAG_WAIT_PUSH)
+			break;
+	}
+	if (!acked) {
+		AICWFDBG(LOGDEBUG, KERN_CRIT "Error: acked cmd not found\n");
+	} else {
+		cmd->flags &= ~RWNX_CMD_FLAG_WAIT_ACK;
+		if (RWNX_CMD_WAIT_COMPLETE(cmd->flags))
+			cmd_complete(cmd_mgr, cmd);
+	}
+	spin_unlock(&cmd_mgr->lock);
+
+	return 0;
+}
+
+void cmd_mgr_task_process(struct work_struct *work)
+{
+	struct rwnx_cmd_mgr *cmd_mgr =
+		container_of(work, struct rwnx_cmd_mgr, cmd_work);
+	struct rwnx_cmd *cur, *next = NULL;
+	unsigned long tout;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	while (1) {
+		next = NULL;
+		spin_lock_bh(&cmd_mgr->lock);
+
+		list_for_each_entry(cur, &cmd_mgr->cmds, list) {
+			if (cur->flags & RWNX_CMD_FLAG_WAIT_PUSH)
+				next = cur;
+			break;
+		}
+		spin_unlock_bh(&cmd_mgr->lock);
+
+		if (!next)
+			break;
+
+		if (next) {
+#ifdef AICWF_SDIO_SUPPORT
+			struct aic_sdio_dev *sdiodev =
+				container_of(cmd_mgr, struct aic_sdio_dev, cmd_mgr);
+#endif
+			next->flags &= ~RWNX_CMD_FLAG_WAIT_PUSH;
+
+#ifdef AICWF_SDIO_SUPPORT
+			aicwf_set_cmd_tx((void *)(sdiodev), next->a2e_msg,
+					 sizeof(struct lmac_msg) +
+						next->a2e_msg->param_len);
+#else
+			aicwf_set_cmd_tx((void *)(usbdev), next->a2e_msg,
+					 sizeof(struct lmac_msg) +
+						next->a2e_msg->param_len);
+#endif
+			kfree(next->a2e_msg);
+
+			tout =
+				msecs_to_jiffies(RWNX_80211_CMD_TIMEOUT_MS * cmd_mgr->queue_sz);
+			if (!wait_for_completion_timeout(&next->complete, tout)) {
+				AICWFDBG(LOGDEBUG, KERN_CRIT "AICWF cmd timed-out\n");
+#ifdef AICWF_SDIO_SUPPORT
+				if (aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.wakeup_reg,
+						      2) < 0) {
+					sdio_err("reg:%d write failed!\n",
+						 sdiodev->sdio_reg.wakeup_reg);
+				}
+#endif
+				cmd_dump(next);
+				spin_lock_bh(&cmd_mgr->lock);
+				cmd_mgr->state = RWNX_CMD_MGR_STATE_CRASHED;
+				if (!(next->flags & RWNX_CMD_FLAG_DONE)) {
+					next->result = -ETIMEDOUT;
+					cmd_complete(cmd_mgr, next);
+				}
+				spin_unlock_bh(&cmd_mgr->lock);
+			} else {
+				rwnx_cmd_free(next); // kfree(next);
+			}
+		}
+	}
+}
+
+static int cmd_mgr_run_callback(struct rwnx_hw *rwnx_hw, struct rwnx_cmd *cmd,
+				struct rwnx_cmd_e2amsg *msg, msg_cb_fct cb)
+{
+	int res;
+
+	if (!cb)
+		return 0;
+
+	res = cb(rwnx_hw, cmd, msg);
+
+	return res;
+}
+
+static int cmd_mgr_msgind(struct rwnx_cmd_mgr *cmd_mgr,
+			  struct rwnx_cmd_e2amsg *msg, msg_cb_fct cb)
+{
+#ifdef AICWF_SDIO_SUPPORT
+	struct aic_sdio_dev *sdiodev =
+		container_of(cmd_mgr, struct aic_sdio_dev, cmd_mgr);
+	struct rwnx_hw *rwnx_hw = sdiodev->rwnx_hw;
+#endif
+	struct rwnx_cmd *cmd, *pos;
+	bool found = false;
+
+	// RWNX_DBG(RWNX_FN_ENTRY_STR); //debug trace
+#ifdef AIC8800_CREATE_TRACE_POINTS
+	trace_msg_recv(msg->id);
+#endif
+	// AICWFDBG(LOGDEBUG, "cmd->id=%x\n", msg->id); //debug trace
+	spin_lock_bh(&cmd_mgr->lock);
+	list_for_each_entry_safe(cmd, pos, &cmd_mgr->cmds, list) {
+		if (cmd->reqid == msg->id && (cmd->flags & RWNX_CMD_FLAG_WAIT_CFM)) {
+			if (!cmd_mgr_run_callback(rwnx_hw, cmd, msg, cb)) {
+				found = true;
+				cmd->flags &= ~RWNX_CMD_FLAG_WAIT_CFM;
+				if (WARN(msg->param_len > RWNX_CMD_E2AMSG_LEN_MAX,
+					 "Unexpect E2A msg len %d > %d\n", msg->param_len,
+					 RWNX_CMD_E2AMSG_LEN_MAX))
+					msg->param_len = RWNX_CMD_E2AMSG_LEN_MAX;
+
+				if (cmd->e2a_msg && msg->param_len)
+					memcpy(cmd->e2a_msg, &msg->param, msg->param_len);
+
+				if (RWNX_CMD_WAIT_COMPLETE(cmd->flags))
+					cmd_complete(cmd_mgr, cmd);
+
+				break;
+			}
+		}
+	}
+	spin_unlock_bh(&cmd_mgr->lock);
+
+	if (!found)
+		cmd_mgr_run_callback(rwnx_hw, NULL, msg, cb);
+
+	return 0;
+}
+
+static void cmd_mgr_print(struct rwnx_cmd_mgr *cmd_mgr)
+{
+	struct rwnx_cmd *cur;
+
+	spin_lock_bh(&cmd_mgr->lock);
+	RWNX_DBG("q_sz/max: %2d / %2d - next tkn: %d\n", cmd_mgr->queue_sz,
+		 cmd_mgr->max_queue_sz, cmd_mgr->next_tkn);
+	list_for_each_entry(cur, &cmd_mgr->cmds, list) {
+		cmd_dump(cur);
+	}
+	spin_unlock_bh(&cmd_mgr->lock);
+}
+
+static void cmd_mgr_drain(struct rwnx_cmd_mgr *cmd_mgr)
+{
+	struct rwnx_cmd *cur, *nxt;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	spin_lock_bh(&cmd_mgr->lock);
+	list_for_each_entry_safe(cur, nxt, &cmd_mgr->cmds, list) {
+		list_del(&cur->list);
+		cmd_mgr->queue_sz--;
+		if (!(cur->flags & RWNX_CMD_FLAG_NONBLOCK))
+			complete(&cur->complete);
+	}
+	spin_unlock_bh(&cmd_mgr->lock);
+}
+
+void rwnx_cmd_mgr_init(struct rwnx_cmd_mgr *cmd_mgr)
+{
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	INIT_LIST_HEAD(&cmd_mgr->cmds);
+	cmd_mgr->state = RWNX_CMD_MGR_STATE_INITED;
+	spin_lock_init(&cmd_mgr->lock);
+	cmd_mgr->max_queue_sz = RWNX_CMD_MAX_QUEUED;
+	cmd_mgr->queue = &cmd_mgr_queue;
+	cmd_mgr->print = &cmd_mgr_print;
+	cmd_mgr->drain = &cmd_mgr_drain;
+	cmd_mgr->llind = &cmd_mgr_llind;
+	cmd_mgr->msgind = &cmd_mgr_msgind;
+
+	INIT_WORK(&cmd_mgr->cmd_work, cmd_mgr_task_process);
+	cmd_mgr->cmd_wq = create_singlethread_workqueue("cmd_wq");
+	if (!cmd_mgr->cmd_wq) {
+		txrx_err("insufficient memory to create cmd workqueue.\n");
+		return;
+	}
+}
+
+void rwnx_cmd_mgr_deinit(struct rwnx_cmd_mgr *cmd_mgr)
+{
+	cmd_mgr->print(cmd_mgr);
+	cmd_mgr->drain(cmd_mgr);
+	cmd_mgr->print(cmd_mgr);
+	flush_workqueue(cmd_mgr->cmd_wq);
+	destroy_workqueue(cmd_mgr->cmd_wq);
+	memset(cmd_mgr, 0, sizeof(*cmd_mgr));
+}
+
+void aicwf_set_cmd_tx(void *dev, struct lmac_msg *msg, uint len)
+{
+	u8 *buffer = NULL;
+	u16 index = 0;
+#ifdef AICWF_SDIO_SUPPORT
+	struct aic_sdio_dev *sdiodev = (struct aic_sdio_dev *)dev;
+	struct aicwf_bus *bus = sdiodev->bus_if;
+#else
+	struct aic_usb_dev *usbdev = (struct aic_usb_dev *)dev;
+	struct aicwf_bus *bus = NULL;
+
+	if (!usbdev->state) {
+		AICWFDBG(LOGDEBUG, "down msg\n");
+		return;
+	}
+	bus = usbdev->bus_if;
+#endif
+	buffer = bus->cmd_buf;
+
+	memset(buffer, 0, CMD_BUF_MAX);
+	buffer[0] = (len + 4) & 0x00ff;
+	buffer[1] = ((len + 4) >> 8) & 0x0f;
+	buffer[2] = 0x11;
+
+	buffer[3] = aic_chip_cmd_hdr_checksum(sdiodev->rwnx_hw, &buffer[0], 3);
+
+	index += 4;
+	// there is a dummy word
+	index += 4;
+
+	// make sure little endian
+	put_u16(&buffer[index], msg->id);
+	index += 2;
+	put_u16(&buffer[index], msg->dest_id);
+	index += 2;
+	put_u16(&buffer[index], msg->src_id);
+	index += 2;
+	put_u16(&buffer[index], msg->param_len);
+	index += 2;
+	memcpy(&buffer[index], (u8 *)msg->param, msg->param_len);
+
+	aicwf_bus_txmsg(bus, buffer, len + 8);
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_cmds.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_cmds.h
new file mode 100644
index 0000000000000..adb4bc49c188a
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_cmds.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_CMDS_H_
+#define _RWNX_CMDS_H_
+
+#include "lmac_msg.h"
+#include "ipc_shared.h"
+#include <linux/completion.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+
+#define RWNX_80211_CMD_TIMEOUT_MS 6000
+
+#define RWNX_CMD_FLAG_NONBLOCK  BIT(0)
+#define RWNX_CMD_FLAG_REQ_CFM   BIT(1)
+#define RWNX_CMD_FLAG_WAIT_PUSH BIT(2)
+#define RWNX_CMD_FLAG_WAIT_ACK  BIT(3)
+#define RWNX_CMD_FLAG_WAIT_CFM  BIT(4)
+#define RWNX_CMD_FLAG_DONE      BIT(5)
+/* ATM IPC design makes it possible to get the CFM before the ACK,
+ * otherwise this could have simply been a state enum
+ */
+#define RWNX_CMD_WAIT_COMPLETE(flags)                                          \
+	(!((flags) & (RWNX_CMD_FLAG_WAIT_ACK | RWNX_CMD_FLAG_WAIT_CFM)))
+
+#define RWNX_CMD_MAX_QUEUED 16
+
+#define rwnx_cmd_e2amsg         ipc_e2a_msg
+#define rwnx_cmd_a2emsg         lmac_msg
+#define RWNX_CMD_A2EMSG_LEN(m)  (sizeof(struct lmac_msg) + (m)->param_len)
+#define RWNX_CMD_E2AMSG_LEN_MAX (IPC_E2A_MSG_PARAM_SIZE * 4)
+
+struct rwnx_hw;
+struct rwnx_cmd;
+typedef int (*msg_cb_fct)(struct rwnx_hw *rwnx_hw, struct rwnx_cmd *cmd,
+			  struct rwnx_cmd_e2amsg *msg);
+
+static inline void put_u16(u8 *buf, u16 data)
+{
+	buf[0] = (u8)(data & 0x00ff);
+	buf[1] = (u8)((data >> 8) & 0x00ff);
+}
+
+enum rwnx_cmd_mgr_state {
+	RWNX_CMD_MGR_STATE_DEINIT,
+	RWNX_CMD_MGR_STATE_INITED,
+	RWNX_CMD_MGR_STATE_CRASHED,
+};
+
+struct rwnx_cmd {
+	struct list_head list;
+	lmac_msg_id_t id;
+	lmac_msg_id_t reqid;
+	struct rwnx_cmd_a2emsg *a2e_msg;
+	char *e2a_msg;
+	u32 tkn;
+	u16 flags;
+
+	struct completion complete;
+	u32 result;
+	u8 used;
+	int array_id;
+};
+
+struct rwnx_cmd_mgr {
+	enum rwnx_cmd_mgr_state state;
+	spinlock_t lock; //
+	u32 next_tkn;
+	u32 queue_sz;
+	u32 max_queue_sz;
+
+	struct list_head cmds;
+
+	int (*queue)(struct rwnx_cmd_mgr *mgr, struct rwnx_cmd *cmd);
+	int (*llind)(struct rwnx_cmd_mgr *mgr, struct rwnx_cmd *cmd);
+	int (*msgind)(struct rwnx_cmd_mgr *mgr, struct rwnx_cmd_e2amsg *msg, msg_cb_fct cb);
+	void (*print)(struct rwnx_cmd_mgr *mgr);
+	void (*drain)(struct rwnx_cmd_mgr *mgr);
+
+	struct work_struct cmd_work;
+	struct workqueue_struct *cmd_wq;
+};
+
+#define WAKE_CMD_WORK(_cmd_mgr)							\
+	do {									\
+		struct rwnx_cmd_mgr *__mgr = (_cmd_mgr);                        \
+		queue_work((__mgr)->cmd_wq, &__mgr->cmd_work);		        \
+	} while (0)
+
+void rwnx_cmd_mgr_init(struct rwnx_cmd_mgr *cmd_mgr);
+void rwnx_cmd_mgr_deinit(struct rwnx_cmd_mgr *cmd_mgr);
+int cmd_mgr_queue_force_defer(struct rwnx_cmd_mgr *cmd_mgr,
+			      struct rwnx_cmd *cmd);
+void aicwf_set_cmd_tx(void *dev, struct lmac_msg *msg, uint len);
+
+void cmd_mgr_task_process(struct work_struct *work);
+
+void rwnx_cmd_free(struct rwnx_cmd *cmd);
+
+#endif /* _RWNX_CMDS_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_compat.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_compat.h
new file mode 100644
index 0000000000000..7316a2f0b8322
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_compat.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief Ensure driver compilation for linux 3.16 to 3.19
+ * To avoid too many #if LINUX_VERSION_CODE if the code, when prototype change
+ * between different kernel version:
+ * - For external function, define a macro whose name is the function name with
+ *   _compat suffix and prototype (actually the number of parameter) of the
+ *   latest version. Then latest version this macro simply call the function
+ *   and for older kernel version it call the function adapting the api.
+ * - For internal function (e.g. cfg80211_ops) do the same but the macro name
+ *   doesn't need to have the _compat suffix when the function is not used
+ *   directly by the driver
+ *
+ ******************************************************************************
+ */
+
+#ifndef _RWNX_COMPAT_H_
+#define _RWNX_COMPAT_H_
+#include <linux/version.h>
+
+#include <linux/bitfield.h>
+
+/* CFG80211 */
+
+#ifdef CONFIG_RWNX_MUMIMO_TX
+#define CONFIG_USER_MAX 2
+#else
+#define CONFIG_USER_MAX 1
+#endif
+
+#define NX_VIRT_DEV_MAX 4
+
+#define NX_REMOTE_STA_MAX_FOR_OLD_IC 10
+#define NX_REMOTE_STA_MAX 32
+
+#define NX_MU_GROUP_MAX 62
+#define NX_TXDESC_CNT 64
+#define NX_TX_MAX_RATES 4
+#define NX_CHAN_CTXT_CNT 3
+
+#ifdef CONFIG_RWNX_BCMC
+#define NX_TXQ_CNT 5
+#else
+#define NX_TXQ_CNT 4
+#endif
+
+// because android kernel 5.15 uses kernel 6.0 or 6.1 kernel api
+#ifdef ANDROID_PLATFORM
+#define HIGH_KERNEL_VERSION  KERNEL_VERSION(5, 15, 41)
+#define HIGH_KERNEL_VERSION2 KERNEL_VERSION(5, 15, 41)
+#define HIGH_KERNEL_VERSION3 KERNEL_VERSION(5, 15, 104)
+#define HIGH_KERNEL_VERSION4 KERNEL_VERSION(6, 1, 0)
+#else
+#define HIGH_KERNEL_VERSION  KERNEL_VERSION(6, 0, 0)
+#define HIGH_KERNEL_VERSION2 KERNEL_VERSION(6, 1, 0)
+#define HIGH_KERNEL_VERSION3 KERNEL_VERSION(6, 3, 0)
+#define HIGH_KERNEL_VERSION4 KERNEL_VERSION(6, 3, 0)
+#endif
+
+#define IEEE80211_MAX_AMPDU_BUF IEEE80211_MAX_AMPDU_BUF_HE
+#define IEEE80211_HE_PHY_CAP6_TRIG_MU_BEAMFORMER_FB                            \
+	IEEE80211_HE_PHY_CAP6_TRIG_MU_BEAMFORMING_PARTIAL_BW_FB
+#define IEEE80211_HE_PHY_CAP6_TRIG_SU_BEAMFORMER_FB                            \
+	IEEE80211_HE_PHY_CAP6_TRIG_SU_BEAMFORMING_FB
+#define IEEE80211_HE_PHY_CAP3_RX_HE_MU_PPDU_FROM_NON_AP_STA                    \
+	IEEE80211_HE_PHY_CAP3_RX_PARTIAL_BW_SU_IN_20MHZ_MU
+
+#define CCFS0(vht) ((vht)->center_freq_seg0_idx)
+#define CCFS1(vht) ((vht)->center_freq_seg1_idx)
+
+#define SURVEY_TIME(s)      ((s)->time)
+#define SURVEY_TIME_BUSY(s) ((s)->time_busy)
+#define STA_TDLS_INITIATOR(sta) ((sta)->tdls_initiator)
+
+#define RX_ENC_HT(s) ((s)->encoding = RX_ENC_HT)
+#define RX_ENC_VHT(s) ((s)->encoding = RX_ENC_VHT)
+#define RX_ENC_HE(s) ((s)->encoding = RX_ENC_HE)
+#define RX_ENC_FLAG_SHORT_GI(s)  ((s)->enc_flags |= RX_ENC_FLAG_SHORT_GI)
+#define RX_ENC_FLAG_SHORT_PRE(s) ((s)->enc_flags |= RX_ENC_FLAG_SHORTPRE)
+#define RX_ENC_FLAG_LDPC(s)      ((s)->enc_flags |= RX_ENC_FLAG_LDPC)
+#define RX_BW_40MHZ(s)           ((s)->bw = RATE_INFO_BW_40)
+#define RX_BW_80MHZ(s)           ((s)->bw = RATE_INFO_BW_80)
+#define RX_BW_160MHZ(s)          ((s)->bw = RATE_INFO_BW_160)
+#define RX_NSS(s)                ((s)->nss)
+
+#ifndef CONFIG_VENDOR_RWNX_AMSDUS_TX
+#endif /* CONFIG_VENDOR_RWNX_AMSDUS_TX */
+
+#endif /* _RWNX_COMPAT_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_defs.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_defs.h
new file mode 100644
index 0000000000000..d41bab76dd1b3
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_defs.h
@@ -0,0 +1,744 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief Main driver structure declarations for fullmac driver
+ *
+ ******************************************************************************
+ */
+
+#ifndef _RWNX_DEFS_H_
+#define _RWNX_DEFS_H_
+
+#include <linux/device.h>
+#include <linux/dmapool.h>
+#include <linux/interrupt.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <net/cfg80211.h>
+
+#include "rwnx_cmds.h"
+#include "rwnx_debugfs.h"
+#include "rwnx_mod_params.h"
+#include "rwnx_mu_group.h"
+#include "rwnx_platform.h"
+#include "rwnx_radar.h"
+#include "rwnx_rx.h"
+#include "rwnx_tx.h"
+#include "rwnx_utils.h"
+#ifdef CONFIG_AIC8800_GKI
+#include "rwnx_gki.h"
+#endif
+#include "rwnx_compat.h"
+#ifdef CONFIG_AIC8800_FILTER_TCP_ACK
+#include "aicwf_tcp_ack.h"
+#endif
+
+#ifdef AICWF_SDIO_SUPPORT
+#include "aicwf_sdio.h"
+#include "sdio_host.h"
+#endif
+
+#ifdef CONFIG_AIC8800_BR_SUPPORT
+#include "aic_br_ext.h"
+#endif /* CONFIG_AIC8800_BR_SUPPORT */
+
+#include "lmac_mac.h"
+#include "aicwf_chip_ops.h"
+
+#define WPI_HDR_LEN    18
+#define WPI_PN_LEN     16
+#define WPI_PN_OFST    2
+#define WPI_MIC_LEN    16
+#define WPI_KEY_LEN    32
+#define WPI_SUBKEY_LEN 16 // WPI key is actually two 16bytes key
+
+#define LEGACY_PS_ID 0
+#define UAPSD_ID     1
+
+#define PS_SP_INTERRUPTED 255
+#define MAC_ADDR_LEN      6
+
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+#define MAX_SEARCH_CNT 10
+
+/// Structure containing the parameters of the auto ccode
+struct scanu_ccode {
+	/// ccode auto/manual switch, auto--true, manual--false
+	bool auto_set;
+	/// ccode find
+	bool ccode_find;
+	/// ccode set
+	bool ccode_set;
+	/// ccode find cnt
+	u8_l ccode_cnt;
+	/// ccode rssi
+	s8_l ccode_rssi;
+	/// link scan
+	atomic_t link_scan;
+	/// record mac addr
+	struct mac_addr bssid;
+};
+#endif
+
+struct wf_bss_info {
+	u32_l length;
+	s8_l rssi;
+	u16_l chan;
+	enum nl80211_band band; /* Band */
+	u8_l width;             /* Channel width  0/1/2--20/40/80 */
+	u16 center_freq;        /* Center frequency */
+	u32 center_freq1;       /* Center frequency 1 */
+	u32 center_freq2;       /* Center frequency 2 */
+	bool ht;
+	bool vht;
+	u8_l bssid[ETH_ALEN];
+	u8_l ssid_len;
+	unsigned char ssid[33];
+};
+
+/**
+ * struct rwnx_bcn - Information of the beacon in used (AP mode)
+ *
+ * @head: head portion of beacon (before TIM IE)
+ * @tail: tail portion of beacon (after TIM IE)
+ * @ies: extra IEs (not used ?)
+ * @head_len: length of head data
+ * @tail_len: length of tail data
+ * @ies_len: length of extra IEs data
+ * @tim_len: length of TIM IE
+ * @len: Total beacon len (head + tim + tail + extra)
+ * @dtim: dtim period
+ */
+struct rwnx_bcn {
+	u8 *head;
+	u8 *tail;
+	u8 *ies;
+	size_t head_len;
+	size_t tail_len;
+	size_t ies_len;
+	size_t tim_len;
+	size_t len;
+	u8 dtim;
+};
+
+/**
+ * struct rwnx_key - Key information
+ *
+ * @hw_idx: Idx of the key from hardware point of view
+ */
+struct rwnx_key {
+	u8 hw_idx;
+};
+
+/**
+ * Structure containing information about a Mesh Path
+ */
+struct rwnx_mesh_path {
+	struct list_head list;        /* For rwnx_vif.mesh_paths */
+	u8 path_idx;                  /* Path Index */
+	struct mac_addr tgt_mac_addr; /* Target MAC Address */
+	struct rwnx_sta *p_nhop_sta;  /* Pointer to the Next Hop STA */
+};
+
+struct rwnx_mesh_proxy {
+	struct list_head list;        /* For rwnx_vif.mesh_proxy */
+	struct mac_addr ext_sta_addr; /* Address of the External STA */
+	struct mac_addr proxy_addr;   /* Proxy MAC Address */
+	bool local; /* Indicate if interface is a proxy for the device */
+};
+
+/**
+ * struct rwnx_csa - Information for CSA (Channel Switch Announcement)
+ *
+ * @vif: Pointer to the vif doing the CSA
+ * @bcn: Beacon to use after CSA
+ * @elem: IPC buffer to send the new beacon to the fw
+ * @chandef: defines the channel to use after the switch
+ * @count: Current csa counter
+ * @status: Status of the CSA at fw level
+ * @ch_idx: Index of the new channel context
+ * @work: work scheduled at the end of CSA
+ */
+struct rwnx_csa {
+	struct rwnx_vif *vif;
+	struct rwnx_bcn bcn;
+	struct rwnx_ipc_elem_var elem;
+	struct cfg80211_chan_def chandef;
+	int count;
+	int status;
+	int ch_idx;
+	struct work_struct work;
+};
+
+struct apm_probe_sta {
+	u8 sta_mac_addr[6];
+	u8 vif_idx;
+	u64 probe_id;
+	struct work_struct apmprobesta_work;
+	struct workqueue_struct *apmprobesta_wq;
+};
+
+/// Possible States of the TDLS link.
+enum tdls_status_tag {
+	/// TDLS link is not active (no TDLS peer connected)
+	TDLS_LINK_IDLE,
+	/// TDLS Setup Request transmitted
+	TDLS_SETUP_REQ_TX,
+	/// TDLS Setup Response transmitted
+	TDLS_SETUP_RSP_TX,
+	/// TDLS link is active (TDLS peer connected)
+	TDLS_LINK_ACTIVE,
+	/// TDLS Max Number of states.
+	TDLS_STATE_MAX
+};
+
+/*
+ * Structure used to save information relative to the TDLS peer.
+ * This is also linked within the rwnx_hw vifs list.
+ *
+ */
+struct rwnx_tdls {
+	bool active;       /* Indicate if TDLS link is active */
+	bool initiator;    /* Indicate if TDLS peer is the TDLS initiator */
+	bool chsw_en;      /* Indicate if channel switch is enabled */
+	u8 last_tid;       /* TID of the latest MPDU transmitted over the
+			    * TDLS direct link to the TDLS STA
+			    */
+	u16 last_sn;       /* Sequence number of the latest MPDU transmitted
+			    * over the TDLS direct link to the TDLS STA
+			    */
+	bool ps_on;        /* Indicate if the power save is enabled on the
+			    * TDLS STA
+			    */
+	bool chsw_allowed; /* Indicate if TDLS channel switch is allowed */
+};
+
+/**
+ * enum rwnx_ap_flags - AP flags
+ *
+ * @RWNX_AP_ISOLATE Isolate clients (i.e. Don't brige packets transmitted by
+ *                                   one client for another one)
+ */
+enum rwnx_ap_flags {
+	RWNX_AP_ISOLATE = BIT(0),
+};
+
+/*
+ * Structure used to save information relative to the managed interfaces.
+ * This is also linked within the rwnx_hw vifs list.
+ *
+ */
+struct rwnx_vif {
+	struct list_head list;
+	struct rwnx_hw *rwnx_hw;
+	struct wireless_dev wdev;
+	struct net_device *ndev;
+	struct net_device_stats net_stats;
+	struct rwnx_key key[6];
+	unsigned long drv_flags;
+	atomic_t drv_conn_state;
+	u8 drv_vif_index;  /* Identifier of the VIF in driver */
+	u8 vif_index;      /* Identifier of the station in FW */
+	u8 ch_index;       /* Channel context identifier */
+	bool up;           /* Indicate if associated netdev is up
+			    * (i.e. Interface is created at fw level)
+			    */
+	bool use_4addr;    /* Should we use 4addresses mode */
+	bool is_resending; /* Indicate if a frame is being resent on this interface */
+	bool user_mpm;  /* In case of Mesh Point VIF, indicate if MPM is handled by
+			 * userspace
+			 */
+	bool roc_tdls;  /* Indicate if the ROC has been called by a
+			 * TDLS station
+			 */
+	u8 tdls_status; /* Status of the TDLS link */
+	bool tdls_chsw_prohibited; /* Indicate if TDLS Channel Switch is prohibited */
+	bool wep_enabled;          /* 1 if WEP is enabled */
+	bool wep_auth_err; /* 1 if auth status code is not supported auth alg when
+			    * WEP enabled
+			    */
+	enum nl80211_auth_type
+		last_auth_type; /* Authentication type (algorithm) sent in the last
+				 * connection when WEP enabled
+				 */
+	union {
+		struct {
+			struct rwnx_sta *ap; /* Pointer to the peer STA entry allocated for
+					      * the AP
+					      */
+			struct rwnx_sta *tdls_sta; /* Pointer to the TDLS station */
+			bool external_auth; /* Indicate if external authentication is in
+					     * progress
+					     */
+			u32 group_cipher_type;
+			u32 paired_cipher_type;
+			// connected network info start
+			char ssid[32];
+			int ssid_len;
+			u8 bssid[ETH_ALEN];
+			u32 conn_owner_nlportid;
+			bool is_roam;
+			unsigned long link_time;
+			u16_l b_interval;
+			u8_l dtim_peroid;
+			u16_l capability;
+			enum nl80211_auth_type auth_type;
+			// connected network info end
+		} sta;
+		struct {
+			u16 flags;                 /* see rwnx_ap_flags */
+			struct list_head sta_list; /* List of STA connected to the AP */
+			struct rwnx_bcn bcn;       /* beacon */
+			u8 bcmc_index;             /* Index of the BCMC sta to use */
+#if (defined CONFIG_AIC8800_HE_FOR_OLD_KERNEL) || (defined CONFIG_AIC8800_VHT_FOR_OLD_KERNEL)
+			u8 aic_index;
+#endif
+			struct rwnx_csa *csa;
+
+			struct list_head
+				mpath_list; /* List of Mesh Paths used on this interface */
+			struct list_head proxy_list; /* List of Proxies Information used on
+						      * this interface
+						      */
+			bool create_path;            /* Indicate if we are waiting for a
+						      * MESH_CREATE_PATH_CFM message
+						      */
+			int generation; /* Increased each time the list of Mesh Paths is
+					 * updated
+					 */
+			enum nl80211_mesh_power_mode
+				mesh_pm; /* mesh power save mode currently set in firmware */
+			enum nl80211_mesh_power_mode
+				next_mesh_pm; /* mesh power save mode for next peer */
+		} ap;
+		struct {
+			struct rwnx_vif *master; /* pointer on master interface */
+			struct rwnx_sta *sta_4a;
+		} ap_vlan;
+	};
+
+	u8_l key_has_add;
+	u8_l is_p2p_vif;
+	struct apm_probe_sta sta_probe;
+
+#ifdef CONFIG_AIC8800_BR_SUPPORT
+	spinlock_t br_ext_lock; //
+	/* unsigned int macclone_completed; */
+	struct nat25_network_db_entry *nethash[NAT25_HASH_SIZE];
+	int pppoe_connection_in_progress;
+	unsigned char pppoe_addr[MACADDRLEN];
+	unsigned char scdb_mac[MACADDRLEN];
+	unsigned char scdb_ip[4];
+	struct nat25_network_db_entry *scdb_entry;
+	unsigned char br_mac[MACADDRLEN];
+	unsigned char br_ip[4];
+
+	struct br_ext_info eth_br_ext_info;
+#endif /* CONFIG_AIC8800_BR_SUPPORT */
+};
+
+#define RWNX_VIF_TYPE(rwnx_vif) ((rwnx_vif)->wdev.iftype)
+
+/**
+ * Structure used to store information relative to PS mode.
+ *
+ * @active: True when the sta is in PS mode.
+ *          If false, other values should be ignored
+ * @pkt_ready: Number of packets buffered for the sta in drv's txq
+ *             (1 counter for Legacy PS and 1 for U-APSD)
+ * @sp_cnt: Number of packets that remain to be pushed in the service period.
+ *          0 means that no service period is in progress
+ *          (1 counter for Legacy PS and 1 for U-APSD)
+ */
+struct rwnx_sta_ps {
+	bool active;
+	u16 pkt_ready[2];
+	u16 sp_cnt[2];
+};
+
+/**
+ * struct rwnx_rx_rate_stats - Store statistics for RX rates
+ *
+ * @table: Table indicating how many frame has been receive which each
+ * rate index. Rate index is the same as the one used by RC algo for TX
+ * @size: Size of the table array
+ * @cpt: number of frames received
+ */
+struct rwnx_rx_rate_stats {
+	int *table;
+	int size;
+	int cpt;
+	int rate_cnt;
+};
+
+/**
+ * struct rwnx_sta_stats - Structure Used to store statistics specific to a STA
+ *
+ * @last_rx: Hardware vector of the last received frame
+ * @rx_rate: Statistics of the received rates
+ */
+struct rwnx_sta_stats {
+	struct hw_vect last_rx;
+	struct rwnx_rx_rate_stats rx_rate;
+};
+
+#if (defined CONFIG_AIC8800_HE_FOR_OLD_KERNEL) || (defined CONFIG_AIC8800_VHT_FOR_OLD_KERNEL)
+struct aic_sta {
+	u8 sta_idx; /* Identifier of the station */
+	bool he;    /* Flag indicating if the station supports HE */
+	bool vht;   /* Flag indicating if the station supports VHT */
+};
+#endif
+
+/*
+ * Structure used to save information relative to the managed stations.
+ */
+struct rwnx_sta {
+	struct list_head list;
+	u16 aid;                /* association ID */
+	u8 sta_idx;             /* Identifier of the station */
+	u8 vif_idx;             /* Identifier of the VIF (fw id) the station
+				 * belongs to
+				 */
+	u8 vlan_idx;            /* Identifier of the VLAN VIF (fw id) the station
+				 * belongs to (= vif_idx if no vlan in used)
+				 */
+	enum nl80211_band band; /* Band */
+	enum nl80211_chan_width width; /* Channel width */
+	u16 center_freq;               /* Center frequency */
+	u32 center_freq1;              /* Center frequency 1 */
+	u32 center_freq2;              /* Center frequency 2 */
+	u8 ch_idx;                     /* Identifier of the channel
+					* context the station belongs to
+					*/
+	bool qos;                      /* Flag indicating if the station
+					* supports QoS
+					*/
+	u8 acm;                        /* Bitfield indicating which queues
+					* have AC mandatory
+					*/
+	u16 uapsd_tids;        /* Bitfield indicating which tids are subject to
+				* UAPSD
+				*/
+	u8 mac_addr[ETH_ALEN]; /* MAC address of the station */
+	struct rwnx_key key;
+	bool valid;            /* Flag indicating if the entry is valid */
+	struct rwnx_sta_ps ps; /* Information when STA is in PS (AP only) */
+#ifdef CONFIG_RWNX_BFMER
+	struct rwnx_bfmer_report *bfm_report; /* Beamforming report to be used for
+					       * VHT TX Beamforming
+					       */
+#ifdef CONFIG_RWNX_MUMIMO_TX
+	struct rwnx_sta_group_info
+		group_info; /* MU grouping information for the STA */
+#endif              /* CONFIG_RWNX_MUMIMO_TX */
+#endif              /* CONFIG_RWNX_BFMER */
+
+	bool ht;               /* Flag indicating if the station
+				* supports HT
+				*/
+	bool vht;              /* Flag indicating if the station
+				* supports VHT
+				*/
+	u32 ac_param[AC_MAX];  /* EDCA parameters */
+	struct rwnx_tdls tdls; /* TDLS station information */
+	struct rwnx_sta_stats stats;
+	enum nl80211_mesh_power_mode
+		mesh_pm; /*  link-specific mesh power save mode */
+};
+
+static inline const u8 *rwnx_sta_addr(struct rwnx_sta *rwnx_sta)
+{
+	return rwnx_sta->mac_addr;
+}
+
+#ifdef CONFIG_RWNX_SPLIT_TX_BUF
+struct rwnx_amsdu_stats {
+	int done;
+	int failed;
+};
+#endif
+
+struct rwnx_stats {
+	int cfm_balance[NX_TXQ_CNT];
+	unsigned long last_rx, last_tx; /* jiffies */
+	int ampdus_tx[IEEE80211_MAX_AMPDU_BUF];
+	int ampdus_rx[IEEE80211_MAX_AMPDU_BUF];
+	int ampdus_rx_map[4];
+	int ampdus_rx_miss;
+#ifdef CONFIG_RWNX_SPLIT_TX_BUF
+	struct rwnx_amsdu_stats amsdus[NX_TX_PAYLOAD_MAX];
+#endif
+	int amsdus_rx[64];
+};
+
+struct rwnx_sec_phy_chan {
+	u16 prim20_freq;
+	u16 center_freq1;
+	u16 center_freq2;
+	enum nl80211_band band;
+	u8 type;
+};
+
+/* Structure that will contains all RoC information received from cfg80211 */
+struct rwnx_roc_elem {
+	struct wireless_dev *wdev;
+	struct ieee80211_channel *chan;
+	unsigned int duration;
+	/* Used to avoid call of CFG80211 callback upon expiration of RoC */
+	bool mgmt_roc;
+	/* Indicate if we have switch on the RoC channel */
+	bool on_chan;
+};
+
+/* Structure containing channel survey information received from MAC */
+struct rwnx_survey_info {
+	// Filled
+	u32 filled;
+	// Amount of time in ms the radio spent on the channel
+	u32 chan_time_ms;
+	// Amount of time the primary channel was sensed busy
+	u32 chan_time_busy_ms;
+	// Noise in dbm
+	s8 noise_dbm;
+};
+
+#define RWNX_CH_NOT_SET  0xFF
+#define RWNX_INVALID_VIF 0xFF
+#define RWNX_INVALID_STA 0xFF
+
+/* Structure containing channel context information */
+struct rwnx_chanctx {
+	struct cfg80211_chan_def chan_def; /* channel description */
+	u8 count;                          /* number of vif using this ctxt */
+};
+
+/**
+ * rwnx_phy_info - Phy information
+ *
+ * @phy_cnt: Number of phy interface
+ * @cfg: Configuration send to firmware
+ * @sec_chan: Channel configuration of the second phy interface (if phy_cnt > 1)
+ * @limit_bw: Set to true to limit BW on requested channel. Only set to use
+ * VHT with old radio that don't support 80MHz (deprecated)
+ */
+struct rwnx_phy_info {
+	u8 cnt;
+	struct phy_cfg_tag cfg;
+	struct rwnx_sec_phy_chan sec_chan;
+	bool limit_bw;
+};
+
+struct defrag_ctrl_info {
+	struct list_head list;
+	u8 sta_idx;
+	u8 tid;
+	u16 sn;
+	u8 next_fn;
+	u16 frm_len;
+	struct sk_buff *skb;
+	struct timer_list defrag_timer;
+	struct rwnx_hw *rwnx_hw;
+};
+
+struct amsdu_subframe_hdr {
+	u8 da[6];
+	u8 sa[6];
+	u16 sublen;
+};
+
+/* rwnx driver status */
+void rwnx_set_conn_state(atomic_t *drv_conn_state, int state);
+
+enum rwnx_drv_connect_status {
+	RWNX_DRV_STATUS_DISCONNECTED = 0,
+	RWNX_DRV_STATUS_DISCONNECTING,
+	RWNX_DRV_STATUS_CONNECTING,
+	RWNX_DRV_STATUS_CONNECTED,
+	RWNX_DRV_STATUS_ROAMING,
+};
+
+struct rwnx_hw {
+	struct rwnx_mod_params *mod_params;
+	struct device *dev;
+#ifdef AICWF_SDIO_SUPPORT
+	struct aic_sdio_dev *sdiodev;
+#endif
+	struct wiphy *wiphy;
+	struct list_head vifs;
+	struct rwnx_vif *
+		vif_table[NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX]; /* indexed with fw id */
+	struct rwnx_sta sta_table[NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX];
+#if (defined CONFIG_AIC8800_HE_FOR_OLD_KERNEL) || (defined CONFIG_AIC8800_VHT_FOR_OLD_KERNEL)
+	struct aic_sta aic_table[NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX];
+#endif
+	struct rwnx_survey_info survey[SCAN_CHANNEL_MAX];
+	struct cfg80211_scan_request *scan_request;
+#ifdef CONFIG_AIC8800_SCHED_SCAN
+	struct cfg80211_sched_scan_request *sched_scan_req;
+#endif
+	struct rwnx_chanctx chanctx_table[NX_CHAN_CTXT_CNT];
+	u8 cur_chanctx;
+
+	u8 monitor_vif; /* FW id of the monitor interface, RWNX_INVALID_VIF if no
+			 * monitor vif at fw level
+			 */
+
+#ifdef CONFIG_AIC8800_FILTER_TCP_ACK
+	/* tcp ack management */
+	struct tcp_ack_manage ack_m;
+#endif
+
+	/* RoC Management */
+	struct rwnx_roc_elem *roc_elem; /* Information provided by cfg80211 in its
+					 * remain on channel request
+					 */
+	u32 roc_cookie_cnt; /* Counter used to identify RoC request sent by cfg80211 */
+
+	struct rwnx_cmd_mgr *cmd_mgr;
+
+	struct rwnx_plat *plat;
+
+	spinlock_t tx_lock; //
+	spinlock_t cb_lock; //
+	struct mutex mutex; /* per-device perimeter lock */
+
+	struct tasklet_struct task;
+	struct mm_version_cfm
+		version_cfm; /* Lower layers versions - obtained via MM_VERSION_REQ */
+
+	u32 tcp_pacing_shift;
+
+	/* IPC */
+	struct ipc_host_env_tag *ipc_env;
+#ifdef AICWF_SDIO_SUPPORT
+	struct sdio_host_env_tag sdio_env;
+#endif
+
+	struct rwnx_ipc_elem_pool e2amsgs_pool;
+	struct rwnx_ipc_elem_pool dbgmsgs_pool;
+	struct rwnx_ipc_elem_pool e2aradars_pool;
+	struct rwnx_ipc_elem_var pattern_elem;
+	struct rwnx_ipc_dbgdump_elem dbgdump_elem;
+	struct rwnx_ipc_elem_pool e2arxdesc_pool;
+	struct rwnx_ipc_skb_elem *e2aunsuprxvec_elems;
+	// struct rwnx_ipc_rxbuf_elems rxbuf_elems;
+	struct rwnx_ipc_elem_var scan_ie;
+
+	struct kmem_cache *sw_txhdr_cache;
+
+	struct rwnx_debugfs debugfs;
+	struct rwnx_stats stats;
+
+#ifdef CONFIG_PREALLOC_TXQ
+	struct rwnx_txq *txq;
+#else
+	struct rwnx_txq txq[NX_NB_TXQ];
+#endif
+
+	struct rwnx_hwq hwq[NX_TXQ_CNT];
+
+	u64 avail_idx_map;
+	u8 vif_started;
+	bool adding_sta;
+	struct rwnx_phy_info phy;
+
+	struct rwnx_radar radar;
+
+	/* extended capabilities supported */
+	u8 ext_capa[8];
+
+#ifdef CONFIG_RWNX_MUMIMO_TX
+	struct rwnx_mu_info mu;
+#endif
+	u8 is_p2p_alive;
+	u8 is_p2p_connected;
+	struct timer_list p2p_alive_timer;
+	struct rwnx_vif *p2p_dev_vif;
+	atomic_t p2p_alive_timer_count;
+	bool band_5g_support;
+	u8_l vendor_info;
+	bool fwlog_en;
+	int testmode;
+	u8_l scanning;
+	char country_abbr[4];
+	const struct aic_chip_ops *chip_ops;
+
+	struct list_head defrag_list;
+	spinlock_t defrag_lock; //
+
+	struct work_struct apm_staloss_work;
+	struct workqueue_struct *apm_staloss_wq;
+	u8 apm_vif_idx;
+	u8 sta_mac_addr[6];
+
+	struct wakeup_source *ws_rx;
+	struct wakeup_source *ws_irqrx;
+	struct wakeup_source *ws_tx;
+	struct wakeup_source *ws_pwrctrl;
+	struct wakeup_source *ws_scan;
+
+#ifdef CONFIG_AIC8800_SCHED_SCAN
+	bool is_sched_scan;
+#endif
+
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+	struct scanu_ccode ccode;
+#endif
+
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+	unsigned long started_jiffies;
+	s8_l temp;
+#endif
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+	struct work_struct pwlt_wk;
+#endif
+#ifdef CONFIG_AIC8800_REGION_PW
+	struct work_struct region_wk;
+#endif
+	char fw_version[32];
+#ifdef CONFIG_AIC8800_USE_WIRELESS_EXT
+	bool wext_scan;
+	struct completion wext_scan_com;
+	struct list_head wext_scanre_list;
+	char wext_essid[33];
+	int support_freqs[SCAN_CHANNEL_MAX];
+	int support_freqs_number;
+#endif
+
+};
+
+u8 *rwnx_build_bcn(struct rwnx_bcn *bcn, struct cfg80211_beacon_data *new);
+
+void rwnx_chanctx_link(struct rwnx_vif *vif, u8 idx,
+		       struct cfg80211_chan_def *chandef);
+void rwnx_chanctx_unlink(struct rwnx_vif *vif);
+int rwnx_chanctx_valid(struct rwnx_hw *rwnx_hw, u8 idx);
+
+extern u8 chip_id;
+
+static inline bool is_multicast_sta(int sta_idx)
+{
+	if (g_rwnx_plat->sdiodev->rwnx_hw->chip_ops->is_old_ic)
+		return (sta_idx >= NX_REMOTE_STA_MAX_FOR_OLD_IC);
+	else
+		return (sta_idx >= NX_REMOTE_STA_MAX);
+}
+
+struct rwnx_sta *rwnx_get_sta(struct rwnx_hw *rwnx_hw, const u8 *mac_addr);
+
+static inline uint8_t master_vif_idx(struct rwnx_vif *vif)
+{
+	if (unlikely(vif->wdev.iftype == NL80211_IFTYPE_AP_VLAN))
+		return vif->ap_vlan.master->vif_index;
+	else
+		return vif->vif_index;
+}
+
+void rwnx_external_auth_enable(struct rwnx_vif *vif);
+void rwnx_external_auth_disable(struct rwnx_vif *vif);
+
+#endif /* _RWNX_DEFS_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_events.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_events.h
new file mode 100644
index 0000000000000..936e006b58773
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_events.h
@@ -0,0 +1,938 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief Trace events definition
+ *
+ ******************************************************************************
+ */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM rwnx
+
+#if !defined(_RWNX_EVENTS_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _RWNX_EVENTS_H
+
+#include <linux/tracepoint.h>
+#include "rwnx_tx.h"
+
+#include "rwnx_compat.h"
+
+/*****************************************************************************
+ * TRACE function for MGMT TX (FULLMAC)
+ ****************************************************************************/
+#ifdef CONFIG_RWNX_FULLMAC
+#include "linux/ieee80211.h"
+#if defined(CONFIG_TRACEPOINTS) && defined(AIC8800_CREATE_TRACE_POINTS) && \
+	!defined(TRACE_HEADER_MULTI_READ)
+#include <linux/trace_seq.h>
+
+/* P2P Public Action Frames Definitions (see WiFi P2P Technical Specification,
+ * section 4.2.8)
+ */
+/* IEEE 802.11 Public Action Usage Category - Define P2P public action frames */
+#define MGMT_ACTION_PUBLIC_CAT         (0x04)
+/* Offset of OUI Subtype field in P2P Action Frame format */
+#define MGMT_ACTION_OUI_SUBTYPE_OFFSET (6)
+/* P2P Public Action Frame Types */
+enum p2p_action_type {
+	P2P_ACTION_GO_NEG_REQ = 0, /* GO Negociation Request */
+	P2P_ACTION_GO_NEG_RSP,     /* GO Negociation Response */
+	P2P_ACTION_GO_NEG_CFM,     /* GO Negociation Confirmation */
+	P2P_ACTION_INVIT_REQ,      /* P2P Invitation Request */
+	P2P_ACTION_INVIT_RSP,      /* P2P Invitation Response */
+	P2P_ACTION_DEV_DISC_REQ,   /* Device Discoverability Request */
+	P2P_ACTION_DEV_DISC_RSP,   /* Device Discoverability Response */
+	P2P_ACTION_PROV_DISC_REQ,  /* Provision Discovery Request */
+	P2P_ACTION_PROV_DISC_RSP,  /* Provision Discovery Response */
+};
+
+static inline const char *ftrace_print_mgmt_info(struct trace_seq *p,
+						 u16 frame_control, u8 cat, u8 type,
+						 u8 p2p)
+{
+	const char *ret = trace_seq_buffer_ptr(p);
+
+	switch (frame_control & IEEE80211_FCTL_STYPE) {
+	case (IEEE80211_STYPE_ASSOC_REQ):
+		trace_seq_printf(p, "Association Request");
+		break;
+	case (IEEE80211_STYPE_ASSOC_RESP):
+		trace_seq_printf(p, "Association Response");
+		break;
+	case (IEEE80211_STYPE_REASSOC_REQ):
+		trace_seq_printf(p, "Reassociation Request");
+		break;
+	case (IEEE80211_STYPE_REASSOC_RESP):
+		trace_seq_printf(p, "Reassociation Response");
+		break;
+	case (IEEE80211_STYPE_PROBE_REQ):
+		trace_seq_printf(p, "Probe Request");
+		break;
+	case (IEEE80211_STYPE_PROBE_RESP):
+		trace_seq_printf(p, "Probe Response");
+		break;
+	case (IEEE80211_STYPE_BEACON):
+		trace_seq_printf(p, "Beacon");
+		break;
+	case (IEEE80211_STYPE_ATIM):
+		trace_seq_printf(p, "ATIM");
+		break;
+	case (IEEE80211_STYPE_DISASSOC):
+		trace_seq_printf(p, "Disassociation");
+		break;
+	case (IEEE80211_STYPE_AUTH):
+		trace_seq_printf(p, "Authentication");
+		break;
+	case (IEEE80211_STYPE_DEAUTH):
+		trace_seq_printf(p, "Deauthentication");
+		break;
+	case (IEEE80211_STYPE_ACTION):
+		trace_seq_printf(p, "Action");
+		if (cat == MGMT_ACTION_PUBLIC_CAT && type == 0x9) {
+			switch (p2p) {
+			case (P2P_ACTION_GO_NEG_REQ):
+				trace_seq_printf(p, ": GO Negociation Request");
+				break;
+			case (P2P_ACTION_GO_NEG_RSP):
+				trace_seq_printf(p, ": GO Negociation Response");
+				break;
+			case (P2P_ACTION_GO_NEG_CFM):
+				trace_seq_printf(p, ": GO Negociation Confirmation");
+				break;
+			case (P2P_ACTION_INVIT_REQ):
+				trace_seq_printf(p, ": P2P Invitation Request");
+				break;
+			case (P2P_ACTION_INVIT_RSP):
+				trace_seq_printf(p, ": P2P Invitation Response");
+				break;
+			case (P2P_ACTION_DEV_DISC_REQ):
+				trace_seq_printf(p, ": Device Discoverability Request");
+				break;
+			case (P2P_ACTION_DEV_DISC_RSP):
+				trace_seq_printf(p, ": Device Discoverability Response");
+				break;
+			case (P2P_ACTION_PROV_DISC_REQ):
+				trace_seq_printf(p, ": Provision Discovery Request");
+				break;
+			case (P2P_ACTION_PROV_DISC_RSP):
+				trace_seq_printf(p, ": Provision Discovery Response");
+				break;
+			default:
+				trace_seq_printf(p, "Unknown p2p %d", p2p);
+				break;
+			}
+		} else {
+			switch (cat) {
+			case 0:
+				trace_seq_printf(p, ":Spectrum %d", type);
+				break;
+			case 1:
+				trace_seq_printf(p, ":QOS %d", type);
+				break;
+			case 2:
+				trace_seq_printf(p, ":DLS %d", type);
+				break;
+			case 3:
+				trace_seq_printf(p, ":BA %d", type);
+				break;
+			case 4:
+				trace_seq_printf(p, ":Public %d", type);
+				break;
+			case 5:
+				trace_seq_printf(p, ":Radio Measure %d", type);
+				break;
+			case 6:
+				trace_seq_printf(p, ":Fast BSS %d", type);
+				break;
+			case 7:
+				trace_seq_printf(p, ":HT Action %d", type);
+				break;
+			case 8:
+				trace_seq_printf(p, ":SA Query %d", type);
+				break;
+			case 9:
+				trace_seq_printf(p, ":Protected Public %d", type);
+				break;
+			case 10:
+				trace_seq_printf(p, ":WNM %d", type);
+				break;
+			case 11:
+				trace_seq_printf(p, ":Unprotected WNM %d", type);
+				break;
+			case 12:
+				trace_seq_printf(p, ":TDLS %d", type);
+				break;
+			case 13:
+				trace_seq_printf(p, ":Mesh %d", type);
+				break;
+			case 14:
+				trace_seq_printf(p, ":MultiHop %d", type);
+				break;
+			case 15:
+				trace_seq_printf(p, ":Self Protected %d", type);
+				break;
+			case 126:
+				trace_seq_printf(p, ":Vendor protected");
+				break;
+			case 127:
+				trace_seq_printf(p, ":Vendor");
+				break;
+			default:
+				trace_seq_printf(p, ":Unknown category %d", cat);
+				break;
+			}
+		}
+		break;
+	default:
+		trace_seq_printf(p, "Unknown subtype %d",
+				 frame_control & IEEE80211_FCTL_STYPE);
+		break;
+	}
+
+	trace_seq_putc(p, 0);
+
+	return ret;
+}
+#endif /* defined(CONFIG_TRACEPOINTS) && defined(AIC8800_CREATE_TRACE_POINTS) */
+
+#undef __print_mgmt_info
+#define __print_mgmt_info(frame_control, cat, type, p2p)                       \
+	ftrace_print_mgmt_info(p, frame_control, cat, type, p2p)
+
+TRACE_EVENT(roc, TP_PROTO(u8 vif_idx, u16 freq, unsigned int duration),
+	    TP_ARGS(vif_idx, freq, duration),
+	    TP_STRUCT__entry(__field(u8, vif_idx) __field(u16, freq)
+			     __field(unsigned int, duration)),
+	    TP_fast_assign(__entry->vif_idx = vif_idx; __entry->freq = freq;
+			   __entry->duration = duration;),
+	    TP_printk("f=%d vif=%d dur=%d", __entry->freq, __entry->vif_idx,
+		      __entry->duration));
+
+TRACE_EVENT(cancel_roc, TP_PROTO(u8 vif_idx), TP_ARGS(vif_idx),
+	    TP_STRUCT__entry(__field(u8, vif_idx)),
+	    TP_fast_assign(__entry->vif_idx = vif_idx;),
+	    TP_printk("vif=%d", __entry->vif_idx));
+
+TRACE_EVENT(roc_exp, TP_PROTO(u8 vif_idx), TP_ARGS(vif_idx),
+	    TP_STRUCT__entry(__field(u8, vif_idx)),
+	    TP_fast_assign(__entry->vif_idx = vif_idx;),
+	    TP_printk("vif=%d", __entry->vif_idx));
+
+TRACE_EVENT(switch_roc, TP_PROTO(u8 vif_idx), TP_ARGS(vif_idx),
+	    TP_STRUCT__entry(__field(u8, vif_idx)),
+	    TP_fast_assign(__entry->vif_idx = vif_idx;),
+	    TP_printk("vif=%d", __entry->vif_idx));
+
+DECLARE_EVENT_CLASS(mgmt_template,
+		    TP_PROTO(u16 freq, u8 vif_idx, u8 sta_idx, struct ieee80211_mgmt *mgmt),
+		    TP_ARGS(freq, vif_idx, sta_idx, mgmt),
+		    TP_STRUCT__entry(__field(u16, freq) __field(u8, vif_idx)
+				     __field(u8, sta_idx) __field(u16, frame_control)
+				     __field(u8, action_cat) __field(u8, action_type)
+				     __field(u8, action_p2p)),
+		    TP_fast_assign(__entry->freq = freq; __entry->vif_idx = vif_idx;
+				   __entry->sta_idx = sta_idx;
+				   __entry->frame_control = mgmt->frame_control;
+				   __entry->action_cat = mgmt->u.action.category;
+				   __entry->action_type = mgmt->u.action.action_code;
+				   __entry->action_p2p = *((u8 *)&mgmt->u.action.category +
+								MGMT_ACTION_OUI_SUBTYPE_OFFSET);),
+		    TP_printk("f=%d vif=%d sta=%d -> %s", __entry->freq, __entry->vif_idx,
+			      __entry->sta_idx,
+			      __print_mgmt_info(__entry->frame_control, __entry->action_cat,
+						__entry->action_type, __entry->action_p2p)));
+
+DEFINE_EVENT(mgmt_template, mgmt_tx,
+	     TP_PROTO(u16 freq, u8 vif_idx, u8 sta_idx,
+		      struct ieee80211_mgmt *mgmt),
+	     TP_ARGS(freq, vif_idx, sta_idx, mgmt));
+
+DEFINE_EVENT(mgmt_template, mgmt_rx,
+	     TP_PROTO(u16 freq, u8 vif_idx, u8 sta_idx,
+		      struct ieee80211_mgmt *mgmt),
+	     TP_ARGS(freq, vif_idx, sta_idx, mgmt));
+
+TRACE_EVENT(mgmt_cfm, TP_PROTO(u8 vif_idx, u8 sta_idx, bool acked),
+	    TP_ARGS(vif_idx, sta_idx, acked),
+	    TP_STRUCT__entry(__field(u8, vif_idx) __field(u8, sta_idx)
+			     __field(bool, acked)),
+	    TP_fast_assign(__entry->vif_idx = vif_idx;
+			   __entry->sta_idx = sta_idx; __entry->acked = acked;),
+	    TP_printk("vif=%d sta=%d ack=%d", __entry->vif_idx,
+		      __entry->sta_idx, __entry->acked));
+#endif /* CONFIG_RWNX_FULLMAC */
+
+/*****************************************************************************
+ * TRACE function for TXQ
+ ****************************************************************************/
+
+#if defined(CONFIG_TRACEPOINTS) && defined(AIC8800_CREATE_TRACE_POINTS) && \
+	!defined(TRACE_HEADER_MULTI_READ)
+
+#include <linux/trace_seq.h>
+#include <linux/trace_events.h>
+
+static inline const char *ftrace_print_txq(struct trace_seq *p, int txq_idx)
+{
+	const char *ret = trace_seq_buffer_ptr(p);
+
+	if (txq_idx == TXQ_INACTIVE) {
+		trace_seq_printf(p, "[INACTIVE]");
+	} else if (txq_idx < NX_FIRST_VIF_TXQ_IDX) {
+		trace_seq_printf(p, "[STA %d/%d]", txq_idx / NX_NB_TXQ_PER_STA,
+				 txq_idx % NX_NB_TXQ_PER_STA);
+#ifdef CONFIG_RWNX_FULLMAC
+	} else if (txq_idx < NX_FIRST_UNK_TXQ_IDX) {
+		trace_seq_printf(p, "[BC/MC %d]", txq_idx - NX_FIRST_BCMC_TXQ_IDX);
+	} else if (txq_idx < NX_OFF_CHAN_TXQ_IDX) {
+		trace_seq_printf(p, "[UNKNOWN %d]", txq_idx - NX_FIRST_UNK_TXQ_IDX);
+	} else if (txq_idx == NX_OFF_CHAN_TXQ_IDX) {
+		trace_seq_printf(p, "[OFFCHAN]");
+#else
+	} else if (txq_idx < NX_NB_TXQ) {
+		txq_idx -= NX_FIRST_VIF_TXQ_IDX;
+		trace_seq_printf(p, "[VIF %d/%d]", txq_idx / NX_NB_TXQ_PER_VIF,
+				 txq_idx % NX_NB_TXQ_PER_VIF);
+#endif
+	} else {
+		trace_seq_printf(p, "[ERROR %d]", txq_idx);
+	}
+
+	trace_seq_putc(p, 0);
+
+	return ret;
+}
+
+static inline const char *ftrace_print_sta(struct trace_seq *p, int sta_idx)
+{
+	const char *ret = trace_seq_buffer_ptr(p);
+
+	if (sta_idx < NX_REMOTE_STA_MAX)
+		trace_seq_printf(p, "[STA %d]", sta_idx);
+	else
+		trace_seq_printf(p, "[BC/MC %d]", sta_idx - NX_REMOTE_STA_MAX);
+
+	trace_seq_putc(p, 0);
+
+	return ret;
+}
+
+static inline const char *ftrace_print_hwq(struct trace_seq *p, int hwq_idx)
+{
+	static const struct trace_print_flags symbols[] = {{RWNX_HWQ_BK, "BK"},
+							   {RWNX_HWQ_BE, "BE"},
+							   {RWNX_HWQ_VI, "VI"},
+							   {RWNX_HWQ_VO, "VO"},
+#ifdef CONFIG_RWNX_FULLMAC
+							   {RWNX_HWQ_BCMC, "BCMC"},
+#else
+							   {RWNX_HWQ_BCN, "BCN"},
+#endif
+							   {-1, NULL}};
+	return trace_print_symbols_seq(p, hwq_idx, symbols, ARRAY_SIZE(symbols));
+}
+
+static inline const char *ftrace_print_hwq_cred(struct trace_seq *p, u8 *cred)
+{
+	const char *ret = trace_seq_buffer_ptr(p);
+
+#if CONFIG_USER_MAX == 1
+	trace_seq_printf(p, "%d", cred[0]);
+#else
+	int i;
+
+	for (i = 0; i < CONFIG_USER_MAX - 1; i++)
+		trace_seq_printf(p, "%d-", cred[i]);
+	trace_seq_printf(p, "%d", cred[i]);
+#endif
+
+	trace_seq_putc(p, 0);
+	return ret;
+}
+
+static inline const char *ftrace_print_mu_info(struct trace_seq *p, u8 mu_info)
+{
+	const char *ret = trace_seq_buffer_ptr(p);
+
+	if (mu_info)
+		trace_seq_printf(p, "MU: %d-%d", (mu_info & 0x3f), (mu_info >> 6));
+
+	trace_seq_putc(p, 0);
+	return ret;
+}
+
+static inline const char *ftrace_print_mu_group(struct trace_seq *p, int nb_user,
+						u8 *users)
+{
+	const char *ret = trace_seq_buffer_ptr(p);
+	int i;
+
+	if (users[0] != 0xff)
+		trace_seq_printf(p, "(%d", users[0]);
+	else
+		trace_seq_printf(p, "(-");
+	for (i = 1; i < CONFIG_USER_MAX; i++) {
+		if (users[i] != 0xff)
+			trace_seq_printf(p, ",%d", users[i]);
+		else
+			trace_seq_printf(p, ",-");
+	}
+
+	trace_seq_printf(p, ")");
+	trace_seq_putc(p, 0);
+	return ret;
+}
+
+static inline const char *ftrace_print_amsdu(struct trace_seq *p, u16 nb_pkt)
+{
+	const char *ret = trace_seq_buffer_ptr(p);
+
+	if (nb_pkt > 1)
+		trace_seq_printf(p, "(AMSDU %d)", nb_pkt);
+
+	trace_seq_putc(p, 0);
+	return ret;
+}
+#endif /* defined(CONFIG_TRACEPOINTS) && defined(AIC8800_CREATE_TRACE_POINTS) */
+
+#undef __print_txq
+#define __print_txq(txq_idx) ftrace_print_txq(p, txq_idx)
+
+#undef __print_sta
+#define __print_sta(sta_idx) ftrace_print_sta(p, sta_idx)
+
+#undef __print_hwq
+#define __print_hwq(hwq) ftrace_print_hwq(p, hwq)
+
+#undef __print_hwq_cred
+#define __print_hwq_cred(cred) ftrace_print_hwq_cred(p, cred)
+
+#undef __print_mu_info
+#define __print_mu_info(mu_info) ftrace_print_mu_info(p, mu_info)
+
+#undef __print_mu_group
+#define __print_mu_group(nb, users) ftrace_print_mu_group(p, nb, users)
+
+#undef __print_amsdu
+#define __print_amsdu(nb_pkt) ftrace_print_amsdu(p, nb_pkt)
+
+#ifdef CONFIG_RWNX_FULLMAC
+
+TRACE_EVENT(txq_select, TP_PROTO(int txq_idx, u16 pkt_ready_up, struct sk_buff *skb),
+	    TP_ARGS(txq_idx, pkt_ready_up, skb),
+	    TP_STRUCT__entry(__field(u16, txq_idx) __field(u16, pkt_ready)
+			     __field(struct sk_buff *, skb)),
+	    TP_fast_assign(__entry->txq_idx = txq_idx;
+			   __entry->pkt_ready = pkt_ready_up; __entry->skb = skb;),
+	    TP_printk("%s pkt_ready_up=%d skb=%p", __print_txq(__entry->txq_idx),
+		      __entry->pkt_ready, __entry->skb));
+
+#endif /* CONFIG_RWNX_FULLMAC */
+
+DECLARE_EVENT_CLASS(hwq_template, TP_PROTO(u8 hwq_idx), TP_ARGS(hwq_idx),
+		    TP_STRUCT__entry(__field(u8, hwq_idx)),
+		    TP_fast_assign(__entry->hwq_idx = hwq_idx;),
+		    TP_printk("%s", __print_hwq(__entry->hwq_idx)));
+
+DEFINE_EVENT(hwq_template, hwq_flowctrl_stop, TP_PROTO(u8 hwq_idx),
+	     TP_ARGS(hwq_idx));
+
+DEFINE_EVENT(hwq_template, hwq_flowctrl_start, TP_PROTO(u8 hwq_idx),
+	     TP_ARGS(hwq_idx));
+
+DECLARE_EVENT_CLASS(txq_template, TP_PROTO(struct rwnx_txq *txq), TP_ARGS(txq),
+		    TP_STRUCT__entry(__field(u16, txq_idx)),
+		    TP_fast_assign(__entry->txq_idx = txq->idx;),
+		    TP_printk("%s", __print_txq(__entry->txq_idx)));
+
+DEFINE_EVENT(txq_template, txq_add_to_hw, TP_PROTO(struct rwnx_txq *txq),
+	     TP_ARGS(txq));
+
+DEFINE_EVENT(txq_template, txq_del_from_hw, TP_PROTO(struct rwnx_txq *txq),
+	     TP_ARGS(txq));
+
+#ifdef CONFIG_RWNX_FULLMAC
+
+DEFINE_EVENT(txq_template, txq_flowctrl_stop, TP_PROTO(struct rwnx_txq *txq),
+	     TP_ARGS(txq));
+
+DEFINE_EVENT(txq_template, txq_flowctrl_restart, TP_PROTO(struct rwnx_txq *txq),
+	     TP_ARGS(txq));
+
+#endif /* CONFIG_RWNX_FULLMAC */
+
+TRACE_EVENT(process_txq, TP_PROTO(struct rwnx_txq *txq), TP_ARGS(txq),
+	    TP_STRUCT__entry(__field(u16, txq_idx) __field(u16, len)
+			     __field(u16, len_retry) __field(s8, credit)
+#ifdef CONFIG_RWNX_FULLMAC
+			     __field(u16, limit)
+#endif /* CONFIG_RWNX_FULLMAC */
+						),
+	    TP_fast_assign(__entry->txq_idx = txq->idx;
+			   __entry->len = skb_queue_len(&txq->sk_list);
+#ifdef CONFIG_MAC80211_TXQ
+			   __entry->len += txq->nb_ready_mac80211;
+#endif
+			   __entry->len_retry = txq->nb_retry;
+			   __entry->credit = txq->credits;
+#ifdef CONFIG_RWNX_FULLMAC
+			   __entry->limit = txq->push_limit;
+#endif /* CONFIG_RWNX_FULLMAC */
+						),
+#ifdef CONFIG_RWNX_FULLMAC
+	    TP_printk("%s txq_credits=%d, len=%d, retry_len=%d, push_limit=%d",
+		      __print_txq(__entry->txq_idx), __entry->credit,
+		      __entry->len, __entry->len_retry, __entry->limit)
+#else
+	    TP_printk("%s txq_credits=%d, len=%d, retry_len=%d",
+		      __print_txq(__entry->txq_idx), __entry->credit,
+		      __entry->len, __entry->len_retry)
+#endif /* CONFIG_RWNX_FULLMAC */
+);
+
+DECLARE_EVENT_CLASS(txq_reason_template, TP_PROTO(struct rwnx_txq *txq, u16 reason),
+		    TP_ARGS(txq, reason),
+		    TP_STRUCT__entry(__field(u16, txq_idx) __field(u16, reason)
+				     __field(u16, status)),
+		    TP_fast_assign(__entry->txq_idx = txq->idx; __entry->reason = reason;
+				   __entry->status = txq->status;),
+		    TP_printk("%s reason=%s status=%s", __print_txq(__entry->txq_idx),
+			      __print_symbolic(__entry->reason, {RWNX_TXQ_STOP_FULL, "FULL"},
+					       {RWNX_TXQ_STOP_CSA, "CSA"},
+					       {RWNX_TXQ_STOP_STA_PS, "PS"},
+					       {RWNX_TXQ_STOP_VIF_PS, "VPS"},
+					       {RWNX_TXQ_STOP_CHAN, "CHAN"},
+					       {RWNX_TXQ_STOP_MU_POS, "MU"}),
+			      __print_flags(__entry->status, "|",
+					    {RWNX_TXQ_IN_HWQ_LIST, "IN LIST"},
+					    {RWNX_TXQ_STOP_FULL, "FULL"},
+					    {RWNX_TXQ_STOP_CSA, "CSA"},
+					    {RWNX_TXQ_STOP_STA_PS, "PS"},
+					    {RWNX_TXQ_STOP_VIF_PS, "VPS"},
+					    {RWNX_TXQ_STOP_CHAN, "CHAN"},
+					    {RWNX_TXQ_STOP_MU_POS, "MU"},
+					    {RWNX_TXQ_NDEV_FLOW_CTRL, "FLW_CTRL"})));
+
+DEFINE_EVENT(txq_reason_template, txq_start,
+	     TP_PROTO(struct rwnx_txq *txq, u16 reason), TP_ARGS(txq, reason));
+
+DEFINE_EVENT(txq_reason_template, txq_stop,
+	     TP_PROTO(struct rwnx_txq *txq, u16 reason), TP_ARGS(txq, reason));
+
+TRACE_EVENT(push_desc,
+	    TP_PROTO(struct sk_buff *skb, struct rwnx_sw_txhdr *sw_txhdr, int push_flags),
+	    TP_ARGS(skb, sw_txhdr, push_flags),
+	    TP_STRUCT__entry(__field(struct sk_buff *, skb) __field(unsigned int, len)
+			     __field(u16, tx_queue) __field(u8, hw_queue)
+			     __field(u8, push_flag) __field(u32, flag)
+			     __field(s8, txq_cred) __field(u8, hwq_cred)
+			     __field(u16, pkt_cnt)
+			     __field(u8, mu_info)),
+	    TP_fast_assign(__entry->skb = skb; __entry->tx_queue = sw_txhdr->txq->idx;
+			   __entry->push_flag = push_flags;
+			   __entry->hw_queue = sw_txhdr->txq->hwq->id;
+			   __entry->txq_cred = sw_txhdr->txq->credits;
+#ifdef CONFIG_RWNX_SPLIT_TX_BUF
+			   __entry->pkt_cnt = sw_txhdr->desc.host.packet_cnt;
+#endif
+#ifdef CONFIG_RWNX_FULLMAC
+			   __entry->flag = sw_txhdr->desc.host.flags;
+#ifdef CONFIG_RWNX_SPLIT_TX_BUF
+#ifdef CONFIG_RWNX_AMSDUS_TX
+			if (sw_txhdr->amsdu.len)
+				__entry->len = sw_txhdr->amsdu.len;
+			else
+#endif /* CONFIG_RWNX_AMSDUS_TX */
+				__entry->len = sw_txhdr->desc.host.packet_len[0];
+#else
+			   __entry->len = sw_txhdr->desc.host.packet_len;
+#endif /* CONFIG_RWNX_SPLIT_TX_BUF */
+#else  /* !CONFIG_RWNX_FULLMAC */
+			   __entry->flag = sw_txhdr->desc.umac.flags;
+			   __entry->len = sw_txhdr->frame_len;
+			   __entry->sn = sw_txhdr->sn;
+#endif /* CONFIG_RWNX_FULLMAC */
+#ifdef CONFIG_RWNX_MUMIMO_TX
+			   __entry->mu_info = sw_txhdr->desc.host.mumimo_info;
+#else
+			   __entry->mu_info = 0;
+#endif
+					),
+#ifdef CONFIG_RWNX_FULLMAC
+	TP_printk("%s skb=%p (len=%d) hw_queue=%s cred_txq=%d cred_hwq=%d %s flag=%s %s %s %s",
+		  __print_txq(__entry->tx_queue), __entry->skb, __entry->len,
+		  __print_hwq(__entry->hw_queue), __entry->txq_cred,
+		  __entry->hwq_cred, __print_mu_info(__entry->mu_info),
+		  __print_flags(__entry->flag, "|", {TXU_CNTRL_RETRY, "RETRY"},
+				{TXU_CNTRL_MORE_DATA, "MOREDATA"}, {TXU_CNTRL_MGMT, "MGMT"},
+				{TXU_CNTRL_MGMT_NO_CCK, "NO_CCK"},
+				{TXU_CNTRL_MGMT_ROBUST, "ROBUST"}, {TXU_CNTRL_AMSDU, "AMSDU"},
+				{TXU_CNTRL_USE_4ADDR, "4ADDR"}, {TXU_CNTRL_EOSP, "EOSP"},
+				{TXU_CNTRL_MESH_FWD, "MESH_FWD"}, {TXU_CNTRL_TDLS, "TDLS"}),
+		  (__entry->push_flag & RWNX_PUSH_IMMEDIATE) ? "(IMMEDIATE)" : "",
+		  (!(__entry->flag & TXU_CNTRL_RETRY) &&
+		  (__entry->push_flag & RWNX_PUSH_RETRY))
+		  ? "(SW_RETRY)" : "",
+		  __print_amsdu(__entry->pkt_cnt))
+#else
+	TP_printk("%s skb=%p (len=%d) hw_queue=%s cred_txq=%d cred_hwq=%d %s flag=%x (%s) sn=%d %s",
+		  __print_txq(__entry->tx_queue), __entry->skb, __entry->len,
+		  __print_hwq(__entry->hw_queue), __entry->txq_cred,
+		  __entry->hwq_cred, __print_mu_info(__entry->mu_info),
+		  __entry->flag,
+		  __print_flags(__entry->push_flag, "|", {RWNX_PUSH_RETRY, "RETRY"},
+				{RWNX_PUSH_IMMEDIATE, "IMMEDIATE"}),
+		  __entry->sn, __print_amsdu(__entry->pkt_cnt))
+#endif /* CONFIG_RWNX_FULLMAC */
+);
+
+TRACE_EVENT(txq_queue_skb, TP_PROTO(struct sk_buff *skb, struct rwnx_txq *txq, bool retry),
+	    TP_ARGS(skb, txq, retry),
+	    TP_STRUCT__entry(__field(struct sk_buff *, skb) __field(u16, txq_idx)
+			     __field(s8, credit) __field(u16, q_len)
+			     __field(u16, q_len_retry) __field(bool, retry)),
+	TP_fast_assign(__entry->skb = skb; __entry->txq_idx = txq->idx;
+		       __entry->credit = txq->credits;
+		       __entry->q_len = skb_queue_len(&txq->sk_list);
+		       __entry->q_len_retry = txq->nb_retry;
+		       __entry->retry = retry;),
+	TP_printk("%s skb=%p retry=%d txq_credits=%d queue_len=%d (retry = %d)",
+		  __print_txq(__entry->txq_idx), __entry->skb, __entry->retry,
+		  __entry->credit, __entry->q_len, __entry->q_len_retry));
+
+#ifdef CONFIG_MAC80211_TXQ
+TRACE_EVENT(txq_wake, TP_PROTO(struct rwnx_txq *txq), TP_ARGS(txq),
+	    TP_STRUCT__entry(__field(u16, txq_idx) __field(u16, q_len)),
+	    TP_fast_assign(__entry->txq_idx = txq->idx;
+			   __entry->q_len = txq->nb_ready_mac80211;),
+	    TP_printk("%s mac80211_queue_len=%d", __print_txq(__entry->txq_idx),
+		      __entry->q_len));
+
+TRACE_EVENT(txq_drop, TP_PROTO(struct rwnx_txq *txq, unsigned long nb_drop),
+	    TP_ARGS(txq, nb_drop),
+	    TP_STRUCT__entry(__field(u16, txq_idx) __field(u16, nb_drop)),
+	    TP_fast_assign(__entry->txq_idx = txq->idx;
+			   __entry->nb_drop = nb_drop;),
+	    TP_printk("%s %u pkt have been dropped by codel in mac80211 txq",
+		      __print_txq(__entry->txq_idx), __entry->nb_drop));
+
+#endif
+
+DECLARE_EVENT_CLASS(idx_template, TP_PROTO(u16 idx), TP_ARGS(idx),
+		    TP_STRUCT__entry(__field(u16, idx)),
+		    TP_fast_assign(__entry->idx = idx;),
+		    TP_printk("idx=%d", __entry->idx));
+
+DEFINE_EVENT(idx_template, txq_vif_start, TP_PROTO(u16 idx), TP_ARGS(idx));
+
+DEFINE_EVENT(idx_template, txq_vif_stop, TP_PROTO(u16 idx), TP_ARGS(idx));
+
+TRACE_EVENT(process_hw_queue, TP_PROTO(struct rwnx_hwq *hwq), TP_ARGS(hwq),
+	    TP_STRUCT__entry(__field(u16, hwq)
+			     __array(u8, credits, CONFIG_USER_MAX)),
+	    TP_fast_assign(__entry->hwq = hwq->id;),
+	    TP_printk("hw_queue=%s hw_credits=%s", __print_hwq(__entry->hwq),
+		      __print_hwq_cred(__entry->credits)));
+
+DECLARE_EVENT_CLASS(sta_idx_template, TP_PROTO(u16 idx), TP_ARGS(idx),
+		    TP_STRUCT__entry(__field(u16, idx)),
+		    TP_fast_assign(__entry->idx = idx;),
+		    TP_printk("%s", __print_sta(__entry->idx)));
+
+DEFINE_EVENT(sta_idx_template, txq_sta_start, TP_PROTO(u16 idx), TP_ARGS(idx));
+
+DEFINE_EVENT(sta_idx_template, txq_sta_stop, TP_PROTO(u16 idx), TP_ARGS(idx));
+
+#ifdef CONFIG_RWNX_FULLMAC
+
+DEFINE_EVENT(sta_idx_template, ps_disable, TP_PROTO(u16 idx), TP_ARGS(idx));
+
+#endif /* CONFIG_RWNX_FULLMAC */
+
+TRACE_EVENT(skb_confirm,
+	    #ifdef CONFIG_RWNX_FULLMAC
+	    TP_PROTO(struct sk_buff *skb, struct rwnx_txq *txq,
+		     struct rwnx_hwq *hwq, struct tx_cfm_tag *cfm),
+	    #else
+	    TP_PROTO(struct sk_buff *skb, struct rwnx_txq *txq,
+		     struct rwnx_hwq *hwq, u8 cfm),
+	    #endif
+	TP_ARGS(skb, txq, hwq, cfm),
+	TP_STRUCT__entry(__field(struct sk_buff *, skb) __field(u16, txq_idx)
+			 __field(u8, hw_queue) __array(u8, hw_credit, CONFIG_USER_MAX)
+			 __field(s8, sw_credit) __field(s8, sw_credit_up)
+#ifdef CONFIG_RWNX_FULLMAC
+			 __field(u8, ampdu_size)
+#ifdef CONFIG_RWNX_SPLIT_TX_BUF
+			 __field(u16, amsdu)
+#endif /* CONFIG_RWNX_SPLIT_TX_BUF */
+			 __field(u16, sn)
+#endif /* CONFIG_RWNX_FULLMAC */
+					),
+	TP_fast_assign(__entry->skb = skb; __entry->txq_idx = txq->idx;
+		       __entry->hw_queue = hwq->id;
+		       __entry->sw_credit = txq->credits;
+#if defined CONFIG_RWNX_FULLMAC
+		       __entry->sw_credit_up = cfm->credits;
+		       __entry->ampdu_size = cfm->ampdu_size;
+#ifdef CONFIG_RWNX_SPLIT_TX_BUF
+		       __entry->amsdu = cfm->amsdu_size; __entry->sn = cfm->sn;
+#endif
+#else
+		       __entry->sw_credit_up = cfm
+#endif /* CONFIG_RWNX_FULLMAC */
+				),
+	TP_printk("%s skb=%p hw_queue=%s, hw_credits=%s, txq_credits=%d (+%d)"
+#ifdef CONFIG_RWNX_FULLMAC
+		  " sn=%u ampdu=%d"
+#ifdef CONFIG_RWNX_SPLIT_TX_BUF
+		  " amsdu=%u"
+#endif
+#endif
+		,
+		  __print_txq(__entry->txq_idx), __entry->skb,
+		  __print_hwq(__entry->hw_queue),
+		  __print_hwq_cred(__entry->hw_credit), __entry->sw_credit,
+		  __entry->sw_credit_up
+#ifdef CONFIG_RWNX_FULLMAC
+		,
+		  __entry->sn, __entry->ampdu_size
+#ifdef CONFIG_RWNX_SPLIT_TX_BUF
+		,
+		  __entry->amsdu
+#endif
+#endif
+			));
+
+TRACE_EVENT(credit_update, TP_PROTO(struct rwnx_txq *txq, s8_l cred_up),
+	    TP_ARGS(txq, cred_up),
+	    TP_STRUCT__entry(__field(struct sk_buff *, skb)
+			     __field(u16, txq_idx) __field(s8, sw_credit)
+			     __field(s8, sw_credit_up)),
+	    TP_fast_assign(__entry->txq_idx = txq->idx;
+			   __entry->sw_credit = txq->credits;
+			   __entry->sw_credit_up = cred_up;),
+	    TP_printk("%s txq_credits=%d (%+d)", __print_txq(__entry->txq_idx),
+		      __entry->sw_credit, __entry->sw_credit_up))
+#ifdef CONFIG_RWNX_FULLMAC
+DECLARE_EVENT_CLASS(ps_template, TP_PROTO(struct rwnx_sta *sta), TP_ARGS(sta),
+		    TP_STRUCT__entry(__field(u16, idx) __field(u16, ready_ps)
+				     __field(u16, sp_ps) __field(u16, ready_uapsd)
+				     __field(u16, sp_uapsd)),
+		    TP_fast_assign(__entry->idx = sta->sta_idx;
+				   __entry->ready_ps = sta->ps.pkt_ready[LEGACY_PS_ID];
+				   __entry->sp_ps = sta->ps.sp_cnt[LEGACY_PS_ID];
+				   __entry->ready_uapsd = sta->ps.pkt_ready[UAPSD_ID];
+				   __entry->sp_uapsd = sta->ps.sp_cnt[UAPSD_ID];),
+		    TP_printk("%s [PS] ready=%d sp=%d [UAPSD] ready=%d sp=%d",
+			      __print_sta(__entry->idx), __entry->ready_ps, __entry->sp_ps,
+			      __entry->ready_uapsd, __entry->sp_uapsd));
+
+DEFINE_EVENT(ps_template, ps_queue, TP_PROTO(struct rwnx_sta *sta), TP_ARGS(sta));
+
+DEFINE_EVENT(ps_template, ps_push, TP_PROTO(struct rwnx_sta *sta), TP_ARGS(sta));
+
+DEFINE_EVENT(ps_template, ps_enable, TP_PROTO(struct rwnx_sta *sta), TP_ARGS(sta));
+
+TRACE_EVENT(ps_traffic_update, TP_PROTO(u16 sta_idx, u8 traffic, bool uapsd),
+	    TP_ARGS(sta_idx, traffic, uapsd),
+	    TP_STRUCT__entry(__field(u16, sta_idx) __field(u8, traffic)
+			     __field(bool, uapsd)),
+	    TP_fast_assign(__entry->sta_idx = sta_idx;
+			   __entry->traffic = traffic; __entry->uapsd = uapsd;),
+	    TP_printk("%s %s%s traffic available ",
+		      __print_sta(__entry->sta_idx),
+		      __entry->traffic ? "" : "no more ",
+		      __entry->uapsd ? "U-APSD" : "legacy PS"));
+
+TRACE_EVENT(ps_traffic_req, TP_PROTO(struct rwnx_sta *sta, u16 pkt_req, u8 ps_id),
+	    TP_ARGS(sta, pkt_req, ps_id),
+	    TP_STRUCT__entry(__field(u16, idx) __field(u16, pkt_req)
+			     __field(u8, ps_id) __field(u16, ready)
+			     __field(u16, sp)),
+	    TP_fast_assign(__entry->idx = sta->sta_idx;
+			   __entry->pkt_req = pkt_req; __entry->ps_id = ps_id;
+			   __entry->ready = sta->ps.pkt_ready[ps_id];
+			   __entry->sp = sta->ps.sp_cnt[ps_id];),
+	    TP_printk("%s %s traffic request %d pkt (ready=%d, sp=%d)",
+		      __print_sta(__entry->idx),
+		      __entry->ps_id == UAPSD_ID ? "U-APSD" : "legacy PS",
+		      __entry->pkt_req, __entry->ready, __entry->sp));
+
+#ifdef CONFIG_RWNX_AMSDUS_TX
+TRACE_EVENT(amsdu_subframe, TP_PROTO(struct rwnx_sw_txhdr *sw_txhdr),
+	    TP_ARGS(sw_txhdr),
+	    TP_STRUCT__entry(__field(struct sk_buff *, skb)
+			     __field(u16, txq_idx) __field(u8, nb)
+			     __field(u32, len)),
+	    TP_fast_assign(__entry->skb = sw_txhdr->skb;
+			   __entry->nb = sw_txhdr->amsdu.nb;
+			   __entry->len = sw_txhdr->amsdu.len;
+			   __entry->txq_idx = sw_txhdr->txq->idx;),
+	    TP_printk("%s skb=%p %s nb_subframe=%d, len=%u",
+		      __print_txq(__entry->txq_idx), __entry->skb,
+		      (__entry->nb == 2) ? "Start new AMSDU" : "Add subframe",
+		      __entry->nb, __entry->len));
+#endif
+
+#endif /* CONFIG_RWNX_FULLMAC */
+
+#ifdef CONFIG_RWNX_MUMIMO_TX
+TRACE_EVENT(mu_group_update, TP_PROTO(struct rwnx_mu_group *group),
+	    TP_ARGS(group),
+	    TP_STRUCT__entry(__field(u8, nb_user) __field(u8, group_id)
+			     __array(u8, users, CONFIG_USER_MAX)),
+	    TP_fast_assign(int i; __entry->nb_user = group->user_cnt;
+			for (i = 0; i < CONFIG_USER_MAX; i++) {
+				if (group->users[i])
+					__entry->users[i] = group->users[i]->sta_idx;
+				else
+					__entry->users[i] = 0xff;
+			}
+			__entry->group_id = group->group_id;),
+	    TP_printk("Group-id = %d, Users = %s", __entry->group_id,
+		      __print_mu_group(__entry->nb_user, __entry->users)));
+
+TRACE_EVENT(mu_group_delete, TP_PROTO(int group_id), TP_ARGS(group_id),
+	    TP_STRUCT__entry(__field(u8, group_id)),
+	    TP_fast_assign(__entry->group_id = group_id;),
+	    TP_printk("Group-id = %d", __entry->group_id));
+
+TRACE_EVENT(mu_group_selection, TP_PROTO(struct rwnx_sta *sta, int group_id),
+	    TP_ARGS(sta, group_id),
+	    TP_STRUCT__entry(__field(u8, sta_idx) __field(u8, group_id)),
+	    TP_fast_assign(__entry->sta_idx = sta->sta_idx;
+			   __entry->group_id = group_id;),
+	    TP_printk("[Sta %d] Group-id = %d", __entry->sta_idx,
+		      __entry->group_id));
+
+TRACE_EVENT(txq_select_mu_group,
+	    TP_PROTO(struct rwnx_txq *txq, int group_id, int pos),
+	    TP_ARGS(txq, group_id, pos),
+	    TP_STRUCT__entry(__field(u16, txq_idx) __field(u8, group_id)
+			     __field(u8, pos)),
+	    TP_fast_assign(__entry->txq_idx = txq->idx;
+			   __entry->group_id = group_id; __entry->pos = pos;),
+	    TP_printk("%s: group=%d pos=%d", __print_txq(__entry->txq_idx),
+		      __entry->group_id, __entry->pos));
+
+#endif /* CONFIG_RWNX_MUMIMO_TX */
+
+/*****************************************************************************
+ * TRACE functions for MESH
+ ****************************************************************************/
+#ifdef CONFIG_RWNX_FULLMAC
+DECLARE_EVENT_CLASS(mesh_path_template, TP_PROTO(struct rwnx_mesh_path *mesh_path),
+		    TP_ARGS(mesh_path),
+		    TP_STRUCT__entry(__field(u8, idx) __field(u8, next_hop_sta)
+				     __array(u8, tgt_mac, ETH_ALEN)),
+		    TP_fast_assign(__entry->idx = mesh_path->path_idx;
+				   memcpy(__entry->tgt_mac, &mesh_path->tgt_mac_addr, ETH_ALEN);
+				if (mesh_path->p_nhop_sta)
+					__entry->next_hop_sta = mesh_path->p_nhop_sta->sta_idx;
+				else
+					__entry->next_hop_sta = 0xff;),
+		    TP_printk("Mpath(%d): target=%pM next_hop=STA-%d", __entry->idx,
+			      __entry->tgt_mac, __entry->next_hop_sta));
+
+DEFINE_EVENT(mesh_path_template, mesh_create_path,
+	     TP_PROTO(struct rwnx_mesh_path *mesh_path), TP_ARGS(mesh_path));
+
+DEFINE_EVENT(mesh_path_template, mesh_delete_path,
+	     TP_PROTO(struct rwnx_mesh_path *mesh_path), TP_ARGS(mesh_path));
+
+DEFINE_EVENT(mesh_path_template, mesh_update_path,
+	     TP_PROTO(struct rwnx_mesh_path *mesh_path), TP_ARGS(mesh_path));
+
+#endif /* CONFIG_RWNX_FULLMAC */
+
+/*****************************************************************************
+ * TRACE functions for RADAR
+ ****************************************************************************/
+#ifdef CONFIG_RWNX_RADAR
+TRACE_EVENT(radar_pulse, TP_PROTO(u8 chain, struct radar_pulse *pulse),
+	    TP_ARGS(chain, pulse),
+	    TP_STRUCT__entry(__field(u8, chain) __field(s16, freq)
+			     __field(u16, pri) __field(u8, len)
+			     __field(u8, fom)),
+	    TP_fast_assign(__entry->freq = pulse->freq * 2;
+			   __entry->len = pulse->len * 2;
+			   __entry->fom = pulse->fom * 6;
+			   __entry->pri = pulse->rep; __entry->chain = chain;),
+	    TP_printk("%s: PRI=%.5d LEN=%.3d FOM=%.2d%% freq=%dMHz ",
+		      __print_symbolic(__entry->chain, {RWNX_RADAR_RIU, "RIU"},
+				       {RWNX_RADAR_FCU, "FCU"}),
+				       __entry->pri, __entry->len, __entry->fom, __entry->freq));
+
+TRACE_EVENT(radar_detected, TP_PROTO(u8 chain, u8 region, s16 freq, u8 type, u16 pri),
+	    TP_ARGS(chain, region, freq, type, pri),
+	    TP_STRUCT__entry(__field(u8, chain) __field(u8, region) __field(s16, freq)
+			     __field(u8, type) __field(u16, pri)),
+	    TP_fast_assign(__entry->chain = chain; __entry->region = region;
+			   __entry->freq = freq; __entry->type = type;
+			   __entry->pri = pri;),
+	    TP_printk("%s: region=%s type=%d freq=%dMHz (pri=%dus)",
+		      __print_symbolic(__entry->chain, {RWNX_RADAR_RIU, "RIU"},
+				       {RWNX_RADAR_FCU, "FCU"}),
+		      __print_symbolic(__entry->region, {NL80211_DFS_UNSET, "UNSET"},
+				       {NL80211_DFS_FCC, "FCC"},
+				       {NL80211_DFS_ETSI, "ETSI"},
+				       {NL80211_DFS_JP, "JP"}),
+		      __entry->type, __entry->freq, __entry->pri));
+
+TRACE_EVENT(radar_set_region, TP_PROTO(u8 region), TP_ARGS(region),
+	    TP_STRUCT__entry(__field(u8, region)),
+	    TP_fast_assign(__entry->region = region;),
+	    TP_printk("region=%s",
+		      __print_symbolic(__entry->region,
+				       {NL80211_DFS_UNSET, "UNSET"},
+				       {NL80211_DFS_FCC, "FCC"},
+				       {NL80211_DFS_ETSI, "ETSI"},
+				       {NL80211_DFS_JP, "JP"})));
+
+TRACE_EVENT(radar_enable_detection, TP_PROTO(u8 region, u8 enable, u8 chain),
+	    TP_ARGS(region, enable, chain),
+	    TP_STRUCT__entry(__field(u8, region) __field(u8, chain)
+			     __field(u8, enable)),
+	    TP_fast_assign(__entry->chain = chain; __entry->enable = enable;
+			   __entry->region = region;),
+	    TP_printk("%s: %s radar detection %s",
+		      __print_symbolic(__entry->chain, {RWNX_RADAR_RIU, "RIU"},
+				       {RWNX_RADAR_FCU, "FCU"}),
+		      __print_symbolic(__entry->enable,
+				       {RWNX_RADAR_DETECT_DISABLE, "Disable"},
+				       {RWNX_RADAR_DETECT_ENABLE,
+				       "Enable (no report)"},
+				       {RWNX_RADAR_DETECT_REPORT, "Enable"}),
+		      __entry->enable == RWNX_RADAR_DETECT_DISABLE
+				? ""	: __print_symbolic(__entry->region,
+							   {NL80211_DFS_UNSET, "UNSET"},
+							   {NL80211_DFS_FCC, "FCC"},
+							   {NL80211_DFS_ETSI, "ETSI"},
+							   {NL80211_DFS_JP, "JP"})));
+#endif /* CONFIG_RWNX_RADAR */
+
+/*****************************************************************************
+ * TRACE functions for IPC message
+ ****************************************************************************/
+#include "rwnx_strs.h"
+
+DECLARE_EVENT_CLASS(ipc_msg_template, TP_PROTO(u16 id), TP_ARGS(id),
+		    TP_STRUCT__entry(__field(u16, id)),
+		    TP_fast_assign(__entry->id = id;),
+		    TP_printk("%s (%d - %d)", RWNX_ID2STR(__entry->id),
+			      MSG_T(__entry->id), MSG_I(__entry->id)));
+
+DEFINE_EVENT(ipc_msg_template, msg_send, TP_PROTO(u16 id), TP_ARGS(id));
+
+DEFINE_EVENT(ipc_msg_template, msg_recv, TP_PROTO(u16 id), TP_ARGS(id));
+
+#endif /* !defined(_RWNX_EVENTS_H) || defined(TRACE_HEADER_MULTI_READ) */
+
+#undef TRACE_INCLUDE_PATH
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_PATH AIC_TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_FILE rwnx_events
+#include <trace/define_trace.h>
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_irqs.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_irqs.c
new file mode 100644
index 0000000000000..a941ab6e76e0b
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_irqs.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief IRQ handler
+ *
+ ******************************************************************************
+ */
+#include <linux/interrupt.h>
+
+#include "rwnx_irqs.h"
+#include "ipc_host.h"
+#include "rwnx_defs.h"
+#include "rwnx_prof.h"
+
+/**
+ * rwnx_irq_hdlr - IRQ handler
+ *
+ * Handler registered by the platform driver
+ */
+irqreturn_t rwnx_irq_hdlr(int irq, void *dev_id)
+{
+	struct rwnx_hw *rwnx_hw = (struct rwnx_hw *)dev_id;
+
+	disable_irq_nosync(irq);
+	tasklet_schedule(&rwnx_hw->task);
+	return IRQ_HANDLED;
+}
+
+/**
+ * rwnx_task - Bottom half for IRQ handler
+ *
+ * Read irq status and process accordingly
+ */
+void rwnx_task(unsigned long data)
+{
+	struct rwnx_hw *rwnx_hw = (struct rwnx_hw *)data;
+
+	spin_lock_bh(&rwnx_hw->tx_lock);
+	rwnx_hwq_process_all(rwnx_hw);
+	spin_unlock_bh(&rwnx_hw->tx_lock);
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_irqs.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_irqs.h
new file mode 100644
index 0000000000000..ff6c766c727c3
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_irqs.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_IRQS_H_
+#define _RWNX_IRQS_H_
+
+#include <linux/interrupt.h>
+
+/* IRQ handler to be registered by platform driver */
+irqreturn_t rwnx_irq_hdlr(int irq, void *dev_id);
+
+void rwnx_task(unsigned long data);
+
+#endif /* _RWNX_IRQS_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_rx.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_rx.c
new file mode 100644
index 0000000000000..75971f7d4cb67
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_rx.c
@@ -0,0 +1,1742 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief RX function definitions
+ *
+ ******************************************************************************
+ */
+
+#include "rwnx_defs.h"
+#include "rwnx_prof.h"
+#include "rwnx_tx.h"
+#include "rwnx_rx.h"
+#include <linux/rtnetlink.h>
+#include <linux/vmalloc.h>
+#ifdef CONFIG_RWNX_BFMER
+#include "rwnx_bfmer.h"
+#endif //(CONFIG_RWNX_BFMER)
+#ifdef CONFIG_RWNX_FULLMAC
+#include "rwnx_debugfs.h"
+#include "rwnx_msg_tx.h"
+#include "rwnx_tdls.h"
+#endif /* CONFIG_RWNX_FULLMAC */
+#include "aicwf_txrxif.h"
+#include "rwnx_compat.h"
+#include "rwnx_events.h"
+#include "rwnx_msg_rx.h"
+#ifdef CONFIG_SDIO_BT
+#include "aic_btsdio.h"
+#endif
+#include "rwnx_mod_params.h"
+#include "rwnx_wakelock.h"
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+#include "aicwf_compat_8800d80.h"
+#endif
+#ifdef CONFIG_AIC8800_USE_WIRELESS_EXT
+#include "aicwf_wext_linux.h"
+#endif
+
+void rwnx_cfg80211_unlink_bss(struct rwnx_hw *rwnx_hw,
+			      struct rwnx_vif *rwnx_vif);
+
+static int rwnx_freq_to_idx(struct rwnx_hw *rwnx_hw, int freq)
+{
+	struct ieee80211_supported_band *sband = NULL;
+	int band, ch, idx = 0;
+
+	for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; band++) {
+#ifdef CONFIG_RWNX_FULLMAC
+		sband = rwnx_hw->wiphy->bands[band];
+#endif /* CONFIG_RWNX_FULLMAC */
+		if (!sband)
+			continue;
+
+		for (ch = 0; ch < sband->n_channels; ch++, idx++) {
+			if (sband->channels[ch].center_freq == freq)
+				goto exit;
+		}
+	}
+
+	WARN_ON_ONCE(1);
+
+exit:
+	// Channel has been found, return the index
+	return idx;
+}
+
+/***************************************************************************
+ * Messages from MM task
+ **************************************************************************/
+static inline int rwnx_rx_chan_pre_switch_ind(struct rwnx_hw *rwnx_hw,
+					      struct rwnx_cmd *cmd,
+					      struct ipc_e2a_msg *msg)
+{
+	struct rwnx_vif *rwnx_vif;
+	int chan_idx = ((struct mm_channel_pre_switch_ind *)msg->param)->chan_index;
+
+	REG_SW_SET_PROFILING_CHAN(rwnx_hw, SW_PROF_CHAN_CTXT_PSWTCH_BIT);
+
+#ifdef CONFIG_RWNX_FULLMAC
+	list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
+		if (rwnx_vif->up && rwnx_vif->ch_index == chan_idx)
+			rwnx_txq_vif_stop(rwnx_vif, RWNX_TXQ_STOP_CHAN, rwnx_hw);
+	}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+	REG_SW_CLEAR_PROFILING_CHAN(rwnx_hw, SW_PROF_CHAN_CTXT_PSWTCH_BIT);
+
+	return 0;
+}
+
+static inline int rwnx_rx_chan_switch_ind(struct rwnx_hw *rwnx_hw,
+					  struct rwnx_cmd *cmd,
+					  struct ipc_e2a_msg *msg)
+{
+	struct rwnx_vif *rwnx_vif;
+	int chan_idx = ((struct mm_channel_switch_ind *)msg->param)->chan_index;
+	bool roc = ((struct mm_channel_switch_ind *)msg->param)->roc;
+	bool roc_tdls = ((struct mm_channel_switch_ind *)msg->param)->roc_tdls;
+
+	REG_SW_SET_PROFILING_CHAN(rwnx_hw, SW_PROF_CHAN_CTXT_SWTCH_BIT);
+
+#ifdef CONFIG_RWNX_FULLMAC
+	if (roc_tdls) {
+		u8 vif_index = ((struct mm_channel_switch_ind *)msg->param)->vif_index;
+
+		list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
+			if (rwnx_vif->vif_index == vif_index) {
+				rwnx_vif->roc_tdls = true;
+				rwnx_txq_tdls_sta_start(rwnx_vif, RWNX_TXQ_STOP_CHAN, rwnx_hw);
+			}
+		}
+	} else if (!roc) {
+		list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
+			if (rwnx_vif->up && rwnx_vif->ch_index == chan_idx)
+				rwnx_txq_vif_start(rwnx_vif, RWNX_TXQ_STOP_CHAN, rwnx_hw);
+		}
+	} else {
+		/* Retrieve the allocated RoC element */
+		struct rwnx_roc_elem *roc_elem = rwnx_hw->roc_elem;
+		/* Get VIF on which RoC has been started */
+		// rwnx_vif = netdev_priv(roc_elem->wdev->netdev);
+
+		/* For debug purpose (use ftrace kernel option) */
+		// trace_switch_roc(rwnx_vif->vif_index);
+
+		if (roc_elem) {
+			/* If mgmt_roc is true, remain on channel has been started by
+			 * ourself
+			 */
+			if (!roc_elem->mgmt_roc) {
+				/* Inform the host that we have switch on the indicated
+				 * off-channel
+				 */
+				cfg80211_ready_on_channel(roc_elem->wdev,
+							  (u64)(rwnx_hw->roc_cookie_cnt),
+							  roc_elem->chan, roc_elem->duration,
+							  GFP_ATOMIC);
+			}
+
+			/* Keep in mind that we have switched on the channel */
+			roc_elem->on_chan = true;
+		} else {
+			AICWFDBG(LOGDEBUG, "roc_elem == null\n");
+		}
+		// Enable traffic on OFF channel queue
+		rwnx_txq_offchan_start(rwnx_hw);
+	}
+
+	tasklet_schedule(&rwnx_hw->task);
+
+	rwnx_hw->cur_chanctx = chan_idx;
+	rwnx_radar_detection_enable_on_cur_channel(rwnx_hw);
+
+#endif /* CONFIG_RWNX_FULLMAC */
+
+	REG_SW_CLEAR_PROFILING_CHAN(rwnx_hw, SW_PROF_CHAN_CTXT_SWTCH_BIT);
+
+	return 0;
+}
+
+static inline int rwnx_rx_tdls_chan_switch_cfm(struct rwnx_hw *rwnx_hw,
+					       struct rwnx_cmd *cmd,
+					       struct ipc_e2a_msg *msg)
+{
+	return 0;
+}
+
+static inline int rwnx_rx_tdls_chan_switch_ind(struct rwnx_hw *rwnx_hw,
+					       struct rwnx_cmd *cmd,
+					       struct ipc_e2a_msg *msg)
+{
+#ifdef CONFIG_RWNX_FULLMAC
+	// Enable traffic on OFF channel queue
+	rwnx_txq_offchan_start(rwnx_hw);
+
+	return 0;
+#endif
+}
+
+static inline int rwnx_rx_tdls_chan_switch_base_ind(struct rwnx_hw *rwnx_hw,
+						    struct rwnx_cmd *cmd,
+						    struct ipc_e2a_msg *msg)
+{
+	struct rwnx_vif *rwnx_vif;
+	u8 vif_index = ((struct tdls_chan_switch_base_ind *)msg->param)->vif_index;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+#ifdef CONFIG_RWNX_FULLMAC
+	list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
+		if (rwnx_vif->vif_index == vif_index) {
+			rwnx_vif->roc_tdls = false;
+			rwnx_txq_tdls_sta_stop(rwnx_vif, RWNX_TXQ_STOP_CHAN, rwnx_hw);
+		}
+	}
+	return 0;
+#endif
+}
+
+static inline int rwnx_rx_tdls_peer_ps_ind(struct rwnx_hw *rwnx_hw,
+					   struct rwnx_cmd *cmd,
+					   struct ipc_e2a_msg *msg)
+{
+	struct rwnx_vif *rwnx_vif;
+	u8 vif_index = ((struct tdls_peer_ps_ind *)msg->param)->vif_index;
+	bool ps_on = ((struct tdls_peer_ps_ind *)msg->param)->ps_on;
+
+#ifdef CONFIG_RWNX_FULLMAC
+	list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
+		if (rwnx_vif->vif_index == vif_index) {
+			rwnx_vif->sta.tdls_sta->tdls.ps_on = ps_on;
+			// Update PS status for the TDLS station
+			rwnx_ps_bh_enable(rwnx_hw, rwnx_vif->sta.tdls_sta, ps_on);
+		}
+	}
+
+	return 0;
+#endif
+}
+
+static inline int rwnx_rx_remain_on_channel_exp_ind(struct rwnx_hw *rwnx_hw,
+						    struct rwnx_cmd *cmd,
+						    struct ipc_e2a_msg *msg)
+{
+#ifdef CONFIG_RWNX_FULLMAC
+	/* Retrieve the allocated RoC element */
+	struct rwnx_roc_elem *roc_elem = rwnx_hw->roc_elem;
+	/* Get VIF on which RoC has been started */
+	struct rwnx_vif *rwnx_vif;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	if (!roc_elem)
+		return 0;
+
+	rwnx_vif = container_of(roc_elem->wdev, struct rwnx_vif, wdev);
+	/* For debug purpose (use ftrace kernel option) */
+#ifdef AIC8800_CREATE_TRACE_POINTS
+	trace_roc_exp(rwnx_vif->vif_index);
+#endif
+	/* If mgmt_roc is true, remain on channel has been started by ourself */
+	/* If RoC has been cancelled before we switched on channel, do not call
+	 * cfg80211
+	 */
+	if (!roc_elem->mgmt_roc && roc_elem->on_chan) {
+		/* Inform the host that off-channel period has expired */
+		cfg80211_remain_on_channel_expired(roc_elem->wdev,
+						   (u64)(rwnx_hw->roc_cookie_cnt),
+						   roc_elem->chan, GFP_ATOMIC);
+	}
+
+	/* De-init offchannel TX queue */
+	rwnx_txq_offchan_deinit(rwnx_vif);
+
+	/* Increase the cookie counter cannot be zero */
+	rwnx_hw->roc_cookie_cnt++;
+
+	if (rwnx_hw->roc_cookie_cnt == 0)
+		rwnx_hw->roc_cookie_cnt = 1;
+
+	/* Free the allocated RoC element */
+	kfree(roc_elem);
+	rwnx_hw->roc_elem = NULL;
+
+#endif /* CONFIG_RWNX_FULLMAC */
+	return 0;
+}
+
+static inline int rwnx_rx_p2p_vif_ps_change_ind(struct rwnx_hw *rwnx_hw,
+						struct rwnx_cmd *cmd,
+						struct ipc_e2a_msg *msg)
+{
+	int vif_idx = ((struct mm_p2p_vif_ps_change_ind *)msg->param)->vif_index;
+	int ps_state = ((struct mm_p2p_vif_ps_change_ind *)msg->param)->ps_state;
+	struct rwnx_vif *vif_entry;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+#ifdef CONFIG_RWNX_FULLMAC
+	vif_entry = rwnx_hw->vif_table[vif_idx];
+
+	if (vif_entry)
+		goto found_vif;
+#endif /* CONFIG_RWNX_FULLMAC */
+
+	goto exit;
+found_vif:
+
+#ifdef CONFIG_RWNX_FULLMAC
+	if (ps_state == MM_PS_MODE_OFF) {
+		// Start TX queues for provided VIF
+		rwnx_txq_vif_start(vif_entry, RWNX_TXQ_STOP_VIF_PS, rwnx_hw);
+	} else {
+		// Stop TX queues for provided VIF
+		rwnx_txq_vif_stop(vif_entry, RWNX_TXQ_STOP_VIF_PS, rwnx_hw);
+	}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+exit:
+	return 0;
+}
+
+static inline int rwnx_rx_channel_survey_ind(struct rwnx_hw *rwnx_hw,
+					     struct rwnx_cmd *cmd,
+					     struct ipc_e2a_msg *msg)
+{
+	struct mm_channel_survey_ind *ind =
+		(struct mm_channel_survey_ind *)msg->param;
+	// Get the channel index
+	int idx = rwnx_freq_to_idx(rwnx_hw, ind->freq);
+	// Get the survey
+	struct rwnx_survey_info *rwnx_survey;
+
+	if (idx > ARRAY_SIZE(rwnx_hw->survey))
+		return 0;
+
+	rwnx_survey = &rwnx_hw->survey[idx];
+
+	// Store the received parameters
+	rwnx_survey->chan_time_ms = ind->chan_time_ms;
+	rwnx_survey->chan_time_busy_ms = ind->chan_time_busy_ms;
+	rwnx_survey->noise_dbm = ind->noise_dbm;
+	rwnx_survey->filled = (SURVEY_INFO_TIME | SURVEY_INFO_TIME_BUSY);
+
+	if (ind->noise_dbm != 0)
+		rwnx_survey->filled |= SURVEY_INFO_NOISE_DBM;
+
+	return 0;
+}
+
+static inline int rwnx_rx_p2p_noa_upd_ind(struct rwnx_hw *rwnx_hw,
+					  struct rwnx_cmd *cmd,
+					  struct ipc_e2a_msg *msg)
+{
+	return 0;
+}
+
+static inline int rwnx_rx_rssi_status_ind(struct rwnx_hw *rwnx_hw,
+					  struct rwnx_cmd *cmd,
+					  struct ipc_e2a_msg *msg)
+{
+	struct mm_rssi_status_ind *ind = (struct mm_rssi_status_ind *)msg->param;
+	int vif_idx = ind->vif_index;
+	bool rssi_status = ind->rssi_status;
+
+	struct rwnx_vif *vif_entry;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+#ifdef CONFIG_RWNX_FULLMAC
+	vif_entry = rwnx_hw->vif_table[vif_idx];
+	if (vif_entry) {
+		cfg80211_cqm_rssi_notify(vif_entry->ndev,
+					 rssi_status ? NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW
+					 : NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH,
+					 ind->rssi, GFP_ATOMIC);
+	}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+	return 0;
+}
+
+static inline int rwnx_rx_pktloss_notify_ind(struct rwnx_hw *rwnx_hw,
+					     struct rwnx_cmd *cmd,
+					     struct ipc_e2a_msg *msg)
+{
+#ifdef CONFIG_RWNX_FULLMAC
+	struct mm_pktloss_ind *ind = (struct mm_pktloss_ind *)msg->param;
+	struct rwnx_vif *vif_entry;
+	int vif_idx = ind->vif_index;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	vif_entry = rwnx_hw->vif_table[vif_idx];
+	if (vif_entry) {
+		cfg80211_cqm_pktloss_notify(vif_entry->ndev,
+					    (const u8 *)ind->mac_addr.array,
+					    ind->num_packets, GFP_ATOMIC);
+	}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+	return 0;
+}
+
+static inline int rwnx_apm_staloss_ind(struct rwnx_hw *rwnx_hw,
+				       struct rwnx_cmd *cmd,
+				       struct ipc_e2a_msg *msg)
+{
+	struct mm_apm_staloss_ind *ind = (struct mm_apm_staloss_ind *)msg->param;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	memcpy(rwnx_hw->sta_mac_addr, ind->mac_addr, 6);
+	rwnx_hw->apm_vif_idx = ind->vif_idx;
+
+	queue_work(rwnx_hw->apm_staloss_wq, &rwnx_hw->apm_staloss_work);
+
+	return 0;
+}
+
+static struct sk_buff *arp_80211_to_8023(struct sk_buff *skb, u8_l hdrlen)
+{
+	struct ethhdr *eth;
+	size_t payload_len;
+	struct sk_buff *new_skb = NULL;
+	char *llc;
+	u8 da[MAC_ADDR_LEN] = {0};
+	u8 sa[MAC_ADDR_LEN] = {0};
+
+	if (skb->len <= hdrlen) {
+		AICWFDBG(LOGERROR, "%s skb_len err1\n", __func__);
+		return NULL;
+	}
+
+	memcpy(da, &skb->data[4], 6);
+	memcpy(sa, &skb->data[16], 6);
+
+	skb_pull(skb, hdrlen);
+	llc = skb->data;
+	if (llc[0] == 0xAA && llc[1] == 0xAA && llc[2] == 0x03) {
+		if (skb->len < 8) {
+			AICWFDBG(LOGERROR, "%s skb_len err2\n", __func__);
+			return NULL;
+		}
+		skb_pull(skb, 8);
+	}
+	payload_len = skb->len;
+
+	new_skb = __dev_alloc_skb(ETH_HLEN + payload_len, GFP_ATOMIC);
+	if (!new_skb) {
+		AICWFDBG(LOGERROR, "%s skb alloc fail\n", __func__);
+		return NULL;
+	}
+
+	skb_reserve(new_skb, ETH_HLEN);
+	skb_put(new_skb, payload_len);
+	memcpy(new_skb->data, skb->data, payload_len);
+
+	eth = (struct ethhdr *)skb_push(new_skb, ETH_HLEN);
+
+	memcpy(eth->h_dest, da, ETH_ALEN);
+	memcpy(eth->h_source, sa, ETH_ALEN);
+
+	eth->h_proto = htons(ETH_P_ARP);
+
+	new_skb->protocol = eth->h_proto;
+	new_skb->dev = skb->dev;
+
+	return new_skb;
+}
+
+static inline int rwnx_arp_announcement_ind(struct rwnx_hw *rwnx_hw,
+					    struct rwnx_cmd *cmd,
+					    struct ipc_e2a_msg *msg)
+{
+	struct rwnx_vif *vif;
+	struct sk_buff *skb = NULL;
+	struct sk_buff *new_skb = NULL;
+	struct hw_rxhdr rxhdr;
+	int hdrlen = 24;
+	struct rxu_arp_announcement_ind *ind = (struct rxu_arp_announcement_ind *)msg->param;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	//AICWFDBG(LOGDEBUG, "vif: %d, length: %d, offset: %d\n",
+	// ind->vif_index, ind->length, ind->offset);
+
+	AICWFDBG(LOGDEBUG, "%s, vif: %d, length: %d, offset: %d\n",
+		 __func__, ind->vif_index, ind->length, ind->offset);
+
+	vif = rwnx_hw->vif_table[ind->vif_index];
+	rxhdr.flags_is_amsdu = false;
+	rxhdr.flags_dst_idx = 0xff;
+
+	if (ind->payload[0] & 0x80)
+		hdrlen += 2;
+
+	switch ((ind->rx_status >> 2) & 0x7) {
+	case RWNX_RX_HD_DECR_CCMP128:
+		hdrlen += 8;
+		break;
+	case RWNX_RX_HD_DECR_TKIP:
+		hdrlen += 8;
+		break;
+	case RWNX_RX_HD_DECR_WEP:
+		hdrlen += 4;
+		break;
+	case RWNX_RX_HD_DECR_WAPI:
+		hdrlen += 18;
+		break;
+	default:
+		break;
+	}
+
+	skb = __dev_alloc_skb(ind->length, GFP_ATOMIC);
+	if (!skb) {
+		AICWFDBG(LOGERROR, "%s skb alloc fail\n", __func__);
+		return -1;
+	}
+	skb_put(skb, ind->length);
+	memcpy(skb->data, ind->payload, ind->length);
+	skb->dev = vif->ndev;
+
+	new_skb = arp_80211_to_8023(skb, hdrlen);
+	if (!new_skb) {
+		AICWFDBG(LOGERROR, "arp_80211_to_8023 fail\n");
+		dev_kfree_skb(skb);
+		return -1;
+	}
+	if (!rwnx_rx_data_skb(rwnx_hw, vif, new_skb, &rxhdr)) {
+		AICWFDBG(LOGERROR, "rwnx_rx_data_skb fail\n");
+		dev_kfree_skb(new_skb);
+		dev_kfree_skb(skb);
+		return -1;
+	}
+
+	dev_kfree_skb(skb);
+	return 0;
+}
+
+#ifdef CONFIG_SDIO_BT
+static inline int rwnx_bt_recv_ind(struct rwnx_hw *rwnx_hw,
+				   struct rwnx_cmd *cmd,
+				   struct ipc_e2a_msg *msg)
+{
+	struct mm_bt_recv_ind *ind = (struct mm_bt_recv_ind *)msg->param;
+
+	bt_sdio_recv(ind->bt_data, ind->data_len);
+	return 0;
+}
+#endif
+
+static inline int rwnx_rx_csa_counter_ind(struct rwnx_hw *rwnx_hw,
+					  struct rwnx_cmd *cmd,
+					  struct ipc_e2a_msg *msg)
+{
+	struct mm_csa_counter_ind *ind = (struct mm_csa_counter_ind *)msg->param;
+	struct rwnx_vif *vif;
+	bool found = false;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	// Look for VIF entry
+	list_for_each_entry(vif, &rwnx_hw->vifs, list) {
+		if (vif->vif_index == ind->vif_index) {
+			found = true;
+			break;
+		}
+	}
+
+	if (found) {
+#ifdef CONFIG_RWNX_FULLMAC
+		if (vif->ap.csa)
+			vif->ap.csa->count = ind->csa_count;
+		else
+			netdev_err(vif->ndev, "CSA counter update but no active CSA");
+
+#endif
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_RWNX_FULLMAC
+static inline int rwnx_rx_csa_finish_ind(struct rwnx_hw *rwnx_hw,
+					 struct rwnx_cmd *cmd,
+					 struct ipc_e2a_msg *msg)
+{
+	struct mm_csa_finish_ind *ind = (struct mm_csa_finish_ind *)msg->param;
+	struct rwnx_vif *vif;
+	bool found = false;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	// Look for VIF entry
+	list_for_each_entry(vif, &rwnx_hw->vifs, list) {
+		if (vif->vif_index == ind->vif_index) {
+			found = true;
+			break;
+		}
+	}
+
+	if (found) {
+		if (RWNX_VIF_TYPE(vif) == NL80211_IFTYPE_AP ||
+		    RWNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_GO) {
+			if (vif->ap.csa) {
+				vif->ap.csa->status = ind->status;
+				vif->ap.csa->ch_idx = ind->chan_idx;
+				schedule_work(&vif->ap.csa->work);
+			} else {
+				netdev_err(vif->ndev,
+					   "CSA finish indication but no active CSA");
+			}
+		} else {
+			if (ind->status == 0) {
+				rwnx_chanctx_unlink(vif);
+				rwnx_chanctx_link(vif, ind->chan_idx, NULL);
+				if (rwnx_hw->cur_chanctx == ind->chan_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);
+				}
+			}
+		}
+	}
+
+	return 0;
+}
+
+static inline int rwnx_rx_csa_traffic_ind(struct rwnx_hw *rwnx_hw,
+					  struct rwnx_cmd *cmd,
+					  struct ipc_e2a_msg *msg)
+{
+	struct mm_csa_traffic_ind *ind = (struct mm_csa_traffic_ind *)msg->param;
+	struct rwnx_vif *vif;
+	bool found = false;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	// Look for VIF entry
+	list_for_each_entry(vif, &rwnx_hw->vifs, list) {
+		if (vif->vif_index == ind->vif_index) {
+			found = true;
+			break;
+		}
+	}
+
+	if (found) {
+		if (ind->enable)
+			rwnx_txq_vif_start(vif, RWNX_TXQ_STOP_CSA, rwnx_hw);
+		else
+			rwnx_txq_vif_stop(vif, RWNX_TXQ_STOP_CSA, rwnx_hw);
+	}
+
+	return 0;
+}
+
+static inline int rwnx_rx_ps_change_ind(struct rwnx_hw *rwnx_hw,
+					struct rwnx_cmd *cmd,
+					struct ipc_e2a_msg *msg)
+{
+	struct mm_ps_change_ind *ind = (struct mm_ps_change_ind *)msg->param;
+	struct rwnx_sta *sta = &rwnx_hw->sta_table[ind->sta_idx];
+
+	if (ind->sta_idx >= (NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX)) {
+		wiphy_err(rwnx_hw->wiphy, "Invalid sta index reported by fw %d\n",
+			  ind->sta_idx);
+		return 1;
+	}
+
+	netdev_dbg(rwnx_hw->vif_table[sta->vif_idx]->ndev,
+		   "Sta %d, change PS mode to %s", sta->sta_idx,
+		   ind->ps_state ? "ON" : "OFF");
+
+	if (sta->valid) {
+		rwnx_ps_bh_enable(rwnx_hw, sta, ind->ps_state);
+	} else if (rwnx_hw->adding_sta) {
+		sta->ps.active = ind->ps_state ? true : false;
+	} else {
+		if (rwnx_hw->vif_table[sta->vif_idx] &&
+		    rwnx_hw->vif_table[sta->vif_idx]->ndev)
+			netdev_err(rwnx_hw->vif_table[sta->vif_idx]->ndev,
+				   "Ignore PS mode change on invalid sta\n");
+	}
+
+	return 0;
+}
+
+static inline int rwnx_rx_traffic_req_ind(struct rwnx_hw *rwnx_hw,
+					  struct rwnx_cmd *cmd,
+					  struct ipc_e2a_msg *msg)
+{
+	struct mm_traffic_req_ind *ind = (struct mm_traffic_req_ind *)msg->param;
+	struct rwnx_sta *sta = &rwnx_hw->sta_table[ind->sta_idx];
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	netdev_dbg(rwnx_hw->vif_table[sta->vif_idx]->ndev,
+		   "Sta %d, asked for %d pkt", sta->sta_idx, ind->pkt_cnt);
+
+	rwnx_ps_bh_traffic_req(rwnx_hw, sta, ind->pkt_cnt,
+			       ind->uapsd ? UAPSD_ID : LEGACY_PS_ID);
+
+	return 0;
+}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+/***************************************************************************
+ * Messages from SCANU task
+ **************************************************************************/
+#ifdef CONFIG_RWNX_FULLMAC
+
+static inline int rwnx_rx_scanu_start_cfm(struct rwnx_hw *rwnx_hw,
+					  struct rwnx_cmd *cmd,
+					  struct ipc_e2a_msg *msg)
+{
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+	struct ieee80211_regdomain *regdomain;
+#endif
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+#ifdef CONFIG_AIC8800_SCHED_SCAN
+#ifdef CONFIG_AIC8800_USE_WIRELESS_EXT
+	if (rwnx_hw->scan_request && !rwnx_hw->is_sched_scan && !rwnx_hw->wext_scan)
+#else
+	if (rwnx_hw->scan_request && !rwnx_hw->is_sched_scan)
+#endif
+#endif // CONFIG_AIC8800_SCHED_SCAN
+	{
+		struct cfg80211_scan_info info = {
+			.aborted = false,
+		};
+
+		cfg80211_scan_done(rwnx_hw->scan_request, &info);
+	}
+#ifdef CONFIG_AIC8800_USE_WIRELESS_EXT
+	else if (rwnx_hw->wext_scan) {
+		rwnx_hw->wext_scan = 0;
+		AICWFDBG(LOGDEBUG, "%s rwnx_hw->wext_scan done!!\r\n", __func__);
+		kfree(rwnx_hw->scan_request->ssids);
+		if (rwnx_hw->scan_request)
+			vfree(rwnx_hw->scan_request);
+		complete(&rwnx_hw->wext_scan_com);
+	}
+#endif
+
+#ifdef CONFIG_AIC8800_SCHED_SCAN
+	if (rwnx_hw->is_sched_scan) {
+		AICWFDBG(LOGINFO, "%s cfg80211_sched_scan_results \r\n", __func__);
+		cfg80211_sched_scan_results(rwnx_hw->scan_request->wiphy,
+					    rwnx_hw->sched_scan_req->reqid);
+		kfree(rwnx_hw->scan_request);
+		rwnx_hw->is_sched_scan = false;
+	}
+#endif // CONFIG_AIC8800_SCHED_SCAN
+
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+	if (rwnx_hw->ccode.auto_set && rwnx_hw->ccode.ccode_find) {
+#ifdef CONFIG_AIC8800_REGION_PW
+		schedule_work(&rwnx_hw->region_wk);
+#endif
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+		if (!work_pending(&rwnx_hw->pwlt_wk))
+			schedule_work(&rwnx_hw->pwlt_wk);
+#endif
+		regdomain = get_regdomain_from_rwnx_db(rwnx_hw->wiphy, rwnx_hw->country_abbr);
+		regulatory_set_wiphy_regd(rwnx_hw->wiphy, regdomain);
+		rwnx_hw->ccode.ccode_cnt = 0;
+		rwnx_hw->ccode.ccode_find = false;
+		rwnx_hw->ccode.ccode_set = true;
+	}
+#endif
+
+	rwnx_hw->scan_request = NULL;
+	rwnx_hw->scanning = 0;
+	rwnx_wakeup_unlock(rwnx_hw->ws_scan);
+
+	return 0;
+}
+
+static void parse_ssid_from_id(const u8 *ssid_id, char ssid_name[33], u8 *ssid_len)
+{
+	u8 len;
+
+	if (!ssid_id)
+		return;
+
+	len = *(ssid_id + 1);
+	if (len > 32)
+		len = 32;
+
+	strscpy(ssid_name, ssid_id + 2, len);
+	ssid_name[len] = '\0';
+	*ssid_len = len;
+
+	if (len > 0)
+		AICWFDBG(LOGDEBUG, "find_ccode ssid: %s\n", ssid_name);
+	else
+		AICWFDBG(LOGDEBUG, "find_ccode ssid is null\n");
+}
+
+static void rwnx_update_country(struct rwnx_hw *rwnx_hw,
+				const u8 *country_id,
+				struct ieee80211_regdomain **regdomain)
+{
+	if (!country_id)
+		return;
+
+	if (memcmp(rwnx_hw->country_abbr, country_id + 2, 2) != 0) {
+		AICWFDBG(LOGINFO, "update ccode of the connected ap\n");
+
+		strscpy(rwnx_hw->country_abbr, country_id + 2, 2);
+
+		*regdomain = get_regdomain_from_rwnx_db(rwnx_hw->wiphy, rwnx_hw->country_abbr);
+
+#ifdef CONFIG_AIC8800_REGION_PW
+		schedule_work(&rwnx_hw->region_wk);
+#endif
+
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+		if (!work_pending(&rwnx_hw->pwlt_wk))
+			schedule_work(&rwnx_hw->pwlt_wk);
+#endif
+
+		regulatory_set_wiphy_regd(rwnx_hw->wiphy, *regdomain);
+	}
+}
+
+static inline int rwnx_rx_scanu_result_ind(struct rwnx_hw *rwnx_hw,
+					   struct rwnx_cmd *cmd,
+					   struct ipc_e2a_msg *msg)
+{
+	struct cfg80211_bss *bss = NULL;
+	struct ieee80211_channel *chan;
+	struct scanu_result_ind *ind = (struct scanu_result_ind *)msg->param;
+	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)ind->payload;
+	u64 tsf;
+
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+	const u8 *country_id;
+	const u8 *ssid_id;
+	char ssid_name[33];
+	u8_l ssid_len;
+	struct ieee80211_regdomain *regdomain;
+#endif
+
+#ifdef CONFIG_AIC8800_USE_WIRELESS_EXT
+	struct scanu_result_wext *scan_re_wext;
+#endif
+
+	// RWNX_DBG(RWNX_FN_ENTRY_STR); //debug trace
+
+	chan = ieee80211_get_channel(rwnx_hw->wiphy, ind->center_freq);
+
+	if (chan) {
+		struct timespec64 ts;
+
+		ts = ktime_to_timespec64(ktime_get_boottime());
+		tsf = (u64)ts.tv_sec * 1000000 + div_u64(ts.tv_nsec, 1000);
+		mgmt->u.probe_resp.timestamp = tsf;
+
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+		if (rwnx_hw->ccode.auto_set && !rwnx_hw->ccode.ccode_set &&
+		    rwnx_hw->ccode.ccode_cnt < MAX_SEARCH_CNT) {
+			country_id =
+				cfg80211_find_ie(WLAN_EID_COUNTRY, mgmt->u.beacon.variable,
+						 ind->length - offsetof(struct ieee80211_mgmt,
+									u.beacon.variable));
+			if (country_id) {
+				if (ind->rssi > rwnx_hw->ccode.ccode_rssi) {
+					rwnx_hw->ccode.ccode_rssi = ind->rssi;
+					// AICWFDBG(LOGINFO, "find_ccode, bssid: %pM\n",
+					// mgmt->bssid); //debug trace
+					if (!rwnx_hw->ccode.ccode_find)
+						rwnx_hw->ccode.ccode_find = true;
+					strscpy(rwnx_hw->country_abbr, country_id + 2, 2);
+					ssid_id =
+					cfg80211_find_ie(WLAN_EID_SSID, mgmt->u.beacon.variable,
+							 ind->length -
+							 offsetof(struct ieee80211_mgmt,
+								  u.beacon.variable));
+					parse_ssid_from_id(ssid_id, ssid_name, &ssid_len);
+				}
+			}
+			rwnx_hw->ccode.ccode_cnt += 1;
+			AICWFDBG(LOGDEBUG, "find cnt: %d\n",
+				 rwnx_hw->ccode.ccode_cnt); // debug trace
+		} else if (rwnx_hw->ccode.auto_set && !rwnx_hw->ccode.ccode_find &&
+			   !rwnx_hw->ccode.ccode_set &&
+			   rwnx_hw->ccode.ccode_cnt >= MAX_SEARCH_CNT) {
+			AICWFDBG(LOGINFO, "find cnt over !!!\n");
+			strscpy(rwnx_hw->country_abbr, "XX", 2);
+			rwnx_hw->ccode.ccode_find = true;
+		}
+
+		if (rwnx_hw->ccode.auto_set &&
+		    atomic_read(&rwnx_hw->ccode.link_scan) == 1) {
+			if (memcmp(&rwnx_hw->ccode.bssid, mgmt->bssid, ETH_ALEN) == 0) {
+				country_id =
+				cfg80211_find_ie(WLAN_EID_COUNTRY,
+						 mgmt->u.probe_resp.variable,
+						 ind->length - offsetof(struct ieee80211_mgmt,
+									u.probe_resp.variable));
+				rwnx_update_country(rwnx_hw, country_id, &regdomain);
+				memset(&rwnx_hw->ccode.bssid, 0, sizeof(rwnx_hw->ccode.bssid));
+			}
+			atomic_set(&rwnx_hw->ccode.link_scan, 0);
+			rwnx_hw->ccode.ccode_cnt = 0;
+		}
+#endif
+		bss =
+		cfg80211_inform_bss_frame(rwnx_hw->wiphy, chan,
+					  (struct ieee80211_mgmt *)ind->payload,
+					  ind->length, ind->rssi * 100, GFP_ATOMIC);
+
+#ifdef CONFIG_AIC8800_USE_WIRELESS_EXT
+		if (rwnx_hw->wext_scan) {
+			list_for_each_entry(scan_re_wext,
+					    &rwnx_hw->wext_scanre_list, scanu_re_list) {
+				if (!memcmp(scan_re_wext->bss->bssid, bss->bssid, ETH_ALEN)) {
+					AICWFDBG(LOGDEBUG,
+						 "%s: BSSID already exists, no need to add again\r\n",
+						 __func__);
+					if (bss)
+						cfg80211_put_bss(rwnx_hw->wiphy, bss);
+					return 0;
+				}
+			}
+			scan_re_wext =
+				vmalloc(sizeof(struct scanu_result_wext));
+			scan_re_wext->ind =
+				vmalloc(sizeof(struct scanu_result_ind));
+
+			scan_re_wext->payload = vmalloc(sizeof(u32_l) * ind->length);
+
+			memset(scan_re_wext->ind, 0, sizeof(struct scanu_result_ind));
+			memset(scan_re_wext->payload, 0, ind->length);
+
+			memcpy(scan_re_wext->ind, ind, sizeof(struct scanu_result_ind));
+			memcpy(scan_re_wext->payload, ind->payload, ind->length);
+
+			scan_re_wext->bss = bss;
+
+			INIT_LIST_HEAD(&scan_re_wext->scanu_re_list);
+			list_add_tail(&scan_re_wext->scanu_re_list, &rwnx_hw->wext_scanre_list);
+			return 0;
+		}
+#endif
+	}
+	if (bss)
+		cfg80211_put_bss(rwnx_hw->wiphy, bss);
+	return 0;
+}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+/***************************************************************************
+ * Messages from ME task
+ **************************************************************************/
+#ifdef CONFIG_RWNX_FULLMAC
+static inline int rwnx_rx_me_tkip_mic_failure_ind(struct rwnx_hw *rwnx_hw,
+						  struct rwnx_cmd *cmd,
+						  struct ipc_e2a_msg *msg)
+{
+	struct me_tkip_mic_failure_ind *ind =
+		(struct me_tkip_mic_failure_ind *)msg->param;
+	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[ind->vif_idx];
+	struct net_device *dev = rwnx_vif->ndev;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	cfg80211_michael_mic_failure(dev, (u8 *)&ind->addr,
+				     (ind->ga ? NL80211_KEYTYPE_GROUP : NL80211_KEYTYPE_PAIRWISE),
+				     ind->keyid, (u8 *)&ind->tsc, GFP_ATOMIC);
+
+	return 0;
+}
+
+static inline int rwnx_rx_me_tx_credits_update_ind(struct rwnx_hw *rwnx_hw,
+						   struct rwnx_cmd *cmd,
+						   struct ipc_e2a_msg *msg)
+{
+	struct me_tx_credits_update_ind *ind =
+		(struct me_tx_credits_update_ind *)msg->param;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	rwnx_txq_credit_update(rwnx_hw, ind->sta_idx, ind->tid, ind->credits);
+
+	return 0;
+}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+/***************************************************************************
+ * Messages from SM task
+ **************************************************************************/
+#ifdef CONFIG_RWNX_FULLMAC
+static inline int rwnx_rx_sm_connect_ind(struct rwnx_hw *rwnx_hw,
+					 struct rwnx_cmd *cmd,
+					 struct ipc_e2a_msg *msg)
+{
+	struct sm_connect_ind *ind = (struct sm_connect_ind *)msg->param;
+	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[ind->vif_idx];
+	struct net_device *dev = rwnx_vif->ndev;
+	const u8 *req_ie, *rsp_ie;
+	const u8 *extcap_ie;
+	const struct ieee_types_extcap *extcap;
+	struct ieee80211_channel *chan;
+	const u8 *ht_ie = NULL;
+	const u8 *vht_ie = NULL;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Retrieve IE addresses and lengths */
+	req_ie = (const u8 *)ind->assoc_ie_buf;
+	rsp_ie = req_ie + ind->assoc_req_ie_len;
+
+	// Fill-in the AP information
+	AICWFDBG(LOGINFO, "%s ind->status_code:%d \r\n", __func__,
+		 ind->status_code);
+
+	if (ind->status_code == 0) {
+		struct rwnx_sta *sta = &rwnx_hw->sta_table[ind->ap_idx];
+		u8 txq_status;
+		struct cfg80211_chan_def chandef;
+
+		sta->valid = true;
+		sta->sta_idx = ind->ap_idx;
+		sta->ch_idx = ind->ch_idx;
+		sta->vif_idx = ind->vif_idx;
+		sta->vlan_idx = sta->vif_idx;
+		sta->qos = ind->qos;
+		sta->acm = ind->acm;
+		sta->ps.active = false;
+		sta->aid = ind->aid;
+		sta->band = ind->band;                 // ind->chan.band;
+		sta->width = ind->width;               // ind->chan.type;
+		sta->center_freq = ind->center_freq;   // ind->chan.prim20_freq;
+		sta->center_freq1 = ind->center_freq1; // ind->chan.center1_freq;
+		sta->center_freq2 = ind->center_freq2; // ind->chan.center2_freq;
+		rwnx_vif->sta.ap = sta;
+		// ind->chan.prim20_freq);
+		chan = ieee80211_get_channel(rwnx_hw->wiphy, ind->center_freq);
+		cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_NO_HT);
+		if (!rwnx_hw->mod_params->ht_on)
+			chandef.width = NL80211_CHAN_WIDTH_20_NOHT;
+		else
+			chandef.width = chnl2bw[ind->width];  //[ind->chan.type];
+		chandef.center_freq1 = ind->center_freq1; // ind->chan.center1_freq;
+		chandef.center_freq2 = ind->center_freq2; // ind->chan.center2_freq;
+		rwnx_chanctx_link(rwnx_vif, ind->ch_idx, &chandef);
+		memcpy(sta->mac_addr, ind->bssid.array, ETH_ALEN);
+		if (ind->ch_idx == rwnx_hw->cur_chanctx)
+			txq_status = 0;
+		else
+			txq_status = RWNX_TXQ_STOP_CHAN;
+		memcpy(sta->ac_param, ind->ac_param, sizeof(sta->ac_param));
+		rwnx_txq_sta_init(rwnx_hw, sta, txq_status);
+		rwnx_txq_tdls_vif_init(rwnx_vif);
+#ifdef CONFIG_RWNX_DEBUGFS
+		rwnx_dbgfs_register_rc_stat(rwnx_hw, sta);
+#endif
+		rwnx_mu_group_sta_init(sta, NULL);
+		/* Look for TDLS Channel Switch Prohibited flag in the Extended
+		 * Capability Information Element
+		 */
+		extcap_ie = cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, rsp_ie,
+					     ind->assoc_rsp_ie_len);
+		if (extcap_ie && extcap_ie[1] >= 5) {
+			extcap = (void *)(extcap_ie);
+			rwnx_vif->tdls_chsw_prohibited =
+				extcap->ext_capab[4] & WLAN_EXT_CAPA5_TDLS_CH_SW_PROHIBITED;
+		}
+
+		if (rwnx_vif->wep_enabled)
+			rwnx_vif->wep_auth_err = false;
+
+#ifdef CONFIG_RWNX_BFMER
+		/* If Beamformer feature is activated, check if features can be used
+		 * with the new peer device
+		 */
+		if (rwnx_hw->mod_params->bfmer) {
+			const u8 *vht_capa_ie;
+			const struct ieee80211_vht_cap *vht_cap;
+
+			do {
+				/* Look for VHT Capability Information Element */
+				vht_capa_ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, rsp_ie,
+							       ind->assoc_rsp_ie_len);
+
+				/* Stop here if peer device does not support VHT */
+				if (!vht_capa_ie)
+					break;
+
+				vht_cap = (const struct ieee80211_vht_cap *)(vht_capa_ie + 2);
+
+				/* Send MM_BFMER_ENABLE_REQ message if needed */
+				rwnx_send_bfmer_enable(rwnx_hw, sta, vht_cap);
+			} while (0);
+		}
+#endif //(CONFIG_RWNX_BFMER)
+
+		ht_ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, rsp_ie,
+					 ind->assoc_rsp_ie_len);
+		if (ht_ie)
+			rwnx_vif->sta.ap->ht = true;
+		vht_ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, rsp_ie,
+					  ind->assoc_rsp_ie_len);
+		if (vht_ie)
+			rwnx_vif->sta.ap->vht = true;
+
+#ifdef CONFIG_RWNX_MON_DATA
+		// If there are 1 sta and 1 monitor interface active at the same time
+		// then monitor interface channel context is always the same as the STA
+		// interface. This doesn't work with 2 STA interfaces but we don't want
+		// to support it.
+		if (rwnx_hw->monitor_vif != RWNX_INVALID_VIF) {
+			struct rwnx_vif *rwnx_mon_vif =
+				rwnx_hw->vif_table[rwnx_hw->monitor_vif];
+			rwnx_chanctx_unlink(rwnx_mon_vif);
+			rwnx_chanctx_link(rwnx_mon_vif, ind->ch_idx, NULL);
+		}
+#endif
+		// atomic_set(&rwnx_vif->drv_conn_state,
+		// (int)RWNX_DRV_STATUS_CONNECTED);
+	} else if (ind->status_code == WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG) {
+		if (rwnx_vif->wep_enabled) {
+			rwnx_vif->wep_auth_err = true;
+			AICWFDBG(LOGINFO, "con ind wep_auth_err %d\n",
+				 rwnx_vif->wep_auth_err);
+		}
+		rwnx_set_conn_state(&rwnx_vif->drv_conn_state, (int)RWNX_DRV_STATUS_DISCONNECTED);
+	} else {
+		rwnx_set_conn_state(&rwnx_vif->drv_conn_state, (int)RWNX_DRV_STATUS_DISCONNECTED);
+	}
+
+	AICWFDBG(LOGINFO,
+		 "%s ind->roamed:%d ind->status_code:%d rwnx_vif->drv_conn_state:%d\r\n",
+		 __func__, ind->roamed, ind->status_code,
+		 (int)atomic_read(&rwnx_vif->drv_conn_state));
+
+	if (ind->status_code == 0 &&
+	    (int)atomic_read(&rwnx_vif->drv_conn_state) == RWNX_DRV_STATUS_DISCONNECTING) {
+		AICWFDBG(LOGINFO, "%s the disconnection has been requested, return it\r\n",
+			 __func__);
+		goto exit;
+	}
+
+	if (!ind->roamed) { // not roaming
+		cfg80211_connect_result(dev, (const u8 *)ind->bssid.array,
+					req_ie, ind->assoc_req_ie_len,
+					rsp_ie, ind->assoc_rsp_ie_len, ind->status_code,
+					GFP_ATOMIC);
+		if (ind->status_code == 0) {
+			rwnx_set_conn_state(&rwnx_vif->drv_conn_state,
+					    (int)RWNX_DRV_STATUS_CONNECTED);
+			AICWFDBG(LOGINFO,
+				 "%s cfg80211_connect_result pass, rwnx_vif->drv_conn_state:%d\r\n",
+				 __func__, (int)atomic_read(&rwnx_vif->drv_conn_state));
+			rwnx_vif->sta.link_time = jiffies;
+		} else {
+			rwnx_set_conn_state(&rwnx_vif->drv_conn_state,
+					    (int)RWNX_DRV_STATUS_DISCONNECTED);
+			rwnx_external_auth_disable(rwnx_vif);
+		}
+	} else { // roaming
+		if (ind->status_code != 0) {
+			AICWFDBG(LOGINFO, "%s roaming fail to notify disconnect \r\n",
+				 __func__);
+			cfg80211_disconnected(dev, 0, NULL, 0, 1, GFP_ATOMIC);
+			rwnx_set_conn_state(&rwnx_vif->drv_conn_state,
+					    (int)RWNX_DRV_STATUS_DISCONNECTED);
+			rwnx_external_auth_disable(rwnx_vif);
+		} else {
+			struct cfg80211_roam_info info;
+
+			memset(&info, 0, sizeof(info));
+			if (rwnx_vif->ch_index < NX_CHAN_CTXT_CNT)
+				info.links[0].channel =
+					rwnx_hw->chanctx_table[rwnx_vif->ch_index].chan_def.chan;
+			info.links[0].bssid = (const u8 *)ind->bssid.array;
+			info.req_ie = req_ie;
+			info.req_ie_len = ind->assoc_req_ie_len;
+			info.resp_ie = rsp_ie;
+			info.resp_ie_len = ind->assoc_rsp_ie_len;
+			cfg80211_roamed(dev, &info, GFP_ATOMIC);
+			rwnx_vif->sta.link_time = jiffies;
+
+			rwnx_set_conn_state(&rwnx_vif->drv_conn_state,
+					    (int)RWNX_DRV_STATUS_CONNECTED);
+			rwnx_vif->sta.link_time = jiffies;
+		}
+		rwnx_vif->sta.is_roam = false;
+	}
+
+	if (ind->status_code == 0) {
+		netif_tx_start_all_queues(dev);
+		netif_carrier_on(dev);
+	}
+
+exit:
+	rwnx_vif->sta.is_roam = false;
+	return 0;
+}
+
+void rwnx_cfg80211_unlink_bss(struct rwnx_hw *rwnx_hw,
+			      struct rwnx_vif *rwnx_vif)
+{
+	struct wiphy *wiphy = rwnx_hw->wiphy;
+	struct cfg80211_bss *bss = NULL;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	bss = cfg80211_get_bss(wiphy, NULL /*notify_channel */, rwnx_vif->sta.bssid,
+			       rwnx_vif->sta.ssid, rwnx_vif->sta.ssid_len,
+			       IEEE80211_BSS_TYPE_ESS,
+			       IEEE80211_PRIVACY(true)); // temp set true
+	if (bss) {
+		cfg80211_unlink_bss(wiphy, bss);
+		AICWFDBG(LOGINFO, "%s(): cfg80211_unlink %s!!\n", __func__,
+			 rwnx_vif->sta.ssid);
+		cfg80211_put_bss(wiphy, bss);
+	} else {
+		AICWFDBG(LOGINFO, "%s(): cfg80211_unlink error %s!!\n", __func__,
+			 rwnx_vif->sta.ssid);
+	}
+}
+
+static inline int rwnx_rx_sm_disconnect_ind(struct rwnx_hw *rwnx_hw,
+					    struct rwnx_cmd *cmd,
+					    struct ipc_e2a_msg *msg)
+{
+	struct sm_disconnect_ind *ind = (struct sm_disconnect_ind *)msg->param;
+	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[ind->vif_idx];
+	struct net_device *dev;
+#ifdef AICWF_RX_REORDER
+	struct reord_ctrl_info *reord_info, *tmp;
+	u8 *macaddr;
+	struct aicwf_rx_priv *rx_priv;
+#endif
+
+	AICWFDBG(LOGINFO, "%s reason code:%d \r\n", __func__, ind->reason_code);
+
+	if (!rwnx_vif)
+		return 0;
+
+	if ((int)atomic_read(&rwnx_vif->drv_conn_state) ==
+		(int)RWNX_DRV_STATUS_DISCONNECTED) {
+		AICWFDBG(LOGINFO, "%s, is already disconnected, drop disconnect ind",
+			 __func__);
+		return 0;
+	}
+
+	dev = rwnx_vif->ndev;
+
+	if (!rwnx_vif->sta.is_roam)
+		rwnx_cfg80211_unlink_bss(rwnx_hw, rwnx_vif);
+	else
+		AICWFDBG(LOGINFO, "%s roaming no rwnx_cfg80211_unlink_bss \r\n",
+			 __func__);
+
+#ifdef CONFIG_RWNX_DEBUGFS
+	rwnx_dbgfs_unregister_rc_stat(rwnx_hw, rwnx_vif->sta.ap);
+#endif
+
+#ifdef CONFIG_AIC8800_BR_SUPPORT
+	/* clear bridge database */
+	nat25_db_cleanup(rwnx_vif);
+#endif /* CONFIG_AIC8800_BR_SUPPORT */
+
+	if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT)
+		rwnx_hw->is_p2p_connected = 0;
+	/* if vif is not up, rwnx_close has already been called */
+	if (rwnx_vif->up) {
+		if (!ind->ft_over_ds && !ind->reassoc) {
+			cfg80211_disconnected(dev, ind->reason_code, NULL, 0,
+					      (ind->reason_code < 1), GFP_ATOMIC);
+		}
+		netif_tx_stop_all_queues(dev);
+		netif_carrier_off(dev);
+	}
+
+#ifdef CONFIG_RWNX_BFMER
+	/* Disable Beamformer if supported */
+	rwnx_bfmer_report_del(rwnx_hw, rwnx_vif->sta.ap);
+#endif //(CONFIG_RWNX_BFMER)
+
+#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) {
+		macaddr = (u8 *)rwnx_vif->ndev->dev_addr;
+		AICWFDBG(LOGDEBUG, "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, tmp, &rx_priv->stas_reord_list,
+					 list) {
+			macaddr = (u8 *)rwnx_vif->ndev->dev_addr;
+			AICWFDBG(LOGDEBUG, "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);
+	} else if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_AP ||
+		   rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_GO) {
+		// WARN_ON_ONCE(); // should be not here: del_sta function
+	}
+#endif
+
+	rwnx_txq_sta_deinit(rwnx_hw, rwnx_vif->sta.ap);
+	rwnx_txq_tdls_vif_deinit(rwnx_vif);
+	rwnx_vif->sta.ap->valid = false;
+	rwnx_vif->sta.ap = NULL;
+	rwnx_external_auth_disable(rwnx_vif);
+	rwnx_chanctx_unlink(rwnx_vif);
+
+	// msleep(200);
+	if (!rwnx_vif->sta.is_roam)
+		rwnx_set_conn_state(&rwnx_vif->drv_conn_state, (int)RWNX_DRV_STATUS_DISCONNECTED);
+
+	return 0;
+}
+
+static inline int rwnx_rx_sm_external_auth_required_ind(struct rwnx_hw *rwnx_hw,
+							struct rwnx_cmd *cmd,
+							struct ipc_e2a_msg *msg)
+{
+	struct sm_external_auth_required_ind *ind =
+		(struct sm_external_auth_required_ind *)msg->param;
+	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[ind->vif_idx];
+	struct net_device *dev = rwnx_vif->ndev;
+	struct cfg80211_external_auth_params params;
+	int ret = 0;
+	struct wireless_dev *wdev = dev->ieee80211_ptr;
+	int retry_counter = 10;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	params.action = NL80211_EXTERNAL_AUTH_START;
+	memcpy(params.bssid, ind->bssid.array, ETH_ALEN);
+	params.ssid.ssid_len = ind->ssid.length;
+	memcpy(params.ssid.ssid, ind->ssid.array,
+	       min_t(size_t, ind->ssid.length, sizeof(params.ssid.ssid)));
+	params.key_mgmt_suite = ind->akm;
+
+	while (wdev->conn_owner_nlportid == 0) {
+		AICWFDBG(LOGINFO,
+			 "%s WARNING conn_owner_nlportid = 0, msleep 100ms.\r\n",
+			 __func__);
+		msleep(100);
+		retry_counter--;
+		if (retry_counter == 0)
+			break;
+	}
+	AICWFDBG(LOGINFO, "%s wdev->conn_owner_nlportid:%d \r\n", __func__,
+		 (int)wdev->conn_owner_nlportid);
+	ret = cfg80211_external_auth_request(dev, &params, GFP_ATOMIC);
+	if (ind->vif_idx > NX_VIRT_DEV_MAX || !rwnx_vif->up ||
+	    RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_STATION ||
+	    ret) {
+		wiphy_err(rwnx_hw->wiphy,
+			  "Failed to start external auth on vif %d, rwnx_vif->up %d, iftype:%d, ret %d",
+			  ind->vif_idx, rwnx_vif->up, RWNX_VIF_TYPE(rwnx_vif), ret);
+		rwnx_send_sm_external_auth_required_rsp(rwnx_hw, rwnx_vif,
+							WLAN_STATUS_UNSPECIFIED_FAILURE);
+		return 0;
+	}
+
+	rwnx_external_auth_enable(rwnx_vif);
+	return 0;
+}
+
+static inline int rwnx_rx_mesh_path_create_cfm(struct rwnx_hw *rwnx_hw,
+					       struct rwnx_cmd *cmd,
+					       struct ipc_e2a_msg *msg)
+{
+	struct mesh_path_create_cfm *cfm =
+		(struct mesh_path_create_cfm *)msg->param;
+	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[cfm->vif_idx];
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Check we well have a Mesh Point Interface */
+	if (rwnx_vif && RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_MESH_POINT)
+		rwnx_vif->ap.create_path = false;
+
+	return 0;
+}
+
+static inline int rwnx_rx_mesh_peer_update_ind(struct rwnx_hw *rwnx_hw,
+					       struct rwnx_cmd *cmd,
+					       struct ipc_e2a_msg *msg)
+{
+	struct mesh_peer_update_ind *ind =
+		(struct mesh_peer_update_ind *)msg->param;
+	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[ind->vif_idx];
+	struct rwnx_sta *rwnx_sta = &rwnx_hw->sta_table[ind->sta_idx];
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	if (ind->vif_idx >= (NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX) ||
+	    (rwnx_vif && RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT) ||
+	    ind->sta_idx >= NX_REMOTE_STA_MAX)
+		return 1;
+
+	/* Check we well have a Mesh Point Interface */
+	if (!rwnx_vif->user_mpm) {
+		/* Check if peer link has been established or lost */
+		if (ind->estab) {
+			if (!rwnx_sta->valid) {
+				u8 txq_status;
+
+				rwnx_sta->valid = true;
+				rwnx_sta->sta_idx = ind->sta_idx;
+				rwnx_sta->ch_idx = rwnx_vif->ch_index;
+				rwnx_sta->vif_idx = ind->vif_idx;
+				rwnx_sta->vlan_idx = rwnx_sta->vif_idx;
+				rwnx_sta->ps.active = false;
+				rwnx_sta->qos = true;
+				rwnx_sta->aid = ind->sta_idx + 1;
+				// rwnx_sta->acm = ind->acm;
+				memcpy(rwnx_sta->mac_addr, ind->peer_addr.array, ETH_ALEN);
+
+				rwnx_chanctx_link(rwnx_vif, rwnx_sta->ch_idx, NULL);
+
+				/* Add the station in the list of VIF's stations */
+				INIT_LIST_HEAD(&rwnx_sta->list);
+				list_add_tail(&rwnx_sta->list, &rwnx_vif->ap.sta_list);
+
+				/* Initialize the TX queues */
+				if (rwnx_sta->ch_idx == rwnx_hw->cur_chanctx)
+					txq_status = 0;
+				else
+					txq_status = RWNX_TXQ_STOP_CHAN;
+
+				rwnx_txq_sta_init(rwnx_hw, rwnx_sta, txq_status);
+#ifdef CONFIG_RWNX_DEBUGFS
+				rwnx_dbgfs_register_rc_stat(rwnx_hw, rwnx_sta);
+#endif
+#ifdef CONFIG_RWNX_BFMER
+				// TODO: update indication to contains vht capabilities
+				if (rwnx_hw->mod_params->bfmer)
+					rwnx_send_bfmer_enable(rwnx_hw, rwnx_sta, NULL);
+
+				rwnx_mu_group_sta_init(rwnx_sta, NULL);
+#endif /* CONFIG_RWNX_BFMER */
+
+			} else {
+				WARN_ON(0);
+			}
+		} else {
+			if (rwnx_sta->valid) {
+				rwnx_sta->ps.active = false;
+				rwnx_sta->valid = false;
+
+				/* Remove the station from the list of VIF's station */
+				list_del_init(&rwnx_sta->list);
+
+				rwnx_txq_sta_deinit(rwnx_hw, rwnx_sta);
+#ifdef CONFIG_RWNX_DEBUGFS
+				rwnx_dbgfs_unregister_rc_stat(rwnx_hw, rwnx_sta);
+#endif
+			} else {
+				WARN_ON(0);
+			}
+		}
+	} else {
+		if (!ind->estab && rwnx_sta->valid) {
+			/* There is no way to inform upper layer for lost of peer, still
+			 * clean everything in the driver
+			 */
+			rwnx_sta->ps.active = false;
+			rwnx_sta->valid = false;
+
+			/* Remove the station from the list of VIF's station */
+			list_del_init(&rwnx_sta->list);
+
+			rwnx_txq_sta_deinit(rwnx_hw, rwnx_sta);
+#ifdef CONFIG_RWNX_DEBUGFS
+			rwnx_dbgfs_unregister_rc_stat(rwnx_hw, rwnx_sta);
+#endif
+		} else {
+			WARN_ON(0);
+		}
+	}
+
+	return 0;
+}
+
+static inline int rwnx_rx_mesh_path_update_ind(struct rwnx_hw *rwnx_hw,
+					       struct rwnx_cmd *cmd,
+					       struct ipc_e2a_msg *msg)
+{
+	struct mesh_path_update_ind *ind =
+		(struct mesh_path_update_ind *)msg->param;
+	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[ind->vif_idx];
+	struct rwnx_mesh_path *mesh_path;
+	bool found = false;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	if (ind->vif_idx >= (NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX))
+		return 1;
+
+	if (!rwnx_vif || (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT))
+		return 0;
+
+	/* Look for path with provided target address */
+	list_for_each_entry(mesh_path, &rwnx_vif->ap.mpath_list, list) {
+		if (mesh_path->path_idx == ind->path_idx) {
+			found = true;
+			break;
+		}
+	}
+
+	/* Check if element has been deleted */
+	if (ind->delete) {
+		if (found) {
+#ifdef AIC8800_CREATE_TRACE_POINTS
+			trace_mesh_delete_path(mesh_path);
+#endif
+			/* Remove element from list */
+			list_del_init(&mesh_path->list);
+			/* Free the element */
+			kfree(mesh_path);
+		}
+	} else {
+		if (found) {
+			// Update the Next Hop STA
+			mesh_path->p_nhop_sta = &rwnx_hw->sta_table[ind->nhop_sta_idx];
+#ifdef AIC8800_CREATE_TRACE_POINTS
+			trace_mesh_update_path(mesh_path);
+#endif
+		} else {
+			// Allocate a Mesh Path structure
+			mesh_path =
+				kmalloc_obj(struct rwnx_mesh_path, GFP_ATOMIC);
+
+			if (mesh_path) {
+				INIT_LIST_HEAD(&mesh_path->list);
+
+				mesh_path->path_idx = ind->path_idx;
+				mesh_path->p_nhop_sta = &rwnx_hw->sta_table[ind->nhop_sta_idx];
+				memcpy(&mesh_path->tgt_mac_addr, &ind->tgt_mac_addr,
+				       MAC_ADDR_LEN);
+
+				// Insert the path in the list of path
+				list_add_tail(&mesh_path->list, &rwnx_vif->ap.mpath_list);
+#ifdef AIC8800_CREATE_TRACE_POINTS
+				trace_mesh_create_path(mesh_path);
+#endif
+			}
+		}
+	}
+
+	return 0;
+}
+
+static inline int rwnx_rx_mesh_proxy_update_ind(struct rwnx_hw *rwnx_hw,
+						struct rwnx_cmd *cmd,
+						struct ipc_e2a_msg *msg)
+{
+	struct mesh_proxy_update_ind *ind =
+		(struct mesh_proxy_update_ind *)msg->param;
+	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[ind->vif_idx];
+	struct rwnx_mesh_proxy *mesh_proxy;
+	bool found = false;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	if (ind->vif_idx >= (NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX))
+		return 1;
+
+	if (!rwnx_vif || RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT)
+		return 0;
+
+	/* Look for path with provided external STA address */
+	list_for_each_entry(mesh_proxy, &rwnx_vif->ap.proxy_list, list) {
+		if (!memcmp(&ind->ext_sta_addr, &mesh_proxy->ext_sta_addr, ETH_ALEN)) {
+			found = true;
+			break;
+		}
+	}
+
+	if (ind->delete && found) {
+		/* Delete mesh path */
+		list_del_init(&mesh_proxy->list);
+		kfree(mesh_proxy);
+	} else if (!ind->delete && !found) {
+		/* Allocate a Mesh Path structure */
+		mesh_proxy =
+			kmalloc_obj(*mesh_proxy, GFP_ATOMIC);
+
+		if (mesh_proxy) {
+			INIT_LIST_HEAD(&mesh_proxy->list);
+
+			memcpy(&mesh_proxy->ext_sta_addr, &ind->ext_sta_addr, MAC_ADDR_LEN);
+			mesh_proxy->local = ind->local;
+
+			if (!ind->local)
+				memcpy(&mesh_proxy->proxy_addr, &ind->proxy_mac_addr,
+				       MAC_ADDR_LEN);
+
+			/* Insert the path in the list of path */
+			list_add_tail(&mesh_proxy->list, &rwnx_vif->ap.proxy_list);
+		}
+	}
+
+	return 0;
+}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+/***************************************************************************
+ * Messages from DEBUG task
+ **************************************************************************/
+static inline int rwnx_rx_dbg_error_ind(struct rwnx_hw *rwnx_hw,
+					struct rwnx_cmd *cmd,
+					struct ipc_e2a_msg *msg)
+{
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	return 0;
+}
+
+#ifdef CONFIG_RWNX_FULLMAC
+
+static msg_cb_fct mm_hdlrs[MSG_I(MM_MAX)] = {
+	[MSG_I(MM_CHANNEL_SWITCH_IND)] = rwnx_rx_chan_switch_ind,
+	[MSG_I(MM_CHANNEL_PRE_SWITCH_IND)] = rwnx_rx_chan_pre_switch_ind,
+	[MSG_I(MM_REMAIN_ON_CHANNEL_EXP_IND)] = rwnx_rx_remain_on_channel_exp_ind,
+	[MSG_I(MM_PS_CHANGE_IND)] = rwnx_rx_ps_change_ind,
+	[MSG_I(MM_TRAFFIC_REQ_IND)] = rwnx_rx_traffic_req_ind,
+	[MSG_I(MM_P2P_VIF_PS_CHANGE_IND)] = rwnx_rx_p2p_vif_ps_change_ind,
+	[MSG_I(MM_CSA_COUNTER_IND)] = rwnx_rx_csa_counter_ind,
+	[MSG_I(MM_CSA_FINISH_IND)] = rwnx_rx_csa_finish_ind,
+	[MSG_I(MM_CSA_TRAFFIC_IND)] = rwnx_rx_csa_traffic_ind,
+	[MSG_I(MM_CHANNEL_SURVEY_IND)] = rwnx_rx_channel_survey_ind,
+	[MSG_I(MM_P2P_NOA_UPD_IND)] = rwnx_rx_p2p_noa_upd_ind,
+	[MSG_I(MM_RSSI_STATUS_IND)] = rwnx_rx_rssi_status_ind,
+	[MSG_I(MM_PKTLOSS_IND)] = rwnx_rx_pktloss_notify_ind,
+	[MSG_I(MM_APM_STALOSS_IND)] = rwnx_apm_staloss_ind,
+	[MSG_I(MM_ARP_ANNOUNCEMENT_IND)] = rwnx_arp_announcement_ind,
+};
+
+static msg_cb_fct scan_hdlrs[MSG_I(SCANU_MAX)] = {
+	[MSG_I(SCANU_START_CFM)] = rwnx_rx_scanu_start_cfm,
+	[MSG_I(SCANU_RESULT_IND)] = rwnx_rx_scanu_result_ind,
+};
+
+static msg_cb_fct me_hdlrs[MSG_I(ME_MAX)] = {
+	[MSG_I(ME_TKIP_MIC_FAILURE_IND)] = rwnx_rx_me_tkip_mic_failure_ind,
+	[MSG_I(ME_TX_CREDITS_UPDATE_IND)] = rwnx_rx_me_tx_credits_update_ind,
+};
+
+static msg_cb_fct sm_hdlrs[MSG_I(SM_MAX)] = {
+	[MSG_I(SM_CONNECT_IND)] = rwnx_rx_sm_connect_ind,
+	[MSG_I(SM_DISCONNECT_IND)] = rwnx_rx_sm_disconnect_ind,
+	[MSG_I(SM_EXTERNAL_AUTH_REQUIRED_IND)] =
+		rwnx_rx_sm_external_auth_required_ind,
+};
+
+static msg_cb_fct apm_hdlrs[MSG_I(APM_MAX)] = {};
+
+static msg_cb_fct mesh_hdlrs[MSG_I(MESH_MAX)] = {
+	[MSG_I(MESH_PATH_CREATE_CFM)] = rwnx_rx_mesh_path_create_cfm,
+	[MSG_I(MESH_PEER_UPDATE_IND)] = rwnx_rx_mesh_peer_update_ind,
+	[MSG_I(MESH_PATH_UPDATE_IND)] = rwnx_rx_mesh_path_update_ind,
+	[MSG_I(MESH_PROXY_UPDATE_IND)] = rwnx_rx_mesh_proxy_update_ind,
+};
+
+#endif /* CONFIG_RWNX_FULLMAC */
+
+static msg_cb_fct dbg_hdlrs[MSG_I(DBG_MAX)] = {
+	[MSG_I(DBG_ERROR_IND)] = rwnx_rx_dbg_error_ind,
+};
+
+static msg_cb_fct tdls_hdlrs[MSG_I(TDLS_MAX)] = {
+	[MSG_I(TDLS_CHAN_SWITCH_CFM)] = rwnx_rx_tdls_chan_switch_cfm,
+	[MSG_I(TDLS_CHAN_SWITCH_IND)] = rwnx_rx_tdls_chan_switch_ind,
+	[MSG_I(TDLS_CHAN_SWITCH_BASE_IND)] = rwnx_rx_tdls_chan_switch_base_ind,
+	[MSG_I(TDLS_PEER_PS_IND)] = rwnx_rx_tdls_peer_ps_ind,
+#ifdef CONFIG_SDIO_BT
+	[MSG_I(TDLS_SDIO_BT_RECV_IND)] = rwnx_bt_recv_ind,
+#endif
+};
+
+static msg_cb_fct *msg_hdlrs[] = {
+	[TASK_MM] = mm_hdlrs,     [TASK_DBG] = dbg_hdlrs,
+#ifdef CONFIG_RWNX_FULLMAC
+	[TASK_TDLS] = tdls_hdlrs, [TASK_SCANU] = scan_hdlrs,
+	[TASK_ME] = me_hdlrs,     [TASK_SM] = sm_hdlrs,
+	[TASK_APM] = apm_hdlrs,   [TASK_MESH] = mesh_hdlrs,
+#endif /* CONFIG_RWNX_FULLMAC */
+};
+
+/**
+ *
+ */
+void rwnx_rx_handle_msg(struct rwnx_hw *rwnx_hw, struct ipc_e2a_msg *msg)
+{
+	// AICWFDBG(LOGDEBUG, "%s msg->id:0x%x \r\n", __func__, msg->id);
+
+	rwnx_hw->cmd_mgr->msgind(rwnx_hw->cmd_mgr, msg,
+				 msg_hdlrs[MSG_T(msg->id)][MSG_I(msg->id)]);
+}
+
+void rwnx_rx_handle_print(struct rwnx_hw *rwnx_hw, u8 *msg, u32 len)
+{
+	u8 *data_end = NULL;
+	(void)data_end;
+
+	if (!rwnx_hw || !rwnx_hw->fwlog_en) {
+		pr_err("FWLOG-OVFL: %s", msg);
+		return;
+	}
+
+	AICWFDBG(LOGDEBUG, "FWLOG: %s", msg);
+
+#ifdef CONFIG_RWNX_DEBUGFS
+	data_end = rwnx_hw->debugfs.fw_log.buf.dataend;
+
+	if (!rwnx_hw->debugfs.fw_log.buf.data)
+		return;
+
+	// AICWFDBG(LOGDEBUG, "end=%lx, len=%d\n", (unsigned
+	// long)rwnx_hw->debugfs.fw_log.buf.end, len);
+
+	spin_lock_bh(&rwnx_hw->debugfs.fw_log.lock);
+
+	if (rwnx_hw->debugfs.fw_log.buf.end + len > data_end) {
+		int rem = data_end - rwnx_hw->debugfs.fw_log.buf.end;
+
+		memcpy(rwnx_hw->debugfs.fw_log.buf.end, msg, rem);
+		memcpy(rwnx_hw->debugfs.fw_log.buf.data, &msg[rem], len - rem);
+		rwnx_hw->debugfs.fw_log.buf.end =
+			rwnx_hw->debugfs.fw_log.buf.data + (len - rem);
+	} else {
+		memcpy(rwnx_hw->debugfs.fw_log.buf.end, msg, len);
+		rwnx_hw->debugfs.fw_log.buf.end += len;
+	}
+
+	rwnx_hw->debugfs.fw_log.buf.size += len;
+	if (rwnx_hw->debugfs.fw_log.buf.size > FW_LOG_SIZE)
+		rwnx_hw->debugfs.fw_log.buf.size = FW_LOG_SIZE;
+
+	spin_unlock_bh(&rwnx_hw->debugfs.fw_log.lock);
+#endif
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_rx.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_rx.h
new file mode 100644
index 0000000000000..211f2e616cbf4
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_rx.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_MSG_RX_H_
+#define _RWNX_MSG_RX_H_
+
+void rwnx_rx_handle_msg(struct rwnx_hw *rwnx_hw, struct ipc_e2a_msg *msg);
+void rwnx_rx_handle_print(struct rwnx_hw *rwnx_hw, u8 *msg, u32 len);
+
+#endif /* _RWNX_MSG_RX_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_tx.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_tx.c
new file mode 100644
index 0000000000000..a45598725737d
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_tx.c
@@ -0,0 +1,3815 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief TX function definitions
+ *
+ ******************************************************************************
+ */
+
+#include "rwnx_msg_tx.h"
+#include "reg_access.h"
+#include "rwnx_mod_params.h"
+#ifdef CONFIG_RWNX_BFMER
+#include "rwnx_bfmer.h"
+#endif //(CONFIG_RWNX_BFMER)
+#include "aicwf_txrxif.h"
+#include "rwnx_cmds.h"
+#include "rwnx_compat.h"
+#include "rwnx_main.h"
+#include "rwnx_strs.h"
+#include "rwnx_wakelock.h"
+#include "aicwf_sdio.h"
+#include "aicwf_chip_ops.h"
+
+const struct mac_addr mac_addr_bcst = {{0xFFFF, 0xFFFF, 0xFFFF}};
+
+/* Default MAC Rx filters that can be changed by mac80211
+ * (via the configure_filter() callback)
+ */
+#define RWNX_MAC80211_CHANGEABLE                                               \
+	(NXMAC_ACCEPT_BA_BIT | NXMAC_ACCEPT_BAR_BIT |                              \
+	 NXMAC_ACCEPT_OTHER_DATA_FRAMES_BIT | NXMAC_ACCEPT_PROBE_REQ_BIT |         \
+	 NXMAC_ACCEPT_PS_POLL_BIT)
+
+/* Default MAC Rx filters that cannot be changed by mac80211 */
+#define RWNX_MAC80211_NOT_CHANGEABLE                                           \
+	(NXMAC_ACCEPT_QO_S_NULL_BIT | NXMAC_ACCEPT_Q_DATA_BIT |                    \
+	 NXMAC_ACCEPT_DATA_BIT | NXMAC_ACCEPT_OTHER_MGMT_FRAMES_BIT |              \
+	 NXMAC_ACCEPT_MY_UNICAST_BIT | NXMAC_ACCEPT_BROADCAST_BIT |                \
+	 NXMAC_ACCEPT_BEACON_BIT | NXMAC_ACCEPT_PROBE_RESP_BIT)
+
+/* Default MAC Rx filter */
+#define RWNX_DEFAULT_RX_FILTER                                                 \
+	(RWNX_MAC80211_CHANGEABLE | RWNX_MAC80211_NOT_CHANGEABLE)
+
+const int bw2chnl[] = {
+	[NL80211_CHAN_WIDTH_20_NOHT] = PHY_CHNL_BW_20,
+	[NL80211_CHAN_WIDTH_20] = PHY_CHNL_BW_20,
+	[NL80211_CHAN_WIDTH_40] = PHY_CHNL_BW_40,
+	[NL80211_CHAN_WIDTH_80] = PHY_CHNL_BW_80,
+	[NL80211_CHAN_WIDTH_160] = PHY_CHNL_BW_160,
+	[NL80211_CHAN_WIDTH_80P80] = PHY_CHNL_BW_80P80,
+};
+
+const int chnl2bw[] = {
+	[PHY_CHNL_BW_20] = NL80211_CHAN_WIDTH_20,
+	[PHY_CHNL_BW_40] = NL80211_CHAN_WIDTH_40,
+	[PHY_CHNL_BW_80] = NL80211_CHAN_WIDTH_80,
+	[PHY_CHNL_BW_160] = NL80211_CHAN_WIDTH_160,
+	[PHY_CHNL_BW_80P80] = NL80211_CHAN_WIDTH_80P80,
+};
+
+#define RWNX_CMD_ARRAY_SIZE      20
+#define RWNX_CMD_HIGH_WATER_SIZE (RWNX_CMD_ARRAY_SIZE / 2)
+
+struct rwnx_cmd cmd_array[RWNX_CMD_ARRAY_SIZE];
+static spinlock_t cmd_array_lock;
+int cmd_array_index;
+
+/*****************************************************************************/
+/*
+ * Parse the ampdu density to retrieve the value in usec, according to the
+ * values defined in ieee80211.h
+ */
+static inline u8 rwnx_ampdudensity2usec(u8 ampdudensity)
+{
+	switch (ampdudensity) {
+	case IEEE80211_HT_MPDU_DENSITY_NONE:
+		return 0;
+		/* 1 microsecond is our granularity */
+	case IEEE80211_HT_MPDU_DENSITY_0_25:
+	case IEEE80211_HT_MPDU_DENSITY_0_5:
+	case IEEE80211_HT_MPDU_DENSITY_1:
+		return 1;
+	case IEEE80211_HT_MPDU_DENSITY_2:
+		return 2;
+	case IEEE80211_HT_MPDU_DENSITY_4:
+		return 4;
+	case IEEE80211_HT_MPDU_DENSITY_8:
+		return 8;
+	case IEEE80211_HT_MPDU_DENSITY_16:
+		return 16;
+	default:
+		return 0;
+	}
+}
+
+static inline bool use_pairwise_key(struct cfg80211_crypto_settings *crypto)
+{
+	if (crypto->cipher_group == WLAN_CIPHER_SUITE_WEP40 ||
+	    crypto->cipher_group == WLAN_CIPHER_SUITE_WEP104)
+		return false;
+
+	return true;
+}
+
+static inline bool is_non_blocking_msg(int id)
+{
+	return (id == MM_TIM_UPDATE_REQ || id == ME_RC_SET_RATE_REQ ||
+		id == MM_BFMER_ENABLE_REQ || id == ME_TRAFFIC_IND_REQ ||
+		id == TDLS_PEER_TRAFFIC_IND_REQ || id == MESH_PATH_CREATE_REQ ||
+		id == MESH_PROXY_ADD_REQ || id == SM_EXTERNAL_AUTH_REQUIRED_RSP);
+}
+
+static inline u8_l get_chan_flags(uint32_t flags)
+{
+	u8_l chan_flags = 0;
+#ifdef AIC8800_RADAR_OR_IR_DETECT
+
+	if (flags & IEEE80211_CHAN_NO_IR)
+		chan_flags |= CHAN_NO_IR;
+
+	if (flags & IEEE80211_CHAN_RADAR)
+		chan_flags |= CHAN_RADAR;
+#endif
+	return chan_flags;
+}
+
+static inline s8_l chan_to_fw_pwr(int power)
+{
+	return power > 127 ? 127 : (s8_l)power;
+}
+
+static inline void limit_chan_bw(u8_l *bw, u16_l primary, u16_l *center1)
+{
+	int oft, new_oft = 10;
+
+	if (*bw <= PHY_CHNL_BW_40)
+		return;
+
+	oft = *center1 - primary;
+	*bw = PHY_CHNL_BW_40;
+
+	if (oft < 0)
+		new_oft = new_oft * -1;
+	if (abs(oft) == 10 || abs(oft) == 50)
+		new_oft = new_oft * -1;
+
+	*center1 = primary + new_oft;
+}
+
+struct rwnx_cmd *rwnx_cmd_malloc(void)
+{
+	struct rwnx_cmd *cmd = NULL;
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(&cmd_array_lock, flags);
+
+	for (cmd_array_index = 0; cmd_array_index < RWNX_CMD_ARRAY_SIZE;
+		 cmd_array_index++) {
+		if (cmd_array[cmd_array_index].used == 0) {
+			AICWFDBG(LOGTRACE, "%s get cmd_array[%d]:%p \r\n", __func__,
+				 cmd_array_index, &cmd_array[cmd_array_index]);
+			cmd = &cmd_array[cmd_array_index];
+			cmd_array[cmd_array_index].used = 1;
+			break;
+		}
+	}
+
+	if (cmd_array_index >= RWNX_CMD_HIGH_WATER_SIZE) {
+		AICWFDBG(LOGERROR, "%s cmd(%d) was pending...\r\n", __func__,
+			 cmd_array_index);
+		mdelay(100);
+	}
+
+	if (!cmd)
+		AICWFDBG(LOGERROR, "%s array is empty...\r\n", __func__);
+
+	spin_unlock_irqrestore(&cmd_array_lock, flags);
+
+	return cmd;
+}
+
+void rwnx_cmd_free(struct rwnx_cmd *cmd)
+{
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(&cmd_array_lock, flags);
+	cmd->used = 0;
+	AICWFDBG(LOGTRACE, "%s cmd_array[%d]:%p \r\n", __func__, cmd->array_id,
+		 cmd);
+	spin_unlock_irqrestore(&cmd_array_lock, flags);
+}
+
+int rwnx_init_cmd_array(void)
+{
+	AICWFDBG(LOGTRACE, "%s Enter \r\n", __func__);
+	spin_lock_init(&cmd_array_lock);
+
+	for (cmd_array_index = 0; cmd_array_index < RWNX_CMD_ARRAY_SIZE;
+	     cmd_array_index++) {
+		AICWFDBG(LOGTRACE, "%s cmd_queue[%d]:%p \r\n", __func__,
+			 cmd_array_index, &cmd_array[cmd_array_index]);
+		cmd_array[cmd_array_index].used = 0;
+		cmd_array[cmd_array_index].array_id = cmd_array_index;
+	}
+	AICWFDBG(LOGTRACE, "%s Exit \r\n", __func__);
+
+	return 0;
+}
+
+void rwnx_free_cmd_array(void)
+{
+	AICWFDBG(LOGTRACE, "%s Enter \r\n", __func__);
+
+	for (cmd_array_index = 0; cmd_array_index < RWNX_CMD_ARRAY_SIZE;
+	     cmd_array_index++)
+		cmd_array[cmd_array_index].used = 0;
+
+	AICWFDBG(LOGTRACE, "%s Exit \r\n", __func__);
+}
+
+/**
+ ******************************************************************************
+ * @brief Allocate memory for a message
+ *
+ * This primitive allocates memory for a message that has to be sent. The memory
+ * is allocated dynamically on the heap and the length of the variable parameter
+ * structure has to be provided in order to allocate the correct size.
+ *
+ * Several additional parameters are provided which will be preset in the
+ *message and which may be used internally to choose the kind of memory to
+ *allocate.
+ *
+ * The memory allocated will be automatically freed by the kernel, after the
+ * pointer has been sent to ke_msg_send(). If the message is not sent, it must
+ * be freed explicitly with ke_msg_free().
+ *
+ * Allocation failure is considered critical and should not happen.
+ *
+ * @param[in] id        Message identifier
+ * @param[in] dest_id   Destination Task Identifier
+ * @param[in] src_id    Source Task Identifier
+ * @param[in] param_len Size of the message parameters to be allocated
+ *
+ * @return Pointer to the parameter member of the ke_msg. If the parameter
+ *         structure is empty, the pointer will point to the end of the message
+ *         and should not be used (except to retrieve the message pointer or to
+ *         send the message)
+ ******************************************************************************
+ */
+static inline void *rwnx_msg_zalloc(lmac_msg_id_t const id,
+				    lmac_task_id_t const dest_id,
+				    lmac_task_id_t const src_id,
+				    uint16_t const param_len)
+{
+	struct lmac_msg *msg;
+	gfp_t flags;
+
+	//  if (is_non_blocking_msg(id) && (in_softirq()||in_atomic()))
+	flags = GFP_ATOMIC;
+	//  else
+	//      flags = GFP_KERNEL;
+
+	// msg = (struct lmac_msg *)kzalloc(sizeof(struct lmac_msg) + param_len, flags);
+	msg = kzalloc(sizeof(*msg) + param_len, flags);
+	if (!msg) {
+		AICWFDBG(LOGERROR, "%s: msg allocation failed\n", __func__);
+		return NULL;
+	}
+	msg->id = id;
+	msg->dest_id = dest_id;
+	msg->src_id = src_id;
+	msg->param_len = param_len;
+	// AICWFDBG(LOGDEBUG, "rwnx_msg_zalloc size=%d  id=%d\n",msg->param_len,msg->id);
+
+	return msg->param;
+}
+
+static void rwnx_msg_free(struct rwnx_hw *rwnx_hw, const void *msg_params)
+{
+	struct lmac_msg *msg = container_of((void *)msg_params, struct lmac_msg, param);
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+	/* Free the message */
+	kfree(msg);
+}
+
+// void rwnx_pm_relax(struct aic_sdio_dev *sdiodev);
+// void rwnx_pm_stay_awake(struct aic_sdio_dev *sdiodev);
+
+static int rwnx_send_msg(struct rwnx_hw *rwnx_hw, const void *msg_params,
+			 int reqcfm, lmac_msg_id_t reqid, void *cfm)
+{
+	struct lmac_msg *msg;
+	struct rwnx_cmd *cmd;
+	bool nonblock;
+	int ret = 0;
+	u8_l empty = 0;
+
+	// RWNX_DBG(RWNX_FN_ENTRY_STR);
+	AICWFDBG(LOGTRACE,
+		 "%s (%d)%s reqcfm:%d in_hardirq:%d in_softirq:%d irqs_disabled:%d\r\n",
+		 __func__, reqid, RWNX_ID2STR(reqid), reqcfm, (int)in_hardirq(),
+		 (int)in_softirq(), (int)irqs_disabled());
+
+#ifdef AICWF_SDIO_SUPPORT
+	rwnx_wakeup_lock(rwnx_hw->ws_tx);
+	if (rwnx_hw->sdiodev->bus_if->state == BUS_DOWN_ST) {
+		rwnx_msg_free(rwnx_hw, msg_params);
+		sdio_err("bus is down\n");
+		rwnx_wakeup_unlock(rwnx_hw->ws_tx);
+		return 0;
+	}
+#endif
+
+	msg = container_of((void *)msg_params, struct lmac_msg, param);
+
+	// nonblock = is_non_blocking_msg(msg->id);
+	nonblock = 0;
+	cmd = rwnx_cmd_malloc(); // kzalloc_obj(struct rwnx_cmd, nonblock ?
+							 // GFP_ATOMIC : GFP_KERNEL);
+	cmd->result = -EINTR;
+	cmd->id = msg->id;
+	cmd->reqid = reqid;
+	cmd->a2e_msg = msg;
+	cmd->e2a_msg = cfm;
+	if (nonblock)
+		cmd->flags = RWNX_CMD_FLAG_NONBLOCK;
+	if (reqcfm)
+		cmd->flags |= RWNX_CMD_FLAG_REQ_CFM;
+
+	if (cfm) {
+		do {
+			if (rwnx_hw->cmd_mgr->state == RWNX_CMD_MGR_STATE_CRASHED)
+				break;
+			spin_lock_bh(&rwnx_hw->cmd_mgr->lock);
+			empty = list_empty(&rwnx_hw->cmd_mgr->cmds);
+			spin_unlock_bh(&rwnx_hw->cmd_mgr->lock);
+			if (!empty) {
+				if (in_softirq()) {
+					AICWFDBG(LOGDEBUG,
+						 "AICWF_LOG in_softirq:check cmdqueue empty\n");
+					mdelay(10);
+				} else {
+					AICWFDBG(LOGDEBUG, "AICWF_LOG check cmdqueue empty\n");
+					msleep(50);
+				}
+			}
+		} while (!empty); // wait for cmd queue empty
+	}
+
+	if (reqcfm) {
+		cmd->flags &= ~RWNX_CMD_FLAG_WAIT_ACK; // we don't need ack any more
+		ret = rwnx_hw->cmd_mgr->queue(rwnx_hw->cmd_mgr, cmd);
+	} else {
+#ifdef AICWF_SDIO_SUPPORT
+		aicwf_set_cmd_tx((void *)(rwnx_hw->sdiodev), cmd->a2e_msg,
+				 sizeof(struct lmac_msg) + cmd->a2e_msg->param_len);
+#else
+		aicwf_set_cmd_tx((void *)(rwnx_hw->usbdev), cmd->a2e_msg,
+				 sizeof(struct lmac_msg) + cmd->a2e_msg->param_len);
+#endif
+	}
+
+	if (!reqcfm || ret)
+		rwnx_cmd_free(cmd); // kfree(cmd);
+
+	rwnx_wakeup_unlock(rwnx_hw->ws_tx);
+	return 0;
+}
+
+static int rwnx_send_msg1(struct rwnx_hw *rwnx_hw, const void *msg_params,
+			  int reqcfm, lmac_msg_id_t reqid, void *cfm,
+			  bool defer)
+{
+	struct lmac_msg *msg;
+	struct rwnx_cmd *cmd;
+	bool nonblock;
+	int ret = 0;
+
+	//  RWNX_DBG(RWNX_FN_ENTRY_STR);
+	AICWFDBG(LOGDEBUG,
+		 "AICWF_LOG %s (%d)%s reqcfm:%d in_hardirq:%d in_softirq:%d irqs_disabled:%d\r\n",
+		 __func__, reqid, RWNX_ID2STR(reqid), reqcfm, (int)in_hardirq(),
+		 (int)in_softirq(), (int)irqs_disabled());
+
+	rwnx_wakeup_lock(rwnx_hw->ws_tx);
+	msg = container_of((void *)msg_params, struct lmac_msg, param);
+
+	// nonblock = is_non_blocking_msg(msg->id);
+	nonblock = 0;
+	cmd = rwnx_cmd_malloc(); // kzalloc_obj(struct rwnx_cmd, nonblock ?
+							 // GFP_ATOMIC : GFP_KERNEL);
+	cmd->result = -EINTR;
+	cmd->id = msg->id;
+	cmd->reqid = reqid;
+	cmd->a2e_msg = msg;
+	cmd->e2a_msg = cfm;
+	if (nonblock)
+		cmd->flags = RWNX_CMD_FLAG_NONBLOCK;
+	if (reqcfm)
+		cmd->flags |= RWNX_CMD_FLAG_REQ_CFM;
+
+	if (reqcfm) {
+		cmd->flags &= ~RWNX_CMD_FLAG_WAIT_ACK; // we don't need ack any more
+		if (!defer)
+			ret = rwnx_hw->cmd_mgr->queue(rwnx_hw->cmd_mgr, cmd);
+		else
+			ret = cmd_mgr_queue_force_defer(rwnx_hw->cmd_mgr, cmd);
+	}
+
+	if (!reqcfm || ret)
+		rwnx_cmd_free(cmd); // kfree(cmd);
+
+	if (!ret)
+		ret = cmd->result;
+
+	rwnx_wakeup_unlock(rwnx_hw->ws_tx);
+	// return ret;
+	return 0;
+}
+
+/******************************************************************************
+ *    Control messages handling functions (FULLMAC)
+ *****************************************************************************/
+int rwnx_send_reset(struct rwnx_hw *rwnx_hw)
+{
+	void *void_param;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* RESET REQ has no parameter */
+	void_param = rwnx_msg_zalloc(MM_RESET_REQ, TASK_MM, DRV_TASK_ID, 0);
+	if (!void_param)
+		return -ENOMEM;
+
+	return rwnx_send_msg(rwnx_hw, void_param, 1, MM_RESET_CFM, NULL);
+}
+
+int rwnx_send_start(struct rwnx_hw *rwnx_hw)
+{
+	struct mm_start_req *start_req_param;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the START REQ message */
+	start_req_param = rwnx_msg_zalloc(MM_START_REQ, TASK_MM, DRV_TASK_ID,
+					  sizeof(struct mm_start_req));
+	if (!start_req_param)
+		return -ENOMEM;
+
+	/* Set parameters for the START message */
+	memcpy(&start_req_param->phy_cfg, &rwnx_hw->phy.cfg,
+	       sizeof(rwnx_hw->phy.cfg));
+	start_req_param->uapsd_timeout = (u32_l)rwnx_hw->mod_params->uapsd_timeout;
+	start_req_param->lp_clk_accuracy = (u16_l)rwnx_hw->mod_params->lp_clk_ppm;
+
+	/* Send the START REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, start_req_param, 1, MM_START_CFM, NULL);
+}
+
+int rwnx_send_version_req(struct rwnx_hw *rwnx_hw, struct mm_version_cfm *cfm)
+{
+	void *void_param;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* VERSION REQ has no parameter */
+	void_param = rwnx_msg_zalloc(MM_VERSION_REQ, TASK_MM, DRV_TASK_ID, 0);
+	if (!void_param)
+		return -ENOMEM;
+
+	return rwnx_send_msg(rwnx_hw, void_param, 1, MM_VERSION_CFM, cfm);
+}
+
+int rwnx_send_add_if(struct rwnx_hw *rwnx_hw, const unsigned char *mac,
+		     enum nl80211_iftype iftype, bool p2p,
+		     struct mm_add_if_cfm *cfm)
+{
+	struct mm_add_if_req *add_if_req_param;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the ADD_IF_REQ message */
+	add_if_req_param = rwnx_msg_zalloc(MM_ADD_IF_REQ, TASK_MM, DRV_TASK_ID,
+					   sizeof(struct mm_add_if_req));
+	if (!add_if_req_param)
+		return -ENOMEM;
+
+	/* Set parameters for the ADD_IF_REQ message */
+	memcpy(&add_if_req_param->addr.array[0], mac, ETH_ALEN);
+	switch (iftype) {
+#ifdef CONFIG_RWNX_FULLMAC
+		// case NL80211_IFTYPE_P2P_DEVICE:
+	case NL80211_IFTYPE_P2P_CLIENT:
+		add_if_req_param->p2p = true;
+		fallthrough;
+		// no break
+#endif /* CONFIG_RWNX_FULLMAC */
+	case NL80211_IFTYPE_STATION:
+		add_if_req_param->type = MM_STA;
+		break;
+
+	case NL80211_IFTYPE_ADHOC:
+		add_if_req_param->type = MM_IBSS;
+		break;
+
+#ifdef CONFIG_RWNX_FULLMAC
+	case NL80211_IFTYPE_P2P_GO:
+		add_if_req_param->p2p = true;
+		fallthrough;
+		// no break
+#endif /* CONFIG_RWNX_FULLMAC */
+	case NL80211_IFTYPE_AP:
+		add_if_req_param->type = MM_AP;
+		break;
+	case NL80211_IFTYPE_MESH_POINT:
+		add_if_req_param->type = MM_MESH_POINT;
+		break;
+	case NL80211_IFTYPE_AP_VLAN:
+		return -1;
+	case NL80211_IFTYPE_MONITOR:
+		add_if_req_param->type = MM_MONITOR;
+		break;
+	default:
+		add_if_req_param->type = MM_STA;
+		break;
+	}
+
+	/* Send the ADD_IF_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, add_if_req_param, 1, MM_ADD_IF_CFM, cfm);
+}
+
+int rwnx_send_remove_if(struct rwnx_hw *rwnx_hw, u8 vif_index, bool defer)
+{
+	struct mm_remove_if_req *remove_if_req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_REMOVE_IF_REQ message */
+	remove_if_req = rwnx_msg_zalloc(MM_REMOVE_IF_REQ, TASK_MM, DRV_TASK_ID,
+					sizeof(struct mm_remove_if_req));
+	if (!remove_if_req)
+		return -ENOMEM;
+
+	/* Set parameters for the MM_REMOVE_IF_REQ message */
+	remove_if_req->inst_nbr = vif_index;
+
+	/* Send the MM_REMOVE_IF_REQ message to LMAC FW */
+	return rwnx_send_msg1(rwnx_hw, remove_if_req, 1, MM_REMOVE_IF_CFM, NULL,
+						  defer);
+}
+
+int rwnx_send_set_channel(struct rwnx_hw *rwnx_hw, int phy_idx,
+			  struct mm_set_channel_cfm *cfm)
+{
+	struct mm_set_channel_req *req;
+	enum nl80211_chan_width width = NL80211_CHAN_WIDTH_20_NOHT;
+	u16 center_freq = 2412, center_freq1 = 2412, center_freq2 = 2412;
+	s8 tx_power = 0;
+	u8 flags = 0;
+	enum nl80211_band band = NL80211_BAND_2GHZ;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	if (phy_idx >= rwnx_hw->phy.cnt)
+		return -EOPNOTSUPP;
+
+	req = rwnx_msg_zalloc(MM_SET_CHANNEL_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_set_channel_req));
+	if (!req)
+		return -ENOMEM;
+
+	if (phy_idx == 0) {
+#ifdef CONFIG_RWNX_FULLMAC
+		/* On FULLMAC only setting channel of secondary chain */
+		wiphy_err(rwnx_hw->wiphy, "Trying to set channel of primary chain");
+		return 0;
+#endif /* CONFIG_RWNX_FULLMAC */
+	} else {
+		struct rwnx_sec_phy_chan *chan = &rwnx_hw->phy.sec_chan;
+
+		width = chnl2bw[chan->type];
+		band = chan->band;
+		center_freq = chan->prim20_freq;
+		center_freq1 = chan->center_freq1;
+		center_freq2 = chan->center_freq2;
+		flags = 0;
+	}
+
+	req->chan.band = band;
+	req->chan.type = bw2chnl[width];
+	req->chan.prim20_freq = center_freq;
+	req->chan.center1_freq = center_freq1;
+	req->chan.center2_freq = center_freq2;
+	req->chan.tx_power = tx_power;
+	req->chan.flags = flags;
+	req->index = phy_idx;
+
+	if (rwnx_hw->phy.limit_bw)
+		limit_chan_bw(&req->chan.type, req->chan.prim20_freq,
+			      &req->chan.center1_freq);
+
+	RWNX_DBG("mac80211:   freq=%d(c1:%d - c2:%d)/width=%d - band=%d\n"
+		 "   hw(%d): prim20=%d(c1:%d - c2:%d)/ type=%d - band=%d\n",
+		 center_freq, center_freq1, center_freq2, width, band, phy_idx,
+		 req->chan.prim20_freq, req->chan.center1_freq,
+		 req->chan.center2_freq, req->chan.type, req->chan.band);
+
+	/* Send the MM_SET_CHANNEL_REQ REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, MM_SET_CHANNEL_CFM, cfm);
+}
+
+int rwnx_send_key_add(struct rwnx_hw *rwnx_hw, u8 vif_idx, u8 sta_idx,
+		      bool pairwise, u8 *key, u8 key_len, u8 key_idx,
+		      u8 cipher_suite, struct mm_key_add_cfm *cfm)
+{
+	struct mm_key_add_req *key_add_req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_KEY_ADD_REQ message */
+	key_add_req = rwnx_msg_zalloc(MM_KEY_ADD_REQ, TASK_MM, DRV_TASK_ID,
+				      sizeof(struct mm_key_add_req));
+	if (!key_add_req)
+		return -ENOMEM;
+
+	/* Set parameters for the MM_KEY_ADD_REQ message */
+	if (sta_idx != 0xFF) {
+		/* Pairwise key */
+		key_add_req->sta_idx = sta_idx;
+	} else {
+		/* Default key */
+		key_add_req->sta_idx = sta_idx;
+		key_add_req->key_idx = (u8_l)key_idx; /* only useful for default keys */
+	}
+	key_add_req->pairwise = pairwise;
+	key_add_req->inst_nbr = vif_idx;
+	key_add_req->key.length = key_len;
+	memcpy(&key_add_req->key.array[0], key, key_len);
+
+	key_add_req->cipher_suite = cipher_suite;
+
+	RWNX_DBG("%s: sta_idx:%d key_idx:%d inst_nbr:%d cipher:%d key_len:%d\n",
+		 __func__, key_add_req->sta_idx, key_add_req->key_idx,
+		 key_add_req->inst_nbr, key_add_req->cipher_suite,
+		 key_add_req->key.length);
+#if defined(CONFIG_RWNX_DBG) || defined(CONFIG_DYNAMIC_DEBUG)
+	print_hex_dump_bytes("key: ", DUMP_PREFIX_OFFSET, key_add_req->key.array,
+			     key_add_req->key.length);
+#endif
+
+	/* Send the MM_KEY_ADD_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, key_add_req, 1, MM_KEY_ADD_CFM, cfm);
+}
+
+int rwnx_send_key_del(struct rwnx_hw *rwnx_hw, uint8_t hw_key_idx)
+{
+	struct mm_key_del_req *key_del_req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_KEY_DEL_REQ message */
+	key_del_req = rwnx_msg_zalloc(MM_KEY_DEL_REQ, TASK_MM, DRV_TASK_ID,
+				      sizeof(struct mm_key_del_req));
+	if (!key_del_req)
+		return -ENOMEM;
+
+	/* Set parameters for the MM_KEY_DEL_REQ message */
+	key_del_req->hw_key_idx = hw_key_idx;
+
+	/* Send the MM_KEY_DEL_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, key_del_req, 1, MM_KEY_DEL_CFM, NULL);
+}
+
+int rwnx_send_bcn(struct rwnx_hw *rwnx_hw, u8 *buf, u8 vif_idx, u16 bcn_len)
+{
+	struct apm_set_bcn_ie_req *bcn_ie_req;
+
+	bcn_ie_req = rwnx_msg_zalloc(APM_SET_BEACON_IE_REQ, TASK_APM, DRV_TASK_ID,
+				     sizeof(struct apm_set_bcn_ie_req));
+	if (!bcn_ie_req)
+		return -ENOMEM;
+
+	bcn_ie_req->vif_idx = vif_idx;
+	bcn_ie_req->bcn_ie_len = bcn_len;
+	memcpy(bcn_ie_req->bcn_ie, (u8 *)buf, bcn_len);
+	kfree(buf);
+
+	return rwnx_send_msg(rwnx_hw, bcn_ie_req, 1, APM_SET_BEACON_IE_CFM, NULL);
+}
+
+int rwnx_send_bcn_change(struct rwnx_hw *rwnx_hw, u8 vif_idx, u32 bcn_addr,
+			 u16 bcn_len, u16 tim_oft, u16 tim_len, u16 *csa_oft)
+{
+	struct mm_bcn_change_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_BCN_CHANGE_REQ message */
+	req = rwnx_msg_zalloc(MM_BCN_CHANGE_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_bcn_change_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the MM_BCN_CHANGE_REQ message */
+	req->bcn_ptr = bcn_addr;
+	req->bcn_len = bcn_len;
+	req->tim_oft = tim_oft;
+	req->tim_len = tim_len;
+	req->inst_nbr = vif_idx;
+
+	if (csa_oft) {
+		int i;
+
+		for (i = 0; i < BCN_MAX_CSA_CPT; i++)
+			req->csa_oft[i] = csa_oft[i];
+	}
+
+	/* Send the MM_BCN_CHANGE_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, MM_BCN_CHANGE_CFM, NULL);
+}
+
+int rwnx_send_roc(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+		  struct ieee80211_channel *chan, unsigned int duration,
+		  struct mm_remain_on_channel_cfm *roc_cfm)
+{
+	struct mm_remain_on_channel_req *req;
+	struct cfg80211_chan_def chandef;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Create channel definition structure */
+	cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_NO_HT);
+
+	/* Build the MM_REMAIN_ON_CHANNEL_REQ message */
+	req = rwnx_msg_zalloc(MM_REMAIN_ON_CHANNEL_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_remain_on_channel_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the MM_REMAIN_ON_CHANNEL_REQ message */
+	req->op_code = MM_ROC_OP_START;
+	req->vif_index = vif->vif_index;
+	req->duration_ms = duration;
+	req->band = chan->band;
+	req->type = bw2chnl[chandef.width];
+	req->prim20_freq = chan->center_freq;
+	req->center1_freq = chandef.center_freq1;
+	req->center2_freq = chandef.center_freq2;
+	req->tx_power = chan_to_fw_pwr(chan->max_power);
+
+	/* Send the MM_REMAIN_ON_CHANNEL_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, MM_REMAIN_ON_CHANNEL_CFM, roc_cfm);
+}
+
+int rwnx_send_cancel_roc(struct rwnx_hw *rwnx_hw)
+{
+	struct mm_remain_on_channel_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_REMAIN_ON_CHANNEL_REQ message */
+	req = rwnx_msg_zalloc(MM_REMAIN_ON_CHANNEL_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_remain_on_channel_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the MM_REMAIN_ON_CHANNEL_REQ message */
+	req->op_code = MM_ROC_OP_CANCEL;
+
+	/* Send the MM_REMAIN_ON_CHANNEL_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, MM_REMAIN_ON_CHANNEL_CFM, NULL);
+}
+
+int rwnx_send_set_power(struct rwnx_hw *rwnx_hw, u8 vif_idx, s8 pwr,
+			struct mm_set_power_cfm *cfm)
+{
+	struct mm_set_power_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_SET_POWER_REQ message */
+	req = rwnx_msg_zalloc(MM_SET_POWER_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_set_power_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the MM_SET_POWER_REQ message */
+	req->inst_nbr = vif_idx;
+	req->power = pwr;
+
+	/* Send the MM_SET_POWER_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, MM_SET_POWER_CFM, cfm);
+}
+
+int rwnx_send_set_edca(struct rwnx_hw *rwnx_hw, u8 hw_queue, u32 param,
+		       bool uapsd, u8 inst_nbr)
+{
+	struct mm_set_edca_req *set_edca_req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_SET_EDCA_REQ message */
+	set_edca_req = rwnx_msg_zalloc(MM_SET_EDCA_REQ, TASK_MM, DRV_TASK_ID,
+				       sizeof(struct mm_set_edca_req));
+	if (!set_edca_req)
+		return -ENOMEM;
+
+	/* Set parameters for the MM_SET_EDCA_REQ message */
+	set_edca_req->ac_param = param;
+	set_edca_req->uapsd = uapsd;
+	set_edca_req->hw_queue = hw_queue;
+	set_edca_req->inst_nbr = inst_nbr;
+
+	/* Send the MM_SET_EDCA_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, set_edca_req, 1, MM_SET_EDCA_CFM, NULL);
+}
+
+#ifdef CONFIG_RWNX_P2P_DEBUGFS
+int rwnx_send_p2p_oppps_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+			    u8 ctw, struct mm_set_p2p_oppps_cfm *cfm)
+{
+	struct mm_set_p2p_oppps_req *p2p_oppps_req;
+	int error;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_SET_P2P_OPPPS_REQ message */
+	p2p_oppps_req = rwnx_msg_zalloc(MM_SET_P2P_OPPPS_REQ, TASK_MM, DRV_TASK_ID,
+					sizeof(struct mm_set_p2p_oppps_req));
+
+	if (!p2p_oppps_req)
+		return -ENOMEM;
+
+	/* Fill the message parameters */
+	p2p_oppps_req->vif_index = rwnx_vif->vif_index;
+	p2p_oppps_req->ctwindow = ctw;
+
+	/* Send the MM_P2P_OPPPS_REQ message to LMAC FW */
+	error = rwnx_send_msg(rwnx_hw, p2p_oppps_req, 1, MM_SET_P2P_OPPPS_CFM, cfm);
+
+	return error;
+}
+
+int rwnx_send_p2p_noa_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+			  int count, int interval, int duration, bool dyn_noa,
+			  struct mm_set_p2p_noa_cfm *cfm)
+{
+	struct mm_set_p2p_noa_req *p2p_noa_req;
+	int error;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Param check */
+	if (count > 255)
+		count = 255;
+
+	if (duration >= interval) {
+		dev_err(rwnx_hw->dev,
+			"Invalid p2p NOA config: interval=%d <= duration=%d\n",
+			interval, duration);
+		return -EINVAL;
+	}
+
+	/* Build the MM_SET_P2P_NOA_REQ message */
+	p2p_noa_req = rwnx_msg_zalloc(MM_SET_P2P_NOA_REQ, TASK_MM, DRV_TASK_ID,
+				      sizeof(struct mm_set_p2p_noa_req));
+
+	if (!p2p_noa_req)
+		return -ENOMEM;
+
+	/* Fill the message parameters */
+	p2p_noa_req->vif_index = rwnx_vif->vif_index;
+	p2p_noa_req->noa_inst_nb = 0;
+	p2p_noa_req->count = count;
+
+	if (count) {
+		p2p_noa_req->duration_us = duration * 1024;
+		p2p_noa_req->interval_us = interval * 1024;
+		p2p_noa_req->start_offset = (interval - duration - 10) * 1024;
+		p2p_noa_req->dyn_noa = dyn_noa;
+	}
+
+	/* Send the MM_SET_2P_NOA_REQ message to LMAC FW */
+	error = rwnx_send_msg(rwnx_hw, p2p_noa_req, 1, MM_SET_P2P_NOA_CFM, cfm);
+
+	return error;
+}
+#endif /* CONFIG_RWNX_P2P_DEBUGFS */
+
+#ifdef AICWF_ARP_OFFLOAD
+int rwnx_send_arpoffload_en_req(struct rwnx_hw *rwnx_hw,
+				struct rwnx_vif *rwnx_vif, u32_l ipaddr,
+				u8_l enable)
+{
+	struct mm_set_arpoffload_en_req *arp_offload_req;
+	int error;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_SET_P2P_NOA_REQ message */
+	arp_offload_req =
+		rwnx_msg_zalloc(MM_SET_ARPOFFLOAD_REQ, TASK_MM, DRV_TASK_ID,
+				sizeof(struct mm_set_arpoffload_en_req));
+
+	if (!arp_offload_req)
+		return -ENOMEM;
+
+	/* Fill the message parameters */
+	arp_offload_req->enable = enable;
+	arp_offload_req->vif_idx = rwnx_vif->vif_index;
+	arp_offload_req->ipaddr = ipaddr;
+
+	/* Send the MM_ARPOFFLOAD_EN_REQ message to UMAC FW */
+	error =
+		rwnx_send_msg(rwnx_hw, arp_offload_req, 1, MM_SET_ARPOFFLOAD_CFM, NULL);
+
+	return error;
+}
+#endif
+
+int rwnx_send_disable_agg_req(struct rwnx_hw *rwnx_hw, u8_l agg_disable,
+			      u8_l agg_disable_rx, u8_l sta_idx)
+{
+	struct mm_set_agg_disable_req *req;
+	int error;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_SET_AGG_DISABLE_REQ message */
+	req = rwnx_msg_zalloc(MM_SET_AGG_DISABLE_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_set_agg_disable_req));
+
+	if (!req)
+		return -ENOMEM;
+
+	req->disable = agg_disable;
+	req->staidx = sta_idx;
+	req->disable_rx = agg_disable_rx;
+
+	/* Send the MM_SET_AGG_DISABLE_REQ message to UMAC FW */
+	error = rwnx_send_msg(rwnx_hw, req, 1, MM_SET_AGG_DISABLE_CFM, NULL);
+
+	return error;
+};
+
+int rwnx_send_coex_req(struct rwnx_hw *rwnx_hw, u8_l disable_coexnull,
+		       u8_l enable_nullcts)
+{
+	struct mm_set_coex_req *coex_req;
+	int error;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_SET_COEX_REQ message */
+	coex_req = rwnx_msg_zalloc(MM_SET_COEX_REQ, TASK_MM, DRV_TASK_ID,
+				   sizeof(struct mm_set_coex_req));
+
+	if (!coex_req)
+		return -ENOMEM;
+
+	coex_req->bt_on = 1;
+	coex_req->disable_coexnull = disable_coexnull;
+	coex_req->enable_nullcts = enable_nullcts;
+	coex_req->enable_periodic_timer = 0;
+	coex_req->coex_timeslot_set = 0;
+
+	/* Send the MM_SET_COEX_REQ message to UMAC FW */
+	error = rwnx_send_msg(rwnx_hw, coex_req, 1, MM_SET_COEX_CFM, NULL);
+
+	return error;
+};
+
+int rwnx_send_rf_config_req(struct rwnx_hw *rwnx_hw, u8_l ofst, u8_l sel,
+			    u8_l *tbl, u16_l len)
+{
+	struct mm_set_rf_config_req *rf_config_req;
+	int error;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_SET_RF_CONFIG_REQ message */
+	rf_config_req = rwnx_msg_zalloc(MM_SET_RF_CONFIG_REQ, TASK_MM, DRV_TASK_ID,
+					sizeof(struct mm_set_rf_config_req));
+
+	if (!rf_config_req)
+		return -ENOMEM;
+
+	rf_config_req->table_sel = sel;
+	rf_config_req->table_ofst = ofst;
+	rf_config_req->table_num = 16;
+	rf_config_req->deft_page = 0;
+
+	memcpy(rf_config_req->data, tbl, len);
+
+	/* Send the MM_SET_RF_CONFIG_REQ message to UMAC FW */
+	error =
+		rwnx_send_msg(rwnx_hw, rf_config_req, 1, MM_SET_RF_CONFIG_CFM, NULL);
+
+	return error;
+}
+
+int rwnx_send_rf_calib_req(struct rwnx_hw *rwnx_hw,
+			   struct mm_set_rf_calib_cfm *cfm)
+{
+	struct mm_set_rf_calib_req *rf_calib_req;
+	struct xtal_cap_conf xtal_cap = {
+		0,
+	};
+	int error;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_SET_P2P_NOA_REQ message */
+	rf_calib_req = rwnx_msg_zalloc(MM_SET_RF_CALIB_REQ, TASK_MM, DRV_TASK_ID,
+				       sizeof(struct mm_set_rf_calib_req));
+
+	if (!rf_calib_req)
+		return -ENOMEM;
+
+	aic_chip_set_rf_calib_cfg(rwnx_hw, rf_calib_req);
+
+	rf_calib_req->param_alpha = 0x0c34c008;
+	rf_calib_req->bt_calib_en = 0;
+	rf_calib_req->bt_calib_param = 0x264203;
+
+	get_userconfig_xtal_cap(&xtal_cap);
+
+	if (xtal_cap.enable) {
+		AICWFDBG(LOGDEBUG, "user xtal cap: %d, cap_fine: %d\n", xtal_cap.xtal_cap,
+			 xtal_cap.xtal_cap_fine);
+		rf_calib_req->xtal_cap = xtal_cap.xtal_cap;
+		rf_calib_req->xtal_cap_fine = xtal_cap.xtal_cap_fine;
+	} else {
+		rf_calib_req->xtal_cap = 0;
+		rf_calib_req->xtal_cap_fine = 0;
+	}
+
+	/* Send the MM_SET_RF_CALIB_REQ message to UMAC FW */
+	error = rwnx_send_msg(rwnx_hw, rf_calib_req, 1, MM_SET_RF_CALIB_CFM, cfm);
+
+	return error;
+};
+
+int rwnx_send_get_macaddr_req(struct rwnx_hw *rwnx_hw,
+			      struct mm_get_mac_addr_cfm *cfm)
+{
+	struct mm_get_mac_addr_req *get_macaddr_req;
+	int error;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_GET_MAC_ADDR_REQ message */
+	get_macaddr_req = rwnx_msg_zalloc(MM_GET_MAC_ADDR_REQ, TASK_MM, DRV_TASK_ID,
+					  sizeof(struct mm_get_mac_addr_req));
+
+	if (!get_macaddr_req)
+		return -ENOMEM;
+
+	get_macaddr_req->get = 1;
+
+	/* Send the MM_GET_MAC_ADDR_REQ  message to UMAC FW */
+	error =
+		rwnx_send_msg(rwnx_hw, get_macaddr_req, 1, MM_GET_MAC_ADDR_CFM, cfm);
+
+	return error;
+};
+
+int rwnx_send_get_sta_info_req(struct rwnx_hw *rwnx_hw, u8_l sta_idx,
+			       struct mm_get_sta_info_cfm *cfm)
+{
+	struct mm_get_sta_info_req *get_info_req;
+	int error;
+
+	/* Build the MM_GET_STA_INFO_REQ message */
+	get_info_req = rwnx_msg_zalloc(MM_GET_STA_INFO_REQ, TASK_MM, DRV_TASK_ID,
+				       sizeof(struct mm_get_sta_info_req));
+
+	if (!get_info_req)
+		return -ENOMEM;
+
+	get_info_req->sta_idx = sta_idx;
+
+	/* Send the MM_GET_STA_INFO_REQ  message to UMAC FW */
+	error = rwnx_send_msg(rwnx_hw, get_info_req, 1, MM_GET_STA_INFO_CFM, cfm);
+
+	return error;
+};
+
+int rwnx_send_set_stack_start_req(struct rwnx_hw *rwnx_hw, u8_l on,
+				  u8_l efuse_valid, u8_l set_vendor_info,
+				  u8_l fwtrace_redir_en,
+				  struct mm_set_stack_start_cfm *cfm)
+{
+	struct mm_set_stack_start_req *req;
+	int error;
+
+	/* Build the MM_SET_STACK_START_REQ message */
+	req = rwnx_msg_zalloc(MM_SET_STACK_START_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_set_stack_start_req));
+
+	if (!req)
+		return -ENOMEM;
+
+	req->is_stack_start = on;
+	req->efuse_valid = efuse_valid;
+	req->set_vendor_info = set_vendor_info;
+	req->fwtrace_redir = fwtrace_redir_en;
+	/* Send the MM_SET_STACK_START_REQ  message to UMAC FW */
+	error = rwnx_send_msg(rwnx_hw, req, 1, MM_SET_STACK_START_CFM, cfm);
+
+	return error;
+}
+
+int rwnx_send_get_temp_req(struct rwnx_hw *rwnx_hw,
+			   struct mm_set_vendor_swconfig_cfm *cfm)
+{
+	struct mm_set_vendor_swconfig_req *req;
+	int ret;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the TEMP_COMP_GET_REQ message */
+	req = rwnx_msg_zalloc(MM_SET_VENDOR_SWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_set_vendor_swconfig_req));
+	if (!req) {
+		AICWFDBG(LOGINFO, "%s msg_alloc fail\n", __func__);
+		return -ENOMEM;
+	}
+	req->swconfig_id = TEMP_COMP_GET_REQ;
+
+	ret = rwnx_send_msg(rwnx_hw, req, 1, MM_SET_VENDOR_SWCONFIG_CFM, cfm);
+	if (!ret) {
+		AICWFDBG(LOGDEBUG, "status=%d, temp=%d\n", cfm->temp_comp_get_cfm.status,
+			 cfm->temp_comp_get_cfm.degree);
+	} else {
+		AICWFDBG(LOGINFO, "%s msg_fail\n", __func__);
+		return ret;
+	}
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+	// rwnx_hw->started_jiffies = jiffies;
+	rwnx_hw->temp = cfm->temp_comp_get_cfm.degree;
+#endif
+
+	return ret;
+}
+
+int rwnx_send_get_temp_hwconfig_req(struct rwnx_hw *rwnx_hw,
+				    struct mm_set_vendor_hwconfig_cfm *cfm)
+{
+	struct mm_get_chip_temp_req *req;
+	int ret;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+	/* Build the TEMP_COMP_HWCONFIG_GET_REQ message */
+	req = rwnx_msg_zalloc(MM_SET_VENDOR_HWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_get_chip_temp_req));
+	if (!req) {
+		AICWFDBG(LOGINFO, "%s msg_alloc fail\n", __func__);
+		return -ENOMEM;
+	}
+	req->hwconfig_id = CHIP_TEMP_GET_REQ;
+
+	ret = rwnx_send_msg(rwnx_hw, req, 1, MM_SET_VENDOR_HWCONFIG_CFM, cfm);
+	if (!ret) {
+		AICWFDBG(LOGDEBUG, "get_chip_temp degree=%d\n",
+			 cfm->chip_temp_cfm.degree);
+	} else {
+		AICWFDBG(LOGINFO, "%s msg_fail\n", __func__);
+		return ret;
+	}
+	return ret;
+}
+
+int rwnx_send_set_temp_comp_req(struct rwnx_hw *rwnx_hw,
+				struct mm_set_vendor_swconfig_cfm *cfm)
+{
+	struct mm_set_vendor_swconfig_req *req;
+	int ret;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+	/* Build the TEMP_COMP_SET_REQ message */
+	req = rwnx_msg_zalloc(MM_SET_VENDOR_SWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_set_vendor_swconfig_req));
+	if (!req) {
+		AICWFDBG(LOGERROR, "%s msg_alloc fail\n", __func__);
+		return -ENOMEM;
+	}
+	req->swconfig_id = TEMP_COMP_SET_REQ;
+	req->temp_comp_set_req.enable = 1;
+#ifdef CONFIG_AIC8800_TEMP_CONTROL
+	req->temp_comp_set_req.tmr_period_ms = TEMP_GET_INTERVAL;
+#else
+	req->temp_comp_set_req.tmr_period_ms = 15 * 1000;
+#endif
+
+	ret = rwnx_send_msg(rwnx_hw, req, 1, MM_SET_VENDOR_SWCONFIG_CFM, cfm);
+	if (!ret) {
+		AICWFDBG(LOGINFO, "temp_comp status: %d\n", cfm->temp_comp_set_cfm.status);
+	} else {
+		AICWFDBG(LOGINFO, "%s msg_fail\n", __func__);
+		return ret;
+	}
+	return ret;
+}
+
+int rwnx_send_set_pkt_filter_req(struct rwnx_hw *rwnx_hw, u16_l *param)
+{
+	struct mm_set_wakeup_info_req *req;
+	int i, err;
+	u16_l pt_start = 3;
+	u16_l total_len = 0;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the WAKEUP_INFO_REQ message */
+	if (param[2] == 0)
+		req = rwnx_msg_zalloc(MM_SET_VENDOR_HWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+				      sizeof(struct mm_set_wakeup_info_req));
+	else
+		req = rwnx_msg_zalloc(MM_SET_VENDOR_HWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+				      sizeof(struct mm_set_wakeup_info_req) + 2 * param[2]);
+
+	if (!req)
+		return -ENOMEM;
+	memset(req, 0, sizeof(struct mm_set_wakeup_info_req));
+
+	req->hwconfig_id = WAKEUP_INFO_REQ;
+	req->code = (u16_l)param[0];
+	req->offset = (u16_l)param[1];
+	req->length = (u16_l)param[2];
+	total_len = (u16_l)param[pt_start + 2 * req->length];
+	//AICWFDBG(LOGDEBUG, "total_len: 0x%x\n", total_len);
+	if (total_len > 0)
+		req->length_win = total_len - req->length + 1;
+	AICWFDBG(LOGINFO,
+		 "wakeup_info: id: 0x%x, offset: 0x%x, length: 0x%x, length_win: 0x%x, total_len: 0x%x\n",
+		 req->code, req->offset, req->length, req->length_win, total_len);
+	// AICWFDBG(LOGINFO, "set_wakeup_info_req mask and pattern: 0x%x\n", 2 * req->length);
+	for (i = 0; i < 2 * req->length; i++) {
+		req->mask_and_pattern[i] = param[pt_start + i];
+		// AICWFDBG(LOGINFO, "0x%x: 0x%02x ", i, req->mask_and_pattern[i]);
+	}
+
+	/* Send the MM_SET_VENDOR_HWCONFIG_CFM  message to UMAC FW */
+	err = rwnx_send_msg(rwnx_hw, req, 1, MM_SET_VENDOR_HWCONFIG_CFM, NULL);
+	if (err) {
+		AICWFDBG(LOGINFO, "%s msg_fail\n", __func__);
+		return err;
+	}
+
+	return err;
+}
+
+int rwnx_send_del_pkt_filter_req(struct rwnx_hw *rwnx_hw, u16_l code)
+{
+	struct mm_set_wakeup_info_req *req;
+	int err;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the WAKEUP_INFO_REQ message */
+	req = rwnx_msg_zalloc(MM_SET_VENDOR_HWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_set_wakeup_info_req));
+	if (!req)
+		return -ENOMEM;
+
+	req->hwconfig_id = WAKEUP_INFO_REQ;
+	req->code = code; // 0xffff;
+	req->offset = 0x0;
+	req->length = 0x0;
+
+	/* Send the MM_SET_VENDOR_HWCONFIG_CFM  message to UMAC FW */
+	err = rwnx_send_msg(rwnx_hw, req, 1, MM_SET_VENDOR_HWCONFIG_CFM, NULL);
+	if (err) {
+		AICWFDBG(LOGINFO, "%s msg_fail\n", __func__);
+		return err;
+	}
+
+	return err;
+}
+
+int rwnx_send_vendor_hwconfig_req(struct rwnx_hw *rwnx_hw, u32_l hwconfig_id,
+				  s32_l *param, s32_l *param_out)
+{
+	struct mm_set_acs_txop_req *req0;
+	struct mm_set_channel_access_req *req1;
+	struct mm_set_mac_timescale_req *req2;
+	struct mm_set_cca_threshold_req *req3;
+	struct mm_set_bwmode_req *req4;
+	struct mm_set_ap_ps_level_req *req5 = NULL;
+	struct mm_set_customized_freq_req *req6;
+
+	int error = 0;
+	int i;
+
+	switch (hwconfig_id) {
+	case ACS_TXOP_REQ:
+		/* Build the ACS_TXOP_REQ message */
+		req0 = rwnx_msg_zalloc(MM_SET_VENDOR_HWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+				       sizeof(struct mm_set_acs_txop_req));
+		if (!req0)
+			return -ENOMEM;
+		req0->hwconfig_id = hwconfig_id;
+		req0->txop_be = param[0];
+		req0->txop_bk = param[1];
+		req0->txop_vi = param[2];
+		req0->txop_vo = param[3];
+		AICWFDBG(LOGDEBUG, "set_acs_txop_req: be: %x,bk: %x,vi: %x,vo: %x\n", req0->txop_be,
+			 req0->txop_bk, req0->txop_vi, req0->txop_vo);
+		/* Send the MM_SET_VENDOR_HWCONFIG_CFM  message to UMAC FW */
+		error =
+			rwnx_send_msg(rwnx_hw, req0, 1, MM_SET_VENDOR_HWCONFIG_CFM, NULL);
+		break;
+
+	case CHANNEL_ACCESS_REQ:
+		/* Build the CHANNEL_ACCESS_REQ message */
+		req1 = rwnx_msg_zalloc(MM_SET_VENDOR_HWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+				       sizeof(struct mm_set_channel_access_req));
+		if (!req1)
+			return -ENOMEM;
+		req1->hwconfig_id = hwconfig_id;
+		req1->edca[0] = param[0];
+		req1->edca[1] = param[1];
+		req1->edca[2] = param[2];
+		req1->edca[3] = param[3];
+		req1->vif_idx = param[4];
+		req1->retry_cnt = param[5];
+		req1->rts_en = param[6];
+		req1->long_nav_en = param[7];
+		req1->cfe_en = param[8];
+		req1->rc_retry_cnt[0] = param[9];
+		req1->rc_retry_cnt[1] = param[10];
+		req1->rc_retry_cnt[2] = param[11];
+		req1->ccademod_th = param[12];
+		AICWFDBG(LOGDEBUG,
+			 "set_channel_access_req:edca[]= %x %x %x %x\nvif_idx: %x, retry_cnt: %x, rts_en: %x, long_nav_en: %x, cfe_en: %x, rc_retry_cnt: %x:%x:%x, ccademod_th = %d\n",
+			 req1->edca[0], req1->edca[1], req1->edca[2], req1->edca[3],
+			 req1->vif_idx, req1->retry_cnt, req1->rts_en, req1->long_nav_en,
+			 req1->cfe_en, req1->rc_retry_cnt[0], req1->rc_retry_cnt[1],
+			 req1->rc_retry_cnt[2], req1->ccademod_th);
+		/* Send the MM_SET_VENDOR_HWCONFIG_CFM  message to UMAC FW */
+		error =
+			rwnx_send_msg(rwnx_hw, req1, 1, MM_SET_VENDOR_HWCONFIG_CFM, NULL);
+		break;
+
+	case MAC_TIMESCALE_REQ:
+		/* Build the MAC_TIMESCALE_REQ message */
+		req2 = rwnx_msg_zalloc(MM_SET_VENDOR_HWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+				       sizeof(struct mm_set_mac_timescale_req));
+		if (!req2)
+			return -ENOMEM;
+		req2->hwconfig_id = hwconfig_id;
+		req2->sifs_a_time = param[0];
+		req2->sifs_b_time = param[1];
+		req2->slot_time = param[2];
+		req2->rx_startdelay_ofdm = param[3];
+		req2->rx_startdelay_long = param[4];
+		req2->rx_startdelay_short = param[5];
+		AICWFDBG(LOGDEBUG,
+			 "set_mac_timescale_req:sifsA_time: %x, sifsB_time: %x, slot_time: %x, rx_startdelay ofdm:%x long %x short %x\n",
+			 req2->sifs_a_time, req2->sifs_b_time, req2->slot_time,
+			 req2->rx_startdelay_ofdm, req2->rx_startdelay_long,
+			 req2->rx_startdelay_short);
+		/* Send the MM_SET_VENDOR_HWCONFIG_CFM  message to UMAC FW */
+		error =
+			rwnx_send_msg(rwnx_hw, req2, 1, MM_SET_VENDOR_HWCONFIG_CFM, NULL);
+		break;
+
+	case CCA_THRESHOLD_REQ:
+		/* Build the CCA_THRESHOLD_REQ message */
+		req3 = rwnx_msg_zalloc(MM_SET_VENDOR_HWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+				       sizeof(struct mm_set_cca_threshold_req));
+		if (!req3)
+			return -ENOMEM;
+		req3->hwconfig_id = hwconfig_id;
+		req3->auto_cca_en = param[0];
+		req3->cca20p_rise_th = param[1];
+		req3->cca20s_rise_th = param[2];
+		req3->cca20p_fall_th = param[3];
+		req3->cca20s_fall_th = param[4];
+		AICWFDBG(LOGDEBUG,
+			 "cca_threshold_req: auto_cca_en:%d\ncca20p_rise_th = %d\ncca20s_rise_th = %d\ncca20p_fall_th = %d\ncca20s_fall_th = %d\n",
+			 req3->auto_cca_en, req3->cca20p_rise_th, req3->cca20s_rise_th,
+			 req3->cca20p_fall_th, req3->cca20s_fall_th);
+		/* Send the MM_SET_VENDOR_HWCONFIG_CFM  message to UMAC FW */
+		error =
+			rwnx_send_msg(rwnx_hw, req3, 1, MM_SET_VENDOR_HWCONFIG_CFM, NULL);
+		break;
+	case BWMODE_REQ:
+		/* Build the SET_BWMODE_REQ message */
+		req4 = rwnx_msg_zalloc(MM_SET_VENDOR_HWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+				       sizeof(struct mm_set_bwmode_req));
+		if (!req4)
+			return -ENOMEM;
+		req4->hwconfig_id = hwconfig_id;
+		req4->bwmode = param[0];
+		AICWFDBG(LOGDEBUG, "bwmode :%d\n", req4->bwmode);
+		/* Send the MM_SET_VENDOR_HWCONFIG_CFM  message to UMAC FW */
+		error =
+			rwnx_send_msg(rwnx_hw, req4, 1, MM_SET_VENDOR_HWCONFIG_CFM, NULL);
+		break;
+	case CHIP_TEMP_GET_REQ:
+		aic_chip_get_temp(rwnx_hw, param_out);
+		break;
+	case AP_PS_LEVEL_SET_REQ:
+		/*
+		 * AP_PS_CLK_1 = 1,  // 3:7 -> active 3, doze 7
+		 * AP_PS_CLK_2,      // 5:5 -> active 5, doze 5
+		 * AP_PS_CLK_3,      // 7:3 -> active 7, doze 3
+		 * AP_PS_CLK_4,      // 8:2 -> active 8, doze 2
+		 * AP_PS_CLK_5,      // 9:1 -> active 9, doze 1
+		 */
+		/* Build the MM_SET_VENDOR_HWCONFIG_REQ message */
+		req5 = rwnx_msg_zalloc(MM_SET_VENDOR_HWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+				       sizeof(struct mm_set_ap_ps_level_req));
+		if (!req5)
+			return -ENOMEM;
+		req5->hwconfig_id = hwconfig_id;
+		req5->ap_ps_level = param[0];
+
+		/* Send the MM_GET_MAC_ADDR_REQ  message to UMAC FW */
+		error =
+			rwnx_send_msg(rwnx_hw, req5, 1, MM_SET_VENDOR_HWCONFIG_CFM, NULL);
+		if (!error)
+			AICWFDBG(LOGDEBUG, "set ap_ps_level=%d\n", param[0]);
+		break;
+
+	case CUSTOMIZED_FREQ_REQ:
+		/* Build the CUSTOMIZED_FREQ_REQ message */
+		req6 = rwnx_msg_zalloc(MM_SET_VENDOR_HWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+				       sizeof(struct mm_set_customized_freq_req));
+		if (!req6)
+			return -ENOMEM;
+		req5->hwconfig_id = hwconfig_id;
+		for (i = 0; i < 4; i++) {
+			req6->raw_freq[i] = param[i];
+			req6->map_freq[i] = param[i + 4];
+		}
+		AICWFDBG(LOGDEBUG,
+			 "customized_freq_req: raw_freq[ %d %d %d %d ], map_freq[ %d %d %d %d]\n",
+			 param[0], param[1], param[2], param[3], param[4], param[5],
+			 param[6], param[7]);
+		/* Send the MM_SET_VENDOR_HWCONFIG_CFM  message to UMAC FW */
+		error =
+			rwnx_send_msg(rwnx_hw, req5, 1, MM_SET_VENDOR_HWCONFIG_CFM, NULL);
+
+		break;
+	default:
+		return -ENOMEM;
+	}
+	return error;
+}
+
+int rwnx_send_vendor_swconfig_req(struct rwnx_hw *rwnx_hw, u32_l swconfig_id,
+				  s32_l *param_in, s32_l *param_out)
+{
+	struct mm_set_vendor_swconfig_req *req;
+	struct mm_set_vendor_swconfig_cfm cfm = {
+		0,
+	};
+	int error;
+
+	req = rwnx_msg_zalloc(MM_SET_VENDOR_SWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_set_vendor_swconfig_req));
+	if (!req)
+		return -ENOMEM;
+	req->swconfig_id = swconfig_id;
+
+	switch (swconfig_id) {
+	case BCN_CFG_REQ:
+		/* Build the BCN_CFG_REQ message */
+		req->bcn_cfg_req.tim_bcmc_ignored_enable = (bool)param_in[0];
+		AICWFDBG(LOGDEBUG, "bcn_cfg_req: tim_bcmc_ignd=%d\n",
+			 req->bcn_cfg_req.tim_bcmc_ignored_enable);
+		/* Send the MM_SET_VENDOR_SWCONFIG_REQ message to UMAC FW */
+		error =
+			rwnx_send_msg(rwnx_hw, req, 1, MM_SET_VENDOR_SWCONFIG_CFM, &cfm);
+		if (!error) {
+			param_out[0] = (s32_l)cfm.bcn_cfg_cfm.tim_bcmc_ignored_status;
+			AICWFDBG(LOGDEBUG, "status=%d\n", cfm.bcn_cfg_cfm.tim_bcmc_ignored_status);
+		}
+		break;
+
+	case TEMP_COMP_SET_REQ:
+		/* Build the TEMP_COMP_SET_REQ message */
+		req->temp_comp_set_req.enable = (u8_l)param_in[0];
+		req->temp_comp_set_req.tmr_period_ms = (u32_l)param_in[1];
+		AICWFDBG(LOGDEBUG, "temp_comp_set_req: en=%d, tmr=%x\n",
+			 req->temp_comp_set_req.enable,
+			 req->temp_comp_set_req.tmr_period_ms);
+		/* Send the MM_SET_VENDOR_SWCONFIG_REQ message to UMAC FW */
+		error =
+			rwnx_send_msg(rwnx_hw, req, 1, MM_SET_VENDOR_SWCONFIG_CFM, &cfm);
+		if (!error) {
+			param_out[0] = (s32_l)cfm.temp_comp_set_cfm.status;
+			AICWFDBG(LOGDEBUG, "status=%d\n", cfm.temp_comp_set_cfm.status);
+		}
+		break;
+
+	case TEMP_COMP_GET_REQ:
+		AICWFDBG(LOGDEBUG, "temp_comp_get_req\n");
+		/* Send the MM_SET_VENDOR_SWCONFIG_REQ message to UMAC FW */
+		error =
+			rwnx_send_msg(rwnx_hw, req, 1, MM_SET_VENDOR_SWCONFIG_CFM, &cfm);
+		if (!error) {
+			param_out[0] = (s32_l)cfm.temp_comp_get_cfm.status;
+			param_out[1] = (s32_l)cfm.temp_comp_get_cfm.degree;
+			AICWFDBG(LOGDEBUG, "status=%d, degree=%d\n", cfm.temp_comp_get_cfm.status,
+				 cfm.temp_comp_get_cfm.degree);
+		}
+		break;
+
+	case EXT_FLAGS_SET_REQ:
+		/* Build the EXT_FLAGS_SET_REQ message */
+		req->ext_flags_set_req.user_flags = (u32_l)param_in[0];
+		AICWFDBG(LOGDEBUG, "ext_flags_set_req: flags=%x\n",
+			 req->ext_flags_set_req.user_flags);
+		/* Send the MM_SET_VENDOR_SWCONFIG_REQ message to UMAC FW */
+		error =
+			rwnx_send_msg(rwnx_hw, req, 1, MM_SET_VENDOR_SWCONFIG_CFM, &cfm);
+		if (!error) {
+			param_out[0] = (u32_l)cfm.ext_flags_set_cfm.user_flags;
+			AICWFDBG(LOGDEBUG, "cfm flags=%x\n", cfm.ext_flags_set_cfm.user_flags);
+		}
+		break;
+
+	case EXT_FLAGS_GET_REQ:
+		/* Build the EXT_FLAGS_GET_REQ message */
+		AICWFDBG(LOGDEBUG, "ext_flags_get_req\n");
+		/* Send the MM_SET_VENDOR_SWCONFIG_REQ message to UMAC FW */
+		error =
+			rwnx_send_msg(rwnx_hw, req, 1, MM_SET_VENDOR_SWCONFIG_CFM, &cfm);
+		if (!error) {
+			param_out[0] = (u32_l)cfm.ext_flags_get_cfm.user_flags;
+			AICWFDBG(LOGDEBUG, "cfm flags=%x\n", cfm.ext_flags_get_cfm.user_flags);
+		}
+		break;
+
+	case EXT_FLAGS_MASK_SET_REQ:
+		/* Build the EXT_FLAGS_MASK_SET_REQ message */
+		req->ext_flags_mask_set_req.user_flags_mask = (u32_l)param_in[0];
+		req->ext_flags_mask_set_req.user_flags_val = (u32_l)param_in[1];
+		AICWFDBG(LOGDEBUG, "ext_flags_mask_set_req: flags mask=0x%x, val=0x%x\n",
+			 req->ext_flags_mask_set_req.user_flags_mask,
+			 req->ext_flags_mask_set_req.user_flags_val);
+		/* Send the MM_SET_VENDOR_SWCONFIG_REQ message to UMAC FW */
+		error =
+			rwnx_send_msg(rwnx_hw, req, 1, MM_SET_VENDOR_SWCONFIG_CFM, &cfm);
+		if (!error) {
+			param_out[0] = (u32_l)cfm.ext_flags_mask_set_cfm.user_flags;
+			AICWFDBG(LOGDEBUG, "cfm flags=%x\n", cfm.ext_flags_mask_set_cfm.user_flags);
+		}
+		break;
+
+	default:
+		error = -ENOMEM;
+		break;
+	}
+
+	return error;
+}
+
+int rwnx_send_mask_set_ext_flags_req(struct rwnx_hw *rwnx_hw,
+				     u32_l flags_mask, u32_l flags_val,
+				     struct mm_set_vendor_swconfig_cfm *cfm)
+{
+	struct mm_set_vendor_swconfig_req *req;
+	int ret;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+	/* Build the REQ message */
+	req = rwnx_msg_zalloc(MM_SET_VENDOR_SWCONFIG_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_set_vendor_swconfig_req));
+	if (!req) {
+		AICWFDBG(LOGERROR, "%s msg_alloc fail\n", __func__);
+		return -ENOMEM;
+	}
+	req->swconfig_id = EXT_FLAGS_MASK_SET_REQ;
+	req->ext_flags_mask_set_req.user_flags_mask = (u32_l)flags_mask;
+	req->ext_flags_mask_set_req.user_flags_val = (u32_l)flags_val;
+
+	ret = rwnx_send_msg(rwnx_hw, req, 1, MM_SET_VENDOR_SWCONFIG_CFM, cfm);
+	if (!ret) {
+		AICWFDBG(LOGINFO, "curr ext_flags=%x\n",
+			 cfm->ext_flags_mask_set_cfm.user_flags);
+	} else {
+		AICWFDBG(LOGERROR, "%s send_msg_fail\n", __func__);
+		return ret;
+	}
+	return ret;
+}
+
+#ifdef CONFIG_SDIO_BT
+int rwnx_sdio_bt_send_req(struct rwnx_hw *rwnx_hw, uint32_t len,
+			  struct sk_buff *skb)
+{
+	struct mm_bt_send_req *req;
+	struct mm_bt_send_cfm cfm;
+	int error;
+
+	cfm.status = 0;
+	req = rwnx_msg_zalloc(TDLS_SDIO_BT_SEND_REQ, TASK_TDLS, DRV_TASK_ID,
+			      sizeof(struct mm_bt_send_req));
+
+	if (!req)
+		return -ENOMEM;
+
+	req->data_len = len;
+	memcpy(req->bt_data, skb->data, len);
+
+	error = rwnx_send_msg(rwnx_hw, req, 1, TDLS_SDIO_BT_SEND_CFM, &cfm);
+	if (!cfm.status)
+		AICWFDBG(LOGDEBUG, "bt send no cfm");
+
+	return error;
+}
+#endif
+
+int rwnx_send_get_fw_version_req(struct rwnx_hw *rwnx_hw,
+				 struct mm_get_fw_version_cfm *cfm)
+{
+	void *req;
+	int error;
+
+	/* Build the MM_GET_FW_VERSION_REQ message */
+	req = rwnx_msg_zalloc(MM_GET_FW_VERSION_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(u8));
+
+	if (!req)
+		return -ENOMEM;
+
+	/* Send the MM_GET_FW_VERSION_REQ  message to UMAC FW */
+	error = rwnx_send_msg(rwnx_hw, req, 1, MM_GET_FW_VERSION_CFM, cfm);
+
+	return error;
+}
+
+int rwnx_send_txpwr_lvl_req(struct rwnx_hw *rwnx_hw)
+{
+	struct mm_set_txpwr_lvl_req *txpwr_lvl_req;
+	struct txpwr_lvl_conf_v2 txpwr_lvl_v2_tmp;
+	struct txpwr_lvl_conf_v2 *txpwr_lvl_v2;
+	int error;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_SET_TXPWR_LVL_REQ message */
+	txpwr_lvl_req =
+		rwnx_msg_zalloc(MM_SET_TXPWR_IDX_LVL_REQ, TASK_MM, DRV_TASK_ID,
+				sizeof(struct mm_set_txpwr_lvl_req));
+
+	if (!txpwr_lvl_req)
+		return -ENOMEM;
+
+	txpwr_lvl_v2 = &txpwr_lvl_v2_tmp;
+
+	get_userconfig_txpwr_lvl_v2_in_fdrv(txpwr_lvl_v2);
+
+	if (txpwr_lvl_v2->enable == 0) {
+		rwnx_msg_free(rwnx_hw, txpwr_lvl_req);
+		return 0;
+	}
+
+	AICWFDBG(LOGINFO, "%s:enable:%d\r\n", __func__, txpwr_lvl_v2->enable);
+	AICWFDBG(LOGINFO, "%s:lvl_11b_11ag_1m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[0]);
+	AICWFDBG(LOGINFO, "%s:lvl_11b_11ag_2m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[1]);
+	AICWFDBG(LOGINFO, "%s:lvl_11b_11ag_5m5_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[2]);
+	AICWFDBG(LOGINFO, "%s:lvl_11b_11ag_11m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[3]);
+	AICWFDBG(LOGINFO, "%s:lvl_11b_11ag_6m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[4]);
+	AICWFDBG(LOGINFO, "%s:lvl_11b_11ag_9m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[5]);
+	AICWFDBG(LOGINFO, "%s:lvl_11b_11ag_12m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[6]);
+	AICWFDBG(LOGINFO, "%s:lvl_11b_11ag_18m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[7]);
+	AICWFDBG(LOGINFO, "%s:lvl_11b_11ag_24m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[8]);
+	AICWFDBG(LOGINFO, "%s:lvl_11b_11ag_36m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[9]);
+	AICWFDBG(LOGINFO, "%s:lvl_11b_11ag_48m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[10]);
+	AICWFDBG(LOGINFO, "%s:lvl_11b_11ag_54m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[11]);
+	AICWFDBG(LOGINFO, "%s:lvl_11n_11ac_mcs0_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[0]);
+	AICWFDBG(LOGINFO, "%s:lvl_11n_11ac_mcs1_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[1]);
+	AICWFDBG(LOGINFO, "%s:lvl_11n_11ac_mcs2_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[2]);
+	AICWFDBG(LOGINFO, "%s:lvl_11n_11ac_mcs3_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[3]);
+	AICWFDBG(LOGINFO, "%s:lvl_11n_11ac_mcs4_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[4]);
+	AICWFDBG(LOGINFO, "%s:lvl_11n_11ac_mcs5_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[5]);
+	AICWFDBG(LOGINFO, "%s:lvl_11n_11ac_mcs6_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[6]);
+	AICWFDBG(LOGINFO, "%s:lvl_11n_11ac_mcs7_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[7]);
+	AICWFDBG(LOGINFO, "%s:lvl_11n_11ac_mcs8_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[8]);
+	AICWFDBG(LOGINFO, "%s:lvl_11n_11ac_mcs9_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[9]);
+	AICWFDBG(LOGINFO, "%s:lvl_11ax_mcs0_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[0]);
+	AICWFDBG(LOGINFO, "%s:lvl_11ax_mcs1_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[1]);
+	AICWFDBG(LOGINFO, "%s:lvl_11ax_mcs2_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[2]);
+	AICWFDBG(LOGINFO, "%s:lvl_11ax_mcs3_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[3]);
+	AICWFDBG(LOGINFO, "%s:lvl_11ax_mcs4_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[4]);
+	AICWFDBG(LOGINFO, "%s:lvl_11ax_mcs5_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[5]);
+	AICWFDBG(LOGINFO, "%s:lvl_11ax_mcs6_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[6]);
+	AICWFDBG(LOGINFO, "%s:lvl_11ax_mcs7_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[7]);
+	AICWFDBG(LOGINFO, "%s:lvl_11ax_mcs8_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[8]);
+	AICWFDBG(LOGINFO, "%s:lvl_11ax_mcs9_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[9]);
+	AICWFDBG(LOGINFO, "%s:lvl_11ax_mcs10_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[10]);
+	AICWFDBG(LOGINFO, "%s:lvl_11ax_mcs11_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[11]);
+
+	if (rwnx_hw->testmode == 0 && chip_sub_id == 0) {
+		txpwr_lvl_req->txpwr_lvl.enable = txpwr_lvl_v2->enable;
+		txpwr_lvl_req->txpwr_lvl.dsss =
+			txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[3]; // 11M
+		txpwr_lvl_req->txpwr_lvl.ofdmlowrate_2g4 =
+			txpwr_lvl_v2->pwrlvl_11ax_2g4[4]; // MCS4
+		txpwr_lvl_req->txpwr_lvl.ofdm64qam_2g4 =
+			txpwr_lvl_v2->pwrlvl_11ax_2g4[7]; // MCS7
+		txpwr_lvl_req->txpwr_lvl.ofdm256qam_2g4 =
+			txpwr_lvl_v2->pwrlvl_11ax_2g4[9]; // MCS9
+		txpwr_lvl_req->txpwr_lvl.ofdm1024qam_2g4 =
+			txpwr_lvl_v2->pwrlvl_11ax_2g4[11];        // MCS11
+		txpwr_lvl_req->txpwr_lvl.ofdmlowrate_5g = 13; // unused
+		txpwr_lvl_req->txpwr_lvl.ofdm64qam_5g = 13;   // unused
+		txpwr_lvl_req->txpwr_lvl.ofdm256qam_5g = 13;  // unused
+		txpwr_lvl_req->txpwr_lvl.ofdm1024qam_5g = 13; // unused
+	} else {
+		txpwr_lvl_req->txpwr_lvl_v2 = *txpwr_lvl_v2;
+	}
+
+	/* Send the MM_SET_TXPWR_LVL_REQ message to UMAC FW */
+	error = rwnx_send_msg(rwnx_hw, txpwr_lvl_req, 1,
+			      MM_SET_TXPWR_IDX_LVL_CFM, NULL);
+
+	return error;
+}
+
+int rwnx_send_txpwr_lvl_v3_req(struct rwnx_hw *rwnx_hw, int index_reg)
+{
+	struct mm_set_txpwr_lvl_req *txpwr_lvl_req;
+	struct txpwr_lvl_conf_v3 txpwr_lvl_v3_tmp;
+	struct txpwr_lvl_conf_v3 *txpwr_lvl_v3;
+	struct txpwr_loss_conf txpwr_loss_tmp;
+	struct txpwr_loss_conf *txpwr_loss;
+	int error;
+	int i;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_SET_TXPWR_LVL_REQ message */
+	txpwr_lvl_req =
+		rwnx_msg_zalloc(MM_SET_TXPWR_IDX_LVL_REQ, TASK_MM, DRV_TASK_ID,
+				sizeof(struct mm_set_txpwr_lvl_req));
+
+	if (!txpwr_lvl_req)
+		return -ENOMEM;
+
+	txpwr_lvl_v3 = &txpwr_lvl_v3_tmp;
+	txpwr_loss = &txpwr_loss_tmp;
+	txpwr_loss->loss_enable_2g4 = 0;
+	txpwr_loss->loss_enable_5g = 0;
+
+	//get_userconfig_txpwr_lvl_v3_in_fdrv(txpwr_lvl_v3);
+	get_userconfig_txpwr_lvl_group_in_fdrv(txpwr_lvl_v3, index_reg);
+	get_userconfig_txpwr_loss(txpwr_loss);
+
+	if (txpwr_loss->loss_enable_2g4 == 1) {
+		AICWFDBG(LOGINFO, "%s:loss_value_2g4: %d\r\n", __func__,
+			 txpwr_loss->loss_value_2g4);
+
+		for (i = 0; i <= 11; i++)
+			txpwr_lvl_v3->pwrlvl_11b_11ag_2g4[i] -= txpwr_loss->loss_value_2g4;
+		for (i = 0; i <= 9; i++)
+			txpwr_lvl_v3->pwrlvl_11n_11ac_2g4[i] -= txpwr_loss->loss_value_2g4;
+		for (i = 0; i <= 11; i++)
+			txpwr_lvl_v3->pwrlvl_11ax_2g4[i] -= txpwr_loss->loss_value_2g4;
+	}
+
+	if (txpwr_loss->loss_enable_5g == 1) {
+		AICWFDBG(LOGINFO, "%s:loss_value_5g: %d\r\n", __func__,
+			 txpwr_loss->loss_value_5g);
+
+		for (i = 0; i <= 11; i++)
+			txpwr_lvl_v3->pwrlvl_11a_5g[i] -= txpwr_loss->loss_value_5g;
+		for (i = 0; i <= 9; i++)
+			txpwr_lvl_v3->pwrlvl_11n_11ac_5g[i] -= txpwr_loss->loss_value_5g;
+		for (i = 0; i <= 11; i++)
+			txpwr_lvl_v3->pwrlvl_11ax_5g[i] -= txpwr_loss->loss_value_5g;
+	}
+
+	if (txpwr_lvl_v3->enable == 0) {
+		rwnx_msg_free(rwnx_hw, txpwr_lvl_req);
+		return 0;
+	}
+
+	AICWFDBG(LOGDEBUG, "%s: dump\n", __func__);
+	AICWFDBG(LOGDEBUG, "txpwr_lvl_v3->enable: %d\r\n", txpwr_lvl_v3->enable);
+	AICWFDBG(LOGDEBUG, "lvl_11b_11ag_1m_2g4:%d\r\n",
+		 txpwr_lvl_v3->pwrlvl_11b_11ag_2g4[0]);
+
+	txpwr_lvl_req->txpwr_lvl_v3 = *txpwr_lvl_v3;
+
+	/* Send the MM_SET_TXPWR_LVL_REQ message to UMAC FW */
+	error = rwnx_send_msg(rwnx_hw, txpwr_lvl_req, 1,
+			      MM_SET_TXPWR_IDX_LVL_CFM, NULL);
+
+	return error;
+}
+
+int rwnx_send_txpwr_lvl_adj_req(struct rwnx_hw *rwnx_hw)
+{
+	struct mm_set_txpwr_lvl_adj_req *txpwr_lvl_adj_req;
+	struct txpwr_lvl_adj_conf txpwr_lvl_adj_tmp;
+	struct txpwr_lvl_adj_conf *txpwr_lvl_adj;
+	int error;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_SET_TXPWR_LVL_REQ message */
+	txpwr_lvl_adj_req =
+		rwnx_msg_zalloc(MM_SET_TXPWR_LVL_ADJ_REQ, TASK_MM, DRV_TASK_ID,
+				sizeof(struct mm_set_txpwr_lvl_adj_req));
+
+	if (!txpwr_lvl_adj_req)
+		return -ENOMEM;
+
+	txpwr_lvl_adj = &txpwr_lvl_adj_tmp;
+
+	get_userconfig_txpwr_lvl_adj_in_fdrv(txpwr_lvl_adj);
+
+	if (txpwr_lvl_adj->enable == 0) {
+		rwnx_msg_free(rwnx_hw, txpwr_lvl_adj_req);
+		return 0;
+	}
+
+	AICWFDBG(LOGINFO, "%s:enable:%d\r\n", __func__, txpwr_lvl_adj->enable);
+	AICWFDBG(LOGINFO, "%s:lvl_adj_2g4_chan_1_4:%d\r\n", __func__,
+		 txpwr_lvl_adj->pwrlvl_adj_tbl_2g4[0]);
+	AICWFDBG(LOGINFO, "%s:lvl_adj_2g4_chan_5_9:%d\r\n", __func__,
+		 txpwr_lvl_adj->pwrlvl_adj_tbl_2g4[1]);
+	AICWFDBG(LOGINFO, "%s:lvl_adj_2g4_chan_10_13:%d\r\n", __func__,
+		 txpwr_lvl_adj->pwrlvl_adj_tbl_2g4[2]);
+	AICWFDBG(LOGINFO, "%s:lvl_adj_5g_chan_42:%d\r\n", __func__,
+		 txpwr_lvl_adj->pwrlvl_adj_tbl_5g[0]);
+	AICWFDBG(LOGINFO, "%s:lvl_adj_5g_chan_58:%d\r\n", __func__,
+		 txpwr_lvl_adj->pwrlvl_adj_tbl_5g[1]);
+	AICWFDBG(LOGINFO, "%s:lvl_adj_5g_chan_106:%d\r\n", __func__,
+		 txpwr_lvl_adj->pwrlvl_adj_tbl_5g[2]);
+	AICWFDBG(LOGINFO, "%s:lvl_adj_5g_chan_122:%d\r\n", __func__,
+		 txpwr_lvl_adj->pwrlvl_adj_tbl_5g[3]);
+	AICWFDBG(LOGINFO, "%s:lvl_adj_5g_chan_138:%d\r\n", __func__,
+		 txpwr_lvl_adj->pwrlvl_adj_tbl_5g[4]);
+	AICWFDBG(LOGINFO, "%s:lvl_adj_5g_chan_155:%d\r\n", __func__,
+		 txpwr_lvl_adj->pwrlvl_adj_tbl_5g[5]);
+
+	txpwr_lvl_adj_req->txpwr_lvl_adj = *txpwr_lvl_adj;
+
+	/* Send the MM_SET_TXPWR_LVL_REQ message to UMAC FW */
+	error = rwnx_send_msg(rwnx_hw, txpwr_lvl_adj_req, 1,
+			      MM_SET_TXPWR_LVL_ADJ_CFM, NULL);
+
+	return error;
+}
+
+int rwnx_send_txpwr_idx_req(struct rwnx_hw *rwnx_hw)
+{
+	struct mm_set_txpwr_idx_req *txpwr_idx_req;
+	struct txpwr_idx_conf *txpwr_idx;
+	int error;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_SET_TXPWR_IDX_LVL_REQ message */
+	txpwr_idx_req =
+		rwnx_msg_zalloc(MM_SET_TXPWR_IDX_LVL_REQ, TASK_MM, DRV_TASK_ID,
+				sizeof(struct mm_set_txpwr_idx_req));
+
+	if (!txpwr_idx_req)
+		return -ENOMEM;
+
+	txpwr_idx = &txpwr_idx_req->txpwr_idx;
+	txpwr_idx->enable = 1;
+	txpwr_idx->dsss = 9;
+	txpwr_idx->ofdmlowrate_2g4 = 8;
+	txpwr_idx->ofdm64qam_2g4 = 8;
+	txpwr_idx->ofdm256qam_2g4 = 8;
+	txpwr_idx->ofdm1024qam_2g4 = 8;
+	txpwr_idx->ofdmlowrate_5g = 11;
+	txpwr_idx->ofdm64qam_5g = 10;
+	txpwr_idx->ofdm256qam_5g = 9;
+	txpwr_idx->ofdm1024qam_5g = 9;
+
+	get_userconfig_txpwr_idx(txpwr_idx);
+
+	if (txpwr_idx->enable == 0) {
+		rwnx_msg_free(rwnx_hw, txpwr_idx_req);
+		return 0;
+	}
+	/* Send the MM_SET_TXPWR_IDX_LVL_REQ message to UMAC FW */
+	error = rwnx_send_msg(rwnx_hw, txpwr_idx_req, 1,
+			      MM_SET_TXPWR_IDX_LVL_CFM, NULL);
+
+	return error;
+};
+
+int rwnx_send_txpwr_ofst_req(struct rwnx_hw *rwnx_hw)
+{
+	struct mm_set_txpwr_ofst_req *txpwr_ofst_req;
+	struct txpwr_ofst_conf *txpwr_ofst;
+	int error;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_SET_TXPWR_OFST_REQ message */
+	txpwr_ofst_req =
+		rwnx_msg_zalloc(MM_SET_TXPWR_OFST_REQ, TASK_MM, DRV_TASK_ID,
+				sizeof(struct mm_set_txpwr_ofst_req));
+
+	if (!txpwr_ofst_req)
+		return -ENOMEM;
+
+	txpwr_ofst = &txpwr_ofst_req->txpwr_ofst;
+	txpwr_ofst->enable = 1;
+	txpwr_ofst->chan_1_4 = 0;
+	txpwr_ofst->chan_5_9 = 0;
+	txpwr_ofst->chan_10_13 = 0;
+	txpwr_ofst->chan_36_64 = 0;
+	txpwr_ofst->chan_100_120 = 0;
+	txpwr_ofst->chan_122_140 = 0;
+	txpwr_ofst->chan_142_165 = 0;
+
+	aic_chip_get_userconfig_txpwr_ofst(rwnx_hw, txpwr_ofst);
+
+	if (txpwr_ofst->enable == 0) {
+		rwnx_msg_free(rwnx_hw, txpwr_ofst_req);
+		return 0;
+	}
+	/* Send the MM_SET_TXPWR_OFST_REQ message to UMAC FW */
+	error = rwnx_send_msg(rwnx_hw, txpwr_ofst_req, 1, MM_SET_TXPWR_OFST_CFM,
+			      NULL);
+
+	return error;
+};
+
+int rwnx_send_txpwr_ofst2x_req(struct rwnx_hw *rwnx_hw)
+{
+	struct mm_set_txpwr_ofst_req *txpwr_ofst_req;
+	struct txpwr_ofst2x_conf *txpwr_ofst2x;
+	int error = 0;
+	int type, ch_grp;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_SET_TXPWR_OFST_REQ message */
+	txpwr_ofst_req =
+		rwnx_msg_zalloc(MM_SET_TXPWR_OFST_REQ, TASK_MM, DRV_TASK_ID,
+				sizeof(struct mm_set_txpwr_ofst_req));
+
+	if (!txpwr_ofst_req)
+		return -ENOMEM;
+
+	txpwr_ofst2x = &txpwr_ofst_req->txpwr_ofst2x;
+	txpwr_ofst2x->enable = 0;
+	for (type = 0; type < 3; type++) {
+		for (ch_grp = 0; ch_grp < 6; ch_grp++) {
+			if (ch_grp < 3)
+				txpwr_ofst2x->pwrofst2x_tbl_2g4[type][ch_grp] = 0;
+			txpwr_ofst2x->pwrofst2x_tbl_5g[type][ch_grp] = 0;
+		}
+	}
+
+	aic_chip_get_userconfig_txpwr_ofst2x(rwnx_hw, txpwr_ofst2x);
+
+	if (txpwr_ofst2x->enable) {
+		AICWFDBG(LOGINFO, "%s:enable:%d\r\n", __func__, txpwr_ofst2x->enable);
+		AICWFDBG(LOGDEBUG,
+			 "pwrofst2x 2.4g: [0]:11b, [1]:ofdm_highrate, [2]:ofdm_lowrate\n  chan=\t1-4\t5-9\t10-13");
+		for (type = 0; type < 3; type++) {
+			AICWFDBG(LOGDEBUG, "\n  [%d] =", type);
+			for (ch_grp = 0; ch_grp < 3; ch_grp++) {
+				AICWFDBG(LOGDEBUG, "\t%d",
+					 txpwr_ofst2x->pwrofst2x_tbl_2g4[type][ch_grp]);
+			}
+		}
+		AICWFDBG(LOGDEBUG,
+			 "\npwrofst2x 5g: [0]:ofdm_lowrate, [1]:ofdm_highrate, [2]:ofdm_midrate\n  chan=\t36-50\t51-64\t98-114\t115-130\t131-146\t147-166");
+		for (type = 0; type < 3; type++) {
+			AICWFDBG(LOGDEBUG, "\n  [%d] =", type);
+			for (ch_grp = 0; ch_grp < 6; ch_grp++)
+				AICWFDBG(LOGDEBUG, "\t%d",
+					 txpwr_ofst2x->pwrofst2x_tbl_5g[type][ch_grp]);
+		}
+		AICWFDBG(LOGDEBUG, "\n");
+
+		/* Send the MM_SET_TXPWR_OFST_REQ message to UMAC FW */
+		error = rwnx_send_msg(rwnx_hw, txpwr_ofst_req, 1, MM_SET_TXPWR_OFST_CFM,
+				      NULL);
+	} else {
+		AICWFDBG(LOGDEBUG, "%s:Do not use txpwr_ofst2x\r\n", __func__);
+		rwnx_msg_free(rwnx_hw, txpwr_ofst_req);
+	}
+
+	return error;
+}
+
+/******************************************************************************
+ *    Control messages handling functions (FULLMAC only)
+ *****************************************************************************/
+#ifdef CONFIG_RWNX_FULLMAC
+#ifdef CONFIG_AIC8800_HE_FOR_OLD_KERNEL
+extern struct ieee80211_sband_iftype_data rwnx_he_capa;
+#endif
+#ifdef CONFIG_AIC8800_VHT_FOR_OLD_KERNEL
+static struct ieee80211_sta_vht_cap *rwnx_vht_capa;
+#endif
+int rwnx_send_me_config_req(struct rwnx_hw *rwnx_hw)
+{
+	struct me_config_req *req;
+	struct wiphy *wiphy = rwnx_hw->wiphy;
+	struct ieee80211_sta_ht_cap *ht_cap;
+	struct ieee80211_sta_vht_cap *vht_cap;
+	struct ieee80211_sta_he_cap const *he_cap = NULL;
+
+	u8_l *ht_mcs;
+	int i;
+
+	if (rwnx_hw->chip_ops->use_80_bandwidth)
+		rwnx_hw->mod_params->use_80 = true;
+
+	if (rwnx_hw->band_5g_support) {
+		ht_cap = &wiphy->bands[NL80211_BAND_5GHZ]->ht_cap;
+		vht_cap = &wiphy->bands[NL80211_BAND_5GHZ]->vht_cap;
+	} else {
+		ht_cap = &wiphy->bands[NL80211_BAND_2GHZ]->ht_cap;
+		vht_cap = &wiphy->bands[NL80211_BAND_2GHZ]->vht_cap;
+	}
+#ifdef CONFIG_AIC8800_VHT_FOR_OLD_KERNEL
+	rwnx_vht_capa = vht_cap;
+#endif
+
+	ht_mcs = (u8_l *)&ht_cap->mcs;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the ME_CONFIG_REQ message */
+	req = rwnx_msg_zalloc(ME_CONFIG_REQ, TASK_ME, DRV_TASK_ID,
+			      sizeof(struct me_config_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the ME_CONFIG_REQ message */
+	req->ht_supp = ht_cap->ht_supported;
+	req->vht_supp = vht_cap->vht_supported;
+	req->ht_cap.ht_capa_info =
+		cpu_to_le16(ht_cap->cap | IEEE80211_HT_CAP_LDPC_CODING);
+	req->ht_cap.a_mpdu_param =
+		ht_cap->ampdu_factor |
+		(ht_cap->ampdu_density << IEEE80211_HT_AMPDU_PARM_DENSITY_SHIFT);
+	for (i = 0; i < sizeof(ht_cap->mcs); i++)
+		req->ht_cap.mcs_rate[i] = ht_mcs[i];
+	req->ht_cap.ht_extended_capa = 0;
+	req->ht_cap.tx_beamforming_capa = 0;
+	req->ht_cap.asel_capa = 0;
+
+	if (req->vht_supp) {
+		req->vht_cap.vht_capa_info = cpu_to_le32(vht_cap->cap);
+		req->vht_cap.rx_highest = cpu_to_le16(vht_cap->vht_mcs.rx_highest);
+		req->vht_cap.rx_mcs_map = cpu_to_le16(vht_cap->vht_mcs.rx_mcs_map);
+		req->vht_cap.tx_highest = cpu_to_le16(vht_cap->vht_mcs.tx_highest);
+		req->vht_cap.tx_mcs_map = cpu_to_le16(vht_cap->vht_mcs.tx_mcs_map);
+	}
+
+	if (wiphy->bands[NL80211_BAND_2GHZ]->iftype_data) {
+		he_cap = &wiphy->bands[NL80211_BAND_2GHZ]->iftype_data->he_cap;
+#if defined(CONFIG_AIC8800_HE_FOR_OLD_KERNEL)
+		he_cap = &rwnx_he_capa.he_cap;
+#endif
+		req->he_supp = he_cap->has_he;
+		for (i = 0; i < ARRAY_SIZE(he_cap->he_cap_elem.mac_cap_info); i++)
+			req->he_cap.mac_cap_info[i] = he_cap->he_cap_elem.mac_cap_info[i];
+
+		for (i = 0; i < ARRAY_SIZE(he_cap->he_cap_elem.phy_cap_info); i++)
+			req->he_cap.phy_cap_info[i] = he_cap->he_cap_elem.phy_cap_info[i];
+
+		req->he_cap.mcs_supp.rx_mcs_80 =
+			cpu_to_le16(he_cap->he_mcs_nss_supp.rx_mcs_80);
+		req->he_cap.mcs_supp.tx_mcs_80 =
+			cpu_to_le16(he_cap->he_mcs_nss_supp.tx_mcs_80);
+		req->he_cap.mcs_supp.rx_mcs_160 =
+			cpu_to_le16(he_cap->he_mcs_nss_supp.rx_mcs_160);
+		req->he_cap.mcs_supp.tx_mcs_160 =
+			cpu_to_le16(he_cap->he_mcs_nss_supp.tx_mcs_160);
+		req->he_cap.mcs_supp.rx_mcs_80p80 =
+			cpu_to_le16(he_cap->he_mcs_nss_supp.rx_mcs_80p80);
+		req->he_cap.mcs_supp.tx_mcs_80p80 =
+			cpu_to_le16(he_cap->he_mcs_nss_supp.tx_mcs_80p80);
+		for (i = 0; i < MAC_HE_PPE_THRES_MAX_LEN; i++)
+			req->he_cap.ppe_thres[i] = he_cap->ppe_thres[i];
+
+		req->he_ul_on = rwnx_hw->mod_params->he_ul_on;
+	}
+	req->ps_on = rwnx_hw->mod_params->ps_on;
+	req->dpsm = rwnx_hw->mod_params->dpsm;
+	req->tx_lft = rwnx_hw->mod_params->tx_lft;
+	req->ant_div_on = rwnx_hw->mod_params->ant_div;
+	if (rwnx_hw->mod_params->use_80)
+		req->phy_bw_max = PHY_CHNL_BW_80;
+	else if (rwnx_hw->mod_params->use_2040)
+		req->phy_bw_max = PHY_CHNL_BW_40;
+	else
+		req->phy_bw_max = PHY_CHNL_BW_20;
+
+	wiphy_info(wiphy, "AICWF HT supp %d, VHT supp %d, HE supp %d\n", req->ht_supp,
+		   req->vht_supp, req->he_supp);
+	/* Send the ME_CONFIG_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, ME_CONFIG_CFM, NULL);
+}
+
+int rwnx_send_me_chan_config_req(struct rwnx_hw *rwnx_hw)
+{
+	struct me_chan_config_req *req;
+	struct wiphy *wiphy = rwnx_hw->wiphy;
+	int i;
+	u8_l enable = 0;
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+	s8_l max_pwr;
+	struct txpwr_loss_conf txpwr_loss_tmp;
+	struct txpwr_loss_conf *txpwr_loss;
+#endif
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the ME_CHAN_CONFIG_REQ message */
+	req = rwnx_msg_zalloc(ME_CHAN_CONFIG_REQ, TASK_ME, DRV_TASK_ID,
+			      sizeof(struct me_chan_config_req));
+	if (!req)
+		return -ENOMEM;
+
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+	txpwr_loss = &txpwr_loss_tmp;
+	get_userconfig_txpwr_loss(txpwr_loss);
+
+	if (txpwr_loss->loss_enable_2g4 == 1)
+		AICWFDBG(LOGINFO, "%s:loss_value_2g4: %d\r\n", __func__,
+			 txpwr_loss->loss_value_2g4);
+	if (txpwr_loss->loss_enable_5g == 1)
+		AICWFDBG(LOGINFO, "%s:loss_value_5g: %d\r\n", __func__,
+			 txpwr_loss->loss_value_5g);
+#endif
+
+	req->chan2G4_cnt = 0;
+	if (wiphy->bands[NL80211_BAND_2GHZ]) {
+		struct ieee80211_supported_band *b = wiphy->bands[NL80211_BAND_2GHZ];
+
+		for (i = 0; i < b->n_channels; i++) {
+			req->chan2G4[req->chan2G4_cnt].flags = 0;
+
+			if (b->channels[i].flags & IEEE80211_CHAN_DISABLED)
+				req->chan2G4[req->chan2G4_cnt].flags |= CHAN_DISABLED;
+			req->chan2G4[req->chan2G4_cnt].flags |=
+				get_chan_flags(b->channels[i].flags);
+			req->chan2G4[req->chan2G4_cnt].band = NL80211_BAND_2GHZ;
+			req->chan2G4[req->chan2G4_cnt].freq = b->channels[i].center_freq;
+			req->chan2G4[req->chan2G4_cnt].tx_power =
+				chan_to_fw_pwr(b->channels[i].max_power);
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+			max_pwr = get_powerlimit_by_freq(PHY_BAND_2G4,
+							 req->chan2G4[req->chan2G4_cnt].freq,
+							 &enable);
+			if (txpwr_loss->loss_enable_2g4 == 1)
+				max_pwr -= txpwr_loss->loss_value_2g4;
+			if (enable) {
+				if (req->chan2G4[req->chan2G4_cnt].tx_power > max_pwr)
+					req->chan2G4[req->chan2G4_cnt].tx_power = max_pwr;
+			}
+#endif
+			req->chan2G4_cnt++;
+			if (req->chan2G4_cnt == MAC_DOMAINCHANNEL_24G_MAX)
+				break;
+		}
+	}
+
+	req->chan5G_cnt = 0;
+	if (wiphy->bands[NL80211_BAND_5GHZ]) {
+		struct ieee80211_supported_band *b = wiphy->bands[NL80211_BAND_5GHZ];
+
+		for (i = 0; i < b->n_channels; i++) {
+			req->chan5G[req->chan5G_cnt].flags = 0;
+			if (b->channels[i].flags & IEEE80211_CHAN_DISABLED)
+				req->chan5G[req->chan5G_cnt].flags |= CHAN_DISABLED;
+			req->chan5G[req->chan5G_cnt].flags |=
+				get_chan_flags(b->channels[i].flags);
+			req->chan5G[req->chan5G_cnt].band = NL80211_BAND_5GHZ;
+			req->chan5G[req->chan5G_cnt].freq = b->channels[i].center_freq;
+			req->chan5G[req->chan5G_cnt].tx_power =
+				chan_to_fw_pwr(b->channels[i].max_power);
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+			max_pwr = get_powerlimit_by_freq(PHY_BAND_5G,
+							 req->chan5G[req->chan5G_cnt].freq,
+							 &enable);
+			if (txpwr_loss->loss_enable_5g == 1)
+				max_pwr -= txpwr_loss->loss_value_5g;
+			if (enable) {
+				if (req->chan5G[req->chan5G_cnt].tx_power > max_pwr)
+					req->chan5G[req->chan5G_cnt].tx_power = max_pwr;
+			}
+#endif
+			req->chan5G_cnt++;
+			if (req->chan5G_cnt == MAC_DOMAINCHANNEL_5G_MAX)
+				break;
+		}
+	}
+
+	/* Send the ME_CHAN_CONFIG_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, ME_CHAN_CONFIG_CFM, NULL);
+}
+
+int rwnx_send_me_set_control_port_req(struct rwnx_hw *rwnx_hw, bool opened,
+				      u8 sta_idx)
+{
+	struct me_set_control_port_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the ME_SET_CONTROL_PORT_REQ message */
+	req = rwnx_msg_zalloc(ME_SET_CONTROL_PORT_REQ, TASK_ME, DRV_TASK_ID,
+			      sizeof(struct me_set_control_port_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the ME_SET_CONTROL_PORT_REQ message */
+	req->sta_idx = sta_idx;
+	req->control_port_open = opened;
+
+	/* Send the ME_SET_CONTROL_PORT_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, ME_SET_CONTROL_PORT_CFM, NULL);
+}
+
+int rwnx_send_me_sta_add(struct rwnx_hw *rwnx_hw,
+			 struct station_parameters *params, const u8 *mac,
+			 u8 inst_nbr, struct me_sta_add_cfm *cfm)
+{
+	struct me_sta_add_req *req;
+	struct link_station_parameters *link_sta_params = &params->link_sta_params;
+
+	u8 *ht_mcs = (u8 *)&link_sta_params->ht_capa->mcs;
+
+	int i;
+	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[inst_nbr];
+#if (defined CONFIG_AIC8800_HE_FOR_OLD_KERNEL) || (defined CONFIG_AIC8800_VHT_FOR_OLD_KERNEL)
+	struct aic_sta *sta = &rwnx_hw->aic_table[rwnx_vif->ap.aic_index];
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+	AICWFDBG(LOGDEBUG, "AICWF assoc_req idx %d, he: %d, vht: %d\n ", rwnx_vif->ap.aic_index,
+		 sta->he, sta->vht);
+	if (rwnx_vif->ap.aic_index < NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX)
+		rwnx_vif->ap.aic_index++;
+	else
+		rwnx_vif->ap.aic_index = 0;
+#endif
+
+	/* Build the MM_STA_ADD_REQ message */
+	req = rwnx_msg_zalloc(ME_STA_ADD_REQ, TASK_ME, DRV_TASK_ID,
+			      sizeof(struct me_sta_add_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the MM_STA_ADD_REQ message */
+	memcpy(&req->mac_addr.array[0], mac, ETH_ALEN);
+
+	req->rate_set.length = link_sta_params->supported_rates_len;
+	for (i = 0; i < link_sta_params->supported_rates_len; i++)
+		req->rate_set.array[i] = link_sta_params->supported_rates[i];
+
+	req->flags = 0;
+	if (link_sta_params->ht_capa) {
+		const struct ieee80211_ht_cap *ht_capa = link_sta_params->ht_capa;
+
+		req->flags |= STA_HT_CAPA;
+		req->ht_cap.ht_capa_info = cpu_to_le16(ht_capa->cap_info);
+		req->ht_cap.a_mpdu_param = ht_capa->ampdu_params_info;
+		for (i = 0; i < sizeof(ht_capa->mcs); i++)
+			req->ht_cap.mcs_rate[i] = ht_mcs[i];
+		req->ht_cap.ht_extended_capa =
+			cpu_to_le16(ht_capa->extended_ht_cap_info);
+		req->ht_cap.tx_beamforming_capa = cpu_to_le32(ht_capa->tx_BF_cap_info);
+		req->ht_cap.asel_capa = ht_capa->antenna_selection_info;
+	}
+
+	if (link_sta_params->vht_capa) {
+		const struct ieee80211_vht_cap *vht_capa = link_sta_params->vht_capa;
+
+		req->flags |= STA_VHT_CAPA;
+		req->vht_cap.vht_capa_info = cpu_to_le32(vht_capa->vht_cap_info);
+		req->vht_cap.rx_highest = cpu_to_le16(vht_capa->supp_mcs.rx_highest);
+		req->vht_cap.rx_mcs_map = cpu_to_le16(vht_capa->supp_mcs.rx_mcs_map);
+		req->vht_cap.tx_highest = cpu_to_le16(vht_capa->supp_mcs.tx_highest);
+		req->vht_cap.tx_mcs_map = cpu_to_le16(vht_capa->supp_mcs.tx_mcs_map);
+	}
+
+	if (link_sta_params->he_capa) {
+		const struct ieee80211_he_cap_elem *he_capa = link_sta_params->he_capa;
+		struct ieee80211_he_mcs_nss_supp *mcs_nss_supp =
+			(struct ieee80211_he_mcs_nss_supp *)(he_capa + 1);
+
+		req->flags |= STA_HE_CAPA;
+		for (i = 0; i < ARRAY_SIZE(he_capa->mac_cap_info); i++)
+			req->he_cap.mac_cap_info[i] = he_capa->mac_cap_info[i];
+		for (i = 0; i < ARRAY_SIZE(he_capa->phy_cap_info); i++)
+			req->he_cap.phy_cap_info[i] = he_capa->phy_cap_info[i];
+		req->he_cap.mcs_supp.rx_mcs_80 = mcs_nss_supp->rx_mcs_80;
+		req->he_cap.mcs_supp.tx_mcs_80 = mcs_nss_supp->tx_mcs_80;
+		req->he_cap.mcs_supp.rx_mcs_160 = mcs_nss_supp->rx_mcs_160;
+		req->he_cap.mcs_supp.tx_mcs_160 = mcs_nss_supp->tx_mcs_160;
+		req->he_cap.mcs_supp.rx_mcs_80p80 = mcs_nss_supp->rx_mcs_80p80;
+		req->he_cap.mcs_supp.tx_mcs_80p80 = mcs_nss_supp->tx_mcs_80p80;
+	}
+
+	if (params->sta_flags_set & BIT(NL80211_STA_FLAG_WME))
+		req->flags |= STA_QOS_CAPA;
+
+	if (params->sta_flags_set & BIT(NL80211_STA_FLAG_MFP))
+		req->flags |= STA_MFP_CAPA;
+
+	if (link_sta_params->opmode_notif_used) {
+		req->flags |= STA_OPMOD_NOTIF;
+		req->opmode = link_sta_params->opmode_notif_used;
+	}
+
+	req->aid = cpu_to_le16(params->aid);
+	req->uapsd_queues = params->uapsd_queues;
+	req->max_sp_len = params->max_sp * 2;
+	req->vif_idx = inst_nbr;
+
+	if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)) {
+		// struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[inst_nbr];
+		req->tdls_sta = true;
+		if ((params->ext_capab[3] & WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH) &&
+		    !rwnx_vif->tdls_chsw_prohibited)
+			req->tdls_chsw_allowed = true;
+		if (rwnx_vif->tdls_status == TDLS_SETUP_RSP_TX)
+			req->tdls_sta_initiator = true;
+	}
+
+	/* Send the ME_STA_ADD_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, ME_STA_ADD_CFM, cfm);
+}
+
+int rwnx_send_me_sta_del(struct rwnx_hw *rwnx_hw, u8 sta_idx, bool tdls_sta)
+{
+	struct me_sta_del_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_STA_DEL_REQ message */
+	req = rwnx_msg_zalloc(ME_STA_DEL_REQ, TASK_ME, DRV_TASK_ID,
+			      sizeof(struct me_sta_del_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the MM_STA_DEL_REQ message */
+	req->sta_idx = sta_idx;
+	req->tdls_sta = tdls_sta;
+
+	/* Send the ME_STA_DEL_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, ME_STA_DEL_CFM, NULL);
+}
+
+int rwnx_send_me_traffic_ind(struct rwnx_hw *rwnx_hw, u8 sta_idx, bool uapsd,
+			     u8 tx_status)
+{
+	struct me_traffic_ind_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the ME_UTRAFFIC_IND_REQ message */
+	req = rwnx_msg_zalloc(ME_TRAFFIC_IND_REQ, TASK_ME, DRV_TASK_ID,
+			      sizeof(struct me_traffic_ind_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the ME_TRAFFIC_IND_REQ message */
+	req->sta_idx = sta_idx;
+	req->tx_avail = tx_status;
+	req->uapsd = uapsd;
+
+	/* Send the ME_TRAFFIC_IND_REQ to UMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, ME_TRAFFIC_IND_CFM, NULL);
+}
+
+int rwnx_send_me_rc_stats(struct rwnx_hw *rwnx_hw, u8 sta_idx,
+			  struct me_rc_stats_cfm *cfm)
+{
+	struct me_rc_stats_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the ME_RC_STATS_REQ message */
+	req = rwnx_msg_zalloc(ME_RC_STATS_REQ, TASK_ME, DRV_TASK_ID,
+			      sizeof(struct me_rc_stats_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the ME_RC_STATS_REQ message */
+	req->sta_idx = sta_idx;
+
+	/* Send the ME_RC_STATS_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, ME_RC_STATS_CFM, cfm);
+}
+
+int rwnx_send_me_rc_set_rate(struct rwnx_hw *rwnx_hw, u8 sta_idx, u16 rate_cfg)
+{
+	struct me_rc_set_rate_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the ME_RC_SET_RATE_REQ message */
+	req = rwnx_msg_zalloc(ME_RC_SET_RATE_REQ, TASK_ME, DRV_TASK_ID,
+			      sizeof(struct me_rc_set_rate_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the ME_RC_SET_RATE_REQ message */
+	req->sta_idx = sta_idx;
+	req->fixed_rate_cfg = rate_cfg;
+
+	/* Send the ME_RC_SET_RATE_REQ message to FW */
+	return rwnx_send_msg(rwnx_hw, req, 0, 0, NULL);
+}
+
+int rwnx_send_me_set_ps_mode(struct rwnx_hw *rwnx_hw, u8 ps_mode)
+{
+	struct me_set_ps_mode_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the ME_SET_PS_MODE_REQ message */
+	req = rwnx_msg_zalloc(ME_SET_PS_MODE_REQ, TASK_ME, DRV_TASK_ID,
+			      sizeof(struct me_set_ps_mode_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the ME_SET_PS_MODE_REQ message */
+	req->ps_state = ps_mode;
+
+	/* Send the ME_SET_PS_MODE_REQ message to FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, ME_SET_PS_MODE_CFM, NULL);
+}
+
+int rwnx_send_me_set_lp_level(struct rwnx_hw *rwnx_hw, u8 lp_level, u8 disable_filter)
+{
+	struct me_set_lp_level_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+	AICWFDBG(LOGDEBUG, "lp_lvl: %d %d\n", lp_level, disable_filter);
+	/* Build the ME_SET_LP_LEVEL_REQ message */
+	req = rwnx_msg_zalloc(ME_SET_LP_LEVEL_REQ, TASK_ME, DRV_TASK_ID,
+			      sizeof(struct me_set_lp_level_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the ME_SET_LP_LEVEL_REQ message */
+	req->lp_level = lp_level;
+	req->disable_filter = disable_filter;
+
+	/* Send the ME_SET_LP_LEVEL_REQ message to FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, ME_SET_LP_LEVEL_CFM, NULL);
+}
+
+int rwnx_send_sm_connect_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+			     struct cfg80211_connect_params *sme,
+			     struct sm_connect_cfm *cfm)
+{
+	struct sm_connect_req *req;
+	int i;
+	u32_l flags = 0;
+	bool gval = false;
+	bool pval = false;
+
+	rwnx_vif->wep_enabled = false;
+	rwnx_vif->wep_auth_err = false;
+	rwnx_vif->last_auth_type = 0;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the SM_CONNECT_REQ message */
+	req = rwnx_msg_zalloc(SM_CONNECT_REQ, TASK_SM, DRV_TASK_ID,
+			      sizeof(struct sm_connect_req));
+	if (!req)
+		return -ENOMEM;
+
+	if (sme->crypto.cipher_group == WLAN_CIPHER_SUITE_WEP40 ||
+	    sme->crypto.cipher_group == WLAN_CIPHER_SUITE_WEP104)
+		gval = true;
+
+	if (sme->crypto.n_ciphers_pairwise &&
+	    (sme->crypto.ciphers_pairwise[0] == WLAN_CIPHER_SUITE_WEP40 ||
+	     sme->crypto.ciphers_pairwise[0] == WLAN_CIPHER_SUITE_WEP104))
+		pval = true;
+
+	/* Set parameters for the SM_CONNECT_REQ message */
+	if (sme->crypto.n_ciphers_pairwise &&
+	    (sme->crypto.ciphers_pairwise[0] == WLAN_CIPHER_SUITE_WEP40 ||
+	     sme->crypto.ciphers_pairwise[0] == WLAN_CIPHER_SUITE_TKIP ||
+	     sme->crypto.ciphers_pairwise[0] == WLAN_CIPHER_SUITE_WEP104))
+		flags |= DISABLE_HT;
+
+	if (sme->crypto.control_port)
+		flags |= CONTROL_PORT_HOST;
+
+	if (sme->crypto.control_port_no_encrypt)
+		flags |= CONTROL_PORT_NO_ENC;
+
+	if (use_pairwise_key(&sme->crypto))
+		flags |= WPA_WPA2_IN_USE;
+
+	if (sme->mfp == NL80211_MFP_REQUIRED)
+		flags |= MFP_IN_USE;
+
+	if (rwnx_vif->sta.ap)
+		flags |= REASSOCIATION;
+
+	req->ctrl_port_ethertype = sme->crypto.control_port_ethertype;
+
+	if (sme->bssid)
+		memcpy(&req->bssid, sme->bssid, ETH_ALEN);
+	else
+		req->bssid = mac_addr_bcst;
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+	if (rwnx_hw->ccode.auto_set) {
+		memcpy(&rwnx_hw->ccode.bssid, &req->bssid, ETH_ALEN);
+		atomic_set(&rwnx_hw->ccode.link_scan, 1);
+	}
+#endif
+	req->vif_idx = rwnx_vif->vif_index;
+	if (sme->channel) {
+		req->chan.band = sme->channel->band;
+		req->chan.freq = sme->channel->center_freq;
+		req->chan.flags = get_chan_flags(sme->channel->flags);
+	} else {
+		req->chan.freq = (u16_l)-1;
+	}
+	for (i = 0; i < sme->ssid_len; i++)
+		req->ssid.array[i] = sme->ssid[i];
+	req->ssid.length = sme->ssid_len;
+	req->flags = flags;
+	if (WARN_ON(sme->ie_len > sizeof(req->ie_buf)))
+		goto invalid_param;
+	if (sme->ie_len)
+		memcpy(req->ie_buf, sme->ie, sme->ie_len);
+	req->ie_len = sme->ie_len;
+	req->listen_interval = rwnx_mod_params.listen_itv;
+	req->dont_wait_bcmc = !rwnx_mod_params.listen_bcmc;
+
+	/* Set auth_type */
+	if (sme->auth_type == NL80211_AUTHTYPE_AUTOMATIC)
+		req->auth_type = WLAN_AUTH_OPEN;
+	else if (sme->auth_type == NL80211_AUTHTYPE_OPEN_SYSTEM)
+		req->auth_type = WLAN_AUTH_OPEN;
+	else if (sme->auth_type == NL80211_AUTHTYPE_SHARED_KEY)
+		req->auth_type = WLAN_AUTH_SHARED_KEY;
+	else if (sme->auth_type == NL80211_AUTHTYPE_FT)
+		req->auth_type = WLAN_AUTH_FT;
+	else if (sme->auth_type == NL80211_AUTHTYPE_SAE)
+		req->auth_type = WLAN_AUTH_SAE;
+	else
+		goto invalid_param;
+
+	rwnx_vif->sta.auth_type = sme->auth_type;
+
+	/* Set UAPSD queues */
+	req->uapsd_queues = rwnx_mod_params.uapsd_queues;
+
+	rwnx_vif->wep_enabled = pval & gval;
+
+	if (rwnx_vif->wep_enabled)
+		rwnx_vif->last_auth_type = sme->auth_type;
+
+#ifdef CONFIG_AIC8800_USE_WIRELESS_EXT
+	memset(rwnx_hw->wext_essid, 0, 33);
+	memcpy(rwnx_hw->wext_essid, sme->ssid, (int)sme->ssid_len);
+#endif
+
+	rwnx_vif->sta.ssid_len = (int)sme->ssid_len;
+	memset(rwnx_vif->sta.ssid, 0, rwnx_vif->sta.ssid_len + 1);
+	memcpy(rwnx_vif->sta.ssid, sme->ssid, rwnx_vif->sta.ssid_len);
+	if (sme->bssid)
+		memcpy(rwnx_vif->sta.bssid, sme->bssid, ETH_ALEN);
+
+	AICWFDBG(LOGDEBUG,
+		 "AICWF %s drv_vif_index:%d connect to %s(%d) channel:%d auth_type:%d\r\n",
+		 __func__, rwnx_vif->drv_vif_index, rwnx_vif->sta.ssid,
+		 rwnx_vif->sta.ssid_len, req->chan.freq, req->auth_type);
+
+	/* Send the SM_CONNECT_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, SM_CONNECT_CFM, cfm);
+
+invalid_param:
+	rwnx_msg_free(rwnx_hw, req);
+	return -EINVAL;
+}
+
+int rwnx_send_sm_disconnect_req(struct rwnx_hw *rwnx_hw,
+				struct rwnx_vif *rwnx_vif, u16 reason)
+{
+	struct sm_disconnect_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the SM_DISCONNECT_REQ message */
+	req = rwnx_msg_zalloc(SM_DISCONNECT_REQ, TASK_SM, DRV_TASK_ID,
+			      sizeof(struct sm_disconnect_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the SM_DISCONNECT_REQ message */
+	req->reason_code = reason;
+	req->vif_idx = rwnx_vif->vif_index;
+
+	/* Send the SM_DISCONNECT_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, SM_DISCONNECT_CFM, NULL);
+}
+
+int rwnx_send_sm_external_auth_required_rsp(struct rwnx_hw *rwnx_hw,
+					    struct rwnx_vif *rwnx_vif,
+					    u16 status)
+{
+	struct sm_external_auth_required_rsp *rsp;
+
+	/* Build the SM_EXTERNAL_AUTH_CFM message */
+	rsp = rwnx_msg_zalloc(SM_EXTERNAL_AUTH_REQUIRED_RSP, TASK_SM, DRV_TASK_ID,
+			      sizeof(struct sm_external_auth_required_rsp));
+	if (!rsp)
+		return -ENOMEM;
+
+	rsp->status = status;
+	rsp->vif_idx = rwnx_vif->vif_index;
+
+	/* send the SM_EXTERNAL_AUTH_REQUIRED_RSP message UMAC FW */
+	return rwnx_send_msg(rwnx_hw, rsp, 1, SM_EXTERNAL_AUTH_REQUIRED_RSP_CFM,
+			     NULL);
+}
+
+int rwnx_send_apm_start_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+			    struct cfg80211_ap_settings *settings,
+			    struct apm_start_cfm *cfm,
+			    struct rwnx_ipc_elem_var *elem)
+{
+	struct apm_start_req *req;
+	struct rwnx_bcn *bcn = &vif->ap.bcn;
+	u8 *buf;
+	u32 flags = 0;
+	const u8 *rate_ie;
+	u8 rate_len = 0;
+	int var_offset = offsetof(struct ieee80211_mgmt, u.beacon.variable);
+	const u8 *var_pos;
+	int len, i;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the APM_START_REQ message */
+	req = rwnx_msg_zalloc(APM_START_REQ, TASK_APM, DRV_TASK_ID,
+			      sizeof(struct apm_start_req));
+	if (!req)
+		return -ENOMEM;
+
+	// Build the beacon
+	bcn->dtim = (u8)settings->dtim_period;
+	buf = rwnx_build_bcn(bcn, &settings->beacon);
+	if (!buf) {
+		rwnx_msg_free(rwnx_hw, req);
+		return -ENOMEM;
+	}
+
+	// Retrieve the basic rate set from the beacon buffer
+	len = bcn->len - var_offset;
+	var_pos = buf + var_offset;
+
+// Assume that rate higher that 54 Mbps are BSS membership
+
+	rate_ie = cfg80211_find_ie(WLAN_EID_SUPP_RATES, var_pos, len);
+	if (rate_ie) {
+		const u8 *rates = rate_ie + 2;
+		u8 rate1 = 0;
+		bool is_basic1;
+
+		for (i = 0; i < rate_ie[1] && rate_len < MAC_RATESET_LEN; i++) {
+			rate1 = rates[i];
+			is_basic1 = ((rate1 & 0x80) && ((rate1 & ~0x80) <= (54 * 2)));
+			if (is_basic1)
+				req->basic_rates.array[rate_len++] = rate1;
+		}
+	}
+	rate_ie = cfg80211_find_ie(WLAN_EID_EXT_SUPP_RATES, var_pos, len);
+	if (rate_ie) {
+		const u8 *rates = rate_ie + 2;
+		u8 rate2 = 0;
+		bool is_basic2;
+
+		for (i = 0; i < rate_ie[1] && rate_len < MAC_RATESET_LEN; i++) {
+			rate2 = rates[i];
+			is_basic2 = ((rate2 & 0x80) && ((rate2 & ~0x80) <= (54 * 2)));
+			if (is_basic2)
+				req->basic_rates.array[rate_len++] = rate2;
+		}
+	}
+	req->basic_rates.length = rate_len;
+
+	rwnx_send_bcn(rwnx_hw, buf, vif->vif_index, bcn->len);
+
+	/* Set parameters for the APM_START_REQ message */
+	req->vif_idx = vif->vif_index;
+	req->bcn_addr = elem->dma_addr;
+	req->bcn_len = bcn->len;
+	req->tim_oft = bcn->head_len;
+	req->tim_len = bcn->tim_len;
+	req->chan.band = settings->chandef.chan->band;
+	req->chan.freq = settings->chandef.chan->center_freq;
+	req->chan.flags = 0;
+	req->chan.tx_power = chan_to_fw_pwr(settings->chandef.chan->max_power);
+	req->center_freq1 = settings->chandef.center_freq1;
+	req->center_freq2 = settings->chandef.center_freq2;
+	req->ch_width = bw2chnl[settings->chandef.width];
+	req->bcn_int = settings->beacon_interval;
+
+	if (settings->crypto.control_port)
+		flags |= CONTROL_PORT_HOST;
+
+	if (settings->crypto.control_port_no_encrypt)
+		flags |= CONTROL_PORT_NO_ENC;
+
+	if (use_pairwise_key(&settings->crypto))
+		flags |= WPA_WPA2_IN_USE;
+
+	if (settings->crypto.control_port_ethertype)
+		req->ctrl_port_ethertype = settings->crypto.control_port_ethertype;
+	else
+		req->ctrl_port_ethertype = ETH_P_PAE;
+	req->flags = flags;
+
+	/* Send the APM_START_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, APM_START_CFM, cfm);
+}
+
+int rwnx_send_apm_stop_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif)
+{
+	struct apm_stop_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the APM_STOP_REQ message */
+	req = rwnx_msg_zalloc(APM_STOP_REQ, TASK_APM, DRV_TASK_ID,
+			      sizeof(struct apm_stop_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the APM_STOP_REQ message */
+	req->vif_idx = vif->vif_index;
+
+	/* Send the APM_STOP_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, APM_STOP_CFM, NULL);
+}
+
+#define P2P_WILDCARD_SSID     "DIRECT-"
+#define P2P_WILDCARD_SSID_LEN (sizeof(P2P_WILDCARD_SSID) - 1)
+
+#ifdef CONFIG_AIC8800_SET_VENDOR_EXTENSION_IE
+u8_l vendor_extension_data[256];
+u8_l vendor_extension_len;
+
+static void rwnx_insert_vendor_extension_data(struct scanu_vendor_ie_req *ie_req)
+{
+	u8_l temp_ie[256];
+	u8_l vendor_extension_subelement[3] = {0x00, 0x37, 0x2A};
+	u8_l vendor_extension_id[2] = {0x10, 0x49};
+	int index = 0;
+	int vendor_extension_subelement_len = 0;
+
+	memset(temp_ie, 0, 256);
+
+	// find vendor_extension_subelement
+	for (index = 0; index < ie_req->add_ie_len; index++) {
+		if (ie_req->ie[index] == vendor_extension_id[0]) {
+			index++;
+			if (index == ie_req->add_ie_len)
+				return;
+
+			if (ie_req->ie[index] == vendor_extension_id[1] &&
+			    ie_req->ie[index + 3] == vendor_extension_subelement[0] &&
+			    ie_req->ie[index + 4] == vendor_extension_subelement[1] &&
+			    ie_req->ie[index + 5] == vendor_extension_subelement[2]) {
+				index = index + 2;
+				vendor_extension_subelement_len = ie_req->ie[index];
+				AICWFDBG(LOGDEBUG,
+					 "AICWF %s find vendor_extension_subelement,index:%d len:%d\r\n",
+					 __func__, index, ie_req->ie[index]);
+				break;
+			}
+		}
+	}
+	index = index + vendor_extension_subelement_len;
+
+	// insert vendor extension
+	memcpy(&temp_ie[0], ie_req->ie, index + 1);
+	memcpy(&temp_ie[index + 1], vendor_extension_data,
+	       vendor_extension_len /*sizeof(vendor_extension_data) */);
+	memcpy(&temp_ie[index + 1 +
+	       vendor_extension_len /*sizeof(vendor_extension_data) */],
+	       &ie_req->ie[index + 1], ie_req->add_ie_len - index);
+
+	memcpy(ie_req->ie, temp_ie,
+	       ie_req->add_ie_len +
+	       vendor_extension_len /*sizeof(vendor_extension_data) */);
+	ie_req->add_ie_len =
+		ie_req->add_ie_len +
+		vendor_extension_len /*sizeof(vendor_extension_data) */;
+	ie_req->ie[1] =
+		ie_req->ie[1] + vendor_extension_len /*sizeof(vendor_extension_data) */;
+
+	// rwnx_data_dump((char*)__func__, (void*)ie_req->ie,
+	// ie_req->add_ie_len);
+}
+#endif // CONFIG_AIC8800_SET_VENDOR_EXTENSION_IE
+
+int rwnx_send_scanu_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+			struct cfg80211_scan_request *param)
+{
+	struct scanu_start_req *req = NULL;
+	struct scanu_vendor_ie_req *ie_req = NULL;
+	struct mm_add_if_cfm add_if_cfm;
+	int i;
+	u8_l chan_flags = 0;
+	int err;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the SCANU_START_REQ message */
+	req = rwnx_msg_zalloc(SCANU_START_REQ, TASK_SCANU, DRV_TASK_ID,
+			      sizeof(struct scanu_start_req));
+	if (!req)
+		return -ENOMEM;
+
+	rwnx_hw->scanning = 1;
+	/* Set parameters */
+	req->vif_idx = rwnx_vif->vif_index;
+	req->chan_cnt = (u8)min_t(int, SCAN_CHANNEL_MAX, param->n_channels);
+
+#ifdef CONFIG_AIC8800_AUTO_CUSTREG
+	if (rwnx_hw->ccode.ccode_set || rwnx_hw->ccode.ccode_cnt >= MAX_SEARCH_CNT)
+		req->ssid_cnt = (u8)min_t(int, SCAN_SSID_MAX, param->n_ssids);
+	else
+		req->ssid_cnt = (u8)min_t(int, 0, param->n_ssids);
+#else
+	req->ssid_cnt = (u8)min_t(int, SCAN_SSID_MAX, param->n_ssids);
+#endif
+
+	req->bssid = mac_addr_bcst;
+	req->no_cck = param->no_cck;
+	if (param->duration_mandatory)
+		// req->duration = ieee80211_tu_to_usec(param->duration);
+		req->duration = 0;
+
+#ifdef AIC8800_RADAR_OR_IR_DETECT
+	if (req->ssid_cnt == 0)
+		chan_flags |= CHAN_NO_IR;
+#endif
+	for (i = 0; i < req->ssid_cnt; i++) {
+		int j;
+
+		for (j = 0; j < param->ssids[i].ssid_len; j++)
+			req->ssid[i].array[j] = param->ssids[i].ssid[j];
+		req->ssid[i].length = param->ssids[i].ssid_len;
+
+		if (!memcmp(P2P_WILDCARD_SSID, param->ssids[i].ssid,
+			    P2P_WILDCARD_SSID_LEN)) {
+			AICWFDBG(LOGINFO, "p2p scanu:%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 && !rwnx_hw->is_p2p_alive) {
+#else
+			if (rwnx_vif == rwnx_hw->p2p_dev_vif && !rwnx_vif->up) {
+#endif
+				err = rwnx_send_add_if(rwnx_hw, rwnx_vif->wdev.address,
+						       RWNX_VIF_TYPE(rwnx_vif), false,
+						       &add_if_cfm);
+				if (err)
+					goto error;
+
+				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
+			AICWFDBG(LOGINFO, "p2p scan start\n");
+		}
+	}
+
+	if (param->ie) {
+		ie_req = rwnx_msg_zalloc(SCANU_VENDOR_IE_REQ, TASK_SCANU, DRV_TASK_ID,
+					 sizeof(struct scanu_vendor_ie_req));
+		if (!ie_req)
+			return -ENOMEM;
+
+		ie_req->add_ie_len = param->ie_len;
+		ie_req->vif_idx = rwnx_vif->vif_index;
+		memcpy(ie_req->ie, param->ie, param->ie_len);
+#ifdef CONFIG_AIC8800_SET_VENDOR_EXTENSION_IE
+		rwnx_insert_vendor_extension_data(ie_req);
+#endif // CONFIG_AIC8800_SET_VENDOR_EXTENSION_IE
+		req->add_ie_len = 0;
+		req->add_ies = 0;
+
+		err = rwnx_send_msg(rwnx_hw, ie_req, 1, SCANU_VENDOR_IE_CFM, NULL);
+		if (err)
+			goto error;
+	} else {
+		req->add_ie_len = 0;
+		req->add_ies = 0;
+	}
+
+	for (i = 0; i < req->chan_cnt; i++) {
+		struct ieee80211_channel *chan = param->channels[i];
+
+		AICWFDBG(LOGDEBUG, "scan channel:%d(%d) \r\n",
+			 ieee80211_frequency_to_channel(chan->center_freq),
+			 chan->center_freq);
+		req->chan[i].band = chan->band;
+		req->chan[i].freq = chan->center_freq;
+		req->chan[i].flags = chan_flags | get_chan_flags(chan->flags);
+		req->chan[i].tx_power = chan_to_fw_pwr(chan->max_reg_power);
+	}
+
+	/* Send the SCANU_START_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, SCANU_START_CFM_ADDTIONAL, NULL);
+error:
+	if (req)
+		rwnx_msg_free(rwnx_hw, req);
+	if (ie_req)
+		rwnx_msg_free(rwnx_hw, ie_req);
+	return -ENOMEM;
+}
+
+int rwnx_send_scanu_cancel_req(struct rwnx_hw *rwnx_hw,
+			       struct scan_cancel_cfm *cfm)
+{
+	struct scan_cancel_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the SCAN_CANCEL_REQ message */
+	req = rwnx_msg_zalloc(SCANU_CANCEL_REQ, TASK_SCANU, DRV_TASK_ID,
+			      sizeof(struct scan_cancel_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Send the SCAN_CANCEL_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, SCANU_CANCEL_CFM, cfm);
+}
+
+int rwnx_send_apm_start_cac_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+				struct cfg80211_chan_def *chandef,
+				struct apm_start_cac_cfm *cfm)
+{
+	struct apm_start_cac_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the APM_START_CAC_REQ message */
+	req = rwnx_msg_zalloc(APM_START_CAC_REQ, TASK_APM, DRV_TASK_ID,
+			      sizeof(struct apm_start_cac_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the APM_START_CAC_REQ message */
+	req->vif_idx = vif->vif_index;
+	req->chan.band = chandef->chan->band;
+	req->chan.freq = chandef->chan->center_freq;
+	req->chan.flags = 0;
+	req->center_freq1 = chandef->center_freq1;
+	req->center_freq2 = chandef->center_freq2;
+	req->ch_width = bw2chnl[chandef->width];
+
+	/* Send the APM_START_CAC_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, APM_START_CAC_CFM, cfm);
+}
+
+int rwnx_send_apm_stop_cac_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif)
+{
+	struct apm_stop_cac_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the APM_STOP_CAC_REQ message */
+	req = rwnx_msg_zalloc(APM_STOP_CAC_REQ, TASK_APM, DRV_TASK_ID,
+			      sizeof(struct apm_stop_cac_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the APM_STOP_CAC_REQ message */
+	req->vif_idx = vif->vif_index;
+
+	/* Send the APM_STOP_CAC_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, APM_STOP_CAC_CFM, NULL);
+}
+
+int rwnx_send_mesh_start_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+			     const struct mesh_config *conf,
+			     const struct mesh_setup *setup,
+			     struct mesh_start_cfm *cfm)
+{
+	// Message to send
+	struct mesh_start_req *req;
+	// Supported basic rates
+	struct ieee80211_supported_band *band_2ghz =
+		rwnx_hw->wiphy->bands[NL80211_BAND_2GHZ];
+	/* Counter */
+	int i;
+	/* Return status */
+	int status;
+	/* DMA Address to be unmapped after confirmation reception */
+	u32 dma_addr = 0;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MESH_START_REQ message */
+	req = rwnx_msg_zalloc(MESH_START_REQ, TASK_MESH, DRV_TASK_ID,
+			      sizeof(struct mesh_start_req));
+	if (!req)
+		return -ENOMEM;
+
+	req->vif_index = vif->vif_index;
+	req->bcn_int = setup->beacon_interval;
+	req->dtim_period = setup->dtim_period;
+	req->mesh_id_len = setup->mesh_id_len;
+
+	for (i = 0; i < setup->mesh_id_len; i++)
+		req->mesh_id[i] = *(setup->mesh_id + i);
+
+	req->user_mpm = setup->user_mpm;
+	req->is_auth = setup->is_authenticated;
+	req->auth_id = setup->auth_id;
+	req->ie_len = setup->ie_len;
+
+	if (setup->ie_len) {
+		/*
+		 * Need to provide a Virtual Address to the MAC so that it can
+		 * download the additional information elements.
+		 */
+		req->ie_addr = dma_map_single(rwnx_hw->dev, (void *)setup->ie,
+					      setup->ie_len, DMA_FROM_DEVICE);
+
+		/* Check DMA mapping result */
+		if (dma_mapping_error(rwnx_hw->dev, req->ie_addr)) {
+			AICWFDBG(LOGDEBUG, KERN_CRIT
+				 "AICWF %s - DMA Mapping error on additional IEs\n",
+				 __func__);
+
+			/* Consider there is no Additional IEs */
+			req->ie_len = 0;
+		} else {
+			/* Store DMA Address so that we can unmap the memory section
+			 * once MESH_START_CFM is received
+			 */
+			dma_addr = req->ie_addr;
+		}
+	}
+
+	/* Provide rate information */
+	req->basic_rates.length = 0;
+	for (i = 0; i < band_2ghz->n_bitrates; i++) {
+		u16 rate = band_2ghz->bitrates[i].bitrate;
+
+		/* Read value is in units of 100 Kbps, provided value is in units
+		 * of 1Mbps, and multiplied by 2 so that 5.5 becomes 11
+		 */
+		rate = (rate << 1) / 10;
+
+		if (setup->basic_rates & CO_BIT(i))
+			rate |= 0x80;
+
+		req->basic_rates.array[i] = (u8)rate;
+		req->basic_rates.length++;
+	}
+
+	/* Provide channel information */
+	req->chan.band = setup->chandef.chan->band;
+	req->chan.freq = setup->chandef.chan->center_freq;
+	req->chan.flags = 0;
+	req->chan.tx_power = chan_to_fw_pwr(setup->chandef.chan->max_power);
+	req->center_freq1 = setup->chandef.center_freq1;
+	req->center_freq2 = setup->chandef.center_freq2;
+	req->ch_width = bw2chnl[setup->chandef.width];
+
+	/* Send the MESH_START_REQ message to UMAC FW */
+	status = rwnx_send_msg(rwnx_hw, req, 1, MESH_START_CFM, cfm);
+
+	/* Unmap DMA area */
+	if (setup->ie_len)
+		dma_unmap_single(rwnx_hw->dev, dma_addr, setup->ie_len, DMA_TO_DEVICE);
+
+	/* Return the status */
+	return status;
+}
+
+int rwnx_send_mesh_stop_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+			    struct mesh_stop_cfm *cfm)
+{
+	// Message to send
+	struct mesh_stop_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MESH_STOP_REQ message */
+	req = rwnx_msg_zalloc(MESH_STOP_REQ, TASK_MESH, DRV_TASK_ID,
+			      sizeof(struct mesh_stop_req));
+	if (!req)
+		return -ENOMEM;
+
+	req->vif_idx = vif->vif_index;
+
+	/* Send the MESH_STOP_REQ message to UMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, MESH_STOP_CFM, cfm);
+}
+
+int rwnx_send_mesh_update_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+			      u32 mask, const struct mesh_config *p_mconf,
+			      struct mesh_update_cfm *cfm)
+{
+	// Message to send
+	struct mesh_update_req *req;
+	// Keep only bit for fields which can be updated
+	u32 supp_mask = (mask << 1) & (CO_BIT(NL80211_MESHCONF_GATE_ANNOUNCEMENTS) |
+			CO_BIT(NL80211_MESHCONF_HWMP_ROOTMODE) |
+			CO_BIT(NL80211_MESHCONF_FORWARDING) |
+			CO_BIT(NL80211_MESHCONF_POWER_MODE));
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	if (!supp_mask)
+		return -ENOENT;
+
+	/* Build the MESH_UPDATE_REQ message */
+	req = rwnx_msg_zalloc(MESH_UPDATE_REQ, TASK_MESH, DRV_TASK_ID,
+			      sizeof(struct mesh_update_req));
+
+	if (!req)
+		return -ENOMEM;
+
+	req->vif_idx = vif->vif_index;
+
+	if (supp_mask & CO_BIT(NL80211_MESHCONF_GATE_ANNOUNCEMENTS)) {
+		req->flags |= CO_BIT(MESH_UPDATE_FLAGS_GATE_MODE_BIT);
+		req->gate_announ = p_mconf->dot11MeshGateAnnouncementProtocol;
+	}
+
+	if (supp_mask & CO_BIT(NL80211_MESHCONF_HWMP_ROOTMODE)) {
+		req->flags |= CO_BIT(MESH_UPDATE_FLAGS_ROOT_MODE_BIT);
+		req->root_mode = p_mconf->dot11MeshHWMPRootMode;
+	}
+
+	if (supp_mask & CO_BIT(NL80211_MESHCONF_FORWARDING)) {
+		req->flags |= CO_BIT(MESH_UPDATE_FLAGS_MESH_FWD_BIT);
+		req->mesh_forward = p_mconf->dot11MeshForwarding;
+	}
+
+	if (supp_mask & CO_BIT(NL80211_MESHCONF_POWER_MODE)) {
+		req->flags |= CO_BIT(MESH_UPDATE_FLAGS_LOCAL_PSM_BIT);
+		req->local_ps_mode = p_mconf->power_mode;
+	}
+
+	/* Send the MESH_UPDATE_REQ message to UMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, MESH_UPDATE_CFM, cfm);
+}
+
+int rwnx_send_mesh_peer_info_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+				 u8 sta_idx, struct mesh_peer_info_cfm *cfm)
+{
+	// Message to send
+	struct mesh_peer_info_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MESH_PEER_INFO_REQ message */
+	req = rwnx_msg_zalloc(MESH_PEER_INFO_REQ, TASK_MESH, DRV_TASK_ID,
+			      sizeof(struct mesh_peer_info_req));
+	if (!req)
+		return -ENOMEM;
+
+	req->sta_idx = sta_idx;
+
+	/* Send the MESH_PEER_INFO_REQ message to UMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, MESH_PEER_INFO_CFM, cfm);
+}
+
+void rwnx_send_mesh_peer_update_ntf(struct rwnx_hw *rwnx_hw,
+				    struct rwnx_vif *vif, u8 sta_idx,
+				    u8 mlink_state)
+{
+	// Message to send
+	struct mesh_peer_update_ntf *ntf;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MESH_PEER_UPDATE_NTF message */
+	ntf = rwnx_msg_zalloc(MESH_PEER_UPDATE_NTF, TASK_MESH, DRV_TASK_ID,
+			      sizeof(struct mesh_peer_update_ntf));
+
+	if (ntf) {
+		ntf->vif_idx = vif->vif_index;
+		ntf->sta_idx = sta_idx;
+		ntf->state = mlink_state;
+
+		/* Send the MESH_PEER_INFO_REQ message to UMAC FW */
+		rwnx_send_msg(rwnx_hw, ntf, 0, 0, NULL);
+	}
+}
+
+void rwnx_send_mesh_path_create_req(struct rwnx_hw *rwnx_hw,
+				    struct rwnx_vif *vif, u8 *tgt_addr)
+{
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Check if we are already waiting for a confirmation */
+	if (!vif->ap.create_path) {
+		// Message to send
+		struct mesh_path_create_req *req;
+
+		/* Build the MESH_PATH_CREATE_REQ message */
+		req = rwnx_msg_zalloc(MESH_PATH_CREATE_REQ, TASK_MESH, DRV_TASK_ID,
+				      sizeof(struct mesh_path_create_req));
+
+		if (req) {
+			req->vif_idx = vif->vif_index;
+			memcpy(&req->tgt_mac_addr, tgt_addr, ETH_ALEN);
+
+			vif->ap.create_path = true;
+
+			/* Send the MESH_PATH_CREATE_REQ message to UMAC FW */
+			rwnx_send_msg(rwnx_hw, req, 0, 0, NULL);
+		}
+	}
+}
+
+int rwnx_send_mesh_path_update_req(struct rwnx_hw *rwnx_hw,
+				   struct rwnx_vif *vif, const u8 *tgt_addr,
+				   const u8 *p_nhop_addr,
+				   struct mesh_path_update_cfm *cfm)
+{
+	// Message to send
+	struct mesh_path_update_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MESH_PATH_UPDATE_REQ message */
+	req = rwnx_msg_zalloc(MESH_PATH_UPDATE_REQ, TASK_MESH, DRV_TASK_ID,
+			      sizeof(struct mesh_path_update_req));
+	if (!req)
+		return -ENOMEM;
+
+	req->delete = (!p_nhop_addr);
+	req->vif_idx = vif->vif_index;
+	memcpy(&req->tgt_mac_addr, tgt_addr, ETH_ALEN);
+
+	if (p_nhop_addr)
+		memcpy(&req->nhop_mac_addr, p_nhop_addr, ETH_ALEN);
+
+	/* Send the MESH_PATH_UPDATE_REQ message to UMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, MESH_PATH_UPDATE_CFM, cfm);
+}
+
+void rwnx_send_mesh_proxy_add_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+				  u8 *ext_addr)
+{
+	// Message to send
+	struct mesh_proxy_add_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MESH_PROXY_ADD_REQ message */
+	req = rwnx_msg_zalloc(MESH_PROXY_ADD_REQ, TASK_MESH, DRV_TASK_ID,
+			      sizeof(struct mesh_proxy_add_req));
+
+	if (req) {
+		req->vif_idx = vif->vif_index;
+		memcpy(&req->ext_sta_addr, ext_addr, ETH_ALEN);
+
+		/* Send the MESH_PROXY_ADD_REQ message to UMAC FW */
+		rwnx_send_msg(rwnx_hw, req, 0, 0, NULL);
+	}
+}
+
+int rwnx_send_tdls_peer_traffic_ind_req(struct rwnx_hw *rwnx_hw,
+					struct rwnx_vif *rwnx_vif)
+{
+	struct tdls_peer_traffic_ind_req *tdls_peer_traffic_ind_req;
+
+	if (!rwnx_vif->sta.tdls_sta)
+		return -ENOLINK;
+
+	/* Build the TDLS_PEER_TRAFFIC_IND_REQ message */
+	tdls_peer_traffic_ind_req =
+		rwnx_msg_zalloc(TDLS_PEER_TRAFFIC_IND_REQ, TASK_TDLS, DRV_TASK_ID,
+				sizeof(struct tdls_peer_traffic_ind_req));
+
+	if (!tdls_peer_traffic_ind_req)
+		return -ENOMEM;
+
+	/* Set parameters for the TDLS_PEER_TRAFFIC_IND_REQ message */
+	tdls_peer_traffic_ind_req->vif_index = rwnx_vif->vif_index;
+	tdls_peer_traffic_ind_req->sta_idx = rwnx_vif->sta.tdls_sta->sta_idx;
+	memcpy(&tdls_peer_traffic_ind_req->peer_mac_addr.array[0],
+	       rwnx_vif->sta.tdls_sta->mac_addr, ETH_ALEN);
+	tdls_peer_traffic_ind_req->dialog_token = 0; // check dialog token value
+	tdls_peer_traffic_ind_req->last_tid = rwnx_vif->sta.tdls_sta->tdls.last_tid;
+	tdls_peer_traffic_ind_req->last_sn = rwnx_vif->sta.tdls_sta->tdls.last_sn;
+
+	/* Send the TDLS_PEER_TRAFFIC_IND_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, tdls_peer_traffic_ind_req, 0, 0, NULL);
+}
+
+int rwnx_send_config_monitor_req(struct rwnx_hw *rwnx_hw,
+				 struct cfg80211_chan_def *chandef,
+				 struct me_config_monitor_cfm *cfm)
+{
+	struct me_config_monitor_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the ME_CONFIG_MONITOR_REQ message */
+	req = rwnx_msg_zalloc(ME_CONFIG_MONITOR_REQ, TASK_ME, DRV_TASK_ID,
+			      sizeof(struct me_config_monitor_req));
+	if (!req)
+		return -ENOMEM;
+
+	if (chandef) {
+		req->chan_set = true;
+
+		req->chan.band = chandef->chan->band;
+		req->chan.type = bw2chnl[chandef->width];
+		req->chan.prim20_freq = chandef->chan->center_freq;
+		req->chan.center1_freq = chandef->center_freq1;
+		req->chan.center2_freq = chandef->center_freq2;
+		req->chan.tx_power = chan_to_fw_pwr(chandef->chan->max_power);
+
+		if (rwnx_hw->phy.limit_bw)
+			limit_chan_bw(&req->chan.type, req->chan.prim20_freq,
+				      &req->chan.center1_freq);
+	} else {
+		req->chan_set = false;
+	}
+
+	req->uf = rwnx_hw->mod_params->uf;
+	req->auto_reply = rwnx_hw->mod_params->auto_reply;
+
+	/* Send the ME_CONFIG_MONITOR_REQ message to FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, ME_CONFIG_MONITOR_CFM, cfm);
+}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+int rwnx_send_tdls_chan_switch_req(struct rwnx_hw *rwnx_hw,
+				   struct rwnx_vif *rwnx_vif,
+				   struct rwnx_sta *rwnx_sta,
+				   bool sta_initiator, u8 oper_class,
+				   struct cfg80211_chan_def *chandef,
+				   struct tdls_chan_switch_cfm *cfm)
+{
+	struct tdls_chan_switch_req *tdls_chan_switch_req;
+
+	/* Build the TDLS_CHAN_SWITCH_REQ message */
+	tdls_chan_switch_req =
+		rwnx_msg_zalloc(TDLS_CHAN_SWITCH_REQ, TASK_TDLS, DRV_TASK_ID,
+				sizeof(struct tdls_chan_switch_req));
+
+	if (!tdls_chan_switch_req)
+		return -ENOMEM;
+
+	/* Set parameters for the TDLS_CHAN_SWITCH_REQ message */
+	tdls_chan_switch_req->vif_index = rwnx_vif->vif_index;
+	tdls_chan_switch_req->sta_idx = rwnx_sta->sta_idx;
+	memcpy(&tdls_chan_switch_req->peer_mac_addr.array[0],
+	       rwnx_sta_addr(rwnx_sta), ETH_ALEN);
+	tdls_chan_switch_req->initiator = sta_initiator;
+	tdls_chan_switch_req->band = chandef->chan->band;
+	tdls_chan_switch_req->type = bw2chnl[chandef->width];
+	tdls_chan_switch_req->prim20_freq = chandef->chan->center_freq;
+	tdls_chan_switch_req->center1_freq = chandef->center_freq1;
+	tdls_chan_switch_req->center2_freq = chandef->center_freq2;
+	tdls_chan_switch_req->tx_power = chan_to_fw_pwr(chandef->chan->max_power);
+	tdls_chan_switch_req->op_class = oper_class;
+
+	/* Send the TDLS_CHAN_SWITCH_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, tdls_chan_switch_req, 1, TDLS_CHAN_SWITCH_CFM,
+			     cfm);
+}
+
+int rwnx_send_tdls_cancel_chan_switch_req(struct rwnx_hw *rwnx_hw,
+					  struct rwnx_vif *rwnx_vif,
+					  struct rwnx_sta *rwnx_sta,
+					  struct tdls_cancel_chan_switch_cfm *cfm)
+{
+	struct tdls_cancel_chan_switch_req *tdls_cancel_chan_switch_req;
+
+	/* Build the TDLS_CHAN_SWITCH_REQ message */
+	tdls_cancel_chan_switch_req =
+		rwnx_msg_zalloc(TDLS_CANCEL_CHAN_SWITCH_REQ, TASK_TDLS, DRV_TASK_ID,
+				sizeof(struct tdls_cancel_chan_switch_req));
+	if (!tdls_cancel_chan_switch_req)
+		return -ENOMEM;
+
+	/* Set parameters for the TDLS_CHAN_SWITCH_REQ message */
+	tdls_cancel_chan_switch_req->vif_index = rwnx_vif->vif_index;
+	tdls_cancel_chan_switch_req->sta_idx = rwnx_sta->sta_idx;
+	memcpy(&tdls_cancel_chan_switch_req->peer_mac_addr.array[0],
+	       rwnx_sta_addr(rwnx_sta), ETH_ALEN);
+
+	/* Send the TDLS_CHAN_SWITCH_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, tdls_cancel_chan_switch_req, 1,
+			     TDLS_CANCEL_CHAN_SWITCH_CFM, cfm);
+}
+
+#ifdef CONFIG_RWNX_BFMER
+#ifdef CONFIG_RWNX_FULLMAC
+void rwnx_send_bfmer_enable(struct rwnx_hw *rwnx_hw, struct rwnx_sta *rwnx_sta,
+			    const struct ieee80211_vht_cap *vht_cap)
+#endif /* CONFIG_RWNX_FULLMAC */
+{
+	struct mm_bfmer_enable_req *bfmer_en_req;
+#ifdef CONFIG_RWNX_FULLMAC
+	__le32 vht_capability;
+	u8 rx_nss = 0;
+#endif /* CONFIG_RWNX_FULLMAC */
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+#ifdef CONFIG_RWNX_FULLMAC
+	if (!vht_cap) {
+#endif /* CONFIG_RWNX_FULLMAC */
+		goto end;
+	}
+
+#ifdef CONFIG_RWNX_FULLMAC
+	vht_capability = vht_cap->vht_cap_info;
+#endif /* CONFIG_RWNX_FULLMAC */
+
+	if (!(vht_capability & IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE))
+		goto end;
+
+#ifdef CONFIG_RWNX_FULLMAC
+	rx_nss = rwnx_bfmer_get_rx_nss(vht_cap);
+#endif /* CONFIG_RWNX_FULLMAC */
+
+	/* Allocate a structure that will contain the beamforming report */
+	if (rwnx_bfmer_report_add(rwnx_hw, rwnx_sta,
+				  RWNX_BFMER_REPORT_SPACE_SIZE))
+		goto end;
+
+	/* Build the MM_BFMER_ENABLE_REQ message */
+	bfmer_en_req = rwnx_msg_zalloc(MM_BFMER_ENABLE_REQ, TASK_MM, DRV_TASK_ID,
+				       sizeof(struct mm_bfmer_enable_req));
+
+	/* Check message allocation */
+	if (!bfmer_en_req) {
+		/* Free memory allocated for the report */
+		rwnx_bfmer_report_del(rwnx_hw, rwnx_sta);
+		/* Do not use beamforming */
+		goto end;
+	}
+
+	/* Provide DMA address to the MAC */
+	bfmer_en_req->host_bfr_addr = rwnx_sta->bfm_report->dma_addr;
+	bfmer_en_req->host_bfr_size = RWNX_BFMER_REPORT_SPACE_SIZE;
+	bfmer_en_req->sta_idx = rwnx_sta->sta_idx;
+#ifdef CONFIG_RWNX_FULLMAC
+	bfmer_en_req->aid = rwnx_sta->aid;
+	bfmer_en_req->rx_nss = rx_nss;
+#endif /* CONFIG_RWNX_FULLMAC */
+
+	if (vht_capability & IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE)
+		bfmer_en_req->vht_mu_bfmee = true;
+	else
+		bfmer_en_req->vht_mu_bfmee = false;
+
+	/* Send the MM_BFMER_EN_REQ message to LMAC FW */
+	rwnx_send_msg(rwnx_hw, bfmer_en_req, 0, 0, NULL);
+
+end:
+	return;
+}
+
+#ifdef CONFIG_RWNX_MUMIMO_TX
+int rwnx_send_mu_group_update_req(struct rwnx_hw *rwnx_hw,
+				  struct rwnx_sta *rwnx_sta)
+{
+	struct mm_mu_group_update_req *req;
+	int group_id, i = 0;
+	u64 map;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_MU_GROUP_UPDATE_REQ message */
+	req =
+		rwnx_msg_zalloc(MM_MU_GROUP_UPDATE_REQ, TASK_MM, DRV_TASK_ID,
+				sizeof(struct mm_mu_group_update_req) +
+				rwnx_sta->group_info.cnt * sizeof(req->groups[0]));
+
+	/* Check message allocation */
+	if (!req)
+		return -ENOMEM;
+
+	map = rwnx_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)) {
+		int user_pos = rwnx_mu_group_sta_get_pos(rwnx_hw, rwnx_sta, group_id);
+
+		if (WARN(i >= rwnx_sta->group_info.cnt,
+			 "STA%d: Too much group (%d)\n", rwnx_sta->sta_idx, i + 1))
+			break;
+
+		req->groups[i].group_id = group_id;
+		req->groups[i].user_pos = user_pos;
+
+		i++;
+	}
+
+	req->group_cnt = rwnx_sta->group_info.cnt;
+	req->sta_idx = rwnx_sta->sta_idx;
+
+	/* Send the MM_MU_GROUP_UPDATE_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, MM_MU_GROUP_UPDATE_CFM, NULL);
+}
+#endif /* CONFIG_RWNX_MUMIMO_TX */
+#endif /* CONFIG_RWNX_BFMER */
+
+/**********************************************************************
+ *    Debug Messages
+ *********************************************************************/
+int rwnx_send_dbg_trigger_req(struct rwnx_hw *rwnx_hw, char *msg)
+{
+	struct mm_dbg_trigger_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_DBG_TRIGGER_REQ message */
+	req = rwnx_msg_zalloc(MM_DBG_TRIGGER_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_dbg_trigger_req));
+	if (!req)
+		return -ENOMEM;
+
+	/* Set parameters for the MM_DBG_TRIGGER_REQ message */
+	strscpy(req->error, msg, sizeof(req->error));
+
+	/* Send the MM_DBG_TRIGGER_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 0, -1, NULL);
+}
+
+int rwnx_send_dbg_mem_read_req(struct rwnx_hw *rwnx_hw, u32 mem_addr,
+			       struct dbg_mem_read_cfm *cfm)
+{
+	struct dbg_mem_read_req *mem_read_req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the DBG_MEM_READ_REQ message */
+	mem_read_req = rwnx_msg_zalloc(DBG_MEM_READ_REQ, TASK_DBG, DRV_TASK_ID,
+				       sizeof(struct dbg_mem_read_req));
+	if (!mem_read_req)
+		return -ENOMEM;
+
+	/* Set parameters for the DBG_MEM_READ_REQ message */
+	mem_read_req->memaddr = mem_addr;
+
+	/* Send the DBG_MEM_READ_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, mem_read_req, 1, DBG_MEM_READ_CFM, cfm);
+}
+
+int rwnx_send_dbg_mem_write_req(struct rwnx_hw *rwnx_hw, u32 mem_addr,
+				u32 mem_data)
+{
+	struct dbg_mem_write_req *mem_write_req;
+
+	// RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the DBG_MEM_WRITE_REQ message */
+	mem_write_req = rwnx_msg_zalloc(DBG_MEM_WRITE_REQ, TASK_DBG, DRV_TASK_ID,
+					sizeof(struct dbg_mem_write_req));
+	if (!mem_write_req)
+		return -ENOMEM;
+
+	/* Set parameters for the DBG_MEM_WRITE_REQ message */
+	mem_write_req->memaddr = mem_addr;
+	mem_write_req->memdata = mem_data;
+
+	/* Send the DBG_MEM_WRITE_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, mem_write_req, 1, DBG_MEM_WRITE_CFM, NULL);
+}
+
+int rwnx_send_dbg_mem_mask_write_req(struct rwnx_hw *rwnx_hw, u32 mem_addr,
+				     u32 mem_mask, u32 mem_data)
+{
+	struct dbg_mem_mask_write_req *mem_mask_write_req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the DBG_MEM_MASK_WRITE_REQ message */
+	mem_mask_write_req =
+		rwnx_msg_zalloc(DBG_MEM_MASK_WRITE_REQ, TASK_DBG, DRV_TASK_ID,
+				sizeof(struct dbg_mem_mask_write_req));
+	if (!mem_mask_write_req)
+		return -ENOMEM;
+
+	/* Set parameters for the DBG_MEM_MASK_WRITE_REQ message */
+	mem_mask_write_req->memaddr = mem_addr;
+	mem_mask_write_req->memmask = mem_mask;
+	mem_mask_write_req->memdata = mem_data;
+
+	/* Send the DBG_MEM_MASK_WRITE_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, mem_mask_write_req, 1, DBG_MEM_MASK_WRITE_CFM,
+			     NULL);
+}
+
+int rwnx_send_rftest_req(struct rwnx_hw *rwnx_hw, u32_l cmd, u32_l argc,
+			 u8_l *argv, struct dbg_rftest_cmd_cfm *cfm)
+{
+	struct dbg_rftest_cmd_req *mem_rftest_cmd_req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the DBG_RFTEST_CMD_REQ message */
+	mem_rftest_cmd_req =
+		rwnx_msg_zalloc(DBG_RFTEST_CMD_REQ, TASK_DBG, DRV_TASK_ID,
+				sizeof(struct dbg_rftest_cmd_req));
+	if (!mem_rftest_cmd_req)
+		return -ENOMEM;
+
+	if (argc > 10)
+		return -ENOMEM;
+
+	/* Set parameters for the DBG_MEM_MASK_WRITE_REQ message */
+	mem_rftest_cmd_req->cmd = cmd;
+	mem_rftest_cmd_req->argc = argc;
+	if (argc != 0)
+		memcpy(mem_rftest_cmd_req->argv, argv, argc);
+
+	/* Send the DBG_RFTEST_CMD_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, mem_rftest_cmd_req, 1, DBG_RFTEST_CMD_CFM,
+			     cfm);
+}
+
+int rwnx_send_dbg_set_mod_filter_req(struct rwnx_hw *rwnx_hw, u32 filter)
+{
+	struct dbg_set_mod_filter_req *set_mod_filter_req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the DBG_SET_MOD_FILTER_REQ message */
+	set_mod_filter_req =
+		rwnx_msg_zalloc(DBG_SET_MOD_FILTER_REQ, TASK_DBG, DRV_TASK_ID,
+				sizeof(struct dbg_set_mod_filter_req));
+	if (!set_mod_filter_req)
+		return -ENOMEM;
+
+	/* Set parameters for the DBG_SET_MOD_FILTER_REQ message */
+	set_mod_filter_req->mod_filter = filter;
+
+	/* Send the DBG_SET_MOD_FILTER_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, set_mod_filter_req, 1, DBG_SET_MOD_FILTER_CFM,
+						 NULL);
+}
+
+int rwnx_send_dbg_set_sev_filter_req(struct rwnx_hw *rwnx_hw, u32 filter)
+{
+	struct dbg_set_sev_filter_req *set_sev_filter_req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the DBG_SET_SEV_FILTER_REQ message */
+	set_sev_filter_req =
+		rwnx_msg_zalloc(DBG_SET_SEV_FILTER_REQ, TASK_DBG, DRV_TASK_ID,
+				sizeof(struct dbg_set_sev_filter_req));
+	if (!set_sev_filter_req)
+		return -ENOMEM;
+
+	/* Set parameters for the DBG_SET_SEV_FILTER_REQ message */
+	set_sev_filter_req->sev_filter = filter;
+
+	/* Send the DBG_SET_SEV_FILTER_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, set_sev_filter_req, 1, DBG_SET_SEV_FILTER_CFM,
+			     NULL);
+}
+
+int rwnx_send_dbg_get_sys_stat_req(struct rwnx_hw *rwnx_hw,
+				   struct dbg_get_sys_stat_cfm *cfm)
+{
+	void *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Allocate the message */
+	req = rwnx_msg_zalloc(DBG_GET_SYS_STAT_REQ, TASK_DBG, DRV_TASK_ID, 0);
+	if (!req)
+		return -ENOMEM;
+
+	/* Send the DBG_MEM_READ_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, DBG_GET_SYS_STAT_CFM, cfm);
+}
+
+int rwnx_send_dbg_mem_block_write_req(struct rwnx_hw *rwnx_hw, u32 mem_addr,
+				      u32 mem_size, u32 *mem_data)
+{
+	struct dbg_mem_block_write_req *mem_blk_write_req;
+
+	// RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the DBG_MEM_BLOCK_WRITE_REQ message */
+	mem_blk_write_req =
+		rwnx_msg_zalloc(DBG_MEM_BLOCK_WRITE_REQ, TASK_DBG, DRV_TASK_ID,
+				sizeof(struct dbg_mem_block_write_req));
+	if (!mem_blk_write_req)
+		return -ENOMEM;
+
+	/* Set parameters for the DBG_MEM_BLOCK_WRITE_REQ message */
+	mem_blk_write_req->memaddr = mem_addr;
+	mem_blk_write_req->memsize = mem_size;
+	memcpy(mem_blk_write_req->memdata, mem_data, mem_size);
+
+	/* Send the DBG_MEM_BLOCK_WRITE_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, mem_blk_write_req, 1, DBG_MEM_BLOCK_WRITE_CFM,
+			     NULL);
+}
+
+int rwnx_send_dbg_start_app_req(struct rwnx_hw *rwnx_hw, u32 boot_addr,
+				u32 boot_type)
+{
+	struct dbg_start_app_req *start_app_req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the DBG_START_APP_REQ message */
+	start_app_req = rwnx_msg_zalloc(DBG_START_APP_REQ, TASK_DBG, DRV_TASK_ID,
+					sizeof(struct dbg_start_app_req));
+	if (!start_app_req)
+		return -ENOMEM;
+
+	/* Set parameters for the DBG_START_APP_REQ message */
+	start_app_req->bootaddr = boot_addr;
+	start_app_req->boottype = boot_type;
+
+	/* Send the DBG_START_APP_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, start_app_req, 1, DBG_START_APP_CFM, NULL);
+}
+
+int rwnx_send_cfg_rssi_req(struct rwnx_hw *rwnx_hw, u8 vif_index,
+			   int rssi_thold, u32 rssi_hyst)
+{
+	struct mm_cfg_rssi_req *req;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Build the MM_CFG_RSSI_REQ message */
+	req = rwnx_msg_zalloc(MM_CFG_RSSI_REQ, TASK_MM, DRV_TASK_ID,
+			      sizeof(struct mm_cfg_rssi_req));
+	if (!req)
+		return -ENOMEM;
+
+	if (!rwnx_hw->vif_table[vif_index])
+		return 0;
+
+	/* Set parameters for the MM_CFG_RSSI_REQ message */
+	req->vif_index = vif_index;
+	req->rssi_thold = (s8)rssi_thold;
+	req->rssi_hyst = (u8)rssi_hyst;
+
+	/* Send the MM_CFG_RSSI_REQ message to LMAC FW */
+	return rwnx_send_msg(rwnx_hw, req, 1, MM_CFG_RSSI_CFM, NULL);
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_tx.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_tx.h
new file mode 100644
index 0000000000000..c9b46ef9d4634
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_msg_tx.h
@@ -0,0 +1,285 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_MSG_TX_H_
+#define _RWNX_MSG_TX_H_
+
+#include "lmac_types.h"
+#include "rwnx_defs.h"
+
+int rwnx_send_reset(struct rwnx_hw *rwnx_hw);
+
+int rwnx_send_start(struct rwnx_hw *rwnx_hw);
+
+int rwnx_send_version_req(struct rwnx_hw *rwnx_hw, struct mm_version_cfm *cfm);
+
+int rwnx_send_add_if(struct rwnx_hw *rwnx_hw, const unsigned char *mac,
+		     enum nl80211_iftype iftype, bool p2p,
+		     struct mm_add_if_cfm *cfm);
+
+int rwnx_send_remove_if(struct rwnx_hw *rwnx_hw, u8 vif_index, bool defer);
+
+int rwnx_send_set_channel(struct rwnx_hw *rwnx_hw, int phy_idx,
+			  struct mm_set_channel_cfm *cfm);
+
+int rwnx_send_key_add(struct rwnx_hw *rwnx_hw, u8 vif_idx, u8 sta_idx,
+		      bool pairwise, u8 *key, u8 key_len, u8 key_idx,
+		      u8 cipher_suite, struct mm_key_add_cfm *cfm);
+
+int rwnx_send_key_del(struct rwnx_hw *rwnx_hw, uint8_t hw_key_idx);
+
+int rwnx_send_bcn(struct rwnx_hw *rwnx_hw, u8 *buf, u8 vif_idx, u16 bcn_len);
+
+int rwnx_send_bcn_change(struct rwnx_hw *rwnx_hw, u8 vif_idx, u32 bcn_addr,
+			 u16 bcn_len, u16 tim_oft, u16 tim_len, u16 *csa_oft);
+
+int rwnx_send_tim_update(struct rwnx_hw *rwnx_hw, u8 vif_idx, u16 aid,
+			 u8 tx_status);
+
+int rwnx_send_roc(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+		  struct ieee80211_channel *chan, unsigned int duration,
+		  struct mm_remain_on_channel_cfm *roc_cfm);
+
+int rwnx_send_cancel_roc(struct rwnx_hw *rwnx_hw);
+
+int rwnx_send_set_power(struct rwnx_hw *rwnx_hw, u8 vif_idx, s8 pwr,
+			struct mm_set_power_cfm *cfm);
+
+int rwnx_send_set_edca(struct rwnx_hw *rwnx_hw, u8 hw_queue, u32 param,
+		       bool uapsd, u8 inst_nbr);
+
+int rwnx_send_tdls_chan_switch_req(struct rwnx_hw *rwnx_hw,
+				   struct rwnx_vif *rwnx_vif,
+				   struct rwnx_sta *rwnx_sta,
+				   bool sta_initiator, u8 oper_class,
+				   struct cfg80211_chan_def *chandef,
+				   struct tdls_chan_switch_cfm *cfm);
+
+int rwnx_send_tdls_cancel_chan_switch_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+					  struct rwnx_sta *rwnx_sta,
+					  struct tdls_cancel_chan_switch_cfm *cfm);
+
+#ifdef CONFIG_RWNX_P2P_DEBUGFS
+int rwnx_send_p2p_oppps_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+			    u8 ctw, struct mm_set_p2p_oppps_cfm *cfm);
+
+int rwnx_send_p2p_noa_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+			  int count, int interval, int duration, bool dyn_noa,
+			  struct mm_set_p2p_noa_cfm *cfm);
+
+#endif /* CONFIG_RWNX_P2P_DEBUGFS */
+
+#ifdef AICWF_ARP_OFFLOAD
+int rwnx_send_arpoffload_en_req(struct rwnx_hw *rwnx_hw,
+				struct rwnx_vif *rwnx_vif, u32_l ipaddr,
+				u8_l enable);
+#endif
+int rwnx_send_rf_config_req(struct rwnx_hw *rwnx_hw, u8_l ofst, u8_l sel, u8_l *tbl, u16_l len);
+
+int rwnx_send_rf_calib_req(struct rwnx_hw *rwnx_hw,
+			   struct mm_set_rf_calib_cfm *cfm);
+
+int rwnx_send_get_macaddr_req(struct rwnx_hw *rwnx_hw,
+			      struct mm_get_mac_addr_cfm *cfm);
+
+#ifdef CONFIG_RWNX_FULLMAC
+int rwnx_send_me_config_req(struct rwnx_hw *rwnx_hw);
+
+int rwnx_send_me_chan_config_req(struct rwnx_hw *rwnx_hw);
+
+int rwnx_send_me_set_control_port_req(struct rwnx_hw *rwnx_hw, bool opened,
+				      u8 sta_idx);
+
+int rwnx_send_me_sta_add(struct rwnx_hw *rwnx_hw,
+			 struct station_parameters *params, const u8 *mac,
+			 u8 inst_nbr, struct me_sta_add_cfm *cfm);
+
+int rwnx_send_me_sta_del(struct rwnx_hw *rwnx_hw, u8 sta_idx, bool tdls_sta);
+
+int rwnx_send_me_traffic_ind(struct rwnx_hw *rwnx_hw, u8 sta_idx, bool uapsd,
+			     u8 tx_status);
+
+int rwnx_send_me_rc_stats(struct rwnx_hw *rwnx_hw, u8 sta_idx,
+			  struct me_rc_stats_cfm *cfm);
+
+int rwnx_send_me_rc_set_rate(struct rwnx_hw *rwnx_hw, u8 sta_idx, u16 rate_idx);
+
+int rwnx_send_me_set_ps_mode(struct rwnx_hw *rwnx_hw, u8 ps_mode);
+
+int rwnx_send_me_set_lp_level(struct rwnx_hw *rwnx_hw, u8 lp_level, u8 disable_filter);
+
+int rwnx_send_sm_connect_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+			     struct cfg80211_connect_params *sme, struct sm_connect_cfm *cfm);
+
+int rwnx_send_sm_disconnect_req(struct rwnx_hw *rwnx_hw,
+				struct rwnx_vif *rwnx_vif, u16 reason);
+
+int rwnx_send_sm_external_auth_required_rsp(struct rwnx_hw *rwnx_hw,
+					    struct rwnx_vif *rwnx_vif, u16 status);
+
+int rwnx_send_apm_start_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+			    struct cfg80211_ap_settings *settings, struct apm_start_cfm *cfm,
+			    struct rwnx_ipc_elem_var *elem);
+
+int rwnx_send_apm_stop_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif);
+
+int rwnx_send_scanu_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+			struct cfg80211_scan_request *param);
+
+int rwnx_send_scanu_cancel_req(struct rwnx_hw *rwnx_hw,
+			       struct scan_cancel_cfm *cfm);
+
+int rwnx_send_apm_start_cac_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+				struct cfg80211_chan_def *chandef,
+				struct apm_start_cac_cfm *cfm);
+
+int rwnx_send_apm_stop_cac_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif);
+
+int rwnx_send_tdls_peer_traffic_ind_req(struct rwnx_hw *rwnx_hw,
+					struct rwnx_vif *rwnx_vif);
+
+int rwnx_send_config_monitor_req(struct rwnx_hw *rwnx_hw,
+				 struct cfg80211_chan_def *chandef,
+				 struct me_config_monitor_cfm *cfm);
+
+int rwnx_send_mesh_start_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+			     const struct mesh_config *conf, const struct mesh_setup *setup,
+			     struct mesh_start_cfm *cfm);
+
+int rwnx_send_mesh_stop_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+			    struct mesh_stop_cfm *cfm);
+
+int rwnx_send_mesh_update_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+			      u32 mask, const struct mesh_config *p_mconf,
+			      struct mesh_update_cfm *cfm);
+
+int rwnx_send_mesh_peer_info_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+				 u8 sta_idx, struct mesh_peer_info_cfm *cfm);
+
+void rwnx_send_mesh_peer_update_ntf(struct rwnx_hw *rwnx_hw,
+				    struct rwnx_vif *vif, u8 sta_idx,
+				    u8 mlink_state);
+
+void rwnx_send_mesh_path_create_req(struct rwnx_hw *rwnx_hw,
+				    struct rwnx_vif *vif, u8 *tgt_addr);
+
+int rwnx_send_mesh_path_update_req(struct rwnx_hw *rwnx_hw,
+				   struct rwnx_vif *vif, const u8 *tgt_addr,
+				   const u8 *p_nhop_addr,
+				   struct mesh_path_update_cfm *cfm);
+
+void rwnx_send_mesh_proxy_add_req(struct rwnx_hw *rwnx_hw, struct rwnx_vif *vif,
+				  u8 *ext_addr);
+
+#endif /* CONFIG_RWNX_FULLMAC */
+
+#ifdef CONFIG_RWNX_BFMER
+#ifdef CONFIG_RWNX_FULLMAC
+void rwnx_send_bfmer_enable(struct rwnx_hw *rwnx_hw, struct rwnx_sta *rwnx_sta,
+			    const struct ieee80211_vht_cap *vht_cap);
+
+#endif /* CONFIG_RWNX_FULLMAC */
+#ifdef CONFIG_RWNX_MUMIMO_TX
+int rwnx_send_mu_group_update_req(struct rwnx_hw *rwnx_hw,
+				  struct rwnx_sta *rwnx_sta);
+#endif /* CONFIG_RWNX_MUMIMO_TX */
+#endif /* CONFIG_RWNX_BFMER */
+
+/* Debug messages */
+int rwnx_send_dbg_trigger_req(struct rwnx_hw *rwnx_hw, char *msg);
+
+int rwnx_send_dbg_mem_read_req(struct rwnx_hw *rwnx_hw, u32 mem_addr,
+			       struct dbg_mem_read_cfm *cfm);
+
+int rwnx_send_dbg_mem_write_req(struct rwnx_hw *rwnx_hw, u32 mem_addr,
+				u32 mem_data);
+
+int rwnx_send_dbg_mem_mask_write_req(struct rwnx_hw *rwnx_hw, u32 mem_addr,
+				     u32 mem_mask, u32 mem_data);
+
+int rwnx_send_dbg_set_mod_filter_req(struct rwnx_hw *rwnx_hw, u32 filter);
+
+int rwnx_send_rftest_req(struct rwnx_hw *rwnx_hw, u32_l cmd, u32_l argc,
+			 u8_l *argv, struct dbg_rftest_cmd_cfm *cfm);
+
+int rwnx_send_dbg_set_sev_filter_req(struct rwnx_hw *rwnx_hw, u32 filter);
+
+int rwnx_send_dbg_get_sys_stat_req(struct rwnx_hw *rwnx_hw,
+				   struct dbg_get_sys_stat_cfm *cfm);
+
+int rwnx_send_dbg_mem_block_write_req(struct rwnx_hw *rwnx_hw, u32 mem_addr,
+				      u32 mem_size, u32 *mem_data);
+
+int rwnx_send_dbg_start_app_req(struct rwnx_hw *rwnx_hw, u32 boot_addr,
+				u32 boot_type);
+
+int rwnx_send_cfg_rssi_req(struct rwnx_hw *rwnx_hw, u8 vif_index,
+			   int rssi_thold, u32 rssi_hyst);
+
+int rwnx_send_disable_agg_req(struct rwnx_hw *rwnx_hw, u8_l agg_disable,
+			      u8_l agg_disable_rx, u8_l sta_idx);
+
+int rwnx_send_coex_req(struct rwnx_hw *rwnx_hw, u8_l disable_coexnull,
+		       u8_l enable_nullcts);
+
+int rwnx_send_get_sta_info_req(struct rwnx_hw *rwnx_hw, u8_l sta_idx,
+			       struct mm_get_sta_info_cfm *cfm);
+
+int rwnx_send_set_stack_start_req(struct rwnx_hw *rwnx_hw, u8_l on,
+				  u8_l efuse_valid, u8_l set_vendor_info,
+				  u8_l fwtrace_redir_en,
+				  struct mm_set_stack_start_cfm *cfm);
+
+int rwnx_send_get_temp_req(struct rwnx_hw *rwnx_hw,
+			   struct mm_set_vendor_swconfig_cfm *cfm);
+
+int rwnx_send_get_temp_hwconfig_req(struct rwnx_hw *rwnx_hw,
+				    struct mm_set_vendor_hwconfig_cfm *cfm);
+
+int rwnx_send_set_temp_comp_req(struct rwnx_hw *rwnx_hw,
+				struct mm_set_vendor_swconfig_cfm *cfm);
+
+int rwnx_send_set_pkt_filter_req(struct rwnx_hw *rwnx_hw, u16_l *param);
+
+int rwnx_send_del_pkt_filter_req(struct rwnx_hw *rwnx_hw, u16_l code);
+
+int rwnx_send_txop_req(struct rwnx_hw *rwnx_hw, uint16_t *txop,
+		       u8_l long_nav_en, u8_l cfe_en);
+
+int rwnx_send_vendor_hwconfig_req(struct rwnx_hw *rwnx_hw, u32_l hwconfig_id,
+				  s32_l *param, s32_l *param_out);
+
+int rwnx_send_vendor_swconfig_req(struct rwnx_hw *rwnx_hw, u32_l swconfig_id,
+				  s32_l *param_in, s32_l *param_out);
+
+int rwnx_send_mask_set_ext_flags_req(struct rwnx_hw *rwnx_hw,
+				     u32_l flags_mask, u32_l flags_val,
+				     struct mm_set_vendor_swconfig_cfm *cfm);
+
+int rwnx_send_get_fw_version_req(struct rwnx_hw *rwnx_hw,
+				 struct mm_get_fw_version_cfm *cfm);
+
+int rwnx_send_txpwr_idx_req(struct rwnx_hw *rwnx_hw);
+
+int rwnx_send_txpwr_ofst_req(struct rwnx_hw *rwnx_hw);
+
+int rwnx_send_txpwr_ofst2x_req(struct rwnx_hw *rwnx_hw);
+
+int rwnx_send_txpwr_lvl_req(struct rwnx_hw *rwnx_hw);
+
+int rwnx_send_txpwr_lvl_v3_req(struct rwnx_hw *rwnx_hw, int index_reg);
+
+int rwnx_send_txpwr_lvl_adj_req(struct rwnx_hw *rwnx_hw);
+
+struct rwnx_cmd *rwnx_cmd_malloc(void);
+
+void rwnx_cmd_free(struct rwnx_cmd *cmd);
+
+int rwnx_init_cmd_array(void);
+
+void rwnx_free_cmd_array(void);
+
+#ifdef CONFIG_SDIO_BT
+int rwnx_sdio_bt_send_req(struct rwnx_hw *rwnx_hw, uint32_t len,
+			  struct sk_buff *skb);
+#endif
+
+#endif /* _RWNX_MSG_TX_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_platform.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_platform.c
new file mode 100644
index 0000000000000..88a574e29a269
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_platform.c
@@ -0,0 +1,2946 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_platform.c
+ *
+ * @brief Platform profile
+ *
+ ******************************************************************************
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sprintf.h>
+#include <linux/string.h>
+#include <linux/vmalloc.h>
+
+#include "hal_desc.h"
+#include "reg_access.h"
+#include "rwnx_main.h"
+#include "rwnx_platform.h"
+#include "ipc_host.h"
+#include "rwnx_msg_tx.h"
+
+#ifdef AICWF_SDIO_SUPPORT
+#include "aicwf_sdio.h"
+#endif
+
+#include "aicwf_compat_8800d80.h"
+#include "aicwf_compat_8800dc.h"
+#include "aicwf_chip_ops.h"
+#include "md5.h"
+
+// Parser state
+#define INIT      0
+#define CMD       1
+#define PRINT     2
+#define GET_VALUE 3
+
+struct reg_table {
+	char ccode[3];
+	enum regions_code region;
+};
+
+/* If the region conflicts with the kernel, the actual authentication standard prevails */
+struct reg_table reg_tables[] = {
+	{.ccode = "CN", .region = REGIONS_SRRC},
+	{.ccode = "US", .region = REGIONS_FCC},
+	{.ccode = "DE", .region = REGIONS_ETSI},
+	{.ccode = "00", .region = REGIONS_DEFAULT},
+	{.ccode = "WW", .region = REGIONS_DEFAULT},
+	{.ccode = "XX", .region = REGIONS_DEFAULT},
+	{.ccode = "JP", .region = REGIONS_JP},
+	{.ccode = "AD", .region = REGIONS_ETSI},
+	{.ccode = "AE", .region = REGIONS_ETSI},
+	{.ccode = "AF", .region = REGIONS_ETSI},
+	{.ccode = "AI", .region = REGIONS_ETSI},
+	{.ccode = "AL", .region = REGIONS_ETSI},
+	{.ccode = "AM", .region = REGIONS_ETSI},
+	{.ccode = "AN", .region = REGIONS_ETSI},
+	{.ccode = "AR", .region = REGIONS_FCC},
+	{.ccode = "AS", .region = REGIONS_FCC},
+	{.ccode = "AT", .region = REGIONS_ETSI},
+	{.ccode = "AU", .region = REGIONS_ETSI},
+	{.ccode = "AW", .region = REGIONS_ETSI},
+	{.ccode = "AZ", .region = REGIONS_ETSI},
+	{.ccode = "BA", .region = REGIONS_ETSI},
+	{.ccode = "BB", .region = REGIONS_FCC},
+	{.ccode = "BD", .region = REGIONS_ETSI},
+	{.ccode = "BE", .region = REGIONS_ETSI},
+	{.ccode = "BF", .region = REGIONS_FCC},
+	{.ccode = "BG", .region = REGIONS_ETSI},
+	{.ccode = "BH", .region = REGIONS_ETSI},
+	{.ccode = "BL", .region = REGIONS_ETSI},
+	{.ccode = "BM", .region = REGIONS_FCC},
+	{.ccode = "BN", .region = REGIONS_JP},
+	{.ccode = "BO", .region = REGIONS_JP},
+	{.ccode = "BR", .region = REGIONS_FCC},
+	{.ccode = "BS", .region = REGIONS_FCC},
+	{.ccode = "BT", .region = REGIONS_ETSI},
+	{.ccode = "BW", .region = REGIONS_ETSI},
+	{.ccode = "BY", .region = REGIONS_ETSI},
+	{.ccode = "BZ", .region = REGIONS_JP},
+	{.ccode = "CA", .region = REGIONS_FCC},
+	{.ccode = "CF", .region = REGIONS_FCC},
+	{.ccode = "CH", .region = REGIONS_ETSI},
+	{.ccode = "CI", .region = REGIONS_FCC},
+	{.ccode = "CL", .region = REGIONS_ETSI},
+	{.ccode = "CO", .region = REGIONS_FCC},
+	{.ccode = "CR", .region = REGIONS_FCC},
+	{.ccode = "CU", .region = REGIONS_FCC},
+	{.ccode = "CX", .region = REGIONS_FCC},
+	{.ccode = "CY", .region = REGIONS_ETSI},
+	{.ccode = "CZ", .region = REGIONS_ETSI},
+	{.ccode = "DK", .region = REGIONS_ETSI},
+	{.ccode = "DM", .region = REGIONS_FCC},
+	{.ccode = "DO", .region = REGIONS_FCC},
+	{.ccode = "DZ", .region = REGIONS_ETSI},
+	{.ccode = "EC", .region = REGIONS_FCC},
+	{.ccode = "EE", .region = REGIONS_ETSI},
+	{.ccode = "EG", .region = REGIONS_ETSI},
+	{.ccode = "ES", .region = REGIONS_ETSI},
+	{.ccode = "ET", .region = REGIONS_ETSI},
+	{.ccode = "FI", .region = REGIONS_ETSI},
+	{.ccode = "FK", .region = REGIONS_ETSI},
+	{.ccode = "FM", .region = REGIONS_FCC},
+	{.ccode = "FO", .region = REGIONS_ETSI},
+	{.ccode = "FR", .region = REGIONS_ETSI},
+	{.ccode = "GB", .region = REGIONS_ETSI},
+	{.ccode = "GD", .region = REGIONS_FCC},
+	{.ccode = "GE", .region = REGIONS_ETSI},
+	{.ccode = "GF", .region = REGIONS_ETSI},
+	{.ccode = "GH", .region = REGIONS_ETSI},
+	{.ccode = "GI", .region = REGIONS_ETSI},
+	{.ccode = "GL", .region = REGIONS_ETSI},
+	{.ccode = "GP", .region = REGIONS_ETSI},
+	{.ccode = "GR", .region = REGIONS_ETSI},
+	{.ccode = "GT", .region = REGIONS_FCC},
+	{.ccode = "GU", .region = REGIONS_FCC},
+	{.ccode = "GY", .region = REGIONS_DEFAULT},
+	{.ccode = "HK", .region = REGIONS_ETSI},
+	{.ccode = "HN", .region = REGIONS_FCC},
+	{.ccode = "HR", .region = REGIONS_ETSI},
+	{.ccode = "HT", .region = REGIONS_FCC},
+	{.ccode = "HU", .region = REGIONS_ETSI},
+	{.ccode = "ID", .region = REGIONS_ETSI},
+	{.ccode = "IE", .region = REGIONS_ETSI},
+	{.ccode = "IL", .region = REGIONS_ETSI},
+	{.ccode = "IN", .region = REGIONS_ETSI},
+	{.ccode = "IQ", .region = REGIONS_ETSI},
+	{.ccode = "IR", .region = REGIONS_JP},
+	{.ccode = "IS", .region = REGIONS_ETSI},
+	{.ccode = "IT", .region = REGIONS_ETSI},
+	{.ccode = "JM", .region = REGIONS_FCC},
+	{.ccode = "JO", .region = REGIONS_ETSI},
+	{.ccode = "KE", .region = REGIONS_ETSI},
+	{.ccode = "KG", .region = REGIONS_ETSI},
+	{.ccode = "KH", .region = REGIONS_ETSI},
+	{.ccode = "KN", .region = REGIONS_ETSI},
+	{.ccode = "KP", .region = REGIONS_JP},
+	{.ccode = "KR", .region = REGIONS_ETSI},
+	{.ccode = "KW", .region = REGIONS_ETSI},
+	{.ccode = "KY", .region = REGIONS_FCC},
+	{.ccode = "KZ", .region = REGIONS_ETSI},
+	{.ccode = "LB", .region = REGIONS_ETSI},
+	{.ccode = "LC", .region = REGIONS_ETSI},
+	{.ccode = "LI", .region = REGIONS_ETSI},
+	{.ccode = "LK", .region = REGIONS_FCC},
+	{.ccode = "LS", .region = REGIONS_ETSI},
+	{.ccode = "LT", .region = REGIONS_ETSI},
+	{.ccode = "LU", .region = REGIONS_ETSI},
+	{.ccode = "LV", .region = REGIONS_ETSI},
+	{.ccode = "LY", .region = REGIONS_ETSI},
+	{.ccode = "MA", .region = REGIONS_ETSI},
+	{.ccode = "MC", .region = REGIONS_ETSI},
+	{.ccode = "MD", .region = REGIONS_ETSI},
+	{.ccode = "ME", .region = REGIONS_ETSI},
+	{.ccode = "MF", .region = REGIONS_ETSI},
+	{.ccode = "MH", .region = REGIONS_FCC},
+	{.ccode = "MK", .region = REGIONS_ETSI},
+	{.ccode = "MN", .region = REGIONS_ETSI},
+	{.ccode = "MO", .region = REGIONS_ETSI},
+	{.ccode = "MP", .region = REGIONS_FCC},
+	{.ccode = "MQ", .region = REGIONS_ETSI},
+	{.ccode = "MR", .region = REGIONS_ETSI},
+	{.ccode = "MT", .region = REGIONS_ETSI},
+	{.ccode = "MU", .region = REGIONS_FCC},
+	{.ccode = "MV", .region = REGIONS_ETSI},
+	{.ccode = "MW", .region = REGIONS_ETSI},
+	{.ccode = "MX", .region = REGIONS_FCC},
+	{.ccode = "MY", .region = REGIONS_ETSI},
+	{.ccode = "NA", .region = REGIONS_ETSI},
+	{.ccode = "NG", .region = REGIONS_ETSI},
+	{.ccode = "NI", .region = REGIONS_FCC},
+	{.ccode = "NL", .region = REGIONS_ETSI},
+	{.ccode = "NO", .region = REGIONS_ETSI},
+	{.ccode = "NP", .region = REGIONS_JP},
+	{.ccode = "NZ", .region = REGIONS_ETSI},
+	{.ccode = "OM", .region = REGIONS_ETSI},
+	{.ccode = "PA", .region = REGIONS_FCC},
+	{.ccode = "PE", .region = REGIONS_ETSI},
+	{.ccode = "PF", .region = REGIONS_ETSI},
+	{.ccode = "PG", .region = REGIONS_FCC},
+	{.ccode = "PH", .region = REGIONS_ETSI},
+	{.ccode = "PK", .region = REGIONS_ETSI},
+	{.ccode = "PL", .region = REGIONS_ETSI},
+	{.ccode = "PM", .region = REGIONS_ETSI},
+	{.ccode = "PR", .region = REGIONS_FCC},
+	{.ccode = "PT", .region = REGIONS_ETSI},
+	{.ccode = "PW", .region = REGIONS_FCC},
+	{.ccode = "PY", .region = REGIONS_FCC},
+	{.ccode = "QA", .region = REGIONS_ETSI},
+	{.ccode = "RE", .region = REGIONS_ETSI},
+	{.ccode = "RO", .region = REGIONS_ETSI},
+	{.ccode = "RS", .region = REGIONS_ETSI},
+	{.ccode = "RU", .region = REGIONS_ETSI},
+	{.ccode = "RW", .region = REGIONS_FCC},
+	{.ccode = "SA", .region = REGIONS_ETSI},
+	{.ccode = "SE", .region = REGIONS_ETSI},
+	{.ccode = "SG", .region = REGIONS_ETSI},
+	{.ccode = "SI", .region = REGIONS_ETSI},
+	{.ccode = "SK", .region = REGIONS_ETSI},
+	{.ccode = "SM", .region = REGIONS_ETSI},
+	{.ccode = "SN", .region = REGIONS_FCC},
+	{.ccode = "SR", .region = REGIONS_ETSI},
+	{.ccode = "SV", .region = REGIONS_FCC},
+	{.ccode = "SY", .region = REGIONS_DEFAULT},
+	{.ccode = "TC", .region = REGIONS_FCC},
+	{.ccode = "TD", .region = REGIONS_ETSI},
+	{.ccode = "TG", .region = REGIONS_ETSI},
+	{.ccode = "TH", .region = REGIONS_ETSI},
+	{.ccode = "TJ", .region = REGIONS_ETSI},
+	{.ccode = "TM", .region = REGIONS_ETSI},
+	{.ccode = "TN", .region = REGIONS_ETSI},
+	{.ccode = "TR", .region = REGIONS_ETSI},
+	{.ccode = "TT", .region = REGIONS_FCC},
+	{.ccode = "TW", .region = REGIONS_FCC},
+	{.ccode = "TZ", .region = REGIONS_DEFAULT},
+	{.ccode = "UA", .region = REGIONS_ETSI},
+	{.ccode = "UG", .region = REGIONS_FCC},
+	{.ccode = "UY", .region = REGIONS_ETSI},
+	{.ccode = "UZ", .region = REGIONS_ETSI},
+	{.ccode = "VC", .region = REGIONS_ETSI},
+	{.ccode = "VE", .region = REGIONS_FCC},
+	{.ccode = "VI", .region = REGIONS_FCC},
+	{.ccode = "VN", .region = REGIONS_JP},
+	{.ccode = "VU", .region = REGIONS_FCC},
+	{.ccode = "WF", .region = REGIONS_ETSI},
+	{.ccode = "WS", .region = REGIONS_ETSI},
+	{.ccode = "YE", .region = REGIONS_DEFAULT},
+	{.ccode = "YT", .region = REGIONS_ETSI},
+	{.ccode = "ZA", .region = REGIONS_ETSI},
+	{.ccode = "ZM", .region = REGIONS_ETSI},
+	{.ccode = "ZW", .region = REGIONS_ETSI},
+	{.ccode = "XK", .region = REGIONS_ETSI},
+};
+
+struct rwnx_plat *g_rwnx_plat;
+
+struct userconfig_info_t {
+	struct txpwr_lvl_conf txpwr_lvl;
+	struct txpwr_lvl_conf_v2 txpwr_lvl_v2;
+	struct txpwr_lvl_conf_v3 txpwr_lvl_v3;
+	struct txpwr_lvl_conf_v3 txpwr_lvl_v3_gp[REGION_NUM_MAX];
+	struct txpwr_lvl_adj_conf txpwr_lvl_adj;
+	struct txpwr_loss_conf txpwr_loss;
+	struct txpwr_ofst_conf txpwr_ofst;
+	struct txpwr_ofst2x_conf txpwr_ofst2x;
+	struct xtal_cap_conf xtal_cap;
+};
+
+struct userconfig_info_t userconfig_info = {
+	.txpwr_lvl = { .enable = 1,
+				  .dsss = 9,
+				  .ofdmlowrate_2g4 = 8,
+				  .ofdm64qam_2g4 = 8,
+				  .ofdm256qam_2g4 = 8,
+				  .ofdm1024qam_2g4 = 8,
+				  .ofdmlowrate_5g = 11,
+				  .ofdm64qam_5g = 10,
+				  .ofdm256qam_5g = 9,
+				  .ofdm1024qam_5g = 9},
+	.txpwr_lvl_v2 =	{ .enable = 1,
+			.pwrlvl_11b_11ag_2g4 =
+				// 1M,   2M,   5M5,  11M,  6M,   9M,   12M,  18M,  24M,  36M,
+				// 48M,  54M
+			{20, 20, 20, 20, 20, 20, 20, 20, 18, 18, 16, 16},
+			.pwrlvl_11n_11ac_2g4 =
+				// MCS0, MCS1, MCS2, MCS3, MCS4, MCS5, MCS6, MCS7, MCS8, MCS9
+			{20, 20, 20, 20, 18, 18, 16, 16, 16, 16},
+			.pwrlvl_11ax_2g4 =
+				// MCS0, MCS1, MCS2, MCS3, MCS4, MCS5, MCS6, MCS7, MCS8, MCS9,
+				// MCS10,MCS11
+			{20, 20, 20, 20, 18, 18, 16, 16, 16, 16, 15, 15},
+		},
+	.txpwr_lvl_v3 =	{ .enable = 1,
+			.pwrlvl_11b_11ag_2g4 =
+				// 1M,   2M,   5M5,  11M,  6M,   9M,   12M,  18M,  24M,  36M,
+				// 48M,  54M
+			{20, 20, 20, 20, 20, 20, 20, 20, 18, 18, 16, 16},
+			.pwrlvl_11n_11ac_2g4 =
+				// MCS0, MCS1, MCS2, MCS3, MCS4, MCS5, MCS6, MCS7, MCS8, MCS9
+			{20, 20, 20, 20, 18, 18, 16, 16, 16, 16},
+			.pwrlvl_11ax_2g4 =
+				// MCS0, MCS1, MCS2, MCS3, MCS4, MCS5, MCS6, MCS7, MCS8, MCS9,
+				// MCS10,MCS11
+			{20, 20, 20, 20, 18, 18, 16, 16, 16, 16, 15, 15},
+			.pwrlvl_11a_5g =
+				// NA,   NA,   NA,   NA,   6M,   9M,   12M,  18M,  24M,  36M,
+				// 48M,  54M
+			{0x80, 0x80, 0x80, 0x80, 20, 20, 20, 20, 18, 18, 16, 16},
+			.pwrlvl_11n_11ac_5g =
+				// MCS0, MCS1, MCS2, MCS3, MCS4, MCS5, MCS6, MCS7, MCS8, MCS9
+			{20, 20, 20, 20, 18, 18, 16, 16, 16, 15},
+			.pwrlvl_11ax_5g =
+				// MCS0, MCS1, MCS2, MCS3, MCS4, MCS5, MCS6, MCS7, MCS8, MCS9,
+				// MCS10,MCS11
+			{20, 20, 20, 20, 18, 18, 16, 16, 16, 15, 14, 14},
+		},
+	.txpwr_lvl_v3_gp[0] = { .enable = 1,
+			.pwrlvl_11b_11ag_2g4 =
+				// 1M,   2M,   5M5,  11M,  6M,   9M,   12M,  18M,  24M,  36M,
+				// 48M,  54M
+			{20, 20, 20, 20, 20, 20, 20, 20, 18, 18, 16, 16},
+			.pwrlvl_11n_11ac_2g4 =
+				// MCS0, MCS1, MCS2, MCS3, MCS4, MCS5, MCS6, MCS7, MCS8, MCS9
+			{20, 20, 20, 20, 18, 18, 16, 16, 16, 16},
+			.pwrlvl_11ax_2g4 =
+				// MCS0, MCS1, MCS2, MCS3, MCS4, MCS5, MCS6, MCS7, MCS8, MCS9,
+				// MCS10,MCS11
+			{20, 20, 20, 20, 18, 18, 16, 16, 16, 16, 15, 15},
+			.pwrlvl_11a_5g =
+				// NA,   NA,   NA,   NA,   6M,   9M,   12M,  18M,  24M,  36M,
+				// 48M,  54M
+			{0x80, 0x80, 0x80, 0x80, 20, 20, 20, 20, 18, 18, 16, 16},
+			.pwrlvl_11n_11ac_5g =
+				// MCS0, MCS1, MCS2, MCS3, MCS4, MCS5, MCS6, MCS7, MCS8, MCS9
+			{20, 20, 20, 20, 18, 18, 16, 16, 16, 15},
+			.pwrlvl_11ax_5g =
+				// MCS0, MCS1, MCS2, MCS3, MCS4, MCS5, MCS6, MCS7, MCS8, MCS9,
+				// MCS10,MCS11
+			{20, 20, 20, 20, 18, 18, 16, 16, 16, 15, 14, 14},
+		},
+	.txpwr_lvl_v3_gp[1] = { .enable = 1,
+			.pwrlvl_11b_11ag_2g4 = {20, 20, 20, 20, 20, 20, 20, 20, 18, 18, 16, 16},
+			.pwrlvl_11n_11ac_2g4 = {20, 20, 20, 20, 18, 18, 16, 16, 16, 16},
+			.pwrlvl_11ax_2g4 = {20, 20, 20, 20, 18, 18, 16, 16, 16, 16, 15, 15},
+			.pwrlvl_11a_5g = {0x80, 0x80, 0x80, 0x80, 20, 20, 20, 20, 18, 18, 16, 16},
+			.pwrlvl_11n_11ac_5g = {20, 20, 20, 20, 18, 18, 16, 16, 16, 15},
+			.pwrlvl_11ax_5g = {20, 20, 20, 20, 18, 18, 16, 16, 16, 15, 14, 14},
+		},
+	.txpwr_lvl_v3_gp[2] = { .enable = 1,
+			.pwrlvl_11b_11ag_2g4 = {20, 20, 20, 20, 20, 20, 20, 20, 18, 18, 16, 16},
+			.pwrlvl_11n_11ac_2g4 = {20, 20, 20, 20, 18, 18, 16, 16, 16, 16},
+			.pwrlvl_11ax_2g4 = {20, 20, 20, 20, 18, 18, 16, 16, 16, 16, 15, 15},
+			.pwrlvl_11a_5g = {0x80, 0x80, 0x80, 0x80, 20, 20, 20, 20, 18, 18, 16, 16},
+			.pwrlvl_11n_11ac_5g = {20, 20, 20, 20, 18, 18, 16, 16, 16, 15},
+			.pwrlvl_11ax_5g = {20, 20, 20, 20, 18, 18, 16, 16, 16, 15, 14, 14},
+		},
+	.txpwr_lvl_v3_gp[3] = { .enable = 1,
+			.pwrlvl_11b_11ag_2g4 = {20, 20, 20, 20, 20, 20, 20, 20, 18, 18, 16, 16},
+			.pwrlvl_11n_11ac_2g4 = {20, 20, 20, 20, 18, 18, 16, 16, 16, 16},
+			.pwrlvl_11ax_2g4 = {20, 20, 20, 20, 18, 18, 16, 16, 16, 16, 15, 15},
+			.pwrlvl_11a_5g = {0x80, 0x80, 0x80, 0x80, 20, 20, 20, 20, 18, 18, 16, 16},
+			.pwrlvl_11n_11ac_5g = {20, 20, 20, 20, 18, 18, 16, 16, 16, 15},
+			.pwrlvl_11ax_5g = {20, 20, 20, 20, 18, 18, 16, 16, 16, 15, 14, 14},
+		},
+	.txpwr_lvl_v3_gp[4] = { .enable = 1,
+			.pwrlvl_11b_11ag_2g4 = {20, 20, 20, 20, 20, 20, 20, 20, 18, 18, 16, 16},
+			.pwrlvl_11n_11ac_2g4 = {20, 20, 20, 20, 18, 18, 16, 16, 16, 16},
+			.pwrlvl_11ax_2g4 = {20, 20, 20, 20, 18, 18, 16, 16, 16, 16, 15, 15},
+			.pwrlvl_11a_5g = {0x80, 0x80, 0x80, 0x80, 20, 20, 20, 20, 18, 18, 16, 16},
+			.pwrlvl_11n_11ac_5g = {20, 20, 20, 20, 18, 18, 16, 16, 16, 15},
+			.pwrlvl_11ax_5g = {20, 20, 20, 20, 18, 18, 16, 16, 16, 15, 14, 14},
+		},
+	.txpwr_loss = { .loss_enable_2g4 = 1,
+			.loss_value_2g4 = 0,
+			.loss_enable_5g = 1,
+			.loss_value_5g = 0,
+		},
+	.txpwr_ofst = { .enable = 1,
+			.chan_1_4 = 0,
+			.chan_5_9 = 0,
+			.chan_10_13 = 0,
+			.chan_36_64 = 0,
+			.chan_100_120 = 0,
+			.chan_122_140 = 0,
+			.chan_142_165 = 0,
+		},
+	.txpwr_ofst2x = { .enable = 0,
+			.pwrofst2x_tbl_2g4 = {
+					// ch1-4, ch5-9, ch10-13
+					{0, 0, 0}, // 11b
+					{0, 0, 0}, // ofdm_highrate
+					{0, 0, 0}, // ofdm_lowrate
+				},
+			.pwrofst2x_tbl_5g = {
+					// ch42,  ch58, ch106,ch122,ch138,ch155
+					{0, 0, 0, 0, 0, 0}, // ofdm_lowrate
+					{0, 0, 0, 0, 0, 0}, // ofdm_highrate
+					{0, 0, 0, 0, 0, 0}, // ofdm_midrate
+				},
+		},
+	.xtal_cap = { .enable = 0,
+			.xtal_cap = 24,
+			.xtal_cap_fine = 31,
+		},
+};
+
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+#define POWER_LIMIT_INVALID_VAL POWER_LEVEL_INVALID_VAL
+
+#define POWER_LIMIT_CC_MATCHED_BIT (0x1U << 0)
+
+struct txpwr_lmt_info_t {
+	u8_l ch_cnt_2g4;
+	u8_l ch_cnt_5g;
+	u8_l ch_num_2g4[MAC_DOMAINCHANNEL_24G_MAX];
+	u8_l ch_num_5g[MAC_DOMAINCHANNEL_5G_MAX];
+	s8_l max_pwr_2g4[MAC_DOMAINCHANNEL_24G_MAX];
+	s8_l max_pwr_5g[MAC_DOMAINCHANNEL_5G_MAX];
+};
+
+struct powerlimit_info_t {
+	u32_l flags;
+	struct txpwr_lmt_info_t txpwr_lmt;
+};
+
+struct powerlimit_info_t powerlimit_info = {
+	0,
+};
+#endif
+
+int aicwf_request_firmware(const struct firmware **fw, const char *name,
+			   struct device *dev)
+{
+	char *fw_name;
+	int ret;
+
+	if (!name || !name[0])
+		return -EINVAL;
+
+	fw_name = kasprintf(GFP_KERNEL, AIC8800_FW_DIR "%s", name);
+	if (!fw_name)
+		return -ENOMEM;
+
+	ret = request_firmware(fw, fw_name, dev);
+	kfree(fw_name);
+
+	return ret;
+}
+
+#ifdef CONFIG_RWNX_TL4
+/**
+ * rwnx_plat_tl4_fw_upload() - Load the requested FW into embedded side.
+ *
+ * @rwnx_plat: pointer to platform structure
+ * @fw_addr: Virtual address where the fw must be loaded
+ * @filename: Name of the fw.
+ *
+ * Load a fw, stored as a hex file, into the specified address
+ */
+static int rwnx_plat_tl4_fw_upload(struct rwnx_plat *rwnx_plat, u8 *fw_addr,
+				   char *filename)
+{
+	struct device *dev = rwnx_platform_get_dev(rwnx_plat);
+	const struct firmware *fw;
+	int err = 0;
+	u32 *dst;
+	u8 const *file_data;
+	char typ0, typ1;
+	u32 addr0, addr1;
+	u32 dat0, dat1;
+	int remain;
+
+	err = aicwf_request_firmware(&fw, filename, dev);
+	if (err)
+		return err;
+
+	file_data = fw->data;
+	remain = fw->size;
+
+	/* Copy the file on the Embedded side */
+	dev_dbg(dev, "\n### Now copy %s firmware, @ = %p\n", filename, fw_addr);
+
+	/* Walk through all the lines of the configuration file */
+	while (remain >= 16) {
+		u32 data, offset;
+
+		if (sscanf(file_data, "%c:%08X %04X", &typ0, &addr0, &dat0) != 3)
+			break;
+		if ((addr0 & 0x01) != 0) {
+			addr0 = addr0 - 1;
+			dat0 = 0;
+		} else {
+			file_data += 16;
+			remain -= 16;
+		}
+		if (remain < 16 ||
+		    (sscanf(file_data, "%c:%08X %04X", &typ1, &addr1, &dat1) != 3) ||
+		    typ1 != typ0 || addr1 != (addr0 + 1)) {
+			typ1 = typ0;
+			addr1 = addr0 + 1;
+			dat1 = 0;
+		} else {
+			file_data += 16;
+			remain -= 16;
+		}
+
+		if (typ0 == 'C') {
+			offset = 0x00200000;
+			if ((addr1 % 4) == 3)
+				offset += 2 * (addr1 - 3);
+			else
+				offset += 2 * (addr1 + 1);
+
+			data = dat1 | (dat0 << 16);
+		} else {
+			offset = 2 * (addr1 - 1);
+			data = dat0 | (dat1 << 16);
+		}
+		dst = (u32 *)(fw_addr + offset);
+		*dst = data;
+	}
+
+	release_firmware(fw);
+
+	return err;
+}
+#endif
+
+#define MD5PINRT                                                               \
+	"file "                                                                    \
+	"md5:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\r\n"
+
+static int rwnx_load_firmware(u32 **fw_buf, const char *name,
+			      struct device *device)
+{
+	const struct firmware *fw = NULL;
+	u32 *dst = NULL;
+	void *buffer = NULL;
+	struct MD5_CTX md5;
+	unsigned char decrypt[16];
+	int size = 0;
+	int ret = 0;
+
+	AICWFDBG(LOGINFO, "AICWF %s: request firmware = %s\n", __func__, name);
+
+	ret = aicwf_request_firmware(&fw, name, device);
+
+	if (ret < 0) {
+		AICWFDBG(LOGDEBUG, "AICWF Load %s fail\n", name);
+		return ret;
+	}
+
+	size = fw->size;
+	dst = (u32 *)fw->data;
+
+	if (size <= 0) {
+		AICWFDBG(LOGDEBUG, "AICWF wrong size of firmware file\n");
+		release_firmware(fw);
+		return -1;
+	}
+
+	buffer = vmalloc(size);
+	if (!buffer) {
+		release_firmware(fw);
+		return -ENOMEM;
+	}
+	memset(buffer, 0, size);
+	memcpy(buffer, dst, size);
+
+	*fw_buf = buffer;
+
+	md5_init(&md5);
+	md5_update(&md5, (unsigned char *)buffer, size);
+	md5_final(&md5, decrypt);
+	AICWFDBG(LOGDEBUG, "AICWF " MD5PINRT, decrypt[0], decrypt[1], decrypt[2], decrypt[3],
+		 decrypt[4], decrypt[5], decrypt[6], decrypt[7], decrypt[8], decrypt[9],
+		 decrypt[10], decrypt[11], decrypt[12], decrypt[13], decrypt[14], decrypt[15]);
+
+	release_firmware(fw);
+
+	return size;
+}
+
+/* buffer is allocated by kzalloc */
+int rwnx_request_firmware_common(struct rwnx_hw *rwnx_hw, u32 **buffer,
+				 const char *filename)
+{
+	int size;
+
+	AICWFDBG(LOGDEBUG, "### Load file %s\n", filename);
+
+	size = rwnx_load_firmware(buffer, filename, rwnx_hw->dev);
+
+	return size;
+}
+
+static void rwnx_restore_firmware(u32 **fw_buf)
+{
+	vfree(*fw_buf);
+	*fw_buf = NULL;
+}
+
+void rwnx_release_firmware_common(u32 **buffer)
+{
+	rwnx_restore_firmware(buffer);
+}
+
+/**
+ * rwnx_plat_bin_fw_upload_2() - Load the requested binary FW into embedded
+ * side.
+ *
+ * @rwnx_hw: Main driver data
+ * @fw_addr: Address where the fw must be loaded
+ * @filename: Name of the fw.
+ *
+ * Load a fw, stored as a binary file, into the specified address
+ */
+int rwnx_plat_bin_fw_upload_2(struct rwnx_hw *rwnx_hw, u32 fw_addr,
+			      char *filename)
+{
+	int err = 0;
+	unsigned int i = 0, size;
+	//    u32 *src;
+	u32 *dst = NULL;
+
+	/* Copy the file on the Embedded side */
+	AICWFDBG(LOGINFO, "### Upload %s firmware, @ = %x\n", filename, fw_addr);
+
+	size = rwnx_request_firmware_common(rwnx_hw, &dst, filename);
+	if (!dst) {
+		AICWFDBG(LOGERROR, "No such file or directory\n");
+		return -1;
+	}
+	if (size <= 0) {
+		AICWFDBG(LOGERROR, "wrong size of firmware file\n");
+		dst = NULL;
+		err = -1;
+	}
+
+	AICWFDBG(LOGINFO, "size=%d, dst[0]=%x\n", size, dst[0]);
+	if (size > 512) {
+		for (; i < (size - 512); i += 512) {
+			// AICWFDBG(LOGDEBUG, "AICWF wr blk 0: %p -> %x\r\n",
+			// dst + i / 4, fw_addr + i);
+			err = rwnx_send_dbg_mem_block_write_req(rwnx_hw, fw_addr + i, 512,
+								dst + i / 4);
+			if (err) {
+				AICWFDBG(LOGERROR, "bin upload fail: %x, err:%d\r\n",
+					 fw_addr + i, err);
+				break;
+			}
+		}
+	}
+	if (!err && i < size) {
+		// AICWFDBG(LOGDEBUG, "AICWF wr blk 1: %p -> %x\r\n", dst + i / 4, fw_addr + i);
+		err = rwnx_send_dbg_mem_block_write_req(rwnx_hw, fw_addr + i, size - i,
+							dst + i / 4);
+		if (err) {
+			AICWFDBG(LOGERROR, "bin upload fail: %x, err:%d\r\n", fw_addr + i,
+				 err);
+		}
+	}
+
+	if (dst)
+		rwnx_release_firmware_common(&dst);
+
+	return err;
+}
+
+struct nvram_info_t {
+	struct txpwr_idx_conf txpwr_idx;
+	struct txpwr_ofst_conf txpwr_ofst;
+	struct xtal_cap_conf xtal_cap;
+};
+
+struct nvram_info_t nvram_info = {
+	.txpwr_idx = {.enable = 1,
+			.dsss = 9,
+			.ofdmlowrate_2g4 = 8,
+			.ofdm64qam_2g4 = 8,
+			.ofdm256qam_2g4 = 8,
+			.ofdm1024qam_2g4 = 8,
+			.ofdmlowrate_5g = 11,
+			.ofdm64qam_5g = 10,
+			.ofdm256qam_5g = 9,
+			.ofdm1024qam_5g = 9},
+	.txpwr_ofst = { .enable = 1,
+			.chan_1_4 = 0,
+			.chan_5_9 = 0,
+			.chan_10_13 = 0,
+			.chan_36_64 = 0,
+			.chan_100_120 = 0,
+			.chan_122_140 = 0,
+			.chan_142_165 = 0,
+		},
+	.xtal_cap = { .enable = 0,
+			.xtal_cap = 24,
+			.xtal_cap_fine = 31,
+		},
+};
+
+void get_userconfig_txpwr_ofst_in_fdrv(struct txpwr_ofst_conf *txpwr_ofst)
+{
+	txpwr_ofst->enable = userconfig_info.txpwr_ofst.enable;
+	txpwr_ofst->chan_1_4 = userconfig_info.txpwr_ofst.chan_1_4;
+	txpwr_ofst->chan_5_9 = userconfig_info.txpwr_ofst.chan_5_9;
+	txpwr_ofst->chan_10_13 = userconfig_info.txpwr_ofst.chan_10_13;
+	txpwr_ofst->chan_36_64 = userconfig_info.txpwr_ofst.chan_36_64;
+	txpwr_ofst->chan_100_120 = userconfig_info.txpwr_ofst.chan_100_120;
+	txpwr_ofst->chan_122_140 = userconfig_info.txpwr_ofst.chan_122_140;
+	txpwr_ofst->chan_142_165 = userconfig_info.txpwr_ofst.chan_142_165;
+
+	AICWFDBG(LOGINFO, "%s:enable      :%d\r\n", __func__, txpwr_ofst->enable);
+	AICWFDBG(LOGINFO, "%s:chan_1_4    :%d\r\n", __func__, txpwr_ofst->chan_1_4);
+	AICWFDBG(LOGINFO, "%s:chan_5_9    :%d\r\n", __func__, txpwr_ofst->chan_5_9);
+	AICWFDBG(LOGINFO, "%s:chan_10_13  :%d\r\n", __func__,
+		 txpwr_ofst->chan_10_13);
+	AICWFDBG(LOGINFO, "%s:chan_36_64  :%d\r\n", __func__,
+		 txpwr_ofst->chan_36_64);
+	AICWFDBG(LOGINFO, "%s:chan_100_120:%d\r\n", __func__,
+		 txpwr_ofst->chan_100_120);
+	AICWFDBG(LOGINFO, "%s:chan_122_140:%d\r\n", __func__,
+		 txpwr_ofst->chan_122_140);
+	AICWFDBG(LOGINFO, "%s:chan_142_165:%d\r\n", __func__,
+		 txpwr_ofst->chan_142_165);
+}
+
+void get_userconfig_txpwr_ofst2x_in_fdrv(struct txpwr_ofst2x_conf *txpwr_ofst2x)
+{
+	int type, ch_grp;
+	*txpwr_ofst2x = userconfig_info.txpwr_ofst2x;
+	AICWFDBG(LOGINFO, "%s:enable      :%d\r\n", __func__, txpwr_ofst2x->enable);
+	AICWFDBG(LOGDEBUG,
+		 "pwrofst2x 2.4g: [0]:11b, [1]:ofdm_highrate, [2]:ofdm_lowrate\n  chan=\t1-4\t5-9\t10-13");
+
+	for (type = 0; type < 3; type++) {
+		AICWFDBG(LOGDEBUG, "\n  [%d] =", type);
+		for (ch_grp = 0; ch_grp < 3; ch_grp++) {
+			AICWFDBG(LOGDEBUG, "\t%d",
+				 txpwr_ofst2x->pwrofst2x_tbl_2g4[type][ch_grp]);
+		}
+	}
+	AICWFDBG(LOGDEBUG,
+		 "\npwrofst2x 5g: [0]:ofdm_lowrate, [1]:ofdm_highrate, [2]:ofdm_midrate\n  chan=\t36-50\t51-64\t98-114\t115-130\t131-146\t147-166");
+	for (type = 0; type < 3; type++) {
+		AICWFDBG(LOGDEBUG, "\n  [%d] =", type);
+		for (ch_grp = 0; ch_grp < 6; ch_grp++) {
+			AICWFDBG(LOGDEBUG, "\t%d",
+				 txpwr_ofst2x->pwrofst2x_tbl_5g[type][ch_grp]);
+		}
+	}
+	AICWFDBG(LOGDEBUG, "\n");
+}
+
+void get_userconfig_txpwr_idx(struct txpwr_idx_conf *txpwr_idx)
+{
+	memcpy(txpwr_idx, &nvram_info.txpwr_idx, sizeof(struct txpwr_idx_conf));
+}
+
+void get_userconfig_txpwr_ofst(struct txpwr_ofst_conf *txpwr_ofst)
+{
+	memcpy(txpwr_ofst, &nvram_info.txpwr_ofst, sizeof(struct txpwr_ofst_conf));
+}
+
+void get_userconfig_xtal_cap(struct xtal_cap_conf *xtal_cap)
+{
+	if (nvram_info.xtal_cap.enable)
+		*xtal_cap = nvram_info.xtal_cap;
+
+	if (userconfig_info.xtal_cap.enable)
+		*xtal_cap = userconfig_info.xtal_cap;
+
+	AICWFDBG(LOGINFO, "%s:enable       :%d\r\n", __func__, xtal_cap->enable);
+	AICWFDBG(LOGINFO, "%s:xtal_cap     :%d\r\n", __func__, xtal_cap->xtal_cap);
+	AICWFDBG(LOGINFO, "%s:xtal_cap_fine:%d\r\n", __func__,
+		 xtal_cap->xtal_cap_fine);
+}
+
+s8_l get_txpwr_max(s8_l power)
+{
+	int i = 0;
+
+	for (i = 0; i <= 11; i++) {
+		if (power < userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[i])
+			power = userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[i];
+	}
+	for (i = 0; i <= 9; i++) {
+		if (power < userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_2g4[i])
+			power = userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_2g4[i];
+	}
+	for (i = 0; i <= 11; i++) {
+		if (power < userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[i])
+			power = userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[i];
+	}
+	for (i = 4; i <= 11; i++) {
+		if (power < userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[i])
+			power = userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[i];
+	}
+	for (i = 0; i <= 9; i++) {
+		if (power < userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_5g[i])
+			power = userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_5g[i];
+	}
+	for (i = 0; i <= 11; i++) {
+		if (power < userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[i])
+			power = userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[i];
+	}
+
+	if (userconfig_info.txpwr_loss.loss_enable_2g4 == 1 ||
+	    userconfig_info.txpwr_loss.loss_enable_5g == 1) {
+		if (userconfig_info.txpwr_loss.loss_value_2g4 <
+		    userconfig_info.txpwr_loss.loss_value_5g)
+			power += userconfig_info.txpwr_loss.loss_value_5g;
+		else
+			power += userconfig_info.txpwr_loss.loss_value_2g4;
+	}
+
+	AICWFDBG(LOGDEBUG, "AICWF %s:txpwr_max:%d \r\n", __func__, power);
+	return power;
+}
+
+void set_txpwr_loss_ofst(s8_l value)
+{
+	int i = 0;
+
+	for (i = 0; i <= 11; i++)
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[i] += value;
+
+	for (i = 0; i <= 9; i++)
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_2g4[i] += value;
+
+	for (i = 0; i <= 11; i++)
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[i] += value;
+
+	for (i = 4; i <= 11; i++)
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[i] += value;
+
+	for (i = 0; i <= 9; i++)
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_5g[i] += value;
+
+	for (i = 0; i <= 11; i++)
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[i] += value;
+
+	AICWFDBG(LOGDEBUG, "AICWF %s:value:%d\r\n", __func__, value);
+}
+
+#define MATCH_NODE(type, node, cfg_key) {cfg_key, offsetof(type, node)}
+
+struct parse_match_t {
+	char keyname[64];
+	int offset;
+};
+
+static const char * const parse_key_prefix[] = {
+	[0x01] = "module0_",
+	[0x21] = "module1_",
+};
+
+static const struct parse_match_t parse_match_tab[] = {
+	MATCH_NODE(struct nvram_info_t, txpwr_idx.enable, "enable"),
+	MATCH_NODE(struct nvram_info_t, txpwr_idx.dsss, "dsss"),
+	MATCH_NODE(struct nvram_info_t, txpwr_idx.ofdmlowrate_2g4, "ofdmlowrate_2g4"),
+	MATCH_NODE(struct nvram_info_t, txpwr_idx.ofdm64qam_2g4, "ofdm64qam_2g4"),
+	MATCH_NODE(struct nvram_info_t, txpwr_idx.ofdm256qam_2g4, "ofdm256qam_2g4"),
+	MATCH_NODE(struct nvram_info_t, txpwr_idx.ofdm1024qam_2g4, "ofdm1024qam_2g4"),
+	MATCH_NODE(struct nvram_info_t, txpwr_idx.ofdmlowrate_5g, "ofdmlowrate_5g"),
+	MATCH_NODE(struct nvram_info_t, txpwr_idx.ofdm64qam_5g, "ofdm64qam_5g"),
+	MATCH_NODE(struct nvram_info_t, txpwr_idx.ofdm256qam_5g, "ofdm256qam_5g"),
+	MATCH_NODE(struct nvram_info_t, txpwr_idx.ofdm1024qam_5g, "ofdm1024qam_5g"),
+
+	MATCH_NODE(struct nvram_info_t, txpwr_ofst.enable, "ofst_enable"),
+	MATCH_NODE(struct nvram_info_t, txpwr_ofst.chan_1_4, "ofst_chan_1_4"),
+	MATCH_NODE(struct nvram_info_t, txpwr_ofst.chan_5_9, "ofst_chan_5_9"),
+	MATCH_NODE(struct nvram_info_t, txpwr_ofst.chan_10_13, "ofst_chan_10_13"),
+	MATCH_NODE(struct nvram_info_t, txpwr_ofst.chan_36_64, "ofst_chan_36_64"),
+	MATCH_NODE(struct nvram_info_t, txpwr_ofst.chan_100_120, "ofst_chan_100_120"),
+	MATCH_NODE(struct nvram_info_t, txpwr_ofst.chan_122_140, "ofst_chan_122_140"),
+	MATCH_NODE(struct nvram_info_t, txpwr_ofst.chan_142_165, "ofst_chan_142_165"),
+
+	MATCH_NODE(struct nvram_info_t, xtal_cap.enable, "xtal_enable"),
+	MATCH_NODE(struct nvram_info_t, xtal_cap.xtal_cap, "xtal_cap"),
+	MATCH_NODE(struct nvram_info_t, xtal_cap.xtal_cap_fine, "xtal_cap_fine"),
+};
+
+static int parse_key_val(const char *str, const char *key, char *val)
+{
+	const char *p = NULL;
+	const char *dst = NULL;
+	int keysize = 0;
+	int bufsize = 0;
+
+	if (!str || !key || !val)
+		return -1;
+
+	keysize = strlen(key);
+	bufsize = strlen(str);
+	if (bufsize <= keysize)
+		return -1;
+
+	p = str;
+	while (*p != 0 && *p == ' ')
+		p++;
+
+	if (*p == '#')
+		return -1;
+
+	if (str + bufsize - p <= keysize)
+		return -1;
+
+	if (strncmp(p, key, keysize) != 0)
+		return -1;
+
+	p += keysize;
+
+	while (*p != 0 && *p == ' ')
+		p++;
+
+	if (*p != '=')
+		return -1;
+
+	p++;
+	while (*p != 0 && *p == ' ')
+		p++;
+
+	if (*p == '"')
+		p++;
+
+	dst = p;
+	while (*p != 0)
+		p++;
+
+	p--;
+	while (*p == ' ')
+		p--;
+
+	if (*p == '"')
+		p--;
+
+	while (*p == '\r' || *p == '\n')
+		p--;
+
+	p++;
+	strscpy(val, dst, p - dst);
+	val[p - dst] = 0;
+	return 0;
+}
+
+int rwnx_atoi(char *value)
+{
+	int len = 0;
+	int i = 0;
+	int result = 0;
+	int flag = 1;
+
+	if (value[0] == '-') {
+		flag = -1;
+		value++;
+	}
+	len = strlen(value);
+
+	for (i = 0; i < len; i++) {
+		result = result * 10;
+		if (value[i] >= 48 && value[i] <= 57) {
+			result += value[i] - 48;
+		} else {
+			result = 0;
+			break;
+		}
+	}
+
+	return result * flag;
+}
+
+static int rwnx_parse_mul_value(char *line, char *val[])
+{
+	int nargs = 0;
+
+	while (nargs < 10) {
+		while ((*line == ' ') || (*line == '\t'))
+			++line;
+
+		if (*line == '\0') { /* end of line, no more args    */
+			//AICWFDBG(LOGDEBUG, "%s p1,%d\n", __func__, nargs);
+			return nargs;
+		}
+
+		val[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    */
+			//AICWFDBG(LOGDEBUG, "%s p2,%d\n", __func__, nargs);
+			return nargs;
+		}
+
+		*line++ = '\0'; /* terminate current arg     */
+	}
+
+	AICWFDBG(LOGDEBUG, "nargs: %d\n", nargs);
+	//AICWFDBG(LOGDEBUG, "%s exit,%d\n", __func__, nargs);
+	return nargs;
+}
+
+int get_ccode_region(char *ccode)
+{
+	int i, cnt;
+
+	AICWFDBG(LOGDEBUG, "%s ccode:%s\r\n", __func__, ccode);
+	cnt = ARRAY_SIZE(reg_tables);
+
+	for (i = 0; i < cnt; i++) {
+		if (reg_tables[i].ccode[0] == ccode[0] &&
+		    reg_tables[i].ccode[1] == ccode[1]) {
+			AICWFDBG(LOGDEBUG, "region: %d\r\n", reg_tables[i].region);
+			return reg_tables[i].region;
+		}
+	}
+	AICWFDBG(LOGDEBUG, "use default region\r\n");
+	return REGIONS_DEFAULT;
+}
+
+void rwnx_plat_nvram_set_value(char *command, char *value)
+{
+	// TODO send command
+	AICWFDBG(LOGDEBUG, "%s:command=%s value=%s\n", __func__, command, value);
+	if (!strcmp(command, "enable")) {
+		userconfig_info.txpwr_lvl.enable = rwnx_atoi(value);
+		userconfig_info.txpwr_lvl_v2.enable = rwnx_atoi(value);
+	} else if (!strcmp(command, "dsss")) {
+		userconfig_info.txpwr_lvl.dsss = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdmlowrate_2g4")) {
+		userconfig_info.txpwr_lvl.ofdmlowrate_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm64qam_2g4")) {
+		userconfig_info.txpwr_lvl.ofdm64qam_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm256qam_2g4")) {
+		userconfig_info.txpwr_lvl.ofdm256qam_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm1024qam_2g4")) {
+		userconfig_info.txpwr_lvl.ofdm1024qam_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdmlowrate_5g")) {
+		userconfig_info.txpwr_lvl.ofdmlowrate_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm64qam_5g")) {
+		userconfig_info.txpwr_lvl.ofdm64qam_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm256qam_5g")) {
+		userconfig_info.txpwr_lvl.ofdm256qam_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm1024qam_5g")) {
+		userconfig_info.txpwr_lvl.ofdm1024qam_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_1m_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11b_11ag_2g4[0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_2m_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11b_11ag_2g4[1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_5m5_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11b_11ag_2g4[2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_11m_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11b_11ag_2g4[3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_6m_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11b_11ag_2g4[4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_9m_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11b_11ag_2g4[5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_12m_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11b_11ag_2g4[6] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_18m_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11b_11ag_2g4[7] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_24m_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11b_11ag_2g4[8] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_36m_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11b_11ag_2g4[9] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_48m_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11b_11ag_2g4[10] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_54m_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11b_11ag_2g4[11] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs0_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11n_11ac_2g4[0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs1_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11n_11ac_2g4[1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs2_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11n_11ac_2g4[2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs3_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11n_11ac_2g4[3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs4_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11n_11ac_2g4[4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs5_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11n_11ac_2g4[5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs6_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11n_11ac_2g4[6] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs7_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11n_11ac_2g4[7] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs8_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11n_11ac_2g4[8] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs9_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11n_11ac_2g4[9] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs0_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11ax_2g4[0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs1_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11ax_2g4[1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs2_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11ax_2g4[2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs3_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11ax_2g4[3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs4_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11ax_2g4[4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs5_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11ax_2g4[5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs6_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11ax_2g4[6] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs7_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11ax_2g4[7] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs8_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11ax_2g4[8] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs9_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11ax_2g4[9] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs10_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11ax_2g4[10] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs11_2g4")) {
+		userconfig_info.txpwr_lvl_v2.pwrlvl_11ax_2g4[11] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_enable")) {
+		userconfig_info.txpwr_ofst.enable = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_1_4")) {
+		userconfig_info.txpwr_ofst.chan_1_4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_5_9")) {
+		userconfig_info.txpwr_ofst.chan_5_9 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_10_13")) {
+		userconfig_info.txpwr_ofst.chan_10_13 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_36_64")) {
+		userconfig_info.txpwr_ofst.chan_36_64 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_100_120")) {
+		userconfig_info.txpwr_ofst.chan_100_120 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_122_140")) {
+		userconfig_info.txpwr_ofst.chan_122_140 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_142_165")) {
+		userconfig_info.txpwr_ofst.chan_142_165 = rwnx_atoi(value);
+	} else if (!strcmp(command, "xtal_enable")) {
+		userconfig_info.xtal_cap.enable = rwnx_atoi(value);
+	} else if (!strcmp(command, "xtal_cap")) {
+		userconfig_info.xtal_cap.xtal_cap = rwnx_atoi(value);
+	} else if (!strcmp(command, "xtal_cap_fine")) {
+		userconfig_info.xtal_cap.xtal_cap_fine = rwnx_atoi(value);
+	} else {
+		AICWFDBG(LOGERROR, "invalid cmd: %s\n", command);
+	}
+}
+
+void rwnx_plat_nvram_set_value_group(char *command, char *value)
+{
+	char *mul_val[10];
+	int i;
+
+	// TODO send command
+	AICWFDBG(LOGDEBUG, "%s:command=%s value=%s\n", __func__, command, value);
+	if (!strcmp(command, "enable")) {
+		userconfig_info.txpwr_lvl.enable = rwnx_atoi(value);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].enable = rwnx_atoi(value);
+	} else if (!strcmp(command, "dsss")) {
+		userconfig_info.txpwr_lvl.dsss = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdmlowrate_2g4")) {
+		userconfig_info.txpwr_lvl.ofdmlowrate_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm64qam_2g4")) {
+		userconfig_info.txpwr_lvl.ofdm64qam_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm256qam_2g4")) {
+		userconfig_info.txpwr_lvl.ofdm256qam_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm1024qam_2g4")) {
+		userconfig_info.txpwr_lvl.ofdm1024qam_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdmlowrate_5g")) {
+		userconfig_info.txpwr_lvl.ofdmlowrate_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm64qam_5g")) {
+		userconfig_info.txpwr_lvl.ofdm64qam_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm256qam_5g")) {
+		userconfig_info.txpwr_lvl.ofdm256qam_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm1024qam_5g")) {
+		userconfig_info.txpwr_lvl.ofdm1024qam_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_1m_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11b_11ag_2g4[0] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11b_11ag_2m_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11b_11ag_2g4[1] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11b_11ag_5m5_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11b_11ag_2g4[2] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11b_11ag_11m_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11b_11ag_2g4[3] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11b_11ag_6m_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11b_11ag_2g4[4] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11b_11ag_9m_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11b_11ag_2g4[5] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11b_11ag_12m_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11b_11ag_2g4[6] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11b_11ag_18m_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11b_11ag_2g4[7] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11b_11ag_24m_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11b_11ag_2g4[8] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11b_11ag_36m_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11b_11ag_2g4[9] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11b_11ag_48m_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11b_11ag_2g4[10] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11b_11ag_54m_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11b_11ag_2g4[11] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs0_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_2g4[0] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs1_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_2g4[1] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs2_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_2g4[2] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs3_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_2g4[3] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs4_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_2g4[4] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs5_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_2g4[5] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs6_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_2g4[6] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs7_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_2g4[7] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs8_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_2g4[8] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs9_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_2g4[9] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs0_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_2g4[0] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs1_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_2g4[1] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs2_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_2g4[2] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs3_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_2g4[3] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs4_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_2g4[4] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs5_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_2g4[5] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs6_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_2g4[6] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs7_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_2g4[7] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs8_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_2g4[8] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs9_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_2g4[9] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs10_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_2g4[10] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs11_2g4")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_2g4[11] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11a_1m_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11a_5g[0] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11a_2m_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11a_5g[1] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11a_5m5_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11a_5g[2] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11a_11m_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11a_5g[3] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11a_6m_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11a_5g[4] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11a_9m_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11a_5g[5] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11a_12m_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11a_5g[6] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11a_18m_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11a_5g[7] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11a_24m_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11a_5g[8] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11a_36m_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11a_5g[9] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11a_48m_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11a_5g[10] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11a_54m_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11a_5g[11] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs0_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_5g[0] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs1_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_5g[1] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs2_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_5g[2] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs3_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_5g[3] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs4_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_5g[4] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs5_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_5g[5] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs6_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_5g[6] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs7_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_5g[7] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs8_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_5g[8] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs9_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11n_11ac_5g[9] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs0_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_5g[0] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs1_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_5g[1] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs2_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_5g[2] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs3_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_5g[3] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs4_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_5g[4] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs5_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_5g[5] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs6_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_5g[6] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs7_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_5g[7] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs8_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_5g[8] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs9_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_5g[9] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs10_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_5g[10] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_11ax_mcs11_5g")) {
+		rwnx_parse_mul_value(value, mul_val);
+		for (i = 0; i < REGION_NUM_MAX; i++)
+			userconfig_info.txpwr_lvl_v3_gp[i].pwrlvl_11ax_5g[11] =
+				rwnx_atoi(mul_val[i]);
+	} else if (!strcmp(command, "lvl_adj_enable")) {
+		userconfig_info.txpwr_lvl_adj.enable = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_2g4_chan_1_4")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_2g4[0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_2g4_chan_5_9")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_2g4[1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_2g4_chan_10_13")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_2g4[2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_5g_chan_42")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_5g[0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_5g_chan_58")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_5g[1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_5g_chan_106")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_5g[2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_5g_chan_122")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_5g[3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_5g_chan_138")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_5g[4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_5g_chan_155")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_5g[5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "loss_enable_2g4")) {
+		userconfig_info.txpwr_loss.loss_enable_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "loss_value_2g4")) {
+		userconfig_info.txpwr_loss.loss_value_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "loss_enable_5g")) {
+		userconfig_info.txpwr_loss.loss_enable_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "loss_value_5g")) {
+		userconfig_info.txpwr_loss.loss_value_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_enable")) {
+		userconfig_info.txpwr_ofst.enable = rwnx_atoi(value);
+		userconfig_info.txpwr_ofst2x.enable = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_1_4")) {
+		userconfig_info.txpwr_ofst.chan_1_4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_5_9")) {
+		userconfig_info.txpwr_ofst.chan_5_9 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_10_13")) {
+		userconfig_info.txpwr_ofst.chan_10_13 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_36_64")) {
+		userconfig_info.txpwr_ofst.chan_36_64 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_100_120")) {
+		userconfig_info.txpwr_ofst.chan_100_120 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_122_140")) {
+		userconfig_info.txpwr_ofst.chan_122_140 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_142_165")) {
+		userconfig_info.txpwr_ofst.chan_142_165 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_11b_chan_1_4")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[0][0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_11b_chan_5_9")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[0][1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_11b_chan_10_13")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[0][2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_ofdm_highrate_chan_1_4")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[1][0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_ofdm_highrate_chan_5_9")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[1][1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_ofdm_highrate_chan_10_13")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[1][2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_ofdm_lowrate_chan_1_4")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[2][0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_ofdm_lowrate_chan_5_9")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[2][1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_ofdm_lowrate_chan_10_13")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[2][0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_lowrate_chan_42")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[0][0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_lowrate_chan_58")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[0][1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_lowrate_chan_106")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[0][2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_lowrate_chan_122")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[0][3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_lowrate_chan_138")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[0][4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_lowrate_chan_155")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[0][5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_highrate_chan_42")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[1][0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_highrate_chan_58")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[1][1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_highrate_chan_106")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[1][2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_highrate_chan_122")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[1][3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_highrate_chan_138")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[1][4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_highrate_chan_155")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[1][5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_midrate_chan_42")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[2][0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_midrate_chan_58")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[2][1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_midrate_chan_106")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[2][2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_midrate_chan_122")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[2][3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_midrate_chan_138")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[2][4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_midrate_chan_155")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[2][5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "xtal_enable")) {
+		userconfig_info.xtal_cap.enable = rwnx_atoi(value);
+	} else if (!strcmp(command, "xtal_cap")) {
+		userconfig_info.xtal_cap.xtal_cap = rwnx_atoi(value);
+	} else if (!strcmp(command, "xtal_cap_fine")) {
+		userconfig_info.xtal_cap.xtal_cap_fine = rwnx_atoi(value);
+	} else {
+		AICWFDBG(LOGERROR, "invalid cmd: %s\n", command);
+	}
+}
+
+void rwnx_plat_nvram_set_value_v3(char *command, char *value)
+{
+	// TODO send command
+	AICWFDBG(LOGDEBUG, "%s:command=%s value=%s\n", __func__, command, value);
+	//AICWFDBG(LOGDEBUG, "AICWF command=%s value=%s\n", command, value);
+	if (!strcmp(command, "enable")) {
+		userconfig_info.txpwr_lvl.enable = rwnx_atoi(value);
+		userconfig_info.txpwr_lvl_v3.enable = rwnx_atoi(value);
+	} else if (!strcmp(command, "dsss")) {
+		userconfig_info.txpwr_lvl.dsss = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdmlowrate_2g4")) {
+		userconfig_info.txpwr_lvl.ofdmlowrate_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm64qam_2g4")) {
+		userconfig_info.txpwr_lvl.ofdm64qam_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm256qam_2g4")) {
+		userconfig_info.txpwr_lvl.ofdm256qam_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm1024qam_2g4")) {
+		userconfig_info.txpwr_lvl.ofdm1024qam_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdmlowrate_5g")) {
+		userconfig_info.txpwr_lvl.ofdmlowrate_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm64qam_5g")) {
+		userconfig_info.txpwr_lvl.ofdm64qam_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm256qam_5g")) {
+		userconfig_info.txpwr_lvl.ofdm256qam_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofdm1024qam_5g")) {
+		userconfig_info.txpwr_lvl.ofdm1024qam_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_1m_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_2m_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_5m5_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_11m_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_6m_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_9m_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_12m_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[6] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_18m_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[7] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_24m_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[8] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_36m_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[9] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_48m_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[10] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11b_11ag_54m_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11b_11ag_2g4[11] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs0_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_2g4[0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs1_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_2g4[1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs2_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_2g4[2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs3_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_2g4[3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs4_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_2g4[4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs5_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_2g4[5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs6_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_2g4[6] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs7_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_2g4[7] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs8_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_2g4[8] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs9_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_2g4[9] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs0_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs1_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs2_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs3_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs4_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs5_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs6_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[6] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs7_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[7] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs8_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[8] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs9_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[9] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs10_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[10] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs11_2g4")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_2g4[11] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11a_1m_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11a_2m_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11a_5m5_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11a_11m_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11a_6m_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11a_9m_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11a_12m_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[6] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11a_18m_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[7] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11a_24m_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[8] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11a_36m_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[9] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11a_48m_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[10] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11a_54m_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11a_5g[11] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs0_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_5g[0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs1_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_5g[1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs2_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_5g[2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs3_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_5g[3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs4_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_5g[4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs5_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_5g[5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs6_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_5g[6] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs7_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_5g[7] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs8_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_5g[8] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11n_11ac_mcs9_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11n_11ac_5g[9] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs0_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs1_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs2_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs3_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs4_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs5_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs6_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[6] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs7_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[7] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs8_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[8] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs9_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[9] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs10_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[10] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_11ax_mcs11_5g")) {
+		userconfig_info.txpwr_lvl_v3.pwrlvl_11ax_5g[11] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_enable")) {
+		userconfig_info.txpwr_lvl_adj.enable = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_2g4_chan_1_4")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_2g4[0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_2g4_chan_5_9")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_2g4[1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_2g4_chan_10_13")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_2g4[2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_5g_chan_42")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_5g[0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_5g_chan_58")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_5g[1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_5g_chan_106")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_5g[2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_5g_chan_122")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_5g[3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_5g_chan_138")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_5g[4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "lvl_adj_5g_chan_155")) {
+		userconfig_info.txpwr_lvl_adj.pwrlvl_adj_tbl_5g[5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "loss_enable_2g4")) {
+		userconfig_info.txpwr_loss.loss_enable_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "loss_value_2g4")) {
+		userconfig_info.txpwr_loss.loss_value_2g4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "loss_enable_5g")) {
+		userconfig_info.txpwr_loss.loss_enable_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "loss_value_5g")) {
+		userconfig_info.txpwr_loss.loss_value_5g = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_enable")) {
+		userconfig_info.txpwr_ofst.enable = rwnx_atoi(value);
+		userconfig_info.txpwr_ofst2x.enable = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_1_4")) {
+		userconfig_info.txpwr_ofst.chan_1_4 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_5_9")) {
+		userconfig_info.txpwr_ofst.chan_5_9 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_10_13")) {
+		userconfig_info.txpwr_ofst.chan_10_13 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_36_64")) {
+		userconfig_info.txpwr_ofst.chan_36_64 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_100_120")) {
+		userconfig_info.txpwr_ofst.chan_100_120 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_122_140")) {
+		userconfig_info.txpwr_ofst.chan_122_140 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_chan_142_165")) {
+		userconfig_info.txpwr_ofst.chan_142_165 = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_11b_chan_1_4")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[0][0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_11b_chan_5_9")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[0][1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_11b_chan_10_13")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[0][2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_ofdm_highrate_chan_1_4")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[1][0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_ofdm_highrate_chan_5_9")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[1][1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_ofdm_highrate_chan_10_13")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[1][2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_ofdm_lowrate_chan_1_4")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[2][0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_ofdm_lowrate_chan_5_9")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[2][1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_2g4_ofdm_lowrate_chan_10_13")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_2g4[2][0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_lowrate_chan_42")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[0][0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_lowrate_chan_58")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[0][1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_lowrate_chan_106")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[0][2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_lowrate_chan_122")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[0][3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_lowrate_chan_138")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[0][4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_lowrate_chan_155")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[0][5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_highrate_chan_42")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[1][0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_highrate_chan_58")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[1][1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_highrate_chan_106")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[1][2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_highrate_chan_122")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[1][3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_highrate_chan_138")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[1][4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_highrate_chan_155")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[1][5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_midrate_chan_42")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[2][0] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_midrate_chan_58")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[2][1] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_midrate_chan_106")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[2][2] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_midrate_chan_122")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[2][3] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_midrate_chan_138")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[2][4] = rwnx_atoi(value);
+	} else if (!strcmp(command, "ofst_5g_ofdm_midrate_chan_155")) {
+		userconfig_info.txpwr_ofst2x.pwrofst2x_tbl_5g[2][5] = rwnx_atoi(value);
+	} else if (!strcmp(command, "xtal_enable")) {
+		userconfig_info.xtal_cap.enable = rwnx_atoi(value);
+	} else if (!strcmp(command, "xtal_cap")) {
+		userconfig_info.xtal_cap.xtal_cap = rwnx_atoi(value);
+	} else if (!strcmp(command, "xtal_cap_fine")) {
+		userconfig_info.xtal_cap.xtal_cap_fine = rwnx_atoi(value);
+	} else {
+		AICWFDBG(LOGERROR, "invalid cmd: %s\n", command);
+	}
+}
+
+void rwnx_plat_userconfig_parsing2(char *buffer, int size)
+{
+	int i = 0;
+	int parse_state = 0;
+	char command[30];
+	char value[100];
+	int char_counter = 0;
+
+	memset(command, 0, 30);
+	memset(value, 0, 100);
+
+	for (i = 0; i < size; i++) {
+		// Send command or print nvram log when char is \r or \n
+		if (buffer[i] == 0x0a || buffer[i] == 0x0d) {
+			if (command[0] != 0 && value[0] != 0) {
+				if (parse_state == PRINT)
+					AICWFDBG(LOGINFO, "%s:%s\r\n", __func__, value);
+				else if (parse_state == GET_VALUE)
+					rwnx_plat_nvram_set_value(command, value);
+			}
+			// Reset command value and char_counter
+			memset(command, 0, 30);
+			memset(value, 0, 100);
+			char_counter = 0;
+			parse_state = INIT;
+			continue;
+		}
+
+		// Switch parser state
+		if (parse_state == INIT) {
+			if (buffer[i] == '#') {
+				parse_state = PRINT;
+				continue;
+			} else if (buffer[i] == 0x0a || buffer[i] == 0x0d) {
+				parse_state = INIT;
+				continue;
+			} else {
+				parse_state = CMD;
+			}
+		}
+
+		// Fill data to command and value
+		if (parse_state == PRINT) {
+			command[0] = 0x01;
+			value[char_counter] = buffer[i];
+			char_counter++;
+		} else if (parse_state == CMD) {
+			if (command[0] != 0 && buffer[i] == '=') {
+				parse_state = GET_VALUE;
+				char_counter = 0;
+				continue;
+			}
+			command[char_counter] = buffer[i];
+			char_counter++;
+		} else if (parse_state == GET_VALUE) {
+			value[char_counter] = buffer[i];
+			char_counter++;
+		}
+	}
+}
+
+void rwnx_plat_userconfig_parsing3(char *buffer, int size)
+{
+	int i = 0;
+	int parse_state = 0;
+	char command[64];
+	char value[100];
+	int char_counter = 0;
+	u8_l area_pw = 0;
+
+	memset(command, 0, 64);
+	memset(value, 0, 100);
+
+	for (i = 0; i < size; i++) {
+		// Send command or print nvram log when char is \r or \n
+		if (buffer[i] == 0x0a || buffer[i] == 0x0d) {
+			if (command[0] != 0 && value[0] != 0) {
+				if (parse_state == PRINT) {
+					if (area_pw == 0 &&
+					    strnstr(value, "SRRC", strlen(value))) {
+						area_pw = 1;
+						AICWFDBG(LOGINFO,
+							 "use diff_area_power userconfig\n");
+					}
+					AICWFDBG(LOGINFO, "%s:%s\r\n", __func__, value);
+				} else if (parse_state == GET_VALUE) {
+					if (area_pw)
+						rwnx_plat_nvram_set_value_group(command, value);
+					else
+						rwnx_plat_nvram_set_value_v3(command, value);
+				}
+			}
+			// Reset command value and char_counter
+			memset(command, 0, 64);
+			memset(value, 0, 100);
+			char_counter = 0;
+			parse_state = INIT;
+			continue;
+		}
+
+		// Switch parser state
+		if (parse_state == INIT) {
+			if (buffer[i] == '#') {
+				parse_state = PRINT;
+				continue;
+			} else if (buffer[i] == 0x0a || buffer[i] == 0x0d) {
+				parse_state = INIT;
+				continue;
+			} else {
+				parse_state = CMD;
+			}
+		}
+
+		// Fill data to command and value
+		if (parse_state == PRINT) {
+			command[0] = 0x01;
+			value[char_counter] = buffer[i];
+			char_counter++;
+		} else if (parse_state == CMD) {
+			if (command[0] != 0 && buffer[i] == '=') {
+				parse_state = GET_VALUE;
+				char_counter = 0;
+				continue;
+			}
+			command[char_counter] = buffer[i];
+			char_counter++;
+		} else if (parse_state == GET_VALUE) {
+			if (buffer[i] != 0x2D &&
+			    (buffer[i] < 0x30 || buffer[i] > 0x39) &&
+			    buffer[i] != 0x20)
+				continue;
+			value[char_counter] = buffer[i];
+			char_counter++;
+		}
+	}
+}
+
+void rwnx_plat_userconfig_parsing(struct rwnx_hw *rwnx_hw, char *buffer,
+				  int size)
+{
+	char conf[100], keyname[64];
+	char *line;
+	char *data;
+	int i = 0, len = 0;
+	long val;
+
+	if (size <= 0) {
+		pr_err("Config buffer size %d error\n", size);
+		return;
+	}
+
+	AICWFDBG(LOGDEBUG, "AICWF %s rwnx_hw->vendor_info:0x%02X \r\n", __func__,
+		 rwnx_hw->vendor_info);
+	if (rwnx_hw->vendor_info == 0x00 ||
+	    rwnx_hw->vendor_info > (ARRAY_SIZE(parse_key_prefix) - 1)) {
+		AICWFDBG(LOGDEBUG, "AICWF Unsuppor vendor info config\n");
+		AICWFDBG(LOGDEBUG, "AICWF Using module0 config\n");
+		rwnx_hw->vendor_info = 0x01;
+		// return;
+	}
+
+	data = vmalloc(size + 1);
+	if (!data) {
+		AICWFDBG(LOGERROR, "%s vmalloc fail\n", __func__);
+		return;
+	}
+
+	memcpy(data, buffer, size);
+	buffer = data;
+
+	while (1) {
+		line = buffer;
+		if (*line == 0)
+			break;
+
+		while (*buffer != '\r' && *buffer != '\n' &&
+		       *buffer != 0 && len++ < size)
+			buffer++;
+
+		while ((*buffer == '\r' || *buffer == '\n') && len++ < size)
+			*buffer++ = 0;
+
+		if (len >= size)
+			*buffer = 0;
+
+		// store value to data struct
+		for (i = 0; i < ARRAY_SIZE(parse_match_tab); i++) {
+			scnprintf(keyname, sizeof(keyname), "%s%s",
+				  parse_key_prefix[rwnx_hw->vendor_info],
+				  parse_match_tab[i].keyname);
+			if (parse_key_val(line, keyname, conf) == 0) {
+				if (kstrtol(conf, 0, &val)) {
+					pr_warn("Invalid value for %s\n", keyname);
+					continue;
+				}
+				*(unsigned long *)((unsigned long)&nvram_info +
+								parse_match_tab[i].offset) = val;
+				AICWFDBG(LOGDEBUG, "AICWF %s, %s = %ld\n", __func__,
+					 parse_match_tab[i].keyname, val);
+				break;
+			}
+		}
+	}
+	vfree(data);
+}
+
+int rwnx_plat_userconfig_upload(struct rwnx_hw *rwnx_hw,
+				const char *filename)
+{
+	int size;
+	u32 *dst = NULL;
+
+	AICWFDBG(LOGDEBUG, "AICWF userconfig file path:%s \r\n", filename);
+
+	/* load aic firmware */
+	size = rwnx_load_firmware(&dst, filename, rwnx_hw->dev);
+	if (size <= 0) {
+		AICWFDBG(LOGDEBUG, "AICWF wrong size of firmware file\n");
+		vfree(dst);
+		dst = NULL;
+		return 0;
+	}
+
+	/* Copy the file on the Embedded side */
+	AICWFDBG(LOGDEBUG, "AICWF ### Upload %s userconfig, size=%d\n", filename, size);
+
+	rwnx_plat_userconfig_parsing(rwnx_hw, (char *)dst, size);
+
+	if (dst) {
+		vfree(dst);
+		dst = NULL;
+	}
+
+	AICWFDBG(LOGDEBUG, "AICWF userconfig download complete\n\n");
+
+	return 0;
+}
+
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+static inline char *get_line_from_buffer(char **buffer)
+{
+	return strsep(buffer, "\n");
+}
+
+int is_all_space_or_tab(u8_l *data, u8_l size)
+{
+	u8_l cnt = 0;
+	u8_l num_of_space_and_tab = 0;
+
+	while (size > cnt) {
+		if (data[cnt] == ' ' || data[cnt] == '\t' || data[cnt] == '\0')
+			++num_of_space_and_tab;
+		++cnt;
+	}
+	return size == num_of_space_and_tab;
+}
+
+int is_comment_string(char *sz_str)
+{
+	if (*sz_str == '#' && *(sz_str + 1) == ' ')
+		return 1;
+	else
+		return 0;
+}
+
+int is_enable_limit(char *szs_str)
+{
+	int i = 0, value = 0;
+	char en[15];
+
+	if (*szs_str == '#' && *(szs_str + 1) == '*') {
+		i = 2;
+		while (szs_str[i] != '=' && (i < 15)) {
+			en[i - 2] = szs_str[i];
+			i++;
+		}
+		en[i - 2] = '\0';
+		i += 1;
+		if (strncmp(en, "enable", 6) == 0) {
+			value = rwnx_atoi(szs_str + i);
+			if (value == 1)
+				return 1;
+			else
+				return 0;
+		}
+	}
+	return -1;
+}
+
+int parse_qualified_string(char *in, u32 *start, char *out, char left_qualifier,
+			   char right_qualifier)
+{
+	u32 i = 0, j = 0;
+	char c = in[(*start)++];
+
+	if (c != left_qualifier)
+		return 0;
+	i = (*start);
+	c = in[(*start)++];
+	while (c != right_qualifier && c != '\0')
+		c = in[(*start)++];
+	if (c == '\0')
+		return 0;
+	j = (*start) - 2;
+	strscpy((char *)out, (const char *)(in + i), j - i + 1);
+	return 1;
+}
+
+int get_u1_byte_integer_from_string_in_decimal(char *str, u8 *p_int)
+{
+	u16 i = 0;
+	*p_int = 0;
+	while (str[i] != '\0') {
+		if (str[i] >= '0' && str[i] <= '9') {
+			*p_int *= 10;
+			*p_int += (str[i] - '0');
+		} else {
+			return 0;
+		}
+		++i;
+	}
+	return 1;
+}
+
+int get_s1_byte_integer_from_string_in_decimal(char *str, s8 *val)
+{
+	u8 negative = 0;
+	u16 i = 0;
+	*val = 0;
+	while (str[i] != '\0') {
+		if (i == 0 && (str[i] == '+' || str[i] == '-')) {
+			if (str[i] == '-')
+				negative = 1;
+		} else if (str[i] >= '0' && str[i] <= '9') {
+			*val *= 10;
+			*val += (str[i] - '0');
+		} else {
+			return 0;
+		}
+		++i;
+	}
+	if (negative)
+		*val = -*val;
+	return 1;
+}
+
+static void update_power_limit(u8 band_cc, const char *channel, u8 channel_num,
+			       const char *power_limit, u8 power_limit_val,
+			       struct powerlimit_info_t *powerlimit_info)
+{
+	if (band_cc == PHY_BAND_2G4) {
+		u8 cur_idx = powerlimit_info->txpwr_lmt.ch_cnt_2g4;
+
+		if (cur_idx == 0)
+			AICWFDBG(LOGDEBUG, "[%d]: ch=%s, pwr=%s\n", cur_idx, channel, power_limit);
+		if (cur_idx < MAC_DOMAINCHANNEL_24G_MAX) {
+			powerlimit_info->txpwr_lmt.ch_num_2g4[cur_idx] = channel_num;
+			powerlimit_info->txpwr_lmt.max_pwr_2g4[cur_idx] = power_limit_val;
+			powerlimit_info->txpwr_lmt.ch_cnt_2g4++;
+		} else {
+			AICWFDBG(LOGERROR, "band %d chan_cnt reached %d\n",
+				 band_cc, MAC_DOMAINCHANNEL_24G_MAX);
+			AICWFDBG(LOGERROR, "channel=%s(%d) powerLimit=%s(%d)\n",
+				 channel, channel_num, power_limit, power_limit_val);
+		}
+	} else if (band_cc == PHY_BAND_5G) {
+		u8 cur_idx = powerlimit_info->txpwr_lmt.ch_cnt_5g;
+
+		if (cur_idx == 0)
+			AICWFDBG(LOGDEBUG, "[%d]: ch=%s, pwr=%s\n", cur_idx, channel, power_limit);
+		if (cur_idx < MAC_DOMAINCHANNEL_5G_MAX) {
+			powerlimit_info->txpwr_lmt.ch_num_5g[cur_idx] = channel_num;
+			powerlimit_info->txpwr_lmt.max_pwr_5g[cur_idx] = power_limit_val;
+			powerlimit_info->txpwr_lmt.ch_cnt_5g++;
+		} else {
+			AICWFDBG(LOGERROR, "band %d chan_cnt reached %d\n",
+				 band_cc, MAC_DOMAINCHANNEL_5G_MAX);
+			AICWFDBG(LOGERROR, "channel=%s(%d) powerLimit=%s(%d)\n",
+				 channel, channel_num, power_limit, power_limit_val);
+		}
+	}
+}
+
+void rwnx_plat_powerlimit_parsing(char *buffer, int size, char *cc)
+{
+#define LD_STAGE_EXC_MAPPING   0
+#define LD_STAGE_TAB_DEFINE    1
+#define LD_STAGE_TAB_START     2
+#define LD_STAGE_COLUMN_DEFINE 3
+#define LD_STAGE_CH_ROW        4
+
+	u8_l loading_stage = LD_STAGE_EXC_MAPPING;
+	u32_l i = 0, for_cnt = 0;
+	u32_l i_cc;
+	char *sz_line, *ptmp;
+	char band[10], col_num_buf[10];
+	u8_l col_num = 0, col_num_cc = 255, band_cc = 0;
+	bool sp_cc = false;
+	// clear powerlimit info at first
+	memset((void *)&powerlimit_info, 0, sizeof(struct powerlimit_info_t));
+	ptmp = buffer;
+	for (sz_line = get_line_from_buffer(&ptmp); sz_line;
+	     sz_line = get_line_from_buffer(&ptmp)) {
+		if (is_all_space_or_tab(sz_line, sizeof(*sz_line)))
+			continue;
+
+		if (is_enable_limit(sz_line) == 0)
+			return;
+
+		if (is_comment_string(sz_line))
+			continue;
+
+		if (loading_stage == LD_STAGE_EXC_MAPPING) {
+			if (sz_line[0] == '#' || sz_line[1] == '#')
+				loading_stage = LD_STAGE_TAB_DEFINE;
+			else
+				continue;
+		}
+
+		if (loading_stage == LD_STAGE_TAB_DEFINE) {
+			/* read "##	2.4G" */
+			if (sz_line[0] != '#' || sz_line[1] != '#')
+				continue;
+
+			/* skip the space */
+			i = 2;
+			while (sz_line[i] == ' ' || sz_line[i] == '\t')
+				++i;
+
+			sz_line[--i] =
+				' '; /* return the space in front of the regulation info */
+
+			/* Parse the label of the table */
+			memset((void *)band, 0, 10);
+			if (!parse_qualified_string(sz_line, &i, band, ' ', ',')) {
+				AICWFDBG(LOGERROR, "Fail to parse band!\n");
+				goto exit;
+			}
+			if (strncmp(band, "2.4G", 4) == 0)
+				band_cc = PHY_BAND_2G4;
+			else if (strncmp(band, "5G", 2) == 0)
+				band_cc = PHY_BAND_5G;
+
+			memset((void *)col_num_buf, 0, 10);
+			if (!parse_qualified_string(sz_line, &i, col_num_buf, '#', '#')) {
+				AICWFDBG(LOGERROR, "Fail to parse column number!\n");
+				goto exit;
+			}
+			if (!get_u1_byte_integer_from_string_in_decimal(col_num_buf, &col_num)) {
+				AICWFDBG(LOGERROR,
+					 "Column number \"%s\" is not unsigned decimal\n",
+					 col_num_buf);
+				goto exit;
+			}
+			if (col_num == 0) {
+				AICWFDBG(LOGERROR, "Column number is 0\n");
+				goto exit;
+			}
+
+			AICWFDBG(LOGDEBUG, "band=%s(%d)\n", band, band_cc);
+			loading_stage = LD_STAGE_TAB_START;
+		} else if (loading_stage == LD_STAGE_TAB_START) {
+			/* read "##	START" */
+			if (sz_line[0] != '#' || sz_line[1] != '#')
+				continue;
+
+			/* skip the space */
+			i = 2;
+			while (sz_line[i] == ' ' || sz_line[i] == '\t')
+				++i;
+
+			if (strncmp((u8 *)(sz_line + i), "START", 5)) {
+				AICWFDBG(LOGERROR, "Missing \"##   START\" label\n");
+				goto exit;
+			}
+
+			loading_stage = LD_STAGE_COLUMN_DEFINE;
+		} else if (loading_stage == LD_STAGE_COLUMN_DEFINE) {
+			/* read "##	CN	US" */
+			if (sz_line[0] != '#' || sz_line[1] != '#')
+				continue;
+
+			/* skip the space */
+			i = 2;
+			while (sz_line[i] == ' ' || sz_line[i] == '\t')
+				++i;
+
+			for (for_cnt = 0; for_cnt < col_num; for_cnt++) {
+				/* skip the space */
+				while (sz_line[i] == ' ' || sz_line[i] == '\t')
+					i++;
+				i_cc = i;
+
+				while (sz_line[i] != ' ' && sz_line[i] != '\t' &&
+				       sz_line[i] != '\0')
+					i++;
+
+				if ((i - i_cc) != 2) {
+					AICWFDBG(LOGERROR, "CC len err\n");
+					goto exit;
+				} else if ((sz_line[i_cc] == cc[0]) &&
+						   (sz_line[i_cc + 1] == cc[1])) {
+					AICWFDBG(LOGDEBUG, "CC matched: %s, col=%d\n", cc, for_cnt);
+					col_num_cc = for_cnt;
+					powerlimit_info.flags |= POWER_LIMIT_CC_MATCHED_BIT;
+					sp_cc = true;
+					break;
+				}
+			}
+			if (!sp_cc) {
+				col_num_cc = col_num - 1;
+				AICWFDBG(LOGDEBUG, "use 00: %s, col_num_cc=%d\n", cc, col_num_cc);
+				powerlimit_info.flags |= POWER_LIMIT_CC_MATCHED_BIT;
+			}
+
+			loading_stage = LD_STAGE_CH_ROW;
+		} else if (loading_stage == LD_STAGE_CH_ROW) {
+			char channel[10] = {0}, power_limit[10] = {0};
+			u8 channel_num, power_limit_val, cnt = 0;
+
+			/* the table ends */
+			if (sz_line[0] == '#' && sz_line[1] == '#') {
+				i = 2;
+				while (sz_line[i] == ' ' || sz_line[i] == '\t')
+					++i;
+
+				if (strncmp((u8 *)(sz_line + i), "END", 3) == 0) {
+					loading_stage = LD_STAGE_TAB_DEFINE;
+					col_num = 0;
+					continue;
+				} else {
+					AICWFDBG(LOGERROR, "Missing \"##   END\" label\n");
+					goto exit;
+				}
+			}
+
+			if ((sz_line[0] != 'c' && sz_line[0] != 'C') ||
+			    (sz_line[1] != 'h' && sz_line[1] != 'H')) {
+				AICWFDBG(LOGERROR, "Wrong channel prefix: '%c','%c'(%d,%d)\n",
+					 sz_line[0], sz_line[1], sz_line[0], sz_line[1]);
+				continue;
+			}
+			i = 2; /* move to the  location behind 'h' */
+
+			/* load the channel number */
+			cnt = 0;
+			while (sz_line[i] >= '0' && sz_line[i] <= '9') {
+				channel[cnt] = sz_line[i];
+				++cnt;
+				++i;
+			}
+
+			for (for_cnt = 0; for_cnt < col_num; ++for_cnt) {
+				/* skip the space between channel number and the power limit
+				 * value
+				 */
+				while (sz_line[i] == ' ' || sz_line[i] == '\t')
+					++i;
+
+				/* load the power limit value */
+				memset((void *)power_limit, 0, 10);
+
+				if (sz_line[i] == 'N' && sz_line[i + 1] == 'A') {
+					/*
+					 * means channel not available
+					 */
+					sprintf(power_limit, "%d", POWER_LIMIT_INVALID_VAL);
+					i += 2;
+				} else if ((sz_line[i] >= '0' && sz_line[i] <= '9') ||
+						   sz_line[i] == '+' || sz_line[i] == '-') {
+					/* case of dBm value */
+					cnt = 0;
+					while ((sz_line[i] >= '0' && sz_line[i] <= '9') ||
+					       sz_line[i] == '+' || sz_line[i] == '-') {
+						power_limit[cnt] = sz_line[i];
+						++cnt;
+						++i;
+					}
+				} else {
+					AICWFDBG
+					(LOGERROR, "Wrong limit expression \"%c%c\"(%d, %d)\n",
+					 sz_line[i], sz_line[i + 1], sz_line[i], sz_line[i + 1]);
+					goto exit;
+				}
+
+				if (for_cnt == col_num_cc) {
+					/* store the power limit value */
+					if (get_u1_byte_integer_from_string_in_decimal
+						((char *)channel, &channel_num) == 0 ||
+					    get_s1_byte_integer_from_string_in_decimal
+						((char *)power_limit, &power_limit_val) == 0) {
+						AICWFDBG(LOGERROR,
+							 "Illegal index of power limit table [ch %s][val %s]\n",
+							 channel, power_limit);
+						goto exit;
+					}
+
+					update_power_limit(band_cc, channel, channel_num,
+							   power_limit, power_limit_val,
+							   &powerlimit_info);
+					break;
+				}
+			}
+		}
+	}
+exit:
+	return;
+}
+
+/// 5G lower bound freq
+#define PHY_FREQ_5G 5000
+
+u16_l phy_channel_to_freq(u8_l band, int channel)
+{
+	if (band == PHY_BAND_2G4 && channel >= 1 && channel <= 14) {
+		if (channel == 14)
+			return 2484;
+		else
+			return 2407 + channel * 5;
+	} else if (band == PHY_BAND_5G && channel >= 1 && channel <= 165) {
+		return PHY_FREQ_5G + channel * 5;
+	}
+	return 0;
+}
+
+s8_l get_powerlimit_by_freq(u8_l band, u16_l freq, u8_l *enable)
+{
+	s8_l ret = POWER_LIMIT_INVALID_VAL;
+	u8_l idx;
+
+	if ((powerlimit_info.flags & POWER_LIMIT_CC_MATCHED_BIT) == 0) {
+		*enable = 0;
+	} else if (powerlimit_info.flags & POWER_LIMIT_CC_MATCHED_BIT) {
+		*enable = 1;
+
+		if (band == PHY_BAND_2G4) {
+			u8_l idx_cnt = powerlimit_info.txpwr_lmt.ch_cnt_2g4;
+
+			for (idx = 0; idx < idx_cnt; idx++) {
+				int ch_num = powerlimit_info.txpwr_lmt.ch_num_2g4[idx];
+				u16_l freq_tmp = phy_channel_to_freq(PHY_BAND_2G4, ch_num);
+
+				if (freq == freq_tmp) {
+					ret = powerlimit_info.txpwr_lmt.max_pwr_2g4[idx];
+					// AICWFDBG(LOGINFO, "[%d]: ch=%d(freq=%d), pwr=%d\n", idx,
+					// ch_num, freq, ret);
+					break;
+				}
+			}
+			if (idx == idx_cnt) {
+				AICWFDBG(LOGERROR,
+					 "powerlimit search failed: band=%d freq=%d\n", band,
+					 freq);
+			}
+		} else if (band == PHY_BAND_5G) {
+			u8_l idx_cnt = powerlimit_info.txpwr_lmt.ch_cnt_5g;
+
+			for (idx = 0; idx < idx_cnt; idx++) {
+				int ch_num = powerlimit_info.txpwr_lmt.ch_num_5g[idx];
+				u16_l freq_tmp = phy_channel_to_freq(PHY_BAND_5G, ch_num);
+
+				if (freq == freq_tmp) {
+					ret = powerlimit_info.txpwr_lmt.max_pwr_5g[idx];
+					// AICWFDBG(LOGINFO, "[%d]: ch=%d(freq=%d), pwr=%d\n", idx,
+					// ch_num, freq, ret);
+					break;
+				}
+			}
+			if (idx == idx_cnt) {
+				AICWFDBG(LOGERROR,
+					 "powerlimit search failed: band=%d freq=%d\n", band,
+					 freq);
+			}
+		}
+	}
+	return ret;
+}
+
+s8_l get_powerlimit_by_chnum(u8_l chnum, u8_l *enable)
+{
+	s8_l ret = POWER_LIMIT_INVALID_VAL;
+	u8_l idx;
+
+	if ((powerlimit_info.flags & POWER_LIMIT_CC_MATCHED_BIT) == 0) {
+		*enable = 0;
+	} else if (powerlimit_info.flags & POWER_LIMIT_CC_MATCHED_BIT) {
+		*enable = 1;
+
+		if (chnum <= 14) {
+			u8_l idx_cnt = powerlimit_info.txpwr_lmt.ch_cnt_2g4;
+
+			for (idx = 0; idx < idx_cnt; idx++) {
+				u8_l ch_num = powerlimit_info.txpwr_lmt.ch_num_2g4[idx];
+
+				if (chnum == ch_num) {
+					ret = powerlimit_info.txpwr_lmt.max_pwr_2g4[idx];
+					// AICWFDBG(LOGINFO, "[%d]: ch=%d, pwr=%d\n", idx, ch_num,
+					// ret);
+					break;
+				}
+			}
+			if (idx == idx_cnt) {
+				AICWFDBG(LOGERROR, "powerlimit search failed: chnum=%d\n",
+					 chnum);
+			}
+		} else if (chnum <= 165) {
+			u8_l idx_cnt = powerlimit_info.txpwr_lmt.ch_cnt_5g;
+
+			for (idx = 0; idx < idx_cnt; idx++) {
+				int ch_num = powerlimit_info.txpwr_lmt.ch_num_5g[idx];
+
+				if (chnum == ch_num) {
+					ret = powerlimit_info.txpwr_lmt.max_pwr_5g[idx];
+					// AICWFDBG(LOGINFO, "[%d]: ch=%d, pwr=%d\n", idx, ch_num,
+					// ret);
+					break;
+				}
+			}
+			if (idx == idx_cnt) {
+				AICWFDBG(LOGERROR, "powerlimit search failed: chnum=%d\n",
+					 chnum);
+			}
+		}
+	}
+	return ret;
+}
+#endif
+
+/**
+ * rwnx_platform_reset() - Reset the platform
+ *
+ * @rwnx_plat: platform data
+ */
+static int rwnx_platform_reset(struct rwnx_plat *rwnx_plat)
+{
+	u32 regval;
+
+#if defined(AICWF_SDIO_SUPPORT)
+	return 0;
+#endif
+
+	/*
+	 * the doc states that SOFT implies FPGA_B_RESET
+	 * adding FPGA_B_RESET is clearer
+	 */
+	RWNX_REG_WRITE(SOFT_RESET | FPGA_B_RESET, rwnx_plat, RWNX_ADDR_SYSTEM,
+		       SYSCTRL_MISC_CNTL_ADDR);
+	msleep(100);
+
+	regval = RWNX_REG_READ(rwnx_plat, RWNX_ADDR_SYSTEM, SYSCTRL_MISC_CNTL_ADDR);
+
+	if (regval & SOFT_RESET) {
+		dev_err(rwnx_platform_get_dev(rwnx_plat), "reset: failed\n");
+		return -EIO;
+	}
+
+	RWNX_REG_WRITE(regval & ~FPGA_B_RESET, rwnx_plat, RWNX_ADDR_SYSTEM,
+		       SYSCTRL_MISC_CNTL_ADDR);
+	msleep(100);
+	return 0;
+}
+
+/**
+ * rwmx_platform_save_config() - Save hardware config before reload
+ *
+ * @rwnx_plat: Pointer to platform data
+ *
+ * Return configuration registers values.
+ */
+static void *rwnx_term_save_config(struct rwnx_plat *rwnx_plat)
+{
+	const u32 *reg_list;
+	u32 *reg_value, *res;
+	int i, size = 0;
+
+	if (rwnx_plat->get_config_reg)
+		size = rwnx_plat->get_config_reg(rwnx_plat, &reg_list);
+
+	if (size <= 0)
+		return NULL;
+
+	// res = kmalloc(sizeof(u32) * size, GFP_KERNEL);
+	res = kmalloc_array(size, sizeof(u32), GFP_KERNEL);
+	if (!res)
+		return NULL;
+
+	reg_value = res;
+	for (i = 0; i < size; i++)
+		*reg_value++ = RWNX_REG_READ(rwnx_plat, RWNX_ADDR_SYSTEM, *reg_list++);
+
+	return res;
+}
+
+void get_userconfig_txpwr_lvl_in_fdrv(struct txpwr_lvl_conf *txpwr_lvl)
+{
+	txpwr_lvl->enable = userconfig_info.txpwr_lvl.enable;
+	txpwr_lvl->dsss = userconfig_info.txpwr_lvl.dsss;
+	txpwr_lvl->ofdmlowrate_2g4 = userconfig_info.txpwr_lvl.ofdmlowrate_2g4;
+	txpwr_lvl->ofdm64qam_2g4 = userconfig_info.txpwr_lvl.ofdm64qam_2g4;
+	txpwr_lvl->ofdm256qam_2g4 = userconfig_info.txpwr_lvl.ofdm256qam_2g4;
+	txpwr_lvl->ofdm1024qam_2g4 = userconfig_info.txpwr_lvl.ofdm1024qam_2g4;
+	txpwr_lvl->ofdmlowrate_5g = userconfig_info.txpwr_lvl.ofdmlowrate_5g;
+	txpwr_lvl->ofdm64qam_5g = userconfig_info.txpwr_lvl.ofdm64qam_5g;
+	txpwr_lvl->ofdm256qam_5g = userconfig_info.txpwr_lvl.ofdm256qam_5g;
+	txpwr_lvl->ofdm1024qam_5g = userconfig_info.txpwr_lvl.ofdm1024qam_5g;
+
+	AICWFDBG(LOGDEBUG, "%s:enable:%d\r\n", __func__, txpwr_lvl->enable);
+	AICWFDBG(LOGDEBUG, "%s:dsss:%d\r\n", __func__, txpwr_lvl->dsss);
+	AICWFDBG(LOGDEBUG, "%s:ofdmlowrate_2g4:%d\r\n", __func__,
+		 txpwr_lvl->ofdmlowrate_2g4);
+	AICWFDBG(LOGDEBUG, "%s:ofdm64qam_2g4:%d\r\n", __func__,
+		 txpwr_lvl->ofdm64qam_2g4);
+	AICWFDBG(LOGDEBUG, "%s:ofdm256qam_2g4:%d\r\n", __func__,
+		 txpwr_lvl->ofdm256qam_2g4);
+	AICWFDBG(LOGDEBUG, "%s:ofdm1024qam_2g4:%d\r\n", __func__,
+		 txpwr_lvl->ofdm1024qam_2g4);
+	AICWFDBG(LOGDEBUG, "%s:ofdmlowrate_5g:%d\r\n", __func__,
+		 txpwr_lvl->ofdmlowrate_5g);
+	AICWFDBG(LOGDEBUG, "%s:ofdm64qam_5g:%d\r\n", __func__,
+		 txpwr_lvl->ofdm64qam_5g);
+	AICWFDBG(LOGDEBUG, "%s:ofdm256qam_5g:%d\r\n", __func__,
+		 txpwr_lvl->ofdm256qam_5g);
+	AICWFDBG(LOGDEBUG, "%s:ofdm1024qam_5g:%d\r\n", __func__,
+		 txpwr_lvl->ofdm1024qam_5g);
+}
+
+void get_userconfig_txpwr_lvl_v2_in_fdrv(struct txpwr_lvl_conf_v2 *txpwr_lvl_v2)
+{
+	*txpwr_lvl_v2 = userconfig_info.txpwr_lvl_v2;
+
+	AICWFDBG(LOGDEBUG, "%s:enable:%d\r\n", __func__, txpwr_lvl_v2->enable);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11b_11ag_1m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[0]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11b_11ag_2m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[1]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11b_11ag_5m5_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[2]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11b_11ag_11m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[3]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11b_11ag_6m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[4]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11b_11ag_9m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[5]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11b_11ag_12m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[6]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11b_11ag_18m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[7]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11b_11ag_24m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[8]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11b_11ag_36m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[9]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11b_11ag_48m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[10]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11b_11ag_54m_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11b_11ag_2g4[11]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11n_11ac_mcs0_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[0]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11n_11ac_mcs1_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[1]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11n_11ac_mcs2_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[2]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11n_11ac_mcs3_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[3]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11n_11ac_mcs4_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[4]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11n_11ac_mcs5_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[5]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11n_11ac_mcs6_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[6]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11n_11ac_mcs7_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[7]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11n_11ac_mcs8_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[8]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11n_11ac_mcs9_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11n_11ac_2g4[9]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11ax_mcs0_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[0]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11ax_mcs1_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[1]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11ax_mcs2_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[2]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11ax_mcs3_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[3]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11ax_mcs4_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[4]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11ax_mcs5_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[5]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11ax_mcs6_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[6]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11ax_mcs7_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[7]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11ax_mcs8_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[8]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11ax_mcs9_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[9]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11ax_mcs10_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[10]);
+	AICWFDBG(LOGDEBUG, "%s:lvl_11ax_mcs11_2g4:%d\r\n", __func__,
+		 txpwr_lvl_v2->pwrlvl_11ax_2g4[11]);
+}
+
+void get_userconfig_txpwr_lvl_group_in_fdrv(struct txpwr_lvl_conf_v3 *txpwr_lvl_v3, int index_reg)
+{
+	*txpwr_lvl_v3 = userconfig_info.txpwr_lvl_v3_gp[index_reg];
+
+	AICWFDBG(LOGDEBUG, "%s:enable:%d\r\n", __func__, txpwr_lvl_v3->enable);
+	AICWFDBG(LOGDEBUG, "lvl_11b_11ag_1m_2g4:%d\r\n", txpwr_lvl_v3->pwrlvl_11b_11ag_2g4[0]);
+}
+
+void get_userconfig_txpwr_lvl_v3_in_fdrv(struct txpwr_lvl_conf_v3 *txpwr_lvl_v3)
+{
+	*txpwr_lvl_v3 = userconfig_info.txpwr_lvl_v3;
+
+	AICWFDBG(LOGDEBUG, "%s:enable:%d\r\n", __func__, txpwr_lvl_v3->enable);
+	AICWFDBG(LOGDEBUG, "lvl_11b_11ag_1m_2g4:%d\r\n", txpwr_lvl_v3->pwrlvl_11b_11ag_2g4[0]);
+}
+
+void get_userconfig_txpwr_lvl_adj_in_fdrv(struct txpwr_lvl_adj_conf *txpwr_lvl_adj)
+{
+	*txpwr_lvl_adj = userconfig_info.txpwr_lvl_adj;
+
+	AICWFDBG(LOGDEBUG, "%s:enable:%d\r\n", __func__, txpwr_lvl_adj->enable);
+	AICWFDBG(LOGDEBUG, "lvl_adj_2g4_chan_1_4:%d\r\n", txpwr_lvl_adj->pwrlvl_adj_tbl_2g4[0]);
+}
+
+/**
+ * rwnx_plat_userconfig_load  ---Load aic_userconfig.txt
+ *@filename name of config
+ */
+static int rwnx_plat_userconfig_load(struct rwnx_hw *rwnx_hw)
+{
+	aic_chip_userconfig_load(rwnx_hw);
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+	aic_chip_powerlimit_load(rwnx_hw);
+#endif
+	return 0;
+}
+
+void get_userconfig_txpwr_loss(struct txpwr_loss_conf *txpwr_loss)
+{
+	txpwr_loss->loss_enable_2g4 = userconfig_info.txpwr_loss.loss_enable_2g4;
+	txpwr_loss->loss_value_2g4 = userconfig_info.txpwr_loss.loss_value_2g4;
+	txpwr_loss->loss_enable_5g = userconfig_info.txpwr_loss.loss_enable_5g;
+	txpwr_loss->loss_value_5g = userconfig_info.txpwr_loss.loss_value_5g;
+
+	AICWFDBG(LOGDEBUG,
+		 "%s:loss_enable_2g4: %d, val_2g4: %d, loss_enable_5g: %d, val_5g: %d\r\n",
+		 __func__,
+		 txpwr_loss->loss_enable_2g4, txpwr_loss->loss_value_2g4,
+		 txpwr_loss->loss_enable_5g, txpwr_loss->loss_value_5g);
+}
+
+/**
+ * rwnx_platform_on() - Start the platform
+ *
+ * @rwnx_hw: Main driver data
+ * @config: Config to restore (NULL if nothing to restore)
+ *
+ * It starts the platform :
+ * - load fw and ucodes
+ * - initialize IPC
+ * - boot the fw
+ * - enable link communication/IRQ
+ *
+ * Called by 802.11 part
+ */
+int rwnx_platform_on(struct rwnx_hw *rwnx_hw, void *config)
+{
+	int ret;
+	struct rwnx_plat *rwnx_plat = rwnx_hw->plat;
+	(void)ret;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	if (rwnx_plat->enabled)
+		return 0;
+
+	rwnx_plat_userconfig_load(rwnx_hw);
+	// rwnx_plat->enabled = true;
+	return 0;
+}
+
+/**
+ * rwnx_platform_off() - Stop the platform
+ *
+ * @rwnx_hw: Main driver data
+ * @config: Updated with pointer to config, to be able to restore it with
+ * rwnx_platform_on(). It's up to the caller to free the config. Set to NULL
+ * if configuration is not needed.
+ *
+ * Called by 802.11 part
+ */
+void rwnx_platform_off(struct rwnx_hw *rwnx_hw, void **config)
+{
+#if defined(AICWF_SDIO_SUPPORT)
+	tasklet_kill(&rwnx_hw->task);
+	rwnx_hw->plat->enabled = false;
+	return;
+#endif
+
+	if (!rwnx_hw->plat->enabled) {
+		if (config)
+			*config = NULL;
+		return;
+	}
+
+	if (config)
+		*config = rwnx_term_save_config(rwnx_hw->plat);
+
+	rwnx_hw->plat->disable(rwnx_hw);
+
+	tasklet_kill(&rwnx_hw->task);
+	rwnx_platform_reset(rwnx_hw->plat);
+
+	rwnx_hw->plat->enabled = false;
+}
+
+/**
+ * rwnx_platform_init() - Initialize the platform
+ *
+ * @rwnx_plat: platform data (already updated by platform driver)
+ * @platform_data: Pointer to store the main driver data pointer (aka rwnx_hw)
+ *                That will be set as driver data for the platform driver
+ * Return: 0 on success, < 0 otherwise
+ *
+ * Called by the platform driver after it has been probed
+ */
+int rwnx_platform_init(struct rwnx_plat *rwnx_plat, void **platform_data)
+{
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	rwnx_plat->enabled = false;
+	g_rwnx_plat = rwnx_plat;
+
+	return rwnx_cfg80211_init(rwnx_plat, platform_data);
+}
+
+/**
+ * rwnx_platform_deinit() - Deinitialize the platform
+ *
+ * @rwnx_hw: main driver data
+ *
+ * Called by the platform driver after it is removed
+ */
+void rwnx_platform_deinit(struct rwnx_hw *rwnx_hw)
+{
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	rwnx_cfg80211_deinit(rwnx_hw);
+}
+
+struct device *rwnx_platform_get_dev(struct rwnx_plat *rwnx_plat)
+{
+#ifdef AICWF_SDIO_SUPPORT
+	return rwnx_plat->sdiodev->dev;
+#endif
+}
+
+MODULE_FIRMWARE(AIC8800_FW_DIR RWNX_AGC_FW_NAME);
+MODULE_FIRMWARE(AIC8800_FW_DIR RWNX_FCU_FW_NAME);
+MODULE_FIRMWARE(AIC8800_FW_DIR RWNX_LDPC_RAM_NAME);
+
+MODULE_FIRMWARE(AIC8800_FW_DIR RWNX_MAC_FW_NAME);
+#ifndef CONFIG_RWNX_TL4
+MODULE_FIRMWARE(AIC8800_FW_DIR RWNX_MAC_FW_NAME2);
+#endif
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_platform.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_platform.h
new file mode 100644
index 0000000000000..ef12cd2af4602
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_platform.h
@@ -0,0 +1,186 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_PLATFORM_H_
+#define _RWNX_PLATFORM_H_
+
+#include "lmac_msg.h"
+#include <linux/pci.h>
+
+#define AIC8800_FW_DIR "aic8800/"
+
+#define RWNX_CONFIG_FW_NAME        "rwnx_settings.ini"
+#define RWNX_PHY_CONFIG_TRD_NAME   "rwnx_trident.ini"
+#define RWNX_PHY_CONFIG_KARST_NAME "rwnx_karst.ini"
+#define RWNX_AGC_FW_NAME           "agcram.bin"
+#define RWNX_LDPC_RAM_NAME         "ldpcram.bin"
+
+#define RWNX_MAC_FW_BASE_NAME "fmacfw"
+
+#ifdef CONFIG_RWNX_TL4
+#define RWNX_MAC_FW_NAME RWNX_MAC_FW_BASE_NAME ".hex"
+#else
+#define RWNX_MAC_FW_NAME  RWNX_MAC_FW_BASE_NAME ".ihex"
+#define RWNX_MAC_FW_NAME2 RWNX_MAC_FW_BASE_NAME ".bin"
+#endif
+
+#define RWNX_FCU_FW_NAME "fcuram.bin"
+#if (defined(CONFIG_DPD) && !defined(CONFIG_FORCE_DPD_CALIB))
+#define FW_DPDRESULT_NAME_8800DC "aic_dpdresult_lite_8800dc.bin"
+#endif
+
+#define POWER_LEVEL_INVALID_VAL (127)
+
+/**
+ * Type of memory to access (cf rwnx_plat.get_address)
+ *
+ * @RWNX_ADDR_CPU To access memory of the embedded CPU
+ * @RWNX_ADDR_SYSTEM To access memory/registers of one subsystem of the
+ * embedded system
+ *
+ */
+enum rwnx_platform_addr {
+	RWNX_ADDR_CPU,
+	RWNX_ADDR_SYSTEM,
+	RWNX_ADDR_MAX,
+};
+
+enum regions_code {
+	REGIONS_SRRC,
+	REGIONS_FCC,
+	REGIONS_ETSI,
+	REGIONS_JP,
+	REGIONS_DEFAULT,
+};
+
+struct rwnx_hw;
+struct device;
+struct firmware;
+
+int aicwf_request_firmware(const struct firmware **fw, const char *name,
+			   struct device *dev);
+
+/**
+ * struct rwnx_plat - Operation pointers for RWNX PCI platform
+ *
+ * @pci_dev: pointer to pci dev
+ * @enabled: Set if embedded platform has been enabled (i.e. fw loaded and
+ *          ipc started)
+ * @enable: Configure communication with the fw (i.e. configure the transfers
+ *         enable and register interrupt)
+ * @disable: Stop communication with the fw
+ * @deinit: Free all resources allocated for the embedded platform
+ * @get_address: Return the virtual address to access the requested address on
+ *              the platform.
+ * @ack_irq: Acknowledge the irq at link level.
+ * @get_config_reg: Return the list (size + pointer) of registers to restore in
+ * order to reload the platform while keeping the current configuration.
+ *
+ * @priv Private data for the link driver
+ */
+struct rwnx_plat {
+	struct pci_dev *pci_dev;
+
+#ifdef AICWF_SDIO_SUPPORT
+	struct aic_sdio_dev *sdiodev;
+#endif
+
+	bool enabled;
+
+	int (*enable)(struct rwnx_hw *rwnx_hw);
+	int (*disable)(struct rwnx_hw *rwnx_hw);
+	void (*deinit)(struct rwnx_plat *rwnx_plat);
+	u8 *(*get_address)(struct rwnx_plat *rwnx_plat, int addr_name,
+			   unsigned int offset);
+	void (*ack_irq)(struct rwnx_plat *rwnx_plat);
+	int (*get_config_reg)(struct rwnx_plat *rwnx_plat, const u32 **list);
+
+	u8 priv[0] __aligned(sizeof(void *));
+};
+
+static inline void *RWNX_ADDR(void *plat, int base, int offset)
+{
+	struct rwnx_plat *p = (struct rwnx_plat *)plat;
+
+	return p->get_address(p, base, offset);
+}
+
+static inline u32 RWNX_REG_READ(void *plat, int base, int offset)
+{
+	struct rwnx_plat *p = (struct rwnx_plat *)plat;
+
+	return readl(p->get_address(p, base, offset));
+}
+
+static inline void RWNX_REG_WRITE(u32 val, void *plat, int base, int offset)
+{
+	struct rwnx_plat *p = (struct rwnx_plat *)plat;
+
+	writel(val, p->get_address(p, base, offset));
+}
+
+extern struct rwnx_plat *g_rwnx_plat;
+
+int rwnx_platform_init(struct rwnx_plat *rwnx_plat, void **platform_data);
+void rwnx_platform_deinit(struct rwnx_hw *rwnx_hw);
+
+int rwnx_platform_on(struct rwnx_hw *rwnx_hw, void *config);
+void rwnx_platform_off(struct rwnx_hw *rwnx_hw, void **config);
+
+int rwnx_platform_register_drv(void);
+void rwnx_platform_unregister_drv(void);
+
+void get_userconfig_txpwr_idx(struct txpwr_idx_conf *txpwr_idx);
+void get_userconfig_txpwr_ofst(struct txpwr_ofst_conf *txpwr_ofst);
+void get_userconfig_xtal_cap(struct xtal_cap_conf *xtal_cap);
+s8_l get_txpwr_max(s8_l power);
+void set_txpwr_loss_ofst(s8_l value);
+
+int get_ccode_region(char *ccode);
+
+void rwnx_release_firmware_common(u32 **buffer);
+
+int rwnx_plat_userconfig_upload(struct rwnx_hw *rwnx_hw,
+				const char *filename);
+void rwnx_plat_userconfig_parsing(struct rwnx_hw *rwnx_hw, char *buffer, int size);
+void rwnx_plat_userconfig_parsing2(char *buffer, int size);
+void rwnx_plat_userconfig_parsing3(char *buffer, int size);
+
+void get_userconfig_txpwr_lvl_in_fdrv(struct txpwr_lvl_conf *txpwr_lvl);
+void get_userconfig_txpwr_lvl_v2_in_fdrv(struct txpwr_lvl_conf_v2 *txpwr_lvl_v2);
+void get_userconfig_txpwr_lvl_group_in_fdrv(struct txpwr_lvl_conf_v3 *txpwr_lvl_v3, int index_reg);
+void get_userconfig_txpwr_lvl_v3_in_fdrv(struct txpwr_lvl_conf_v3 *txpwr_lvl_v3);
+void get_userconfig_txpwr_lvl_adj_in_fdrv(struct txpwr_lvl_adj_conf *txpwr_lvl_adj);
+
+void get_userconfig_txpwr_ofst_in_fdrv(struct txpwr_ofst_conf *txpwr_ofst);
+void get_userconfig_txpwr_ofst2x_in_fdrv(struct txpwr_ofst2x_conf *txpwr_ofst2x);
+void get_userconfig_txpwr_loss(struct txpwr_loss_conf *txpwr_loss);
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+int is_all_space_or_tab(u8_l *data, u8_l size);
+int is_comment_string(char *sz_str);
+int is_enable_limit(char *sz_str);
+int parse_qualified_string(char *in, u32 *start, char *out, char left_qualifier,
+			   char right_qualifier);
+int get_u1_byte_integer_from_string_in_decimal(char *str, u8 *p_int);
+int get_s1_byte_integer_from_string_in_decimal(char *str, s8 *val);
+void rwnx_plat_powerlimit_parsing(char *buffer, int size, char *cc);
+s8_l get_powerlimit_by_freq(u8_l band, u16_l freq, u8_l *enable);
+s8_l get_powerlimit_by_chnum(u8_l chnum, u8_l *enable);
+u16_l phy_channel_to_freq(u8_l band, int channel);
+#endif
+
+struct device *rwnx_platform_get_dev(struct rwnx_plat *rwnx_plat);
+
+int rwnx_request_firmware_common(struct rwnx_hw *rwnx_hw, u32 **buffer,
+				 const char *filename);
+int rwnx_plat_bin_fw_upload_2(struct rwnx_hw *rwnx_hw, u32 fw_addr,
+			      char *filename);
+int rwnx_atoi(char *value);
+void rwnx_plat_nvram_set_value(char *command, char *value);
+void rwnx_plat_nvram_set_value_group(char *command, char *value);
+void rwnx_plat_nvram_set_value_v3(char *command, char *value);
+
+static inline unsigned int rwnx_platform_get_irq(struct rwnx_plat *rwnx_plat)
+{
+	return rwnx_plat->pci_dev->irq;
+}
+
+#endif /* _RWNX_PLATFORM_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_prof.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_prof.h
new file mode 100644
index 0000000000000..093e243f3c94f
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_prof.h
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_PROF_H_
+#define _RWNX_PROF_H_
+
+#include "reg_access.h"
+#include "rwnx_platform.h"
+#include "rwnx_radar.h"
+
+static inline void rwnx_prof_set(struct rwnx_hw *rwnx_hw, int val)
+{
+	struct rwnx_plat *rwnx_plat = rwnx_hw->plat;
+
+	RWNX_REG_WRITE(val, rwnx_plat, RWNX_ADDR_SYSTEM,
+		       NXMAC_SW_SET_PROFILING_ADDR);
+}
+
+static inline void rwnx_prof_clear(struct rwnx_hw *rwnx_hw, int val)
+{
+	struct rwnx_plat *rwnx_plat = rwnx_hw->plat;
+
+	RWNX_REG_WRITE(val, rwnx_plat, RWNX_ADDR_SYSTEM,
+		       NXMAC_SW_CLEAR_PROFILING_ADDR);
+}
+
+enum {
+	SW_PROF_HOSTBUF_IDX = 12,
+	/****** IPC IRQs related signals ******/
+	/* E2A direction */
+	SW_PROF_IRQ_E2A_RXDESC =
+		16, // to make sure we let 16 bits available for LMAC FW
+	SW_PROF_IRQ_E2A_TXCFM,
+	SW_PROF_IRQ_E2A_DBG,
+	SW_PROF_IRQ_E2A_MSG,
+	SW_PROF_IPC_MSGPUSH,
+	SW_PROF_MSGALLOC,
+	SW_PROF_MSGIND,
+	SW_PROF_DBGIND,
+
+	/* A2E direction */
+	SW_PROF_IRQ_A2E_TXCFM_BACK,
+
+	/****** Driver functions related signals ******/
+	SW_PROF_WAIT_QUEUE_STOP,
+	SW_PROF_WAIT_QUEUE_WAKEUP,
+	SW_PROF_RWNXDATAIND,
+	SW_PROF_RWNX_IPC_IRQ_HDLR,
+	SW_PROF_RWNX_IPC_THR_IRQ_HDLR,
+	SW_PROF_IEEE80211RX,
+	SW_PROF_RWNX_PATTERN,
+	SW_PROF_MAX
+};
+
+// [LT]For debug purpose only
+#if (0)
+#define SW_PROF_CHAN_CTXT_CFM_HDL_BIT    (21)
+#define SW_PROF_CHAN_CTXT_CFM_BIT        (22)
+#define SW_PROF_CHAN_CTXT_CFM_SWDONE_BIT (23)
+#define SW_PROF_CHAN_CTXT_PUSH_BIT       (24)
+#define SW_PROF_CHAN_CTXT_QUEUE_BIT      (25)
+#define SW_PROF_CHAN_CTXT_TX_BIT         (26)
+#define SW_PROF_CHAN_CTXT_TX_PAUSE_BIT   (27)
+#define SW_PROF_CHAN_CTXT_PSWTCH_BIT     (28)
+#define SW_PROF_CHAN_CTXT_SWTCH_BIT      (29)
+
+#else
+#define SW_PROF_CHAN_CTXT_CFM_HDL_BIT    (0)
+#define SW_PROF_CHAN_CTXT_CFM_BIT        (0)
+#define SW_PROF_CHAN_CTXT_CFM_SWDONE_BIT (0)
+#define SW_PROF_CHAN_CTXT_PUSH_BIT       (0)
+#define SW_PROF_CHAN_CTXT_QUEUE_BIT      (0)
+#define SW_PROF_CHAN_CTXT_TX_BIT         (0)
+#define SW_PROF_CHAN_CTXT_TX_PAUSE_BIT   (0)
+#define SW_PROF_CHAN_CTXT_PSWTCH_BIT     (0)
+#define SW_PROF_CHAN_CTXT_SWTCH_BIT      (0)
+#endif
+
+static inline void REG_SW_SET_PROFILING_CHAN(void *env, int bit)
+{
+#if (0)
+	rwnx_prof_clear((struct rwnx_hw *)env, BIT(bit));
+#else
+
+#endif
+}
+
+static inline void REG_SW_CLEAR_PROFILING_CHAN(void *env, int bit)
+{
+#if (0)
+	rwnx_prof_clear((struct rwnx_hw *)env, BIT(bit));
+#else
+
+#endif
+}
+
+#ifdef CONFIG_RWNX_SW_PROFILING
+/* Macros for SW PRofiling registers access */
+static inline void REG_SW_SET_PROFILING(void *env, int bit)
+{
+	rwnx_prof_set((struct rwnx_hw *)env, BIT(bit));
+}
+
+static inline void REG_SW_SET_HOSTBUF_IDX_PROFILING(void *env, int val)
+{
+	rwnx_prof_set((struct rwnx_hw *)env, (val) << (SW_PROF_HOSTBUF_IDX));
+}
+
+static inline void REG_SW_CLEAR_PROFILING(void *env, int bit)
+{
+	rwnx_prof_clear((struct rwnx_hw *)env, BIT(bit));
+}
+
+static inline void REG_SW_CLEAR_HOSTBUF_IDX_PROFILING(void *env)
+{
+	rwnx_prof_clear((struct rwnx_hw *)env, 0x0F << (SW_PROF_HOSTBUF_IDX));
+}
+
+#else
+static inline void REG_SW_SET_PROFILING(void *env, int value)
+{
+	(void)env;
+	(void)value;
+}
+
+static inline void REG_SW_CLEAR_PROFILING(void *env, int value)
+{
+	(void)env;
+	(void)value;
+}
+
+static inline void REG_SW_SET_HOSTBUF_IDX_PROFILING(void *env, int val)
+{
+	(void)env;
+	(void)val;
+}
+
+static inline void REG_SW_CLEAR_HOSTBUF_IDX_PROFILING(void *env)
+{
+	(void)env;
+}
+#endif
+
+#endif /* _RWNX_PROF_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_strs.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_strs.c
new file mode 100644
index 0000000000000..49e7ce3560b49
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_strs.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_strs.c
+ *
+ * @brief Miscellaneous debug strings
+ *
+ ****************************************************************************************
+ */
+
+#include "lmac_msg.h"
+static const char *const rwnx_mmid2str[MSG_I(MM_MAX)] = {
+	[MSG_I(MM_RESET_REQ)] = "MM_RESET_REQ",
+	[MSG_I(MM_RESET_CFM)] = "MM_RESET_CFM",
+	[MSG_I(MM_START_REQ)] = "MM_START_REQ",
+	[MSG_I(MM_START_CFM)] = "MM_START_CFM",
+	[MSG_I(MM_VERSION_REQ)] = "MM_VERSION_REQ",
+	[MSG_I(MM_VERSION_CFM)] = "MM_VERSION_CFM",
+	[MSG_I(MM_ADD_IF_REQ)] = "MM_ADD_IF_REQ",
+	[MSG_I(MM_ADD_IF_CFM)] = "MM_ADD_IF_CFM",
+	[MSG_I(MM_REMOVE_IF_REQ)] = "MM_REMOVE_IF_REQ",
+	[MSG_I(MM_REMOVE_IF_CFM)] = "MM_REMOVE_IF_CFM",
+	[MSG_I(MM_STA_ADD_REQ)] = "MM_STA_ADD_REQ",
+	[MSG_I(MM_STA_ADD_CFM)] = "MM_STA_ADD_CFM",
+	[MSG_I(MM_STA_DEL_REQ)] = "MM_STA_DEL_REQ",
+	[MSG_I(MM_STA_DEL_CFM)] = "MM_STA_DEL_CFM",
+	[MSG_I(MM_SET_FILTER_REQ)] = "MM_SET_FILTER_REQ",
+	[MSG_I(MM_SET_FILTER_CFM)] = "MM_SET_FILTER_CFM",
+	[MSG_I(MM_SET_CHANNEL_REQ)] = "MM_SET_CHANNEL_REQ",
+	[MSG_I(MM_SET_CHANNEL_CFM)] = "MM_SET_CHANNEL_CFM",
+	[MSG_I(MM_SET_DTIM_REQ)] = "MM_SET_DTIM_REQ",
+	[MSG_I(MM_SET_DTIM_CFM)] = "MM_SET_DTIM_CFM",
+	[MSG_I(MM_SET_BEACON_INT_REQ)] = "MM_SET_BEACON_INT_REQ",
+	[MSG_I(MM_SET_BEACON_INT_CFM)] = "MM_SET_BEACON_INT_CFM",
+	[MSG_I(MM_SET_BASIC_RATES_REQ)] = "MM_SET_BASIC_RATES_REQ",
+	[MSG_I(MM_SET_BASIC_RATES_CFM)] = "MM_SET_BASIC_RATES_CFM",
+	[MSG_I(MM_SET_BSSID_REQ)] = "MM_SET_BSSID_REQ",
+	[MSG_I(MM_SET_BSSID_CFM)] = "MM_SET_BSSID_CFM",
+	[MSG_I(MM_SET_EDCA_REQ)] = "MM_SET_EDCA_REQ",
+	[MSG_I(MM_SET_EDCA_CFM)] = "MM_SET_EDCA_CFM",
+	[MSG_I(MM_SET_MODE_REQ)] = "MM_SET_MODE_REQ",
+	[MSG_I(MM_SET_MODE_CFM)] = "MM_SET_MODE_CFM",
+	[MSG_I(MM_SET_VIF_STATE_REQ)] = "MM_SET_VIF_STATE_REQ",
+	[MSG_I(MM_SET_VIF_STATE_CFM)] = "MM_SET_VIF_STATE_CFM",
+	[MSG_I(MM_SET_SLOTTIME_REQ)] = "MM_SET_SLOTTIME_REQ",
+	[MSG_I(MM_SET_SLOTTIME_CFM)] = "MM_SET_SLOTTIME_CFM",
+	[MSG_I(MM_SET_IDLE_REQ)] = "MM_SET_IDLE_REQ",
+	[MSG_I(MM_SET_IDLE_CFM)] = "MM_SET_IDLE_CFM",
+	[MSG_I(MM_KEY_ADD_REQ)] = "MM_KEY_ADD_REQ",
+	[MSG_I(MM_KEY_ADD_CFM)] = "MM_KEY_ADD_CFM",
+	[MSG_I(MM_KEY_DEL_REQ)] = "MM_KEY_DEL_REQ",
+	[MSG_I(MM_KEY_DEL_CFM)] = "MM_KEY_DEL_CFM",
+	[MSG_I(MM_BA_ADD_REQ)] = "MM_BA_ADD_REQ",
+	[MSG_I(MM_BA_ADD_CFM)] = "MM_BA_ADD_CFM",
+	[MSG_I(MM_BA_DEL_REQ)] = "MM_BA_DEL_REQ",
+	[MSG_I(MM_BA_DEL_CFM)] = "MM_BA_DEL_CFM",
+	[MSG_I(MM_PRIMARY_TBTT_IND)] = "MM_PRIMARY_TBTT_IND",
+	[MSG_I(MM_SECONDARY_TBTT_IND)] = "MM_SECONDARY_TBTT_IND",
+	[MSG_I(MM_SET_POWER_REQ)] = "MM_SET_POWER_REQ",
+	[MSG_I(MM_SET_POWER_CFM)] = "MM_SET_POWER_CFM",
+	[MSG_I(MM_DBG_TRIGGER_REQ)] = "MM_DBG_TRIGGER_REQ",
+	[MSG_I(MM_SET_PS_MODE_REQ)] = "MM_SET_PS_MODE_REQ",
+	[MSG_I(MM_SET_PS_MODE_CFM)] = "MM_SET_PS_MODE_CFM",
+	[MSG_I(MM_CHAN_CTXT_ADD_REQ)] = "MM_CHAN_CTXT_ADD_REQ",
+	[MSG_I(MM_CHAN_CTXT_ADD_CFM)] = "MM_CHAN_CTXT_ADD_CFM",
+	[MSG_I(MM_CHAN_CTXT_DEL_REQ)] = "MM_CHAN_CTXT_DEL_REQ",
+	[MSG_I(MM_CHAN_CTXT_DEL_CFM)] = "MM_CHAN_CTXT_DEL_CFM",
+	[MSG_I(MM_CHAN_CTXT_LINK_REQ)] = "MM_CHAN_CTXT_LINK_REQ",
+	[MSG_I(MM_CHAN_CTXT_LINK_CFM)] = "MM_CHAN_CTXT_LINK_CFM",
+	[MSG_I(MM_CHAN_CTXT_UNLINK_REQ)] = "MM_CHAN_CTXT_UNLINK_REQ",
+	[MSG_I(MM_CHAN_CTXT_UNLINK_CFM)] = "MM_CHAN_CTXT_UNLINK_CFM",
+	[MSG_I(MM_CHAN_CTXT_UPDATE_REQ)] = "MM_CHAN_CTXT_UPDATE_REQ",
+	[MSG_I(MM_CHAN_CTXT_UPDATE_CFM)] = "MM_CHAN_CTXT_UPDATE_CFM",
+	[MSG_I(MM_CHAN_CTXT_SCHED_REQ)] = "MM_CHAN_CTXT_SCHED_REQ",
+	[MSG_I(MM_CHAN_CTXT_SCHED_CFM)] = "MM_CHAN_CTXT_SCHED_CFM",
+	[MSG_I(MM_BCN_CHANGE_REQ)] = "MM_BCN_CHANGE_REQ",
+	[MSG_I(MM_BCN_CHANGE_CFM)] = "MM_BCN_CHANGE_CFM",
+	[MSG_I(MM_TIM_UPDATE_REQ)] = "MM_TIM_UPDATE_REQ",
+	[MSG_I(MM_TIM_UPDATE_CFM)] = "MM_TIM_UPDATE_CFM",
+	[MSG_I(MM_CONNECTION_LOSS_IND)] = "MM_CONNECTION_LOSS_IND",
+	[MSG_I(MM_CHANNEL_SWITCH_IND)] = "MM_CHANNEL_SWITCH_IND",
+	[MSG_I(MM_CHANNEL_PRE_SWITCH_IND)] = "MM_CHANNEL_PRE_SWITCH_IND",
+	[MSG_I(MM_REMAIN_ON_CHANNEL_REQ)] = "MM_REMAIN_ON_CHANNEL_REQ",
+	[MSG_I(MM_REMAIN_ON_CHANNEL_CFM)] = "MM_REMAIN_ON_CHANNEL_CFM",
+	[MSG_I(MM_REMAIN_ON_CHANNEL_EXP_IND)] = "MM_REMAIN_ON_CHANNEL_EXP_IND",
+	[MSG_I(MM_PS_CHANGE_IND)] = "MM_PS_CHANGE_IND",
+	[MSG_I(MM_TRAFFIC_REQ_IND)] = "MM_TRAFFIC_REQ_IND",
+	[MSG_I(MM_SET_PS_OPTIONS_REQ)] = "MM_SET_PS_OPTIONS_REQ",
+	[MSG_I(MM_SET_PS_OPTIONS_CFM)] = "MM_SET_PS_OPTIONS_CFM",
+	[MSG_I(MM_P2P_VIF_PS_CHANGE_IND)] = "MM_P2P_VIF_PS_CHANGE_IND",
+	[MSG_I(MM_CSA_COUNTER_IND)] = "MM_CSA_COUNTER_IND",
+	[MSG_I(MM_CHANNEL_SURVEY_IND)] = "MM_CHANNEL_SURVEY_IND",
+	[MSG_I(MM_SET_P2P_NOA_REQ)] = "MM_SET_P2P_NOA_REQ",
+	[MSG_I(MM_SET_P2P_OPPPS_REQ)] = "MM_SET_P2P_OPPPS_REQ",
+	[MSG_I(MM_SET_P2P_NOA_CFM)] = "MM_SET_P2P_NOA_CFM",
+	[MSG_I(MM_SET_P2P_OPPPS_CFM)] = "MM_SET_P2P_OPPPS_CFM",
+	[MSG_I(MM_CFG_RSSI_REQ)] = "MM_CFG_RSSI_REQ",
+	[MSG_I(MM_RSSI_STATUS_IND)] = "MM_RSSI_STATUS_IND",
+	[MSG_I(MM_CSA_FINISH_IND)] = "MM_CSA_FINISH_IND",
+	[MSG_I(MM_CSA_TRAFFIC_IND)] = "MM_CSA_TRAFFIC_IND",
+	[MSG_I(MM_MU_GROUP_UPDATE_REQ)] = "MM_MU_GROUP_UPDATE_REQ",
+	[MSG_I(MM_MU_GROUP_UPDATE_CFM)] = "MM_MU_GROUP_UPDATE_CFM",
+
+	[MSG_I(MM_SET_ARPOFFLOAD_REQ)] = "MM_SET_ARPOFFLOAD_REQ",
+	[MSG_I(MM_SET_ARPOFFLOAD_CFM)] = "MM_SET_ARPOFFLOAD_CFM",
+	[MSG_I(MM_SET_AGG_DISABLE_REQ)] = "MM_SET_AGG_DISABLE_REQ",
+	[MSG_I(MM_SET_AGG_DISABLE_CFM)] = "MM_SET_AGG_DISABLE_CFM",
+	[MSG_I(MM_SET_COEX_REQ)] = "MM_SET_COEX_REQ",
+	[MSG_I(MM_SET_COEX_CFM)] = "MM_SET_COEX_CFM",
+	[MSG_I(MM_SET_RF_CONFIG_REQ)] = "MM_SET_RF_CONFIG_REQ",
+	[MSG_I(MM_SET_RF_CONFIG_CFM)] = "MM_SET_RF_CONFIG_CFM",
+	[MSG_I(MM_SET_RF_CALIB_REQ)] = "MM_SET_RF_CALIB_REQ",
+	[MSG_I(MM_SET_RF_CALIB_CFM)] = "MM_SET_RF_CALIB_CFM",
+
+	[MSG_I(MM_GET_MAC_ADDR_REQ)] = "MM_GET_MAC_ADDR_REQ",
+	[MSG_I(MM_GET_MAC_ADDR_CFM)] = "MM_GET_MAC_ADDR_CFM",
+	[MSG_I(MM_GET_STA_INFO_REQ)] = "MM_GET_STA_INFO_REQ",
+	[MSG_I(MM_GET_STA_INFO_CFM)] = "MM_GET_STA_INFO_CFM",
+	[MSG_I(MM_SET_TXPWR_IDX_LVL_REQ)] = "MM_SET_TXPWR_IDX_LVL_REQ",
+	[MSG_I(MM_SET_TXPWR_IDX_LVL_CFM)] = "MM_SET_TXPWR_IDX_LVL_CFM",
+	[MSG_I(MM_SET_TXPWR_OFST_REQ)] = "MM_SET_TXPWR_OFST_REQ",
+	[MSG_I(MM_SET_TXPWR_OFST_CFM)] = "MM_SET_TXPWR_OFST_CFM",
+	[MSG_I(MM_SET_STACK_START_REQ)] = "MM_SET_STACK_START_REQ",
+	[MSG_I(MM_SET_STACK_START_CFM)] = "MM_SET_STACK_START_CFM",
+	[MSG_I(MM_APM_STALOSS_IND)] = "MM_APM_STALOSS_IND",
+	[MSG_I(MM_SET_VENDOR_HWCONFIG_REQ)] = "MM_SET_VENDOR_HWCONFIG_REQ",
+	[MSG_I(MM_SET_VENDOR_HWCONFIG_CFM)] = "MM_SET_VENDOR_HWCONFIG_CFM",
+	[MSG_I(MM_GET_FW_VERSION_REQ)] = "MM_GET_FW_VERSION_REQ",
+	[MSG_I(MM_GET_FW_VERSION_CFM)] = "MM_GET_FW_VERSION_CFM",
+	[MSG_I(MM_SET_RESUME_RESTORE_REQ)] = "MM_SET_RESUME_RESTORE_REQ",
+	[MSG_I(MM_SET_RESUME_RESTORE_CFM)] = "MM_SET_RESUME_RESTORE_CFM",
+	[MSG_I(MM_GET_WIFI_DISABLE_REQ)] = "MM_GET_WIFI_DISABLE_REQ",
+	[MSG_I(MM_GET_WIFI_DISABLE_CFM)] = "MM_GET_WIFI_DISABLE_CFM",
+	[MSG_I(MM_CFG_RSSI_CFM)] = "MM_CFG_RSSI_CFM",
+	[MSG_I(MM_SET_VENDOR_SWCONFIG_REQ)] = "MM_SET_VENDOR_SWCONFIG_REQ",
+	[MSG_I(MM_SET_VENDOR_SWCONFIG_CFM)] = "MM_SET_VENDOR_SWCONFIG_CFM",
+};
+
+static const char *const rwnx_dbgid2str[MSG_I(DBG_MAX)] = {
+	[MSG_I(DBG_MEM_READ_REQ)] = "DBG_MEM_READ_REQ",
+	[MSG_I(DBG_MEM_READ_CFM)] = "DBG_MEM_READ_CFM",
+	[MSG_I(DBG_MEM_WRITE_REQ)] = "DBG_MEM_WRITE_REQ",
+	[MSG_I(DBG_MEM_WRITE_CFM)] = "DBG_MEM_WRITE_CFM",
+	[MSG_I(DBG_SET_MOD_FILTER_REQ)] = "DBG_SET_MOD_FILTER_REQ",
+	[MSG_I(DBG_SET_MOD_FILTER_CFM)] = "DBG_SET_MOD_FILTER_CFM",
+	[MSG_I(DBG_SET_SEV_FILTER_REQ)] = "DBG_SET_SEV_FILTER_REQ",
+	[MSG_I(DBG_SET_SEV_FILTER_CFM)] = "DBG_SET_SEV_FILTER_CFM",
+	[MSG_I(DBG_ERROR_IND)] = "DBG_ERROR_IND",
+	[MSG_I(DBG_GET_SYS_STAT_REQ)] = "DBG_GET_SYS_STAT_REQ",
+	[MSG_I(DBG_GET_SYS_STAT_CFM)] = "DBG_GET_SYS_STAT_CFM",
+};
+
+static const char *const rwnx_scanid2str[MSG_I(SCAN_MAX)] = {
+	[MSG_I(SCAN_START_REQ)] = "SCAN_START_REQ",
+	[MSG_I(SCAN_START_CFM)] = "SCAN_START_CFM",
+	[MSG_I(SCAN_DONE_IND)] = "SCAN_DONE_IND",
+};
+
+static const char *const rwnx_tdlsid2str[MSG_I(TDLS_MAX)] = {
+	[MSG_I(TDLS_CHAN_SWITCH_CFM)] = "TDLS_CHAN_SWITCH_CFM",
+	[MSG_I(TDLS_CHAN_SWITCH_REQ)] = "TDLS_CHAN_SWITCH_REQ",
+	[MSG_I(TDLS_CHAN_SWITCH_IND)] = "TDLS_CHAN_SWITCH_IND",
+	[MSG_I(TDLS_CHAN_SWITCH_BASE_IND)] = "TDLS_CHAN_SWITCH_BASE_IND",
+	[MSG_I(TDLS_CANCEL_CHAN_SWITCH_REQ)] = "TDLS_CANCEL_CHAN_SWITCH_REQ",
+	[MSG_I(TDLS_CANCEL_CHAN_SWITCH_CFM)] = "TDLS_CANCEL_CHAN_SWITCH_CFM",
+	[MSG_I(TDLS_PEER_PS_IND)] = "TDLS_PEER_PS_IND",
+	[MSG_I(TDLS_PEER_TRAFFIC_IND_REQ)] = "TDLS_PEER_TRAFFIC_IND_REQ",
+	[MSG_I(TDLS_PEER_TRAFFIC_IND_CFM)] = "TDLS_PEER_TRAFFIC_IND_CFM",
+};
+
+#ifdef CONFIG_RWNX_FULLMAC
+
+static const char *const rwnx_scanuid2str[MSG_I(SCANU_MAX)] = {
+	[MSG_I(SCANU_START_REQ)] = "SCANU_START_REQ",
+	[MSG_I(SCANU_START_CFM)] = "SCANU_START_CFM",
+	[MSG_I(SCANU_JOIN_REQ)] = "SCANU_JOIN_REQ",
+	[MSG_I(SCANU_JOIN_CFM)] = "SCANU_JOIN_CFM",
+	[MSG_I(SCANU_RESULT_IND)] = "SCANU_RESULT_IND",
+	[MSG_I(SCANU_FAST_REQ)] = "SCANU_FAST_REQ",
+	[MSG_I(SCANU_FAST_CFM)] = "SCANU_FAST_CFM",
+	[MSG_I(SCANU_VENDOR_IE_REQ)] = "SCANU_VENDOR_IE_REQ",
+	[MSG_I(SCANU_VENDOR_IE_CFM)] = "SCANU_VENDOR_IE_CFM",
+	[MSG_I(SCANU_START_CFM_ADDTIONAL)] = "SCANU_START_CFM_ADDTIONAL",
+	[MSG_I(SCANU_CANCEL_REQ)] = "SCANU_CANCEL_REQ",
+	[MSG_I(SCANU_CANCEL_CFM)] = "SCANU_CANCEL_CFM",
+};
+
+static const char *const rwnx_meid2str[MSG_I(ME_MAX)] = {
+	[MSG_I(ME_CONFIG_REQ)] = "ME_CONFIG_REQ",
+	[MSG_I(ME_CONFIG_CFM)] = "ME_CONFIG_CFM",
+	[MSG_I(ME_CHAN_CONFIG_REQ)] = "ME_CHAN_CONFIG_REQ",
+	[MSG_I(ME_CHAN_CONFIG_CFM)] = "ME_CHAN_CONFIG_CFM",
+	[MSG_I(ME_SET_CONTROL_PORT_REQ)] = "ME_SET_CONTROL_PORT_REQ",
+	[MSG_I(ME_SET_CONTROL_PORT_CFM)] = "ME_SET_CONTROL_PORT_CFM",
+	[MSG_I(ME_TKIP_MIC_FAILURE_IND)] = "ME_TKIP_MIC_FAILURE_IND",
+	[MSG_I(ME_STA_ADD_REQ)] = "ME_STA_ADD_REQ",
+	[MSG_I(ME_STA_ADD_CFM)] = "ME_STA_ADD_CFM",
+	[MSG_I(ME_STA_DEL_REQ)] = "ME_STA_DEL_REQ",
+	[MSG_I(ME_STA_DEL_CFM)] = "ME_STA_DEL_CFM",
+	[MSG_I(ME_TX_CREDITS_UPDATE_IND)] = "ME_TX_CREDITS_UPDATE_IND",
+	[MSG_I(ME_RC_STATS_REQ)] = "ME_RC_STATS_REQ",
+	[MSG_I(ME_RC_STATS_CFM)] = "ME_RC_STATS_CFM",
+	[MSG_I(ME_RC_SET_RATE_REQ)] = "ME_RC_SET_RATE_REQ",
+	[MSG_I(ME_TRAFFIC_IND_REQ)] = "ME_TRAFFIC_IND_REQ",
+	[MSG_I(ME_TRAFFIC_IND_CFM)] = "ME_TRAFFIC_IND_CFM",
+	[MSG_I(ME_SET_PS_MODE_REQ)] = "ME_SET_PS_MODE_REQ",
+	[MSG_I(ME_SET_PS_MODE_CFM)] = "ME_SET_PS_MODE_CFM",
+	[MSG_I(ME_SET_LP_LEVEL_REQ)] = "ME_SET_LP_LEVEL_REQ",
+	[MSG_I(ME_SET_LP_LEVEL_CFM)] = "ME_SET_LP_LEVEL_CFM",
+};
+
+static const char *const rwnx_smid2str[MSG_I(SM_MAX)] = {
+	[MSG_I(SM_CONNECT_REQ)] = "SM_CONNECT_REQ",
+	[MSG_I(SM_CONNECT_CFM)] = "SM_CONNECT_CFM",
+	[MSG_I(SM_CONNECT_IND)] = "SM_CONNECT_IND",
+	[MSG_I(SM_DISCONNECT_REQ)] = "SM_DISCONNECT_REQ",
+	[MSG_I(SM_DISCONNECT_CFM)] = "SM_DISCONNECT_CFM",
+	[MSG_I(SM_DISCONNECT_IND)] = "SM_DISCONNECT_IND",
+	[MSG_I(SM_EXTERNAL_AUTH_REQUIRED_IND)] = "SM_EXTERNAL_AUTH_REQUIRED_IND",
+	[MSG_I(SM_EXTERNAL_AUTH_REQUIRED_RSP)] = "SM_EXTERNAL_AUTH_REQUIRED_RSP",
+	[MSG_I(SM_EXTERNAL_AUTH_REQUIRED_RSP_CFM)] =
+		"SM_EXTERNAL_AUTH_REQUIRED_RSP_CFM",
+};
+
+static const char *const rwnx_apmid2str[MSG_I(APM_MAX)] = {
+	[MSG_I(APM_START_REQ)] = "APM_START_REQ",
+	[MSG_I(APM_START_CFM)] = "APM_START_CFM",
+	[MSG_I(APM_STOP_REQ)] = "APM_STOP_REQ",
+	[MSG_I(APM_STOP_CFM)] = "APM_STOP_CFM",
+	[MSG_I(APM_START_CAC_REQ)] = "APM_START_CAC_REQ",
+	[MSG_I(APM_START_CAC_CFM)] = "APM_START_CAC_CFM",
+	[MSG_I(APM_STOP_CAC_REQ)] = "APM_STOP_CAC_REQ",
+	[MSG_I(APM_STOP_CAC_CFM)] = "APM_STOP_CAC_CFM",
+	[MSG_I(APM_SET_BEACON_IE_REQ)] = "APM_SET_BEACON_IE_REQ",
+	[MSG_I(APM_SET_BEACON_IE_CFM)] = "APM_SET_BEACON_IE_CFM",
+};
+
+static const char *const rwnx_meshid2str[MSG_I(MESH_MAX)] = {
+	[MSG_I(MESH_START_REQ)] = "MESH_START_REQ",
+	[MSG_I(MESH_START_CFM)] = "MESH_START_CFM",
+	[MSG_I(MESH_STOP_REQ)] = "MESH_STOP_REQ",
+	[MSG_I(MESH_STOP_CFM)] = "MESH_STOP_CFM",
+	[MSG_I(MESH_UPDATE_REQ)] = "MESH_UPDATE_REQ",
+	[MSG_I(MESH_UPDATE_CFM)] = "MESH_UPDATE_CFM",
+	[MSG_I(MESH_PATH_CREATE_REQ)] = "MESH_PATH_CREATE_REQ",
+	[MSG_I(MESH_PATH_CREATE_CFM)] = "MESH_PATH_CREATE_CFM",
+	[MSG_I(MESH_PATH_UPDATE_REQ)] = "MESH_PATH_UPDATE_REQ",
+	[MSG_I(MESH_PATH_UPDATE_CFM)] = "MESH_PATH_UPDATE_CFM",
+	[MSG_I(MESH_PROXY_ADD_REQ)] = "MESH_PROXY_ADD_REQ",
+	[MSG_I(MESH_PEER_UPDATE_IND)] = "MESH_PEER_UPDATE_IND",
+	[MSG_I(MESH_PATH_UPDATE_IND)] = "MESH_PATH_UPDATE_IND",
+	[MSG_I(MESH_PROXY_UPDATE_IND)] = "MESH_PROXY_UPDATE_IND",
+};
+
+#endif /* CONFIG_RWNX_FULLMAC */
+
+const char *const *rwnx_id2str[TASK_LAST_EMB + 1] = {
+	[TASK_MM] = rwnx_mmid2str,       [TASK_DBG] = rwnx_dbgid2str,
+	[TASK_SCAN] = rwnx_scanid2str,   [TASK_TDLS] = rwnx_tdlsid2str,
+#ifdef CONFIG_RWNX_FULLMAC
+	[TASK_SCANU] = rwnx_scanuid2str, [TASK_ME] = rwnx_meid2str,
+	[TASK_SM] = rwnx_smid2str,       [TASK_APM] = rwnx_apmid2str,
+	[TASK_MESH] = rwnx_meshid2str,
+#endif
+};
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_strs.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_strs.h
new file mode 100644
index 0000000000000..73de33c4f1c7d
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_strs.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_strs.h
+ *
+ * @brief Miscellaneous debug strings
+ *
+ ****************************************************************************************
+ */
+
+#ifndef _RWNX_STRS_H_
+#define _RWNX_STRS_H_
+
+#include "lmac_msg.h"
+
+#define RWNX_ID2STR(tag)                                               \
+	({                                                                 \
+		const typeof(tag) __tag = (tag);                               \
+		((MSG_T(__tag) < ARRAY_SIZE(rwnx_id2str)) &&                   \
+		 (rwnx_id2str[MSG_T(__tag)]) &&                                \
+		 ((rwnx_id2str[MSG_T(__tag)])[MSG_I(__tag)]))                  \
+		 ? (rwnx_id2str[MSG_T(__tag)])[MSG_I(__tag)]                \
+		 : "unknown";                                               \
+	})
+
+extern const char *const *rwnx_id2str[TASK_LAST_EMB + 1];
+
+#endif /* _RWNX_STRS_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_utils.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_utils.c
new file mode 100644
index 0000000000000..532b7cbb55b0e
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_utils.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_utils.c
+ *
+ * @brief IPC utility function declarations
+ *
+ ****************************************************************************************
+ */
+
+#include "rwnx_utils.h"
+#include "ipc_host.h"
+#include "rwnx_debugfs.h"
+#include "rwnx_defs.h"
+#include "rwnx_msg_rx.h"
+#include "rwnx_prof.h"
+#include "rwnx_rx.h"
+#include "rwnx_tx.h"
+
+int rwnx_init_aic(struct rwnx_hw *rwnx_hw)
+{
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+#ifdef AICWF_SDIO_SUPPORT
+	aicwf_sdio_host_init(&rwnx_hw->sdio_env, NULL, NULL, rwnx_hw);
+#else
+	aicwf_usb_host_init(&rwnx_hw->usb_env, NULL, NULL, rwnx_hw);
+#endif
+	rwnx_cmd_mgr_init(rwnx_hw->cmd_mgr);
+
+	rwnx_hw->testmode = get_testmode();
+
+	return 0;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_utils.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_utils.h
new file mode 100644
index 0000000000000..3383a27581377
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_utils.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_utils.c
+ *
+ * @brief IPC utility function declarations
+ *
+ ****************************************************************************************
+ */
+
+#ifndef _RWNX_IPC_UTILS_H_
+#define _RWNX_IPC_UTILS_H_
+
+#include "aicwf_debug.h"
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/skbuff.h>
+#include "lmac_msg.h"
+#include "rwnx_compat.h"
+
+enum rwnx_dev_flag {
+	RWNX_DEV_RESTARTING,
+	RWNX_DEV_STACK_RESTARTING,
+	RWNX_DEV_STARTED,
+};
+
+struct rwnx_hw;
+struct rwnx_sta;
+
+/**
+ * struct rwnx_ipc_elem - Generic IPC buffer of fixed size
+ *
+ * @addr: Host address of the buffer.
+ * @dma_addr: DMA address of the buffer.
+ */
+struct rwnx_ipc_elem {
+	void *addr;
+	dma_addr_t dma_addr;
+};
+
+/**
+ * struct rwnx_ipc_elem_pool - Generic pool of IPC buffers of fixed size
+ *
+ * @nb: Number of buffers currenlty allocated in the pool
+ * @buf: Array of buffers (size of array is @nb)
+ * @pool: DMA pool in which buffers have been allocated
+ */
+struct rwnx_ipc_elem_pool {
+	int nb;
+	struct rwnx_ipc_elem *buf;
+	struct dma_pool *pool;
+};
+
+/**
+ * struct rwnx_ipc_elem - Generic IPC buffer of variable size
+ *
+ * @addr: Host address of the buffer.
+ * @dma_addr: DMA address of the buffer.
+ * @size: Size, in bytes, of the buffer
+ */
+struct rwnx_ipc_elem_var {
+	void *addr;
+	dma_addr_t dma_addr;
+	size_t size;
+};
+
+/**
+ * struct rwnx_ipc_dbgdump_elem - IPC buffer for debug dump
+ *
+ * @mutex: Mutex to protect access to debug dump
+ * @buf: IPC buffer
+ */
+struct rwnx_ipc_dbgdump_elem {
+	struct mutex mutex; //Mutex to protect access to debug dump
+	struct rwnx_ipc_elem_var buf;
+};
+
+/*
+ * Maximum Length of Radiotap header vendor specific data(in bytes)
+ */
+#define RADIOTAP_HDR_VEND_MAX_LEN 16
+
+/*
+ * Maximum Radiotap Header Length without vendor specific data (in bytes)
+ */
+#define RADIOTAP_HDR_MAX_LEN 80
+
+/*
+ * Unsupported HT Frame data length (in bytes)
+ */
+#define UNSUP_RX_VEC_DATA_LEN 2
+
+/**
+ * struct rwnx_ipc_skb_elem - IPC buffer for SKB element
+ *
+ * @skb: Pointer to the skb buffer allocated
+ * @dma_addr: DMA address of the data buffer fo skb
+ *
+ */
+struct rwnx_ipc_skb_elem {
+	struct sk_buff *skb;
+	dma_addr_t dma_addr;
+};
+
+#ifdef CONFIG_RWNX_FULLMAC
+
+/* Maximum number of rx buffer the fw may use at the same time */
+#define RWNX_RXBUFF_MAX (64 * NX_REMOTE_STA_MAX)
+
+/**
+ * struct rwnx_ipc_rxbuf_elems - IPC buffers for RX
+ *
+ * @skb: Array of buffer push to FW.
+ * @idx: Index of the last pushed skb.(Use to find the next free entry quicker)
+ *
+ * Note: contrary to softmac version, dma_addr are stored inside skb->cb.
+ */
+struct rwnx_ipc_rxbuf_elems {
+	struct sk_buff *skb[RWNX_RXBUFF_MAX];
+	int idx;
+};
+
+#endif /* CONFIG_RWNX_FULLMAC */
+int rwnx_init_aic(struct rwnx_hw *rwnx_hw);
+
+int get_testmode(void);
+
+#endif /* _RWNX_IPC_UTILS_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_v7.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_v7.c
new file mode 100644
index 0000000000000..d240d5e04bf7e
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_v7.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_v7.h
+ *
+ ****************************************************************************************
+ */
+
+#include "rwnx_v7.h"
+#include "hal_desc.h"
+#include "reg_access.h"
+#include "rwnx_defs.h"
+#include "rwnx_irqs.h"
+
+struct rwnx_v7 {
+	u8 *pci_bar0_vaddr;
+	u8 *pci_bar1_vaddr;
+};
+
+static int rwnx_v7_platform_enable(struct rwnx_hw *rwnx_hw)
+{
+	int ret;
+
+	/* sched_setscheduler on ONESHOT threaded irq handler for BCNs ? */
+	ret = request_irq(rwnx_hw->plat->pci_dev->irq, rwnx_irq_hdlr, 0, "rwnx",
+			  rwnx_hw);
+	return ret;
+}
+
+static int rwnx_v7_platform_disable(struct rwnx_hw *rwnx_hw)
+{
+	free_irq(rwnx_hw->plat->pci_dev->irq, rwnx_hw);
+	return 0;
+}
+
+static void rwnx_v7_platform_deinit(struct rwnx_plat *rwnx_plat)
+{
+#ifdef CONFIG_PCI
+	struct rwnx_v7 *rwnx_v7 = (struct rwnx_v7 *)rwnx_plat->priv;
+
+	pci_disable_device(rwnx_plat->pci_dev);
+	iounmap(rwnx_v7->pci_bar0_vaddr);
+	iounmap(rwnx_v7->pci_bar1_vaddr);
+	pci_release_regions(rwnx_plat->pci_dev);
+	pci_clear_master(rwnx_plat->pci_dev);
+	pci_disable_msi(rwnx_plat->pci_dev);
+#endif
+	kfree(rwnx_plat);
+}
+
+static u8 *rwnx_v7_get_address(struct rwnx_plat *rwnx_plat, int addr_name,
+			       unsigned int offset)
+{
+	struct rwnx_v7 *rwnx_v7 = (struct rwnx_v7 *)rwnx_plat->priv;
+
+	if (WARN(addr_name >= RWNX_ADDR_MAX, "Invalid address %d", addr_name))
+		return NULL;
+
+	if (addr_name == RWNX_ADDR_CPU)
+		return rwnx_v7->pci_bar0_vaddr + offset;
+	else
+		return rwnx_v7->pci_bar1_vaddr + offset;
+}
+
+static void rwnx_v7_ack_irq(struct rwnx_plat *rwnx_plat)
+{
+}
+
+static const u32 rwnx_v7_config_reg[] = {
+	NXMAC_DEBUG_PORT_SEL_ADDR, SYSCTRL_DIAG_CONF_ADDR,
+	SYSCTRL_PHYDIAG_CONF_ADDR, SYSCTRL_RIUDIAG_CONF_ADDR,
+	RF_V7_DIAGPORT_CONF1_ADDR,
+};
+
+static const u32 rwnx_v7_he_config_reg[] = {
+	SYSCTRL_DIAG_CONF0,
+	SYSCTRL_DIAG_CONF1,
+	SYSCTRL_DIAG_CONF2,
+	SYSCTRL_DIAG_CONF3,
+};
+
+static int rwnx_v7_get_config_reg(struct rwnx_plat *rwnx_plat, const u32 **list)
+{
+	u32 fpga_sign;
+
+	if (!list)
+		return 0;
+
+	fpga_sign =
+		RWNX_REG_READ(rwnx_plat, RWNX_ADDR_SYSTEM, SYSCTRL_SIGNATURE_ADDR);
+	if (__FPGA_TYPE(fpga_sign) == 0xc0ca) {
+		*list = rwnx_v7_he_config_reg;
+		return ARRAY_SIZE(rwnx_v7_he_config_reg);
+	}
+	*list = rwnx_v7_config_reg;
+	return ARRAY_SIZE(rwnx_v7_config_reg);
+}
+
+/**
+ * rwnx_v7_platform_init - Initialize the DINI platform
+ *
+ * @pci_dev PCI device
+ * @rwnx_plat Pointer on struct rwnx_stat * to be populated
+ *
+ * @return 0 on success, < 0 otherwise
+ *
+ * Allocate and initialize a rwnx_plat structure for the dini platform.
+ */
+int rwnx_v7_platform_init(struct pci_dev *pci_dev, struct rwnx_plat **rwnx_plat)
+{
+	struct rwnx_v7 *rwnx_v7;
+	u16 pci_cmd;
+	int ret = 0;
+
+	*rwnx_plat =
+		kzalloc(sizeof(struct rwnx_plat) + sizeof(struct rwnx_v7), GFP_KERNEL);
+	if (!*rwnx_plat)
+		return -ENOMEM;
+
+	rwnx_v7 = (struct rwnx_v7 *)(*rwnx_plat)->priv;
+
+	/* Hotplug fixups */
+	pci_read_config_word(pci_dev, PCI_COMMAND, &pci_cmd);
+	pci_cmd |= PCI_COMMAND_PARITY | PCI_COMMAND_SERR;
+	pci_write_config_word(pci_dev, PCI_COMMAND, pci_cmd);
+
+	ret = pci_enable_device(pci_dev);
+	if (ret) {
+		dev_err(&pci_dev->dev, "pci_enable_device failed\n");
+		goto out_enable;
+	}
+
+	pci_set_master(pci_dev);
+
+#ifdef CONFIG_PCI
+	if (pci_enable_msi(pci_dev)) {
+		dev_err(&pci_dev->dev, "pci_enable_msi failed\n");
+		goto out_msi;
+	}
+#endif
+
+	rwnx_v7->pci_bar0_vaddr = (u8 *)pci_ioremap_bar(pci_dev, 0);
+	if (!rwnx_v7->pci_bar0_vaddr) {
+		dev_err(&pci_dev->dev, "pci_ioremap_bar(%d) failed\n", 0);
+		ret = -ENOMEM;
+		goto out_bar0;
+	}
+	rwnx_v7->pci_bar1_vaddr = (u8 *)pci_ioremap_bar(pci_dev, 1);
+	if (!rwnx_v7->pci_bar1_vaddr) {
+		dev_err(&pci_dev->dev, "pci_ioremap_bar(%d) failed\n", 1);
+		ret = -ENOMEM;
+		goto out_bar1;
+	}
+
+	(*rwnx_plat)->enable = rwnx_v7_platform_enable;
+	(*rwnx_plat)->disable = rwnx_v7_platform_disable;
+	(*rwnx_plat)->deinit = rwnx_v7_platform_deinit;
+	(*rwnx_plat)->get_address = rwnx_v7_get_address;
+	(*rwnx_plat)->ack_irq = rwnx_v7_ack_irq;
+	(*rwnx_plat)->get_config_reg = rwnx_v7_get_config_reg;
+
+	return 0;
+
+out_bar1:
+	iounmap(rwnx_v7->pci_bar0_vaddr);
+out_bar0:
+#ifdef CONFIG_PCI
+	pci_disable_msi(pci_dev);
+out_msi:
+#endif
+	pci_release_regions(pci_dev);
+#ifdef CONFIG_PCI
+	pci_clear_master(pci_dev);
+#endif
+	pci_disable_device(pci_dev);
+out_enable:
+	kfree(*rwnx_plat);
+	return ret;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_v7.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_v7.h
new file mode 100644
index 0000000000000..c77f26e48ee26
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_v7.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_v7.h
+ *
+ ****************************************************************************************
+ */
+
+#ifndef _RWNX_V7_H_
+#define _RWNX_V7_H_
+
+#include "rwnx_platform.h"
+#include <linux/pci.h>
+
+int rwnx_v7_platform_init(struct pci_dev *pci_dev,
+			  struct rwnx_plat **rwnx_plat);
+
+#endif /* _RWNX_V7_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_version.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_version.h
new file mode 100644
index 0000000000000..7f3f2d88d2d98
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_version.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_VERSION_H_
+#define _RWNX_VERSION_H_
+
+#include "rwnx_version_gen.h"
+#include "aicwf_debug.h"
+
+static inline void rwnx_print_version(void)
+{
+	// AICWFDBG(LOGINFO, RWNX_VERS_BANNER "\n");
+	pr_info("AIC_WF RELEASE VERSION:%s \r\n", RELEASE_VERSION);
+}
+
+#endif /* _RWNX_VERSION_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_version_gen.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_version_gen.h
new file mode 100644
index 0000000000000..8768db424ce78
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_version_gen.h
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#define RWNX_VERS_REV    "241c091M (master)"
+#define RWNX_VERS_MOD    "6.4.3.0"
+#define RWNX_VERS_BANNER "rwnx v6.4.3.0 - - 241c091M (master)"
+#define RELEASE_VERSION "2.0.14"
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_wakelock.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_wakelock.c
new file mode 100644
index 0000000000000..ee0879fd14215
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_wakelock.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ****************************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @file rwnx_wakelock.c
+ *
+ * @brief wakelock function declarations
+ *
+ ****************************************************************************************
+ */
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/version.h>
+#include <linux/pm_wakeirq.h>
+#include "rwnx_defs.h"
+#include "rwnx_wakelock.h"
+
+struct wakeup_source *rwnx_wakeup_init(const char *name, struct rwnx_hw *rwnx_hw)
+{
+	struct wakeup_source *ws;
+
+	ws = wakeup_source_register(rwnx_hw->dev, name);
+	return ws;
+}
+
+void rwnx_wakeup_deinit(struct wakeup_source *ws)
+{
+	if (ws && ws->active)
+		__pm_relax(ws);
+	wakeup_source_unregister(ws);
+}
+
+struct wakeup_source *rwnx_wakeup_register(struct device *dev, const char *name)
+{
+	return wakeup_source_register(dev, name);
+}
+
+void rwnx_wakeup_unregister(struct wakeup_source *ws)
+{
+	if (ws && ws->active)
+		__pm_relax(ws);
+	wakeup_source_unregister(ws);
+}
+
+void rwnx_wakeup_lock(struct wakeup_source *ws)
+{
+	__pm_stay_awake(ws);
+}
+
+void rwnx_wakeup_unlock(struct wakeup_source *ws)
+{
+	__pm_relax(ws);
+}
+
+void rwnx_wakeup_lock_timeout(struct wakeup_source *ws, unsigned int msec)
+{
+	__pm_wakeup_event(ws, msec);
+}
+
+void aicwf_wakeup_lock_init(struct rwnx_hw *rwnx_hw)
+{
+	rwnx_hw->ws_tx = rwnx_wakeup_init("rwnx_tx_wakelock", rwnx_hw);
+	rwnx_hw->ws_rx = rwnx_wakeup_init("rwnx_rx_wakelock", rwnx_hw);
+	rwnx_hw->ws_irqrx = rwnx_wakeup_init("rwnx_irqrx_wakelock", rwnx_hw);
+	rwnx_hw->ws_pwrctrl = rwnx_wakeup_init("rwnx_pwrcrl_wakelock", rwnx_hw);
+	rwnx_hw->ws_scan = rwnx_wakeup_init("rwnx_scan_wakelock", rwnx_hw);
+}
+
+void aicwf_wakeup_lock_deinit(struct rwnx_hw *rwnx_hw)
+{
+	rwnx_wakeup_deinit(rwnx_hw->ws_tx);
+	rwnx_wakeup_deinit(rwnx_hw->ws_rx);
+	rwnx_wakeup_deinit(rwnx_hw->ws_irqrx);
+	rwnx_wakeup_deinit(rwnx_hw->ws_pwrctrl);
+	rwnx_wakeup_deinit(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;
+}
+
+int aicwf_wakeup_lock_status(struct rwnx_hw *rwnx_hw)
+{
+	if (rwnx_hw->ws_tx && rwnx_hw->ws_tx->active) {
+		AICWFDBG(LOGDEBUG, "AICWF ws_tx active\n");
+		return -1;
+	}
+	if (rwnx_hw->ws_rx && rwnx_hw->ws_rx->active) {
+		AICWFDBG(LOGDEBUG, "AICWF ws_rx active\n");
+		return -1;
+	}
+	if (rwnx_hw->ws_pwrctrl && rwnx_hw->ws_pwrctrl->active) {
+		AICWFDBG(LOGDEBUG, "AICWF ws_pwrctrl active\n");
+		return -1;
+	}
+	if (rwnx_hw->ws_irqrx && rwnx_hw->ws_irqrx->active) {
+		AICWFDBG(LOGDEBUG, "AICWF ws_irqrx active\n");
+		return -1;
+	}
+	if (rwnx_hw->ws_scan && rwnx_hw->ws_scan->active) {
+		AICWFDBG(LOGDEBUG, "AICWF ws_scan active\n");
+		return -1;
+	}
+	return 0;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_wakelock.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_wakelock.h
new file mode 100644
index 0000000000000..f3ec804f5732d
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_wakelock.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __RWNX_WAKELOCK_H
+#define __RWNX_WAKELOCK_H
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/version.h>
+
+struct wakeup_source *rwnx_wakeup_init(const char *name, struct rwnx_hw *rwnx_hw);
+void rwnx_wakeup_deinit(struct wakeup_source *ws);
+
+struct wakeup_source *rwnx_wakeup_register(struct device *dev,
+					   const char *name);
+void rwnx_wakeup_unregister(struct wakeup_source *ws);
+
+void rwnx_wakeup_lock(struct wakeup_source *ws);
+void rwnx_wakeup_unlock(struct wakeup_source *ws);
+void rwnx_wakeup_lock_timeout(struct wakeup_source *ws, unsigned int msec);
+
+void aicwf_wakeup_lock_init(struct rwnx_hw *rwnx_hw);
+void aicwf_wakeup_lock_deinit(struct rwnx_hw *rwnx_hw);
+int aicwf_wakeup_lock_status(struct rwnx_hw *rwnx_hw);
+
+#endif

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [RFC PATCH wireless-next 3/4] wifi: aic: add FullMAC WLAN driver
  2026-07-21 10:20 [RFC PATCH wireless-next 0/4] wifi: aic: add AIC8800 SDIO FullMAC driver Yanli Yang
  2026-07-21 10:21 ` [RFC PATCH wireless-next 1/4] wifi: aic: add SDIO BSP and build configuration Yanli Yang
  2026-07-21 10:21 ` [RFC PATCH wireless-next 2/4] wifi: aic: add FullMAC firmware and platform interface Yanli Yang
@ 2026-07-21 10:21 ` Yanli Yang
  2026-07-21 10:21 ` [RFC PATCH wireless-next 4/4] wifi: aic: integrate AIC8800 driver Yanli Yang
  3 siblings, 0 replies; 5+ messages in thread
From: Yanli Yang @ 2026-07-21 10:21 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg, Zhirun Liu, dijiaxu, chunqiuliu, liheng.wei

From: Zhirun Liu <zhirunliu@aicsemi.com>

Add the cfg80211 FullMAC core, regulatory and configuration paths,
TX/RX data path, SDIO and chipset integration, advanced wireless features,
diagnostics and retained downstream compatibility interfaces.

This is the complete untrimmed RFC implementation. Private interfaces,
the copied regulatory database and INI configuration are included for
design review and are not claimed to be merge-ready.

Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
 .../wireless/aic/aic8800_fdrv/aic_br_ext.c    | 1598 +++++
 .../wireless/aic/aic8800_fdrv/aic_br_ext.h    |   72 +
 .../wireless/aic/aic8800_fdrv/aic_btsdio.c    | 1164 ++++
 .../wireless/aic/aic8800_fdrv/aic_btsdio.h    |  472 ++
 .../wireless/aic/aic8800_fdrv/aic_priv_cmd.c  | 1967 ++++++
 .../wireless/aic/aic8800_fdrv/aic_priv_cmd.h  |  127 +
 .../wireless/aic/aic8800_fdrv/aic_vendor.c    |  950 +++
 .../wireless/aic/aic8800_fdrv/aic_vendor.h    |  370 ++
 .../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         |  267 +
 .../aic/aic8800_fdrv/aicwf_chip_ops.h         |  186 +
 .../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    |  932 +++
 .../wireless/aic/aic8800_fdrv/aicwf_genl.h    |  112 +
 .../aic/aic8800_fdrv/aicwf_rx_prealloc.c      |  102 +
 .../aic/aic8800_fdrv/aicwf_rx_prealloc.h      |   32 +
 .../wireless/aic/aic8800_fdrv/aicwf_sdio.c    | 2813 +++++++++
 .../wireless/aic/aic8800_fdrv/aicwf_sdio.h    |  264 +
 .../wireless/aic/aic8800_fdrv/aicwf_tcp_ack.c |  572 ++
 .../wireless/aic/aic8800_fdrv/aicwf_tcp_ack.h |  104 +
 .../wireless/aic/aic8800_fdrv/aicwf_txrxif.c  |  753 +++
 .../wireless/aic/aic8800_fdrv/aicwf_txrxif.h  |  217 +
 .../aic/aic8800_fdrv/aicwf_wext_linux.c       | 1137 ++++
 .../aic/aic8800_fdrv/aicwf_wext_linux.h       |   15 +
 drivers/net/wireless/aic/aic8800_fdrv/regdb.c | 2770 +++++++++
 .../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 +
 .../wireless/aic/aic8800_fdrv/rwnx_debugfs.c  | 2585 ++++++++
 .../wireless/aic/aic8800_fdrv/rwnx_debugfs.h  |  134 +
 .../wireless/aic/aic8800_fdrv/rwnx_fw_trace.c |   50 +
 .../wireless/aic/aic8800_fdrv/rwnx_fw_trace.h |   28 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_gki.c  |  406 ++
 .../net/wireless/aic/aic8800_fdrv/rwnx_gki.h  |   46 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_main.c | 5406 +++++++++++++++++
 .../net/wireless/aic/aic8800_fdrv/rwnx_main.h |   34 +
 .../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        | 1295 ++++
 .../aic/aic8800_fdrv/rwnx_mod_params.h        |   70 +
 .../wireless/aic/aic8800_fdrv/rwnx_mu_group.c |  646 ++
 .../wireless/aic/aic8800_fdrv/rwnx_mu_group.h |  159 +
 .../wireless/aic/aic8800_fdrv/rwnx_radar.c    | 1965 ++++++
 .../wireless/aic/aic8800_fdrv/rwnx_radar.h    |  278 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_rx.c   | 2443 ++++++++
 .../net/wireless/aic/aic8800_fdrv/rwnx_rx.h   |  358 ++
 .../net/wireless/aic/aic8800_fdrv/rwnx_tdls.c |  776 +++
 .../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   | 1805 ++++++
 .../net/wireless/aic/aic8800_fdrv/rwnx_tx.h   |  193 +
 .../net/wireless/aic/aic8800_fdrv/rwnx_txq.c  | 1379 +++++
 .../net/wireless/aic/aic8800_fdrv/rwnx_txq.h  |  372 ++
 .../net/wireless/aic/aic8800_fdrv/sdio_host.c |  132 +
 .../net/wireless/aic/aic8800_fdrv/sdio_host.h |   39 +
 63 files changed, 40010 insertions(+)
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.h
 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/aic_vendor.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aic_vendor.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/aicwf_wext_linux.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/aicwf_wext_linux.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_debugfs.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_debugfs.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_fw_trace.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_fw_trace.h
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_gki.c
 create mode 100644 drivers/net/wireless/aic/aic8800_fdrv/rwnx_gki.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_br_ext.c b/drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.c
new file mode 100644
index 0000000000000..4dc33b367cac3
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.c
@@ -0,0 +1,1598 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *****************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief bridge function definitions
+ *
+ ****************************************************************************
+ */
+
+#define _AIC_BR_EXT_C_
+
+#ifdef __KERNEL__
+#include "rwnx_defs.h"
+#include <linux/atalk.h>
+#include <linux/if_arp.h>
+#include <linux/if_pppox.h>
+#include <linux/udp.h>
+#include <net/ip.h>
+#include <net/ipx.h>
+#endif
+
+#ifdef CL_IPV6_PASS
+#ifdef __KERNEL__
+#include <linux/icmpv6.h>
+#include <linux/ipv6.h>
+#include <net/ndisc.h>
+#include <net/checksum.h>
+#endif
+#endif
+
+#ifdef CONFIG_AIC8800_BR_SUPPORT
+
+/* #define BR_SUPPORT_DEBUG */
+
+#define NAT25_IPV4  01
+#define NAT25_IPV6  02
+#define NAT25_IPX   03
+#define NAT25_APPLE 04
+#define NAT25_PPPOE 05
+
+#define RTL_RELAY_TAG_LEN (ETH_ALEN)
+#define TAG_HDR_LEN       4
+
+#define MAGIC_CODE      0x8186
+#define MAGIC_CODE_LEN  2
+#define WAIT_TIME_PPPOE 5 /* waiting time for pppoe server in sec */
+
+/*-----------------------------------------------------------------
+ * How database records network address:
+ *		   0    1    2    3    4    5    6    7    8    9   10
+ *		|----|----|----|----|----|----|----|----|----|----|----|
+ * IPv4  |type|                             |      IP addr      |
+ * IPX   |type|      Net addr     |          Node addr          |
+ * IPX   |type|      Net addr     |Sckt addr|
+ * Apple |type| Network |node|
+ * PPPoE |type|   SID   |           AC MAC            |
+ *-----------------------------------------------------------------
+ */
+
+/* Find a tag in pppoe frame and return the pointer */
+static inline unsigned char *__nat25_find_pppoe_tag(struct pppoe_hdr *ph,
+						    unsigned short type)
+{
+	unsigned char *cur_ptr, *start_ptr;
+	unsigned short tag_len, tag_type;
+
+	cur_ptr = (unsigned char *)ph->tag;
+	start_ptr = cur_ptr;
+	while ((cur_ptr - start_ptr) < ntohs(ph->length)) {
+		/* prevent un-alignment access */
+		tag_type = (unsigned short)((cur_ptr[0] << 8) + cur_ptr[1]);
+		tag_len = (unsigned short)((cur_ptr[2] << 8) + cur_ptr[3]);
+		if (tag_type == type)
+			return cur_ptr;
+		cur_ptr = cur_ptr + TAG_HDR_LEN + tag_len;
+	}
+	return 0;
+}
+
+static inline int __nat25_add_pppoe_tag(struct sk_buff *skb,
+					struct pppoe_tag *tag)
+{
+	struct pppoe_hdr *ph = (struct pppoe_hdr *)(skb->data + ETH_HLEN);
+	int data_len;
+
+	data_len = tag->tag_len + TAG_HDR_LEN;
+	if (skb_tailroom(skb) < data_len) {
+		pr_err("skb_tailroom() failed in add SID tag!\n");
+		return -1;
+	}
+
+	skb_put(skb, data_len);
+	/* have a room for new tag */
+	memmove(((unsigned char *)ph->tag + data_len), (unsigned char *)ph->tag,
+		ntohs(ph->length));
+	ph->length = htons(ntohs(ph->length) + data_len);
+	memcpy((unsigned char *)ph->tag, tag, data_len);
+	return data_len;
+}
+
+static int skb_pull_and_merge(struct sk_buff *skb, unsigned char *src, int len)
+{
+	int tail_len;
+	unsigned long end, tail;
+
+	if ((src + len) > skb_tail_pointer(skb) || skb->len < len)
+		return -1;
+
+	tail = (unsigned long)skb_tail_pointer(skb);
+	end = (unsigned long)src + len;
+	if (tail < end)
+		return -1;
+
+	tail_len = (int)(tail - end);
+	if (tail_len > 0)
+		memmove(src, src + len, tail_len);
+
+	skb_trim(skb, skb->len - len);
+	return 0;
+}
+
+static inline unsigned long __nat25_timeout(struct rwnx_vif *vif)
+{
+	unsigned long timeout;
+
+	timeout = jiffies - NAT25_AGEING_TIME * HZ;
+
+	return timeout;
+}
+
+static inline int __nat25_has_expired(struct rwnx_vif *vif,
+				      struct nat25_network_db_entry *fdb)
+{
+	if (time_before_eq(fdb->ageing_timer, __nat25_timeout(vif)))
+		return 1;
+
+	return 0;
+}
+
+static inline void
+__nat25_generate_ipv4_network_addr(unsigned char *network_addr,
+				   unsigned int *ip_addr)
+{
+	memset(network_addr, 0, MAX_NETWORK_ADDR_LEN);
+
+	network_addr[0] = NAT25_IPV4;
+	memcpy(network_addr + 7, (unsigned char *)ip_addr, 4);
+}
+
+static inline void
+__nat25_generate_ipx_network_addr_with_node(unsigned char *network_addr,
+					    unsigned int *ipx_net_addr,
+					    unsigned char *ipx_node_addr)
+{
+	memset(network_addr, 0, MAX_NETWORK_ADDR_LEN);
+
+	network_addr[0] = NAT25_IPX;
+	memcpy(network_addr + 1, (unsigned char *)ipx_net_addr, 4);
+	memcpy(network_addr + 5, ipx_node_addr, 6);
+}
+
+static inline void
+__nat25_generate_ipx_network_addr_with_socket(unsigned char *network_addr,
+					      unsigned int *ipx_net_addr,
+					      unsigned short *ipx_socket_addr)
+{
+	memset(network_addr, 0, MAX_NETWORK_ADDR_LEN);
+
+	network_addr[0] = NAT25_IPX;
+	memcpy(network_addr + 1, (unsigned char *)ipx_net_addr, 4);
+	memcpy(network_addr + 5, (unsigned char *)ipx_socket_addr, 2);
+}
+
+static inline void __nat25_generate_apple_network_addr(unsigned char *network_addr,
+						       unsigned short *network,
+						       unsigned char *node)
+{
+	memset(network_addr, 0, MAX_NETWORK_ADDR_LEN);
+
+	network_addr[0] = NAT25_APPLE;
+	memcpy(network_addr + 1, (unsigned char *)network, 2);
+	network_addr[3] = *node;
+}
+
+static inline void
+__nat25_generate_pppoe_network_addr(unsigned char *network_addr,
+				    unsigned char *ac_mac, unsigned short *sid)
+{
+	memset(network_addr, 0, MAX_NETWORK_ADDR_LEN);
+
+	network_addr[0] = NAT25_PPPOE;
+	memcpy(network_addr + 1, (unsigned char *)sid, 2);
+	memcpy(network_addr + 3, (unsigned char *)ac_mac, 6);
+}
+
+#ifdef CL_IPV6_PASS
+static void __nat25_generate_ipv6_network_addr(unsigned char *network_addr,
+					       unsigned int *ip_addr)
+{
+	memset(network_addr, 0, MAX_NETWORK_ADDR_LEN);
+
+	network_addr[0] = NAT25_IPV6;
+	memcpy(network_addr + 1, (unsigned char *)ip_addr, 16);
+}
+
+static unsigned char *scan_tlv(unsigned char *data, int len, unsigned char tag,
+			       unsigned char len8b)
+{
+	while (len > 0) {
+		if (*data == tag && *(data + 1) == len8b && len >= len8b * 8)
+			return data + 2;
+
+		len -= (*(data + 1)) * 8;
+		data += (*(data + 1)) * 8;
+	}
+	return NULL;
+}
+
+static int update_nd_link_layer_addr(unsigned char *data, int len,
+				     unsigned char *replace_mac)
+{
+	struct icmp6hdr *icmphdr = (struct icmp6hdr *)data;
+	unsigned char *mac;
+
+	if (icmphdr->icmp6_type == NDISC_ROUTER_SOLICITATION) {
+		if (len >= 8) {
+			mac = scan_tlv(&data[8], len - 8, 1, 1);
+			if (mac) {
+				pr_info("Router Solicitation, replace MAC From: %02x:%02x:%02x:%02x:%02x:%02x, To: %02x:%02x:%02x:%02x:%02x:%02x\n",
+					mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
+					replace_mac[0], replace_mac[1], replace_mac[2],
+					replace_mac[3], replace_mac[4], replace_mac[5]);
+				memcpy(mac, replace_mac, 6);
+				return 1;
+			}
+		}
+	} else if (icmphdr->icmp6_type == NDISC_ROUTER_ADVERTISEMENT) {
+		if (len >= 16) {
+			mac = scan_tlv(&data[16], len - 16, 1, 1);
+			if (mac) {
+				pr_info("Router Advertisement, replace MAC From: %02x:%02x:%02x:%02x:%02x:%02x, To: %02x:%02x:%02x:%02x:%02x:%02x\n",
+					mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
+					replace_mac[0], replace_mac[1], replace_mac[2],
+					replace_mac[3], replace_mac[4], replace_mac[5]);
+				memcpy(mac, replace_mac, 6);
+				return 1;
+			}
+		}
+	} else if (icmphdr->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION) {
+		if (len >= 24) {
+			mac = scan_tlv(&data[24], len - 24, 1, 1);
+			if (mac) {
+				pr_info("Neighbor Solicitation, replace MAC From: %02x:%02x:%02x:%02x:%02x:%02x, To: %02x:%02x:%02x:%02x:%02x:%02x\n",
+					mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
+					replace_mac[0], replace_mac[1], replace_mac[2],
+					replace_mac[3], replace_mac[4], replace_mac[5]);
+				memcpy(mac, replace_mac, 6);
+				return 1;
+			}
+		}
+	} else if (icmphdr->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT) {
+		if (len >= 24) {
+			mac = scan_tlv(&data[24], len - 24, 2, 1);
+			if (mac) {
+				pr_info("Neighbor Advertisement, replace MAC From: %02x:%02x:%02x:%02x:%02x:%02x, To: %02x:%02x:%02x:%02x:%02x:%02x\n",
+					mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
+					replace_mac[0], replace_mac[1], replace_mac[2],
+					replace_mac[3], replace_mac[4], replace_mac[5]);
+				memcpy(mac, replace_mac, 6);
+				return 1;
+			}
+		}
+	} else if (icmphdr->icmp6_type == NDISC_REDIRECT) {
+		if (len >= 40) {
+			mac = scan_tlv(&data[40], len - 40, 2, 1);
+			if (mac) {
+				pr_info("Redirect,  replace MAC From: %02x:%02x:%02x:%02x:%02x:%02x, To: %02x:%02x:%02x:%02x:%02x:%02x\n",
+					mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
+					replace_mac[0], replace_mac[1], replace_mac[2],
+					replace_mac[3], replace_mac[4], replace_mac[5]);
+				memcpy(mac, replace_mac, 6);
+				return 1;
+			}
+		}
+	}
+	return 0;
+}
+
+#ifdef SUPPORT_RX_UNI2MCAST
+static void convert_ipv6_mac_to_mc(struct sk_buff *skb)
+{
+	struct ipv6hdr *iph = (struct ipv6hdr *)(skb->data + ETH_HLEN);
+	unsigned char *dst_mac = skb->data;
+
+	/*modified by qinjunjie,ipv6 multicast address ix 0x33-33-xx-xx-xx-xx */
+	dst_mac[0] = 0x33;
+	dst_mac[1] = 0x33;
+	memcpy(&dst_mac[2], &iph->daddr.s6_addr32[3], 4);
+#if defined(__LINUX_2_6__)
+	/*modified by qinjunjie,warning:should not remove next line */
+	skb->pkt_type = PACKET_MULTICAST;
+#endif
+}
+#endif /* CL_IPV6_PASS */
+#endif /* SUPPORT_RX_UNI2MCAST */
+
+static inline int __nat25_network_hash(unsigned char *network_addr)
+{
+	if (network_addr[0] == NAT25_IPV4) {
+		unsigned long x;
+
+		x = network_addr[7] ^ network_addr[8] ^ network_addr[9] ^ network_addr[10];
+
+		return x & (NAT25_HASH_SIZE - 1);
+	} else if (network_addr[0] == NAT25_IPX) {
+		unsigned long x;
+
+		x = network_addr[1] ^ network_addr[2] ^ network_addr[3] ^ network_addr[4] ^
+			network_addr[5] ^ network_addr[6] ^ network_addr[7] ^ network_addr[8] ^
+			network_addr[9] ^ network_addr[10];
+
+		return x & (NAT25_HASH_SIZE - 1);
+	} else if (network_addr[0] == NAT25_APPLE) {
+		unsigned long x;
+
+		x = network_addr[1] ^ network_addr[2] ^ network_addr[3];
+
+		return x & (NAT25_HASH_SIZE - 1);
+	} else if (network_addr[0] == NAT25_PPPOE) {
+		unsigned long x;
+
+		x = network_addr[0] ^ network_addr[1] ^ network_addr[2] ^ network_addr[3] ^
+			network_addr[4] ^ network_addr[5] ^ network_addr[6] ^ network_addr[7] ^
+			network_addr[8];
+
+		return x & (NAT25_HASH_SIZE - 1);
+#ifdef CL_IPV6_PASS
+	} else if (network_addr[0] == NAT25_IPV6) {
+		unsigned long x;
+
+		x = network_addr[1] ^ network_addr[2] ^ network_addr[3] ^ network_addr[4] ^
+			network_addr[5] ^ network_addr[6] ^ network_addr[7] ^ network_addr[8] ^
+			network_addr[9] ^ network_addr[10] ^ network_addr[11] ^
+			network_addr[12] ^ network_addr[13] ^ network_addr[14] ^
+			network_addr[15] ^ network_addr[16];
+
+		return x & (NAT25_HASH_SIZE - 1);
+#endif
+	} else {
+		unsigned long x = 0;
+		int i;
+
+		for (i = 0; i < MAX_NETWORK_ADDR_LEN; i++)
+			x ^= network_addr[i];
+
+		return x & (NAT25_HASH_SIZE - 1);
+	}
+}
+
+static inline void __network_hash_link(struct rwnx_vif *vif,
+				       struct nat25_network_db_entry *ent,
+				       int hash)
+{
+	/* Caller must _enter_critical_bh already! */
+	/* _irqL irqL; */
+	/* _enter_critical_bh(&priv->br_ext_lock, &irqL); */
+
+	ent->next_hash = vif->nethash[hash];
+	if (ent->next_hash)
+		ent->next_hash->pprev_hash = &ent->next_hash;
+	vif->nethash[hash] = ent;
+	ent->pprev_hash = &vif->nethash[hash];
+
+	/* _exit_critical_bh(&priv->br_ext_lock, &irqL); */
+}
+
+static inline void __network_hash_unlink(struct nat25_network_db_entry *ent)
+{
+	/* Caller must _enter_critical_bh already! */
+	/* _irqL irqL; */
+	/* _enter_critical_bh(&priv->br_ext_lock, &irqL); */
+
+	*ent->pprev_hash = ent->next_hash;
+	if (ent->next_hash)
+		ent->next_hash->pprev_hash = ent->pprev_hash;
+	ent->next_hash = NULL;
+	ent->pprev_hash = NULL;
+
+	/* _exit_critical_bh(&priv->br_ext_lock, &irqL); */
+}
+
+static int __nat25_db_network_lookup_and_replace(struct rwnx_vif *vif,
+						 struct sk_buff *skb,
+						 unsigned char *network_addr)
+{
+	struct nat25_network_db_entry *db;
+
+	spin_lock_bh(&vif->br_ext_lock);
+
+	db = vif->nethash[__nat25_network_hash(network_addr)];
+	while (db) {
+		if (!memcmp(db->network_addr, network_addr, MAX_NETWORK_ADDR_LEN)) {
+			if (!__nat25_has_expired(vif, db)) {
+				/* replace the destination mac address */
+				memcpy(skb->data, db->mac_addr, ETH_ALEN);
+				atomic_inc(&db->use_count);
+
+#ifdef CL_IPV6_PASS
+				pr_info("NAT25: Lookup M:%02x%02x%02x%02x%02x%02x N:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+					db->mac_addr[0], db->mac_addr[1], db->mac_addr[2],
+					db->mac_addr[3], db->mac_addr[4], db->mac_addr[5],
+					db->network_addr[0], db->network_addr[1],
+					db->network_addr[2], db->network_addr[3],
+					db->network_addr[4], db->network_addr[5],
+					db->network_addr[6], db->network_addr[7],
+					db->network_addr[8], db->network_addr[9],
+					db->network_addr[10], db->network_addr[11],
+					db->network_addr[12], db->network_addr[13],
+					db->network_addr[14], db->network_addr[15],
+					db->network_addr[16]);
+#else
+				pr_info("NAT25: Lookup M:%02x%02x%02x%02x%02x%02x N:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+					db->mac_addr[0], db->mac_addr[1], db->mac_addr[2],
+					db->mac_addr[3], db->mac_addr[4], db->mac_addr[5],
+					db->network_addr[0], db->network_addr[1],
+					db->network_addr[2], db->network_addr[3],
+					db->network_addr[4], db->network_addr[5],
+					db->network_addr[6], db->network_addr[7],
+					db->network_addr[8], db->network_addr[9],
+					db->network_addr[10]);
+#endif
+			}
+			spin_unlock_bh(&vif->br_ext_lock);
+			return 1;
+		}
+
+		db = db->next_hash;
+	}
+
+	spin_unlock_bh(&vif->br_ext_lock);
+	return 0;
+}
+
+static void __nat25_db_network_insert(struct rwnx_vif *vif,
+				      unsigned char *mac_addr,
+				      unsigned char *network_addr)
+{
+	struct nat25_network_db_entry *db;
+	int hash;
+
+	spin_lock_bh(&vif->br_ext_lock);
+
+	hash = __nat25_network_hash(network_addr);
+	db = vif->nethash[hash];
+
+	while (db) {
+		if (!memcmp(db->network_addr, network_addr, MAX_NETWORK_ADDR_LEN)) {
+			memcpy(db->mac_addr, mac_addr, ETH_ALEN);
+			db->ageing_timer = jiffies;
+			spin_unlock_bh(&vif->br_ext_lock);
+			return;
+		}
+
+		db = db->next_hash;
+	}
+
+	db = kmalloc(sizeof(*db), in_interrupt() ? GFP_ATOMIC : GFP_KERNEL);
+	if (!db) {
+		spin_unlock_bh(&vif->br_ext_lock);
+		return;
+	}
+
+	memcpy(db->network_addr, network_addr, MAX_NETWORK_ADDR_LEN);
+	memcpy(db->mac_addr, mac_addr, ETH_ALEN);
+	atomic_set(&db->use_count, 1);
+	db->ageing_timer = jiffies;
+
+	__network_hash_link(vif, db, hash);
+
+	spin_unlock_bh(&vif->br_ext_lock);
+}
+
+static void __nat25_db_print(struct rwnx_vif *vif)
+{
+	spin_lock_bh(&vif->br_ext_lock);
+
+#ifdef BR_SUPPORT_DEBUG
+	static int counter;
+	int i, j;
+	struct nat25_network_db_entry *db;
+
+	counter++;
+	if ((counter % 16) != 0)
+		return;
+
+	for (i = 0, j = 0; i < NAT25_HASH_SIZE; i++) {
+		db = vif->nethash[i];
+
+		while (db) {
+#ifdef CL_IPV6_PASS
+			pr_info("NAT25: DB(%d) H(%02d) C(%d) M:%02x%02x%02x%02x%02x%02x N:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+				j, i, atomic_read(&db->use_count), db->mac_addr[0],
+				db->mac_addr[1], db->mac_addr[2], db->mac_addr[3], db->mac_addr[4],
+				db->mac_addr[5], db->network_addr[0], db->network_addr[1],
+				db->network_addr[2], db->network_addr[3], db->network_addr[4],
+				db->network_addr[5], db->network_addr[6], db->network_addr[7],
+				db->network_addr[8], db->network_addr[9], db->network_addr[10],
+				db->network_addr[11], db->network_addr[12], db->network_addr[13],
+				db->network_addr[14], db->network_addr[15], db->network_addr[16]);
+#else
+			pr_info("NAT25: DB(%d) H(%02d) C(%d) M:%02x%02x%02x%02x%02x%02x N:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+				j, i, atomic_read(&db->use_count), db->mac_addr[0],
+				db->mac_addr[1], db->mac_addr[2], db->mac_addr[3],
+				db->mac_addr[4], db->mac_addr[5], db->network_addr[0],
+				db->network_addr[1], db->network_addr[2], db->network_addr[3],
+				db->network_addr[4], db->network_addr[5], db->network_addr[6],
+				db->network_addr[7], db->network_addr[8], db->network_addr[9],
+				db->network_addr[10]);
+#endif
+			j++;
+
+			db = db->next_hash;
+		}
+	}
+#endif
+
+	spin_unlock_bh(&vif->br_ext_lock);
+}
+
+/*
+ *	NAT2.5 interface
+ */
+
+void nat25_db_cleanup(struct rwnx_vif *vif)
+{
+	int i;
+
+	spin_lock_bh(&vif->br_ext_lock);
+
+	for (i = 0; i < NAT25_HASH_SIZE; i++) {
+		struct nat25_network_db_entry *f;
+
+		f = vif->nethash[i];
+		while (f) {
+			struct nat25_network_db_entry *g;
+
+			g = f->next_hash;
+			if (vif->scdb_entry == f) {
+				memset(vif->scdb_mac, 0, ETH_ALEN);
+				memset(vif->scdb_ip, 0, 4);
+				vif->scdb_entry = NULL;
+			}
+			__network_hash_unlink(f);
+			kfree(f);
+
+			f = g;
+		}
+	}
+
+	spin_unlock_bh(&vif->br_ext_lock);
+}
+
+static inline void clear_scdb_entry(struct rwnx_vif *vif,
+				    struct nat25_network_db_entry *f)
+{
+	if (vif->scdb_entry == f) {
+		memset(vif->scdb_mac, 0, ETH_ALEN);
+		memset(vif->scdb_ip, 0, 4);
+		vif->scdb_entry = NULL;
+	}
+}
+
+void nat25_db_expire(struct rwnx_vif *vif)
+{
+	int i;
+
+	spin_lock_bh(&vif->br_ext_lock);
+
+	/* if(!priv->eth_br_ext_info.nat25_disable) */
+	{
+		for (i = 0; i < NAT25_HASH_SIZE; i++) {
+			struct nat25_network_db_entry *f;
+
+			f = vif->nethash[i];
+
+			while (f) {
+				struct nat25_network_db_entry *g;
+
+				g = f->next_hash;
+
+				if (__nat25_has_expired(vif, f)) {
+					if (atomic_dec_and_test(&f->use_count)) {
+#ifdef BR_SUPPORT_DEBUG
+#ifdef CL_IPV6_PASS
+						panic_printk("NAT25 Expire H(%02d) M:%02x%02x%02x%02x%02x%02x N:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+							     i, f->mac_addr[0], f->mac_addr[1],
+							     f->mac_addr[2], f->mac_addr[3],
+							     f->mac_addr[4], f->mac_addr[5],
+							     f->network_addr[0], f->network_addr[1],
+							     f->network_addr[2], f->network_addr[3],
+							     f->network_addr[4], f->network_addr[5],
+							     f->network_addr[6], f->network_addr[7],
+							     f->network_addr[8], f->network_addr[9],
+							     f->network_addr[10],
+							     f->network_addr[11],
+							     f->network_addr[12],
+							     f->network_addr[13],
+							     f->network_addr[14],
+							     f->network_addr[15],
+							     f->network_addr[16]);
+#else
+
+						panic_printk("NAT25 Expire H(%02d) M:%02x%02x%02x%02x%02x%02x N:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+							     i, f->mac_addr[0], f->mac_addr[1],
+							     f->mac_addr[2], f->mac_addr[3],
+							     f->mac_addr[4], f->mac_addr[5],
+							     f->network_addr[0], f->network_addr[1],
+							     f->network_addr[2], f->network_addr[3],
+							     f->network_addr[4], f->network_addr[5],
+							     f->network_addr[6], f->network_addr[7],
+							     f->network_addr[8], f->network_addr[9],
+							     f->network_addr[10]);
+#endif
+#endif
+						clear_scdb_entry(vif, f);
+						__network_hash_unlink(f);
+						kfree(f);
+					}
+				}
+
+				f = g;
+			}
+		}
+	}
+
+	spin_unlock_bh(&vif->br_ext_lock);
+}
+
+#ifdef SUPPORT_TX_MCAST2UNI
+static int check_ip_mc_and_replace(struct rwnx_vif *vif, struct sk_buff *skb,
+				   unsigned int *dst_ip)
+{
+	struct stat_info *pstat;
+	struct list_head *phead, *plist;
+	int i;
+
+	phead = &vif->asoc_list;
+	plist = phead->next;
+
+	while (plist != phead) {
+		pstat = list_entry(plist, struct stat_info, asoc_list);
+		plist = plist->next;
+
+		if (pstat->ipmc_num == 0)
+			continue;
+
+		for (i = 0; i < MAX_IP_MC_ENTRY; i++) {
+			if (pstat->ipmc[i].used &&
+			    !memcmp(&pstat->ipmc[i].mcmac[3], ((unsigned char *)dst_ip) + 1,
+						3)) {
+				memcpy(skb->data, pstat->ipmc[i].mcmac, ETH_ALEN);
+				return 1;
+			}
+		}
+	}
+	return 0;
+}
+#endif
+
+static inline void check_and_reinit_br_mac(struct rwnx_vif *vif)
+{
+	if ((*(u32 *)vif->br_mac) == 0 &&
+	    (*(u16 *)(vif->br_mac + 4)) == 0) {
+		pr_info("Re-init netdev_br_init() due to br_mac==0!\n");
+		netdev_br_init(vif->ndev);
+	}
+}
+
+static int nat25_handle_pppoe_packet(struct rwnx_vif *vif,
+				     struct pppoe_hdr *ph,
+				     struct sk_buff *skb,
+				     unsigned char network_addr[MAX_NETWORK_ADDR_LEN])
+{
+	struct pppoe_tag *tag, *old_tag;
+	unsigned char tag_buf[40];
+	int old_tag_len = 0;
+	unsigned short *p_magic;
+
+	if (ph->sid == 0) {
+		/* Discovery phase */
+		if (ph->code != PADI_CODE && ph->code != PADR_CODE)
+			return -EINVAL;
+
+		if (vif->eth_br_ext_info.add_pppoe_tag) {
+			tag = (struct pppoe_tag *)tag_buf;
+			old_tag = __nat25_find_pppoe_tag(ph,
+							 ntohs(PTT_RELAY_SID));
+
+			if (old_tag) {
+				/* Copy old value and delete it */
+				old_tag_len = ntohs(old_tag->tag_len);
+
+				if (old_tag_len + TAG_HDR_LEN +
+				    MAGIC_CODE_LEN + RTL_RELAY_TAG_LEN >
+				    sizeof(tag_buf)) {
+					pr_err("SID tag length too long!\n");
+					return -EINVAL;
+				}
+
+				memcpy(tag->tag_data + MAGIC_CODE_LEN +
+				       RTL_RELAY_TAG_LEN,
+				       old_tag->tag_data, old_tag_len);
+
+				if (skb_pull_and_merge(skb,
+						       (unsigned char *)old_tag,
+						       TAG_HDR_LEN + old_tag_len)
+						       < 0) {
+					pr_err("skb_pull_and_merge() failed in PADI/R packet!\n");
+					return -EINVAL;
+				}
+
+				ph->length = htons(ntohs(ph->length) -
+						   TAG_HDR_LEN - old_tag_len);
+			}
+
+			tag->tag_type = PTT_RELAY_SID;
+			tag->tag_len = htons(MAGIC_CODE_LEN +
+					     RTL_RELAY_TAG_LEN + old_tag_len);
+
+			/* Insert magic_code + client MAC */
+			p_magic = (unsigned short *)tag->tag_data;
+			*p_magic = htons(MAGIC_CODE);
+			memcpy(tag->tag_data + MAGIC_CODE_LEN,
+			       skb->data + ETH_ALEN, ETH_ALEN);
+
+			if (__nat25_add_pppoe_tag(skb, tag) < 0)
+				return -EINVAL;
+
+			pr_info("NAT25: Insert PPPoE, forward %s packet\n",
+				(ph->code == PADI_CODE) ? "PADI" : "PADR");
+
+		} else {
+			/* Not adding relay tag */
+			if (vif->pppoe_connection_in_progress &&
+			    memcmp(skb->data + ETH_ALEN,
+				   vif->pppoe_addr, ETH_ALEN)) {
+				pr_info("Discard PPPoE packet: another connection in progress!\n");
+				return -EBUSY;
+			}
+
+			if (vif->pppoe_connection_in_progress == 0)
+				memcpy(vif->pppoe_addr, skb->data + ETH_ALEN,
+				       ETH_ALEN);
+
+			vif->pppoe_connection_in_progress = WAIT_TIME_PPPOE;
+		}
+
+	} else {
+		/* Session phase */
+		pr_info("NAT25: Insert PPPoE session packet to %s\n",
+			skb->dev->name);
+
+		__nat25_generate_pppoe_network_addr(network_addr,
+						    skb->data, &ph->sid);
+
+		__nat25_db_network_insert(vif, skb->data + ETH_ALEN,
+					  network_addr);
+
+		__nat25_db_print(vif);
+
+		if (!vif->eth_br_ext_info.add_pppoe_tag &&
+		    vif->pppoe_connection_in_progress &&
+		    !memcmp(skb->data + ETH_ALEN,
+			    vif->pppoe_addr, ETH_ALEN))
+			vif->pppoe_connection_in_progress = 0;
+	}
+
+	return 0;
+}
+
+int nat25_db_handle(struct rwnx_vif *vif, struct sk_buff *skb, int method)
+{
+	unsigned short protocol;
+	unsigned char network_addr[MAX_NETWORK_ADDR_LEN];
+
+	if (!skb)
+		return -1;
+
+	if (method <= NAT25_MIN || method >= NAT25_MAX)
+		return -1;
+
+	protocol = *((unsigned short *)(skb->data + 2 * ETH_ALEN));
+
+	/*---------------------------------------------------*/
+	/*                 Handle IP frame                  */
+	/*---------------------------------------------------*/
+	if (protocol == htons(ETH_P_IP)) {
+		struct iphdr *iph = (struct iphdr *)(skb->data + ETH_HLEN);
+
+		if (((unsigned char *)(iph) + (iph->ihl << 2)) >=
+			(skb->data + ETH_HLEN + skb->len)) {
+			pr_err("NAT25: malformed IP packet !\n");
+			return -1;
+		}
+
+		switch (method) {
+		case NAT25_CHECK:
+			return -1;
+
+		case NAT25_INSERT: {
+			/* some multicast with source IP is all zero, maybe other case is
+			 * illegal
+			 */
+			/* in class A, B, C, host address is all zero or all one is illegal
+			 */
+			if (iph->saddr == 0)
+				return 0;
+			pr_info("NAT25: Insert IP, SA=%08x, DA=%08x\n", iph->saddr,
+				iph->daddr);
+			__nat25_generate_ipv4_network_addr(network_addr, &iph->saddr);
+			/* record source IP address and , source mac address into db */
+			__nat25_db_network_insert(vif, skb->data + ETH_ALEN, network_addr);
+
+			__nat25_db_print(vif);
+		}
+			return 0;
+
+		case NAT25_LOOKUP: {
+			pr_info("NAT25: Lookup IP, SA=%08x, DA=%08x\n", iph->saddr,
+				iph->daddr);
+#ifdef SUPPORT_TX_MCAST2UNI
+			if (vif->pshare->rf_ft_var.mc2u_disable ||
+			    ((((OPMODE & (WIFI_STATION_STATE | WIFI_ASOC_STATE)) ==
+				   (WIFI_STATION_STATE | WIFI_ASOC_STATE)) &&
+				  !check_ip_mc_and_replace(vif, skb, &iph->daddr)) ||
+				 (OPMODE & WIFI_ADHOC_STATE)))
+#endif
+			{
+				__nat25_generate_ipv4_network_addr(network_addr, &iph->daddr);
+
+				if (!__nat25_db_network_lookup_and_replace(vif, skb,
+									   network_addr)) {
+					if (*((unsigned char *)&iph->daddr + 3) == 0xff) {
+						/* L2 is unicast but L3 is broadcast, make L2 bacome
+						 * broadcast
+						 */
+						pr_info("NAT25: Set DA as broadcast\n");
+						memset(skb->data, 0xff, ETH_ALEN);
+					} else {
+						/* forward unknown IP packet to upper TCP/IP */
+						pr_info("NAT25: Replace DA with BR's MAC\n");
+						check_and_reinit_br_mac(vif);
+						memcpy(skb->data, vif->br_mac, ETH_ALEN);
+					}
+				}
+			}
+		}
+			return 0;
+
+		default:
+			return -1;
+		}
+	}
+
+	/*---------------------------------------------------*/
+	/*                 Handle ARP frame                 */
+	/*---------------------------------------------------*/
+	else if (protocol == htons(ETH_P_ARP)) {
+		struct arphdr *arp = (struct arphdr *)(skb->data + ETH_HLEN);
+		unsigned char *arp_ptr = (unsigned char *)(arp + 1);
+		unsigned int *sender, *target;
+
+		if (arp->ar_pro != htons(ETH_P_IP)) {
+			pr_err("NAT25: arp protocol unknown (%4x)!\n", htons(arp->ar_pro));
+			return -1;
+		}
+
+		switch (method) {
+		case NAT25_CHECK:
+			return 0; /* skb_copy for all ARP frame */
+
+		case NAT25_INSERT: {
+			pr_info("NAT25: Insert ARP, MAC=%02x%02x%02x%02x%02x%02x\n",
+				arp_ptr[0], arp_ptr[1], arp_ptr[2], arp_ptr[3], arp_ptr[4],
+				arp_ptr[5]);
+
+			/* change to ARP sender mac address to wlan STA address */
+			memcpy(arp_ptr, vif->ndev->dev_addr, ETH_ALEN);
+
+			arp_ptr += arp->ar_hln;
+			sender = (unsigned int *)arp_ptr;
+
+			__nat25_generate_ipv4_network_addr(network_addr, sender);
+
+			__nat25_db_network_insert(vif, skb->data + ETH_ALEN, network_addr);
+
+			__nat25_db_print(vif);
+		}
+			return 0;
+
+		case NAT25_LOOKUP: {
+			pr_info("NAT25: Lookup ARP\n");
+
+			arp_ptr += arp->ar_hln;
+			sender = (unsigned int *)arp_ptr;
+			arp_ptr += (arp->ar_hln + arp->ar_pln);
+			target = (unsigned int *)arp_ptr;
+
+			__nat25_generate_ipv4_network_addr(network_addr, target);
+
+			__nat25_db_network_lookup_and_replace(vif, skb, network_addr);
+
+			/* change to ARP target mac address to Lookup result */
+			arp_ptr = (unsigned char *)(arp + 1);
+			arp_ptr += (arp->ar_hln + arp->ar_pln);
+			memcpy(arp_ptr, skb->data, ETH_ALEN);
+		}
+			return 0;
+
+		default:
+			return -1;
+		}
+	}
+
+	/*---------------------------------------------------*/
+	/*         Handle IPX and Apple Talk frame          */
+	/*---------------------------------------------------*/
+	else if ((protocol == htons(ETH_P_IPX)) ||
+		 (protocol == htons(ETH_P_ATALK)) ||
+			 (protocol == htons(ETH_P_AARP))) {
+		unsigned char ipx_header[2] = {0xFF, 0xFF};
+		struct ipxhdr *ipx = NULL;
+		struct elapaarp *ea = NULL;
+		struct ddpehdr *ddp = NULL;
+		unsigned char *frame_ptr = skb->data + ETH_HLEN;
+
+		if (protocol == htons(ETH_P_IPX)) {
+			pr_info("NAT25: Protocol=IPX (Ethernet II)\n");
+			ipx = (struct ipxhdr *)frame_ptr;
+		} else { /* if(protocol <= __constant_htons(ETH_FRAME_LEN)) */
+			if (!memcmp(ipx_header, frame_ptr, 2)) {
+				pr_info("NAT25: Protocol=IPX (Ethernet 802.3)\n");
+				ipx = (struct ipxhdr *)frame_ptr;
+			} else {
+				unsigned char ipx_8022_type = 0xE0;
+				unsigned char snap_8022_type = 0xAA;
+
+				if (*frame_ptr == snap_8022_type) {
+					unsigned char ipx_snap_id[5] = {0x0, 0x0, 0x0, 0x81,
+									0x37}; /* IPX SNAP ID */
+					unsigned char aarp_snap_id[5] = {
+						0x00, 0x00, 0x00, 0x80,
+						0xF3}; /* Apple Talk AARP SNAP ID */
+					unsigned char ddp_snap_id[5] = {
+						0x08, 0x00, 0x07, 0x80,
+						0x9B}; /* Apple Talk DDP SNAP ID */
+
+					frame_ptr += 3; /* eliminate the 802.2 header */
+
+					if (!memcmp(ipx_snap_id, frame_ptr, 5)) {
+						frame_ptr += 5; /* eliminate the SNAP header */
+
+						pr_info("NAT25: Protocol=IPX (Ethernet SNAP)\n");
+						ipx = (struct ipxhdr *)frame_ptr;
+					} else if (!memcmp(aarp_snap_id, frame_ptr, 5)) {
+						frame_ptr += 5; /* eliminate the SNAP header */
+
+						ea = (struct elapaarp *)frame_ptr;
+					} else if (!memcmp(ddp_snap_id, frame_ptr, 5)) {
+						frame_ptr += 5; /* eliminate the SNAP header */
+
+						ddp = (struct ddpehdr *)frame_ptr;
+					} else {
+						pr_info("NAT25: Protocol=Ethernet SNAP %02x%02x%02x%02x%02x\n",
+							frame_ptr[0], frame_ptr[1], frame_ptr[2],
+							frame_ptr[3], frame_ptr[4]);
+						return -1;
+					}
+				} else if (*frame_ptr == ipx_8022_type) {
+					frame_ptr += 3; /* eliminate the 802.2 header */
+
+					if (!memcmp(ipx_header, frame_ptr, 2)) {
+						pr_info("NAT25: Protocol=IPX (Ethernet 802.2)\n");
+						ipx = (struct ipxhdr *)frame_ptr;
+					} else {
+						return -1;
+					}
+				}
+			}
+		}
+
+		/*   IPX  */
+		if (ipx) {
+			unsigned int *ipx_net = NULL;
+			unsigned short *ipx_socket = NULL;
+
+			switch (method) {
+			case NAT25_CHECK:
+				if (!memcmp(skb->data + ETH_ALEN, ipx->ipx_source.node,
+					    ETH_ALEN)) {
+					pr_info("NAT25: Check IPX skb_copy\n");
+					return 0;
+				}
+				return -1;
+
+			case NAT25_INSERT: {
+				pr_info("NAT25: Insert IPX, Dest=%08x,%02x%02x%02x%02x%02x%02x,%04x Source=%08x,%02x%02x%02x%02x%02x%02x,%04x\n",
+					ipx->ipx_dest.net, ipx->ipx_dest.node[0],
+					ipx->ipx_dest.node[1], ipx->ipx_dest.node[2],
+					ipx->ipx_dest.node[3], ipx->ipx_dest.node[4],
+					ipx->ipx_dest.node[5], ipx->ipx_dest.sock,
+					ipx->ipx_source.net, ipx->ipx_source.node[0],
+					ipx->ipx_source.node[1], ipx->ipx_source.node[2],
+					ipx->ipx_source.node[3], ipx->ipx_source.node[4],
+					ipx->ipx_source.node[5], ipx->ipx_source.sock);
+
+				if (!memcmp(skb->data + ETH_ALEN, ipx->ipx_source.node,
+					    ETH_ALEN)) {
+					unsigned int *ipx_net = NULL;
+					unsigned short *ipx_socket = NULL;
+
+					pr_info("NAT25: Use IPX Net, and Socket as network addr\n");
+
+					ipx_net = &ipx->ipx_source.net;
+					ipx_socket = &ipx->ipx_source.sock;
+					__nat25_generate_ipx_network_addr_with_socket(network_addr,
+										      ipx_net,
+										      ipx_socket);
+
+					/* change IPX source node addr to wlan STA address */
+					memcpy(ipx->ipx_source.node, vif->ndev->dev_addr, ETH_ALEN);
+				} else {
+					unsigned int *ipx_net = NULL;
+					unsigned char *ipx_node = NULL;
+
+					ipx_net = &ipx->ipx_source.net;
+					ipx_node = ipx->ipx_source.node;
+					__nat25_generate_ipx_network_addr_with_node(network_addr,
+										    ipx_net,
+										    ipx_node);
+				}
+
+				__nat25_db_network_insert(vif, skb->data + ETH_ALEN,
+							  network_addr);
+
+				__nat25_db_print(vif);
+			}
+				return 0;
+
+			case NAT25_LOOKUP: {
+				if (!memcmp(vif->ndev->dev_addr, ipx->ipx_dest.node,
+					    ETH_ALEN)) {
+					unsigned int *ipx_net = &ipx->ipx_dest.net;
+					unsigned short *ipx_socket = &ipx->ipx_dest.sock;
+
+					pr_info("NAT25: Lookup IPX, Modify Destination IPX Node addr\n");
+
+					__nat25_generate_ipx_network_addr_with_socket(network_addr,
+										      ipx_net,
+										      ipx_socket);
+
+					__nat25_db_network_lookup_and_replace(vif, skb,
+									      network_addr);
+
+					/* replace IPX destination node addr with Lookup destination
+					 * MAC addr
+					 */
+					memcpy(ipx->ipx_dest.node, skb->data, ETH_ALEN);
+				} else {
+					unsigned int *ipx_net = &ipx->ipx_dest.net;
+					unsigned char *ipx_node = ipx->ipx_dest.node;
+
+					__nat25_generate_ipx_network_addr_with_node(network_addr,
+										    ipx_net,
+										    ipx_node);
+
+					__nat25_db_network_lookup_and_replace(vif, skb,
+									      network_addr);
+				}
+			}
+				return 0;
+
+			default:
+				return -1;
+			}
+		}
+
+		/*   AARP  */
+		else if (ea) {
+			/* Sanity check fields. */
+			if (ea->hw_len != ETH_ALEN || ea->pa_len != AARP_PA_ALEN) {
+				pr_err("NAT25: Appletalk AARP Sanity check fail!\n");
+				return -1;
+			}
+
+			switch (method) {
+			case NAT25_CHECK:
+				return 0;
+
+			case NAT25_INSERT: {
+				/* change to AARP source mac address to wlan STA address */
+				memcpy(ea->hw_src, vif->ndev->dev_addr, ETH_ALEN);
+
+				pr_info("NAT25: Insert AARP, Source=%d,%d Destination=%d,%d\n",
+					ea->pa_src_net, ea->pa_src_node, ea->pa_dst_net,
+					ea->pa_dst_node);
+
+				__nat25_generate_apple_network_addr(network_addr,
+								    &ea->pa_src_net,
+								    &ea->pa_src_node);
+
+				__nat25_db_network_insert(vif, skb->data + ETH_ALEN,
+							  network_addr);
+
+				__nat25_db_print(vif);
+			}
+				return 0;
+
+			case NAT25_LOOKUP: {
+				pr_info("NAT25: Lookup AARP, Source=%d,%d Destination=%d,%d\n",
+					ea->pa_src_net, ea->pa_src_node, ea->pa_dst_net,
+					ea->pa_dst_node);
+
+				__nat25_generate_apple_network_addr(network_addr,
+								    &ea->pa_dst_net,
+								    &ea->pa_dst_node);
+
+				__nat25_db_network_lookup_and_replace(vif, skb, network_addr);
+
+				/* change to AARP destination mac address to Lookup result */
+				memcpy(ea->hw_dst, skb->data, ETH_ALEN);
+			}
+				return 0;
+
+			default:
+				return -1;
+			}
+		}
+
+		/*   DDP  */
+		else if (ddp) {
+			switch (method) {
+			case NAT25_CHECK:
+				return -1;
+
+			case NAT25_INSERT: {
+				pr_info("NAT25: Insert DDP, Source=%d,%d Destination=%d,%d\n",
+					ddp->deh_snet, ddp->deh_snode, ddp->deh_dnet,
+					ddp->deh_dnode);
+
+				__nat25_generate_apple_network_addr(network_addr, &ddp->deh_snet,
+								    &ddp->deh_snode);
+
+				__nat25_db_network_insert(vif, skb->data + ETH_ALEN,
+							  network_addr);
+
+				__nat25_db_print(vif);
+			}
+				return 0;
+
+			case NAT25_LOOKUP: {
+				pr_info("NAT25: Lookup DDP, Source=%d,%d Destination=%d,%d\n",
+					ddp->deh_snet, ddp->deh_snode, ddp->deh_dnet,
+					ddp->deh_dnode);
+
+				__nat25_generate_apple_network_addr(network_addr, &ddp->deh_dnet,
+								    &ddp->deh_dnode);
+
+				__nat25_db_network_lookup_and_replace(vif, skb, network_addr);
+			}
+				return 0;
+
+			default:
+				return -1;
+			}
+		}
+
+		return -1;
+	}
+
+	/*---------------------------------------------------*/
+	/*                Handle PPPoE frame                */
+	/*---------------------------------------------------*/
+	else if ((protocol == htons(ETH_P_PPP_DISC)) ||
+		 (protocol == htons(ETH_P_PPP_SES))) {
+		struct pppoe_hdr *ph = (struct pppoe_hdr *)(skb->data + ETH_HLEN);
+		unsigned short *pmagic;
+
+		switch (method) {
+		case NAT25_CHECK:
+			if (ph->sid == 0)
+				return 0;
+			return 1;
+
+		case NAT25_INSERT:
+			return nat25_handle_pppoe_packet(vif, ph, skb,
+							 network_addr);
+
+		case NAT25_LOOKUP:
+			if (ph->code == PADO_CODE || ph->code == PADS_CODE) {
+				if (vif->eth_br_ext_info.add_pppoe_tag) {
+					struct pppoe_tag *tag;
+					unsigned char *ptr;
+					unsigned short tag_type, tag_len;
+					int offset = 0;
+
+					ptr = __nat25_find_pppoe_tag(ph, ntohs(PTT_RELAY_SID));
+					if (ptr == 0) {
+						pr_err("Fail to find PTT_RELAY_SID in FADO!\n");
+						return -1;
+					}
+
+					tag = (struct pppoe_tag *)ptr;
+					tag_type = (unsigned short)((ptr[0] << 8) + ptr[1]);
+					tag_len = (unsigned short)((ptr[2] << 8) + ptr[3]);
+
+					if (tag_type != ntohs(PTT_RELAY_SID) ||
+					    (tag_len < (MAGIC_CODE_LEN + RTL_RELAY_TAG_LEN))) {
+						pr_err("Invalid PTT_RELAY_SID tag length [%d]!\n",
+						       tag_len);
+						return -1;
+					}
+
+					pmagic = (unsigned short *)tag->tag_data;
+					if (ntohs(*pmagic) != MAGIC_CODE) {
+						pr_err("Can't find MAGIC_CODE in %s packet!\n",
+						       (ph->code == PADO_CODE ? "PADO" : "PADS"));
+						return -1;
+					}
+
+					memcpy(skb->data, tag->tag_data + MAGIC_CODE_LEN, ETH_ALEN);
+
+					if (tag_len > MAGIC_CODE_LEN + RTL_RELAY_TAG_LEN)
+						offset = TAG_HDR_LEN;
+
+					if (skb_pull_and_merge(skb, ptr + offset,
+							       TAG_HDR_LEN + MAGIC_CODE_LEN +
+							       RTL_RELAY_TAG_LEN - offset) <
+						0) {
+						pr_err("call skb_pull_and_merge() failed in PADO packet!\n");
+						return -1;
+					}
+					ph->length = htons(ntohs(ph->length) -
+							   (TAG_HDR_LEN + MAGIC_CODE_LEN +
+							    RTL_RELAY_TAG_LEN - offset));
+					if (offset > 0)
+						tag->tag_len =
+							htons(tag_len - MAGIC_CODE_LEN -
+							      RTL_RELAY_TAG_LEN);
+
+					pr_info("NAT25: Lookup PPPoE, forward %s Packet from %s\n",
+						(ph->code == PADO_CODE ? "PADO" : "PADS"),
+						   skb->dev->name);
+				} else { /* not add relay tag */
+					if (!vif->pppoe_connection_in_progress) {
+						pr_err("Discard PPPoE packet due to no connection in progress!\n");
+						return -1;
+					}
+					memcpy(skb->data, vif->pppoe_addr, ETH_ALEN);
+					vif->pppoe_connection_in_progress = WAIT_TIME_PPPOE;
+				}
+			} else {
+				if (ph->sid != 0) {
+					pr_info("NAT25: Lookup PPPoE, lookup session packet from %s\n",
+						skb->dev->name);
+					__nat25_generate_pppoe_network_addr(network_addr,
+									    skb->data +
+									    ETH_ALEN,
+									    &ph->sid);
+
+					__nat25_db_network_lookup_and_replace(vif, skb,
+									      network_addr);
+
+					__nat25_db_print(vif);
+				} else {
+					return -1;
+				}
+			}
+			return 0;
+
+		default:
+			return -1;
+		}
+	}
+
+	/*---------------------------------------------------*/
+	/*                 Handle EAP frame                 */
+	/*---------------------------------------------------*/
+	else if (protocol == htons(0x888e)) {
+		switch (method) {
+		case NAT25_CHECK:
+			return -1;
+
+		case NAT25_INSERT:
+			return 0;
+
+		case NAT25_LOOKUP:
+			return 0;
+
+		default:
+			return -1;
+		}
+	}
+
+	/*---------------------------------------------------*/
+	/*         Handle C-Media proprietary frame         */
+	/*---------------------------------------------------*/
+	else if ((protocol == htons(0xe2ae)) ||
+		 (protocol == htons(0xe2af))) {
+		switch (method) {
+		case NAT25_CHECK:
+			return -1;
+
+		case NAT25_INSERT:
+			return 0;
+
+		case NAT25_LOOKUP:
+			return 0;
+
+		default:
+			return -1;
+		}
+	}
+
+	/*---------------------------------------------------*/
+	/*         Handle IPV6 frame                                */
+	/*---------------------------------------------------*/
+#ifdef CL_IPV6_PASS
+	else if (protocol == htons(ETH_P_IPV6)) {
+		struct ipv6hdr *iph = (struct ipv6hdr *)(skb->data + ETH_HLEN);
+
+		if (sizeof(*iph) >= (skb->len - ETH_HLEN)) {
+			pr_err("NAT25: malformed IPv6 packet !\n");
+			return -1;
+		}
+
+		switch (method) {
+		case NAT25_CHECK:
+			if (skb->data[0] & 1)
+				return 0;
+			return -1;
+
+		case NAT25_INSERT: {
+			pr_info("NAT25: Insert IP, SA=%4x:%4x:%4x:%4x:%4x:%4x:%4x:%4x, DA=%4x:%4x:%4x:%4x:%4x:%4x:%4x:%4x\n",
+				iph->saddr.s6_addr16[0], iph->saddr.s6_addr16[1],
+				iph->saddr.s6_addr16[2], iph->saddr.s6_addr16[3],
+				iph->saddr.s6_addr16[4], iph->saddr.s6_addr16[5],
+				iph->saddr.s6_addr16[6], iph->saddr.s6_addr16[7],
+				iph->daddr.s6_addr16[0], iph->daddr.s6_addr16[1],
+				iph->daddr.s6_addr16[2], iph->daddr.s6_addr16[3],
+				iph->daddr.s6_addr16[4], iph->daddr.s6_addr16[5],
+				iph->daddr.s6_addr16[6], iph->daddr.s6_addr16[7]);
+
+			if (memcmp(&iph->saddr,
+				   "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0",
+					   16)) {
+				__nat25_generate_ipv6_network_addr(network_addr,
+								   (unsigned int *)&iph->saddr);
+				__nat25_db_network_insert(vif, skb->data + ETH_ALEN,
+							  network_addr);
+				__nat25_db_print(vif);
+
+				if (iph->nexthdr == IPPROTO_ICMPV6 &&
+				    skb->len > (ETH_HLEN + sizeof(*iph) + 4)) {
+					if (update_nd_link_layer_addr(skb->data +
+								      ETH_HLEN +
+								      sizeof(*iph),
+								      skb->len -
+								      ETH_HLEN -
+								      sizeof(*iph),
+								      vif->ndev->dev_addr)) {
+						struct icmp6hdr *hdr =
+							(struct icmp6hdr *)(skb->data + ETH_HLEN +
+									    sizeof(*iph));
+						hdr->icmp6_cksum = 0;
+						hdr->icmp6_cksum =
+						csum_ipv6_magic(&iph->saddr, &iph->daddr,
+								iph->payload_len, IPPROTO_ICMPV6,
+								csum_partial((__u8 *)hdr,
+									     iph->payload_len,
+									     0));
+					}
+				}
+			}
+		}
+			return 0;
+
+		case NAT25_LOOKUP:
+			pr_info("NAT25: Lookup IP, SA=%4x:%4x:%4x:%4x:%4x:%4x:%4x:%4x, DA=%4x:%4x:%4x:%4x:%4x:%4x:%4x:%4x\n",
+				iph->saddr.s6_addr16[0], iph->saddr.s6_addr16[1],
+				iph->saddr.s6_addr16[2], iph->saddr.s6_addr16[3],
+				iph->saddr.s6_addr16[4], iph->saddr.s6_addr16[5],
+				iph->saddr.s6_addr16[6], iph->saddr.s6_addr16[7],
+				iph->daddr.s6_addr16[0], iph->daddr.s6_addr16[1],
+				iph->daddr.s6_addr16[2], iph->daddr.s6_addr16[3],
+				iph->daddr.s6_addr16[4], iph->daddr.s6_addr16[5],
+				iph->daddr.s6_addr16[6], iph->daddr.s6_addr16[7]);
+
+			__nat25_generate_ipv6_network_addr(network_addr,
+							   (unsigned int *)&iph->daddr);
+			if (!__nat25_db_network_lookup_and_replace(vif, skb, network_addr)) {
+#ifdef SUPPORT_RX_UNI2MCAST
+				if (iph->daddr.s6_addr[0] == 0xff)
+					convert_ipv6_mac_to_mc(skb);
+#endif
+			}
+			return 0;
+
+		default:
+			return -1;
+		}
+	}
+#endif /* CL_IPV6_PASS */
+
+	return -1;
+}
+
+int nat25_handle_frame(struct rwnx_vif *vif, struct sk_buff *skb)
+{
+	// printk("%s : vif_type=%d\n",__func__,RWNX_VIF_TYPE(vif));
+#ifdef BR_SUPPORT_DEBUG
+	if (!vif->eth_br_ext_info.nat25_disable && (!(skb->data[0] & 1))) {
+		pr_info("NAT25: Input Frame: DA=%02x%02x%02x%02x%02x%02x SA=%02x%02x%02x%02x%02x%02x\n",
+			skb->data[0], skb->data[1], skb->data[2], skb->data[3],
+			skb->data[4], skb->data[5], skb->data[6], skb->data[7],
+			skb->data[8], skb->data[9], skb->data[10], skb->data[11]);
+	}
+#endif
+
+	if (!(skb->data[0] & 1)) {
+		int is_vlan_tag = 0, i, retval = 0;
+		unsigned short vlan_hdr = 0;
+
+		if (*((unsigned short *)(skb->data + ETH_ALEN * 2)) ==
+			htons(ETH_P_8021Q)) {
+			is_vlan_tag = 1;
+			vlan_hdr = *((unsigned short *)(skb->data + ETH_ALEN * 2 + 2));
+			for (i = 0; i < 6; i++)
+				*((unsigned short *)(skb->data + ETH_ALEN * 2 + 2 - i * 2)) =
+					*((unsigned short *)(skb->data + ETH_ALEN * 2 - 2 - i * 2));
+			skb_pull(skb, 4);
+		}
+
+		if (!vif->eth_br_ext_info.nat25_disable) {
+			unsigned long irqL;
+
+			spin_lock_bh(&vif->br_ext_lock);
+			/*
+			 *  This function look up the destination network address from
+			 *  the NAT2.5 database. Return value = -1 means that the
+			 *  corresponding network protocol is NOT support.
+			 */
+			if (!vif->eth_br_ext_info.nat25sc_disable &&
+			    (*((unsigned short *)(skb->data + ETH_ALEN * 2)) ==
+				 htons(ETH_P_IP)) &&
+				!memcmp(vif->scdb_ip, skb->data + ETH_HLEN + 16, 4)) {
+				memcpy(skb->data, vif->scdb_mac, ETH_ALEN);
+
+				spin_unlock_bh(&vif->br_ext_lock);
+			} else {
+				spin_unlock_bh(&vif->br_ext_lock);
+
+				retval = nat25_db_handle(vif, skb, NAT25_LOOKUP);
+			}
+		} else {
+			if (((*((unsigned short *)(skb->data + ETH_ALEN * 2)) ==
+				  htons(ETH_P_IP)) &&
+				 !memcmp(vif->br_ip, skb->data + ETH_HLEN + 16, 4)) ||
+				((*((unsigned short *)(skb->data + ETH_ALEN * 2)) ==
+				  htons(ETH_P_ARP)) &&
+				 !memcmp(vif->br_ip, skb->data + ETH_HLEN + 24, 4))) {
+				/* for traffic to upper TCP/IP */
+				retval = nat25_db_handle(vif, skb, NAT25_LOOKUP);
+			}
+		}
+
+		if (is_vlan_tag) {
+			skb_push(skb, 4);
+			for (i = 0; i < 6; i++)
+				*((unsigned short *)(skb->data + i * 2)) =
+					*((unsigned short *)(skb->data + 4 + i * 2));
+			*((unsigned short *)(skb->data + ETH_ALEN * 2)) =
+				htons(ETH_P_8021Q);
+			*((unsigned short *)(skb->data + ETH_ALEN * 2 + 2)) = vlan_hdr;
+		}
+
+		if (retval == -1) {
+			/* DEBUG_ERR("NAT25: Lookup fail!\n"); */
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+#define SERVER_PORT    67
+#define CLIENT_PORT    68
+#define DHCP_MAGIC     0x63825363
+#define BROADCAST_FLAG 0x8000
+
+struct dhcp_message {
+	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];
+	u32 cookie;
+	u8 options[308]; /* 312 - cookie */
+};
+
+static inline void dhcp_fix_broadcast_flag(struct dhcp_message *dhcph,
+					   struct udphdr *udph)
+{
+	int sum;
+
+	if (dhcph->cookie == __constant_htonl(DHCP_MAGIC)) {
+		/* match magic word */
+		if (!(dhcph->flags & htons(BROADCAST_FLAG))) {
+			/* if not broadcast */
+			pr_info("DHCP: change flag of DHCP request to broadcast.\n");
+
+			/* OR BROADCAST flag */
+			dhcph->flags |= htons(BROADCAST_FLAG);
+
+			/* recalculate checksum */
+			sum = ~(udph->check) & 0xffff;
+			sum += dhcph->flags;
+			while (sum >> 16)
+				sum = (sum & 0xffff) + (sum >> 16);
+			udph->check = ~sum;
+		}
+	}
+}
+
+void dhcp_flag_bcast(struct rwnx_vif *vif, struct sk_buff *skb)
+{
+	if (!skb)
+		return;
+	// debug trace
+	// print_hex_dump(KERN_ERR, "SKB DUMP: SKB->DATA== ", DUMP_PREFIX_NONE, 32,
+	// 1, skb->data, 64,false);
+	if (!vif->eth_br_ext_info.dhcp_bcst_disable) {
+		unsigned short protocol =
+			*((unsigned short *)(skb->data + 2 * ETH_ALEN));
+		pr_info("%s  protocol: %04x\n", __func__, protocol);
+
+		if (protocol == htons(ETH_P_IP)) { /* IP */
+			struct iphdr *iph = (struct iphdr *)(skb->data + ETH_HLEN);
+
+			if (iph->protocol == IPPROTO_UDP) { /* UDP */
+				struct udphdr *udph =
+					(struct udphdr *)((u8 *)iph + (iph->ihl << 2));
+
+				if (udph->source == htons(CLIENT_PORT) &&
+				    udph->dest ==
+					 htons(SERVER_PORT)) { /* DHCP request */
+					struct dhcp_message *dhcph =
+						(struct dhcp_message *)((u8 *)udph +
+									sizeof(struct udphdr));
+					dhcp_fix_broadcast_flag(dhcph, udph);
+				}
+			}
+		}
+	}
+}
+
+void *scdb_find_entry(struct rwnx_vif *vif, unsigned char *mac_addr,
+		      unsigned char *ip_addr)
+{
+	unsigned char network_addr[MAX_NETWORK_ADDR_LEN];
+	struct nat25_network_db_entry *db;
+	int hash;
+
+	__nat25_generate_ipv4_network_addr(network_addr, (unsigned int *)ip_addr);
+	hash = __nat25_network_hash(network_addr);
+	db = vif->nethash[hash];
+	while (db) {
+		if (!memcmp(db->network_addr, network_addr, MAX_NETWORK_ADDR_LEN))
+			return (void *)db;
+
+		db = db->next_hash;
+	}
+
+	return NULL;
+}
+
+#endif /* CONFIG_AIC8800_BR_SUPPORT */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.h b/drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.h
new file mode 100644
index 0000000000000..f64a7592cbff2
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_br_ext.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ ****************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief bridge function definitions
+ *
+ ****************************************************************************
+ */
+
+#ifndef _AIC_BR_EXT_H_
+#define _AIC_BR_EXT_H_
+
+#define CL_IPV6_PASS    1
+#define MACADDRLEN      6
+#define WLAN_ETHHDR_LEN 14
+
+#define NAT25_HASH_BITS   4
+#define NAT25_HASH_SIZE   BIT(NAT25_HASH_BITS)
+#define NAT25_AGEING_TIME 300
+
+#define NDEV_FMT            "%s"
+#define NDEV_ARG(ndev)      ((ndev)->name)
+#define ADPT_FMT            "%s"
+// #define ADPT_ARG(adapter) (adapter->pnetdev ? adapter->pnetdev->name : NULL)
+#define FUNC_NDEV_FMT       "%s(%s)"
+#define FUNC_NDEV_ARG(ndev) (__func__, (ndev)->name)
+#define FUNC_ADPT_FMT       "%s(%s)"
+// #define FUNC_ADPT_ARG(adapter) __func__, (adapter->pnetdev ?
+// adapter->pnetdev->name : NULL)
+#define MAC_FMT             "%02x:%02x:%02x:%02x:%02x:%02x"
+
+#ifdef CL_IPV6_PASS
+#define MAX_NETWORK_ADDR_LEN 17
+#else
+#define MAX_NETWORK_ADDR_LEN 11
+#endif
+
+struct nat25_network_db_entry {
+	struct nat25_network_db_entry *next_hash;
+	struct nat25_network_db_entry **pprev_hash;
+	atomic_t use_count;
+	unsigned char mac_addr[6];
+	unsigned long ageing_timer;
+	unsigned char network_addr[MAX_NETWORK_ADDR_LEN];
+};
+
+enum NAT25_METHOD {
+	NAT25_MIN,
+	NAT25_CHECK,
+	NAT25_INSERT,
+	NAT25_LOOKUP,
+	NAT25_PARSE,
+	NAT25_MAX
+};
+
+struct br_ext_info {
+	unsigned int nat25_disable;
+	unsigned int macclone_enable;
+	unsigned int dhcp_bcst_disable;
+	int add_pppoe_tag; /* 1: Add PPPoE relay-SID, 0: disable */
+	unsigned char nat25_dmz_mac[MACADDRLEN];
+	unsigned int nat25sc_disable;
+};
+
+void nat25_db_cleanup(struct rwnx_vif *vif);
+int nat25_handle_frame(struct rwnx_vif *vif, struct sk_buff *skb);
+int aic_br_client_tx(struct rwnx_vif *vif, struct sk_buff **pskb);
+void netdev_br_init(struct net_device *netdev);
+
+#endif /* _AIC_BR_EXT_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.c b/drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.c
new file mode 100644
index 0000000000000..5c9ed79c7618f
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.c
@@ -0,0 +1,1164 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ****************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief bt uses the sdio interface function definitions
+ *
+ ****************************************************************************
+ */
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/ioctl.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+
+#include "aic_btsdio.h"
+#include "rwnx_msg_tx.h"
+
+static spinlock_t queue_lock;
+
+static inline struct sk_buff *bt_skb_alloc(unsigned int len, gfp_t how)
+{
+	struct sk_buff *skb;
+
+	skb = alloc_skb(len + BT_SKB_RESERVE, how);
+	if (skb) {
+		skb_reserve(skb, BT_SKB_RESERVE);
+		bt_cb(skb)->incoming = 0;
+	}
+	return skb;
+}
+
+static spinlock_t queue_lock;
+static spinlock_t dlfw_lock;
+static u16 dlfw_dis_state;
+
+/* Global parameters for bt usb char driver */
+#define BT_CHAR_DEVICE_NAME "aicbt_dev"
+/* mutex for btchr */
+struct mutex btchr_mutex;
+static struct sk_buff_head btchr_readq;
+static wait_queue_head_t btchr_read_wait;
+static wait_queue_head_t bt_dlfw_wait;
+static int bt_char_dev_registered;
+static dev_t bt_devid;              /* bt char device number */
+static struct cdev bt_char_dev;     /* bt character device structure */
+static struct class *bt_char_class; /* device class for usb char driver */
+static int bt_reset;
+/* HCI device & lock */
+DEFINE_RWLOCK(hci_dev_lock);
+
+struct hci_dev *ghdev;
+
+static struct sk_buff *aic_skb_queue[QUEUE_SIZE];
+static int aic_skb_queue_front;
+static int aic_skb_queue_rear;
+
+static inline int check_set_dlfw_state_value(uint16_t change_value)
+{
+	spin_lock(&dlfw_lock);
+	if (!dlfw_dis_state)
+		dlfw_dis_state = change_value;
+	spin_unlock(&dlfw_lock);
+	return dlfw_dis_state;
+}
+
+static inline void set_dlfw_state_value(uint16_t change_value)
+{
+	spin_lock(&dlfw_lock);
+	dlfw_dis_state = change_value;
+	spin_unlock(&dlfw_lock);
+}
+
+static void print_acl(struct sk_buff *skb, int direction)
+{
+#if PRINT_ACL_DATA
+	u16 *handle = (u16 *)(skb->data);
+	u16 len = *(handle + 1);
+
+	AICBT_INFO("aic %s: direction %d, handle %04x, len %d", __func__, direction,
+		   *handle, len);
+#endif
+}
+
+static void print_sco(struct sk_buff *skb, int direction)
+{
+#if PRINT_SCO_DATA
+	uint wlength = skb->len;
+	u16 *handle = (u16 *)(skb->data);
+	u8 len = *(u8 *)(handle + 1);
+
+	AICBT_INFO("aic %s: direction %d, handle %04x, len %d,wlength %d", __func__,
+		   direction, *handle, len, wlength);
+#endif
+}
+
+int bt_bypass_event(struct sk_buff *skb)
+{
+	int ret = 0;
+	u8 *opcode = (u8 *)(skb->data);
+	// debug trace
+	// printk("bypass_event
+	// %x,%x,%x,%x,%x\r\n",opcode[0],opcode[1],opcode[2],opcode[3],opcode[4]);
+
+	switch (opcode[1]) {
+	case HCI_EV_LE_Meta: {
+		u8 subevent_code;
+
+		subevent_code = opcode[3];
+		switch (subevent_code) {
+		case HCI_BLE_ADV_PKT_RPT_EVT:
+		case HCI_LE_EXTENDED_ADVERTISING_REPORT_EVT: {
+			if (aic_queue_cnt() > (QUEUE_SIZE - 490)) {
+				pr_warn("more adv report bypass\r\n");
+				ret = 1;
+			}
+		} break;
+		}
+	} break;
+	default:
+		break;
+	}
+	return ret;
+}
+
+int bt_sdio_recv(u8 *data, u32 data_len)
+{
+	struct sk_buff *skb;
+	int type = data[0];
+	struct hci_dev *hdev;
+	u32 len = data_len;
+
+	hdev = hci_dev_get(0);
+	if (!hdev) {
+		AICWFDBG(LOGERROR, "%s: Failed to get hci dev[NULL]", __func__);
+		return -ENODEV;
+	}
+
+	skb = alloc_skb(len, GFP_ATOMIC);
+	if (!skb)
+		AICWFDBG(LOGERROR, "alloc skb fail %s\n", __func__);
+	memcpy(skb_put(skb, len), data, len);
+	if (bt_bypass_event(skb)) {
+		kfree_skb(skb);
+		return 0;
+	}
+	// bt_data_dump("bt_skb", skb, skb->len); //debug trace
+
+	if (aic_enqueue(skb) < 0) {
+		kfree_skb(skb);
+	} else {
+		// printk("wake up\n"); //debug trace
+		wake_up_interruptible(&btchr_read_wait);
+	}
+	return 0;
+}
+
+static int bypass_event(struct sk_buff *skb)
+{
+	int ret = 0;
+	u8 *opcode = (u8 *)(skb->data);
+
+	switch (*opcode) {
+#ifdef CONFIG_SUPPORT_VENDOR_APCF
+	case HCI_EV_CMD_COMPLETE: {
+		u16 sub_opcpde;
+
+		sub_opcpde = ((u16)opcode[3] | (u16)(opcode[4]) << 8);
+		if (sub_opcpde == 0xfd57) {
+			if (vendor_apcf_sent_done) {
+				vendor_apcf_sent_done--;
+				pr_warn("apcf bypass\r\n");
+				ret = 1;
+			}
+		}
+	} break;
+#endif // CONFIG_SUPPORT_VENDOR_APCF
+	case HCI_EV_LE_Meta: {
+		u8 subevent_code;
+
+		subevent_code = opcode[2];
+		switch (subevent_code) {
+		case HCI_BLE_ADV_PKT_RPT_EVT:
+		case HCI_LE_EXTENDED_ADVERTISING_REPORT_EVT: {
+			if (aic_queue_cnt() > (QUEUE_SIZE - 100)) {
+				pr_warn("more adv report bypass\r\n");
+				ret = 1;
+			}
+		} break;
+		}
+	} break;
+	default:
+		break;
+	}
+	return ret;
+}
+
+static void print_event(struct sk_buff *skb)
+{
+#if PRINT_CMD_EVENT
+	u8 *opcode = (u8 *)(skb->data);
+
+	pr_info("aic %s ", __func__);
+	switch (*opcode) {
+	case HCI_EV_INQUIRY_COMPLETE:
+		pr_info("HCI_EV_INQUIRY_COMPLETE");
+		break;
+	case HCI_EV_INQUIRY_RESULT:
+		pr_info("HCI_EV_INQUIRY_RESULT");
+		break;
+	case HCI_EV_CONN_COMPLETE:
+		pr_info("HCI_EV_CONN_COMPLETE");
+		break;
+	case HCI_EV_CONN_REQUEST:
+		pr_info("HCI_EV_CONN_REQUEST");
+		break;
+	case HCI_EV_DISCONN_COMPLETE:
+		pr_info("HCI_EV_DISCONN_COMPLETE");
+		break;
+	case HCI_EV_AUTH_COMPLETE:
+		pr_info("HCI_EV_AUTH_COMPLETE");
+		break;
+	case HCI_EV_REMOTE_NAME:
+		pr_info("HCI_EV_REMOTE_NAME");
+		break;
+	case HCI_EV_ENCRYPT_CHANGE:
+		pr_info("HCI_EV_ENCRYPT_CHANGE");
+		break;
+	case HCI_EV_CHANGE_LINK_KEY_COMPLETE:
+		pr_info("HCI_EV_CHANGE_LINK_KEY_COMPLETE");
+		break;
+	case HCI_EV_REMOTE_FEATURES:
+		pr_info("HCI_EV_REMOTE_FEATURES");
+		break;
+	case HCI_EV_REMOTE_VERSION:
+		pr_info("HCI_EV_REMOTE_VERSION");
+		break;
+	case HCI_EV_QOS_SETUP_COMPLETE:
+		pr_info("HCI_EV_QOS_SETUP_COMPLETE");
+		break;
+	case HCI_EV_CMD_COMPLETE:
+		pr_info("HCI_EV_CMD_COMPLETE");
+		break;
+	case HCI_EV_CMD_STATUS:
+		pr_info("HCI_EV_CMD_STATUS");
+		break;
+	case HCI_EV_ROLE_CHANGE:
+		pr_info("HCI_EV_ROLE_CHANGE");
+		break;
+	case HCI_EV_NUM_COMP_PKTS:
+		pr_info("HCI_EV_NUM_COMP_PKTS");
+		break;
+	case HCI_EV_MODE_CHANGE:
+		pr_info("HCI_EV_MODE_CHANGE");
+		break;
+	case HCI_EV_PIN_CODE_REQ:
+		pr_info("HCI_EV_PIN_CODE_REQ");
+		break;
+	case HCI_EV_LINK_KEY_REQ:
+		pr_info("HCI_EV_LINK_KEY_REQ");
+		break;
+	case HCI_EV_LINK_KEY_NOTIFY:
+		pr_info("HCI_EV_LINK_KEY_NOTIFY");
+		break;
+	case HCI_EV_CLOCK_OFFSET:
+		pr_info("HCI_EV_CLOCK_OFFSET");
+		break;
+	case HCI_EV_PKT_TYPE_CHANGE:
+		pr_info("HCI_EV_PKT_TYPE_CHANGE");
+		break;
+	case HCI_EV_PSCAN_REP_MODE:
+		pr_info("HCI_EV_PSCAN_REP_MODE");
+		break;
+	case HCI_EV_INQUIRY_RESULT_WITH_RSSI:
+		pr_info("HCI_EV_INQUIRY_RESULT_WITH_RSSI");
+		break;
+	case HCI_EV_REMOTE_EXT_FEATURES:
+		pr_info("HCI_EV_REMOTE_EXT_FEATURES");
+		break;
+	case HCI_EV_SYNC_CONN_COMPLETE:
+		pr_info("HCI_EV_SYNC_CONN_COMPLETE");
+		break;
+	case HCI_EV_SYNC_CONN_CHANGED:
+		pr_info("HCI_EV_SYNC_CONN_CHANGED");
+		break;
+	case HCI_EV_SNIFF_SUBRATE:
+		pr_info("HCI_EV_SNIFF_SUBRATE");
+		break;
+	case HCI_EV_EXTENDED_INQUIRY_RESULT:
+		pr_info("HCI_EV_EXTENDED_INQUIRY_RESULT");
+		break;
+	case HCI_EV_IO_CAPA_REQUEST:
+		pr_info("HCI_EV_IO_CAPA_REQUEST");
+		break;
+	case HCI_EV_SIMPLE_PAIR_COMPLETE:
+		pr_info("HCI_EV_SIMPLE_PAIR_COMPLETE");
+		break;
+	case HCI_EV_REMOTE_HOST_FEATURES:
+		pr_info("HCI_EV_REMOTE_HOST_FEATURES");
+		break;
+	default:
+		pr_info("unknown event");
+		break;
+	}
+	pr_info("\n");
+#endif
+}
+
+static inline ssize_t sdio_put_user(struct sk_buff *skb, char __user *buf,
+				    int count)
+{
+	char __user *ptr = buf;
+	int len = min_t(unsigned int, skb->len, count);
+
+	if (copy_to_user(ptr, skb->data, len))
+		return -EFAULT;
+
+	return len;
+}
+
+int aic_enqueue(struct sk_buff *skb)
+{
+	unsigned long flags = 0;
+	int ret = 0;
+
+	spin_lock_irqsave(&queue_lock, flags);
+	if (aic_skb_queue_front == (aic_skb_queue_rear + 1) % QUEUE_SIZE) {
+		/*
+		 * If queue is full, current solution is to drop
+		 * the following entries.
+		 */
+		AICBT_WARN("%s: Queue is full, entry will be dropped", __func__);
+		ret = -1;
+	} else {
+		aic_skb_queue[aic_skb_queue_rear] = skb;
+
+		aic_skb_queue_rear++;
+		aic_skb_queue_rear %= QUEUE_SIZE;
+	}
+	spin_unlock_irqrestore(&queue_lock, flags);
+	return ret;
+}
+
+static struct sk_buff *aic_dequeue_try(unsigned int deq_len)
+{
+	struct sk_buff *skb;
+	struct sk_buff *skb_copy;
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(&queue_lock, flags);
+	if (aic_skb_queue_front == aic_skb_queue_rear) {
+		AICBT_WARN("%s: Queue is empty", __func__);
+		spin_unlock_irqrestore(&queue_lock, flags);
+		return NULL;
+	}
+
+	skb = aic_skb_queue[aic_skb_queue_front];
+	if (deq_len >= skb->len) {
+		aic_skb_queue_front++;
+		aic_skb_queue_front %= QUEUE_SIZE;
+
+		/*
+		 * Return skb addr to be dequeued, and the caller
+		 * should free the skb eventually.
+		 */
+		spin_unlock_irqrestore(&queue_lock, flags);
+		return skb;
+	}
+	skb_copy = pskb_copy(skb, GFP_ATOMIC);
+	skb_pull(skb, deq_len);
+	/* Return its copy to be freed */
+	spin_unlock_irqrestore(&queue_lock, flags);
+	return skb_copy;
+}
+
+static inline int is_queue_empty(void)
+{
+	return (aic_skb_queue_front == aic_skb_queue_rear) ? 1 : 0;
+}
+
+void aic_clear_queue(void)
+{
+	struct sk_buff *skb;
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(&queue_lock, flags);
+	while (!is_queue_empty()) {
+		skb = aic_skb_queue[aic_skb_queue_front];
+		aic_skb_queue[aic_skb_queue_front] = NULL;
+		aic_skb_queue_front++;
+		aic_skb_queue_front %= QUEUE_SIZE;
+		if (skb)
+			kfree_skb(skb);
+	}
+	spin_unlock_irqrestore(&queue_lock, flags);
+}
+
+int aic_queue_cnt(void)
+{
+	int ret_cnt = 0;
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(&queue_lock, flags);
+	if (is_queue_empty()) {
+		ret_cnt = 0;
+	} else {
+		if (aic_skb_queue_rear > aic_skb_queue_front)
+			ret_cnt = aic_skb_queue_rear - aic_skb_queue_front;
+		else
+			ret_cnt = aic_skb_queue_rear + QUEUE_SIZE - aic_skb_queue_front;
+	}
+	spin_unlock_irqrestore(&queue_lock, flags);
+	return ret_cnt;
+}
+
+/*
+ * AicSemi - Integrate from hci_core.c
+ */
+
+/* Get HCI device by index.
+ * Device is held on return.
+ */
+struct hci_dev *hci_dev_get(int index)
+{
+	if (index != 0)
+		return NULL;
+
+	return ghdev;
+}
+
+/* ---- HCI ioctl helpers ---- */
+static int hci_dev_open(__u16 dev)
+{
+	struct hci_dev *hdev;
+	int ret = 0;
+
+	AICBT_DBG("%s: dev %d", __func__, dev);
+
+	hdev = hci_dev_get(dev);
+	if (!hdev) {
+		AICBT_ERR("%s: Failed to get hci dev[Null]", __func__);
+		return -ENODEV;
+	}
+
+done:
+	return ret;
+}
+
+static int hci_dev_do_close(struct hci_dev *hdev)
+{
+	/* Clear flags */
+	hdev->flags = 0;
+	return 0;
+}
+
+static int hci_dev_close(__u16 dev)
+{
+	struct hci_dev *hdev;
+	int err;
+
+	hdev = hci_dev_get(dev);
+	if (!hdev) {
+		AICBT_ERR("%s: failed to get hci dev[Null]", __func__);
+		return -ENODEV;
+	}
+
+	err = hci_dev_do_close(hdev);
+
+	return err;
+}
+
+#if CONFIG_BLUEDROID
+static struct hci_dev *hci_alloc_dev(void)
+{
+	struct hci_dev *hdev;
+
+	hdev = kzalloc_obj(*hdev, GFP_KERNEL);
+	if (!hdev)
+		return NULL;
+
+	return hdev;
+}
+
+/* Free HCI device */
+static void hci_free_dev(struct hci_dev *hdev)
+{
+	kfree(hdev);
+}
+
+/* Register HCI device */
+static int hci_register_dev(struct hci_dev *hdev)
+{
+	int i, id;
+
+	AICBT_DBG("%s: %p name %s bus %d", __func__, hdev, hdev->name, hdev->bus);
+	/* Do not allow HCI_AMP devices to register at index 0,
+	 * so the index can be used as the AMP controller ID.
+	 */
+	id = (hdev->dev_type == HCI_BREDR) ? 0 : 1;
+
+	write_lock(&hci_dev_lock);
+
+	sprintf(hdev->name, "hci%d", id);
+	hdev->id = id;
+	hdev->flags = 0;
+	hdev->dev_flags = 0;
+	mutex_init(&hdev->lock);
+
+	AICBT_DBG("%s: id %d, name %s", __func__, hdev->id, hdev->name);
+
+	for (i = 0; i < NUM_REASSEMBLY; i++)
+		hdev->reassembly[i] = NULL;
+
+	memset(&hdev->stat, 0, sizeof(struct hci_dev_stats));
+	atomic_set(&hdev->promisc, 0);
+
+	if (ghdev) {
+		write_unlock(&hci_dev_lock);
+		AICBT_ERR("%s: Hci device has been registered already", __func__);
+		return -1;
+	}
+	ghdev = hdev;
+
+	write_unlock(&hci_dev_lock);
+
+	return id;
+}
+
+/* Unregister HCI device */
+static void hci_unregister_dev(struct hci_dev *hdev)
+{
+	int i;
+
+	AICBT_DBG("%s: hdev %p name %s bus %d", __func__, hdev, hdev->name,
+		  hdev->bus);
+	set_bit(HCI_UNREGISTER, &hdev->dev_flags);
+
+	write_lock(&hci_dev_lock);
+	ghdev = NULL;
+	write_unlock(&hci_dev_lock);
+
+	hci_dev_do_close(hdev);
+	for (i = 0; i < NUM_REASSEMBLY; i++)
+		kfree_skb(hdev->reassembly[i]);
+}
+
+static void hci_send_to_stack(struct hci_dev *hdev, struct sk_buff *skb)
+{
+	struct sk_buff *aic_skb_copy = NULL;
+
+	if (!hdev) {
+		AICBT_ERR("%s: Frame for unknown HCI device", __func__);
+		return;
+	}
+
+	if (!test_bit(HCI_RUNNING, &hdev->flags)) {
+		AICBT_ERR("%s: HCI not running", __func__);
+		return;
+	}
+
+	aic_skb_copy = pskb_copy(skb, GFP_ATOMIC);
+	if (!aic_skb_copy) {
+		AICBT_ERR("%s: Copy skb error", __func__);
+		return;
+	}
+
+	memcpy(skb_push(aic_skb_copy, 1), &bt_cb(skb)->pkt_type, 1);
+	aic_enqueue(aic_skb_copy);
+
+	/* Make sure bt char device existing before wakeup read queue */
+	hdev = hci_dev_get(0);
+	if (hdev)
+		wake_up_interruptible(&btchr_read_wait);
+}
+
+/* Receive frame from HCI drivers */
+static int hci_recv_frame(struct sk_buff *skb)
+{
+	struct hci_dev *hdev = (struct hci_dev *)skb->dev;
+
+	if (!hdev || (!test_bit(HCI_UP, &hdev->flags) &&
+		      !test_bit(HCI_INIT, &hdev->flags))) {
+		kfree_skb(skb);
+		return -ENXIO;
+	}
+
+	/* Incoming skb */
+	bt_cb(skb)->incoming = 1;
+
+	/* Time stamp */
+	__net_timestamp(skb);
+
+	if (atomic_read(&hdev->promisc)) {
+#ifdef CONFIG_SCO_OVER_HCI
+		if (bt_cb(skb)->pkt_type == HCI_SCODATA_PKT) {
+			hci_send_to_alsa_ringbuffer(hdev, skb);
+		} else {
+			if (bt_cb(skb)->pkt_type == HCI_EVENT_PKT) {
+				if (bypass_event(skb)) {
+					kfree_skb(skb);
+					return 0;
+				}
+			}
+			hci_send_to_stack(hdev, skb);
+		}
+#else
+		if (bt_cb(skb)->pkt_type == HCI_EVENT_PKT) {
+			if (bypass_event(skb)) {
+				kfree_skb(skb);
+				return 0;
+			}
+		}
+		/* Send copy to the sockets */
+		hci_send_to_stack(hdev, skb);
+#endif
+	}
+
+	kfree_skb(skb);
+	return 0;
+}
+
+static int hci_reassembly(struct hci_dev *hdev, int type, void *data, int count,
+			  __u8 index)
+{
+	int len = 0;
+	int hlen = 0;
+	int remain = count;
+	struct sk_buff *skb;
+	struct bt_skb_cb *scb;
+
+	if ((type < HCI_ACLDATA_PKT || type > HCI_EVENT_PKT) ||
+	    index >= NUM_REASSEMBLY)
+		return -EILSEQ;
+
+	skb = hdev->reassembly[index];
+
+	if (!skb) {
+		switch (type) {
+		case HCI_ACLDATA_PKT:
+			len = HCI_MAX_FRAME_SIZE;
+			hlen = HCI_ACL_HDR_SIZE;
+			break;
+		case HCI_EVENT_PKT:
+			len = HCI_MAX_EVENT_SIZE;
+			hlen = HCI_EVENT_HDR_SIZE;
+			break;
+		case HCI_SCODATA_PKT:
+			len = HCI_MAX_SCO_SIZE;
+			hlen = HCI_SCO_HDR_SIZE;
+			break;
+		}
+
+		skb = bt_skb_alloc(len, GFP_ATOMIC);
+		if (!skb)
+			return -ENOMEM;
+
+		scb = (void *)skb->cb;
+		scb->expect = hlen;
+		scb->pkt_type = type;
+
+		skb->dev = (void *)hdev;
+		hdev->reassembly[index] = skb;
+	}
+
+	while (count) {
+		scb = (void *)skb->cb;
+		len = min_t(uint, scb->expect, count);
+
+		memcpy(skb_put(skb, len), data, len);
+
+		count -= len;
+		data += len;
+		scb->expect -= len;
+		remain = count;
+
+		switch (type) {
+		case HCI_EVENT_PKT:
+			if (skb->len == HCI_EVENT_HDR_SIZE) {
+				struct hci_event_hdr *h = hci_event_hdr(skb);
+
+				scb->expect = h->plen;
+
+				if (skb_tailroom(skb) < scb->expect) {
+					kfree_skb(skb);
+					hdev->reassembly[index] = NULL;
+					return -ENOMEM;
+				}
+			}
+			break;
+
+		case HCI_ACLDATA_PKT:
+			if (skb->len == HCI_ACL_HDR_SIZE) {
+				struct hci_acl_hdr *h = hci_acl_hdr(skb);
+
+				scb->expect = __le16_to_cpu(h->dlen);
+
+				if (skb_tailroom(skb) < scb->expect) {
+					kfree_skb(skb);
+					hdev->reassembly[index] = NULL;
+					return -ENOMEM;
+				}
+			}
+			break;
+
+		case HCI_SCODATA_PKT:
+			if (skb->len == HCI_SCO_HDR_SIZE) {
+				struct hci_sco_hdr *h = hci_sco_hdr(skb);
+
+				scb->expect = h->dlen;
+
+				if (skb_tailroom(skb) < scb->expect) {
+					kfree_skb(skb);
+					hdev->reassembly[index] = NULL;
+					return -ENOMEM;
+				}
+			}
+			break;
+		}
+
+		if (scb->expect == 0) {
+			/* Complete frame */
+			if (type == HCI_ACLDATA_PKT)
+				print_acl(skb, 0);
+			if (type == HCI_SCODATA_PKT)
+				print_sco(skb, 0);
+			if (type == HCI_EVENT_PKT)
+				print_event(skb);
+
+			bt_cb(skb)->pkt_type = type;
+			hci_recv_frame(skb);
+
+			hdev->reassembly[index] = NULL;
+			return remain;
+		}
+	}
+
+	return remain;
+}
+
+int hci_recv_fragment(struct hci_dev *hdev, int type, void *data, int count)
+{
+	int rem = 0;
+
+	if (type < HCI_ACLDATA_PKT || type > HCI_EVENT_PKT)
+		return -EILSEQ;
+
+	while (count) {
+		rem = hci_reassembly(hdev, type, data, count, type - 1);
+		if (rem < 0)
+			return rem;
+
+		data += (count - rem);
+		count = rem;
+	}
+
+	return rem;
+}
+#endif // CONFIG_BLUEDROID
+
+static int btchr_open(struct inode *inode_p, struct file *file_p)
+{
+	struct btusb_data *data;
+	struct hci_dev *hdev;
+
+	AICBT_DBG("%s: BT sdio char device is opening", __func__);
+
+	hdev = hci_dev_get(0);
+	if (!hdev) {
+		AICBT_DBG("%s: Failed to get hci dev[NULL]", __func__);
+		return -ENODEV;
+	}
+	data = GET_DRV_DATA(hdev);
+
+	atomic_inc(&hdev->promisc);
+	/*
+	 * As bt device is not re-opened when hotplugged out, we cannot
+	 * trust on file's private data(may be null) when other file ops
+	 * are invoked.
+	 */
+	file_p->private_data = data;
+
+	mutex_lock(&btchr_mutex);
+	hci_dev_open(0);
+	mutex_unlock(&btchr_mutex);
+
+	aic_clear_queue();
+	return nonseekable_open(inode_p, file_p);
+}
+
+static int btchr_close(struct inode *inode_p, struct file *file_p)
+{
+	struct btusb_data *data;
+	struct hci_dev *hdev;
+
+	AICBT_INFO("%s: BT sdio char device is closing", __func__);
+
+	data = file_p->private_data;
+	file_p->private_data = NULL;
+
+#if CONFIG_BLUEDROID
+	/*
+	 * If the upper layer closes bt char interfaces, no reset
+	 * action required even bt device hotplugged out.
+	 */
+	bt_reset = 0;
+#endif
+
+	hdev = hci_dev_get(0);
+	if (hdev) {
+		atomic_set(&hdev->promisc, 0);
+		mutex_lock(&btchr_mutex);
+		hci_dev_close(0);
+		mutex_unlock(&btchr_mutex);
+	}
+
+	return 0;
+}
+
+void bt_data_dump(char *tag, void *data, unsigned long len)
+{
+	unsigned long i = 0;
+	u8 *data_ = (uint8_t *)data;
+
+	pr_info("%s %s len:(%lu)\r\n", __func__, tag, len);
+
+	for (i = 0; i < len; i += 16) {
+		pr_info("%02X %02X %02X %02X %02X %02X %02X %02X  %02X %02X %02X %02X %02X %02X %02X %02X\r\n",
+			data_[0 + i], data_[1 + i], data_[2 + i], data_[3 + i],
+			data_[4 + i], data_[5 + i], data_[6 + i], data_[7 + i],
+			data_[8 + i], data_[9 + i], data_[10 + i], data_[11 + i],
+			data_[12 + i], data_[13 + i], data_[14 + i], data_[15 + i]);
+	}
+}
+
+static ssize_t btchr_read(struct file *file_p, char __user *buf_p, size_t count,
+			  loff_t *pos_p)
+{
+	struct hci_dev *hdev;
+	struct sk_buff *skb;
+	ssize_t ret = 0;
+
+	while (count) {
+		hdev = hci_dev_get(0);
+		if (!hdev) {
+			/*
+			 * Note: Only when BT device hotplugged out, we wil get
+			 * into such situation. In order to keep the upper layer
+			 * stack alive (blocking the read), we should never return
+			 * EFAULT or break the loop.
+			 */
+			AICBT_ERR("%s: Failed to get hci dev[Null]", __func__);
+		}
+
+		ret = wait_event_interruptible(btchr_read_wait, !is_queue_empty());
+		if (ret < 0) {
+			AICBT_ERR("%s: wait event is signaled %d", __func__, (int)ret);
+			break;
+		}
+
+		skb = aic_dequeue_try(count);
+
+		if (skb) {
+			ret = sdio_put_user(skb, buf_p, count);
+			if (ret < 0)
+				AICBT_ERR("%s: Failed to put data to user space", __func__);
+			kfree_skb(skb);
+			break;
+		}
+	}
+
+	return ret;
+}
+
+#ifdef CONFIG_SUPPORT_VENDOR_APCF
+void btchr_external_write(char *buff, int len)
+{
+	struct hci_dev *hdev;
+	struct sk_buff *skb;
+	int i;
+	struct btusb_data *data;
+
+	AICBT_INFO("%s \r\n", __func__);
+	for (i = 0; i < len; i++)
+		pr_info("0x%x ", (u8)buff[i]);
+	pr_info("\r\n");
+	hdev = hci_dev_get(0);
+	if (!hdev) {
+		AICBT_WARN("%s: Failed to get hci dev[Null]", __func__);
+		return;
+	}
+	/* Never trust on btusb_data, as bt device may be hotplugged out */
+	data = GET_DRV_DATA(hdev);
+	if (!data) {
+		AICBT_WARN("%s: Failed to get bt sdio driver data[Null]", __func__);
+		return;
+	}
+	vendor_apcf_sent_done++;
+
+	skb = bt_skb_alloc(len, GFP_ATOMIC);
+	if (!skb)
+		return;
+	skb_reserve(skb, -1); // Add this line
+	skb->dev = (void *)hdev;
+	memcpy((__u8 *)skb->data, (__u8 *)buff, len);
+	skb_put(skb, len);
+	bt_cb(skb)->pkt_type = *((__u8 *)skb->data);
+	skb_pull(skb, 1);
+	data->hdev->send(skb);
+}
+EXPORT_SYMBOL_GPL(btchr_external_write);
+#endif // CONFIG_SUPPORT_VENDOR_APCF
+
+static ssize_t btchr_write(struct file *file_p, const char __user *buf_p,
+			   size_t count, loff_t *pos_p)
+{
+	struct btusb_data *data = file_p->private_data;
+	struct hci_dev *hdev;
+	struct sk_buff *skb;
+	int err = 0;
+
+	hdev = hci_dev_get(0);
+	if (!hdev) {
+		AICBT_WARN("%s: Failed to get hci dev[Null]", __func__);
+		/*
+		 * Note: we bypass the data from the upper layer if bt device
+		 * is hotplugged out. Fortunatelly, H4 or H5 HCI stack does
+		 * NOT check btchr_write's return value. However, returning
+		 * count instead of EFAULT is preferable.
+		 */
+		/* return -EFAULT; */
+		return count;
+	}
+
+	if (count > HCI_MAX_FRAME_SIZE)
+		return -EINVAL;
+
+	skb = bt_skb_alloc(count, GFP_ATOMIC);
+	if (!skb)
+		return -ENOMEM;
+	skb_reserve(skb, -1);
+
+	if (copy_from_user(skb_put(skb, count), buf_p, count)) {
+		AICBT_ERR("%s: Failed to get data from user space", __func__);
+		kfree_skb(skb);
+		return -EFAULT;
+	}
+
+	skb->dev = (void *)hdev;
+	bt_cb(skb)->pkt_type = *((__u8 *)skb->data);
+
+	err = rwnx_sdio_bt_send_req(g_rwnx_plat->sdiodev->rwnx_hw, skb->len, skb);
+	if (err < 0)
+		pr_err("%s rwnx_sdio_bt_send_req error %d", __func__, err);
+
+	kfree_skb(skb);
+	return count;
+}
+
+static unsigned int btchr_poll(struct file *file_p, poll_table *wait)
+{
+	struct btusb_data *data = file_p->private_data;
+	struct hci_dev *hdev;
+
+	poll_wait(file_p, &btchr_read_wait, wait);
+
+	hdev = hci_dev_get(0);
+	if (!hdev) {
+		AICBT_ERR("%s: Failed to get hci dev[Null]", __func__);
+		// mdelay(URB_CANCELING_DELAY_MS);
+		return POLLERR | POLLHUP;
+		return POLLOUT | POLLWRNORM;
+	}
+
+	if (!is_queue_empty())
+		return POLLIN | POLLRDNORM;
+
+	return POLLOUT | POLLWRNORM;
+}
+
+static long btchr_ioctl(struct file *file_p, unsigned int cmd,
+			unsigned long arg)
+{
+	int ret = 0;
+	struct hci_dev *hdev;
+	struct btusb_data *data;
+
+	pr_info("%s cmd support %d\n", __func__, cmd);
+
+	if (check_set_dlfw_state_value(1) != 1) {
+		AICBT_ERR("%s bt controller is disconnecting!", __func__);
+		return 0;
+	}
+
+	hdev = hci_dev_get(0);
+	if (!hdev) {
+		AICBT_ERR("%s device is NULL!", __func__);
+		set_dlfw_state_value(0);
+		return 0;
+	}
+
+	AICBT_INFO(" %s DOWN_FW_CFG with Cmd:%d", __func__, cmd);
+	switch (cmd) {
+	case DOWN_FW_CFG:
+		AICBT_INFO(" %s DOWN_FW_CFG", __func__);
+		set_bit(HCI_UP, &hdev->flags);
+		set_dlfw_state_value(0);
+		wake_up_interruptible(&bt_dlfw_wait);
+		return 1;
+	case DWFW_CMPLT:
+		AICBT_INFO(" %s DWFW_CMPLT", __func__);
+	case SET_ISO_CFG:
+		AICBT_INFO("%s SET_ISO_CFG", __func__);
+		if (copy_from_user(&hdev->voice_setting, (__u16 *)arg,
+				   sizeof(__u16))) {
+			AICBT_INFO(" voice settings err");
+		}
+		AICBT_INFO(" voice settings = %d", hdev->voice_setting);
+	case GET_USB_INFO:
+		AICBT_INFO(" %s GET_USB_INFO", __func__);
+		set_bit(HCI_UP, &hdev->flags);
+		set_dlfw_state_value(0);
+		wake_up_interruptible(&bt_dlfw_wait);
+		return 1;
+	case RESET_CONTROLLER:
+		AICBT_INFO(" %s RESET_CONTROLLER", __func__);
+		return 1;
+	default:
+		AICBT_ERR("%s:Failed with wrong Cmd:%d", __func__, cmd);
+		goto failed;
+	}
+failed:
+	set_dlfw_state_value(0);
+	wake_up_interruptible(&bt_dlfw_wait);
+	return ret;
+}
+
+typedef u32 compat_uptr_t;
+static inline void __user *compat_ptr(compat_uptr_t uptr)
+{
+	return (void __user *)(unsigned long)uptr;
+}
+
+#ifdef CONFIG_COMPAT
+static long compat_btchr_ioctl(struct file *filp, unsigned int cmd,
+			       unsigned long arg)
+{
+	return btchr_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+static const struct file_operations bt_chrdev_ops = {
+	.open = btchr_open,
+	.release = btchr_close,
+	.read = btchr_read,
+	.write = btchr_write,
+	.poll = btchr_poll,
+	.unlocked_ioctl = btchr_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = compat_btchr_ioctl,
+#endif
+};
+
+int btchr_init(void)
+{
+	int res = 0;
+	struct device *dev;
+
+	AICBT_INFO("Register sdio char device interface for BT driver");
+	/*
+	 * btchr mutex is used to sync between
+	 * 1) downloading patch and opening bt char driver
+	 * 2) the file operations of bt char driver
+	 */
+	mutex_init(&btchr_mutex);
+
+	skb_queue_head_init(&btchr_readq);
+	init_waitqueue_head(&btchr_read_wait);
+	init_waitqueue_head(&bt_dlfw_wait);
+
+	bt_char_class = class_create(THIS_MODULE, BT_CHAR_DEVICE_NAME);
+	if (IS_ERR(bt_char_class)) {
+		AICBT_ERR("Failed to create bt char class");
+		return PTR_ERR(bt_char_class);
+	}
+
+	res = alloc_chrdev_region(&bt_devid, 0, 1, BT_CHAR_DEVICE_NAME);
+	if (res < 0) {
+		AICBT_ERR("Failed to allocate bt char device");
+		goto err_alloc;
+	}
+
+	dev =
+		device_create(bt_char_class, NULL, bt_devid, NULL, BT_CHAR_DEVICE_NAME);
+	if (IS_ERR(dev)) {
+		AICBT_ERR("Failed to create bt char device");
+		res = PTR_ERR(dev);
+		goto err_create;
+	}
+
+	cdev_init(&bt_char_dev, &bt_chrdev_ops);
+	res = cdev_add(&bt_char_dev, bt_devid, 1);
+	if (res < 0) {
+		AICBT_ERR("Failed to add bt char device");
+		goto err_add;
+	}
+
+	return 0;
+
+err_add:
+	device_destroy(bt_char_class, bt_devid);
+err_create:
+	unregister_chrdev_region(bt_devid, 1);
+err_alloc:
+	class_destroy(bt_char_class);
+	return res;
+}
+
+void btchr_exit(void)
+{
+	AICBT_INFO("Unregister sdio char device interface for BT driver");
+
+	device_destroy(bt_char_class, bt_devid);
+	cdev_del(&bt_char_dev);
+	unregister_chrdev_region(bt_devid, 1);
+	class_destroy(bt_char_class);
+}
+
+int hdev_init(void)
+{
+	struct hci_dev *hdev;
+	int err = 0;
+
+	hdev = hci_alloc_dev();
+
+	err = hci_register_dev(hdev);
+	if (err < 0) {
+		hci_free_dev(hdev);
+		hdev = NULL;
+		return err;
+	}
+
+	spin_lock_init(&queue_lock);
+
+	return 0;
+}
+
+void hdev_exit(void)
+{
+	struct hci_dev *hdev;
+
+	hdev = ghdev;
+	hci_unregister_dev(hdev);
+	hci_free_dev(hdev);
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.h b/drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.h
new file mode 100644
index 0000000000000..81f0f05957566
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_btsdio.h
@@ -0,0 +1,472 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AICWF_SDIO_BT_H_
+#define _AICWF_SDIO_BT_H_
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <linux/firmware.h>
+#include <linux/pm_runtime.h>
+#include <linux/suspend.h>
+#include <linux/version.h>
+
+#define CONFIG_BLUEDROID 1 /* bleuz 0, bluedroid 1 */
+
+#define PRINT_CMD_EVENT 1
+#define PRINT_ACL_DATA  1
+#define PRINT_SCO_DATA  1
+
+#define AICBT_DBG_FLAG 1
+
+#if AICBT_DBG_FLAG
+#define AICBT_DBG(fmt, arg...) pr_debug("aic_btsdio: " fmt "\n", ##arg)
+#else
+#define AICBT_DBG(fmt, arg...)
+#endif
+
+#define AICBT_INFO(fmt, arg...) pr_info("aic_btsdio: " fmt "\n", ##arg)
+#define AICBT_WARN(fmt, arg...) pr_warn("aic_btsdio: " fmt "\n", ##arg)
+#define AICBT_ERR(fmt, arg...)  pr_err("aic_btsdio: " fmt "\n", ##arg)
+
+#define GET_DRV_DATA(x) hci_get_drvdata(x)
+
+struct btusb_data {
+	struct hci_dev *hdev;
+	/* lock for btusb */
+	spinlock_t lock;
+
+	unsigned long flags;
+
+	struct work_struct work;
+	struct work_struct waker;
+
+	int tx_in_flight;
+	/* lock for tx */
+	spinlock_t txlock;
+
+#if (CONFIG_BLUEDROID == 0)
+	/* lock for tx */
+	spinlock_t rxlock;
+	struct sk_buff *evt_skb;
+	struct sk_buff *acl_skb;
+	struct sk_buff *sco_skb;
+#endif
+
+	__u8 cmdreq_type;
+
+	unsigned int sco_num;
+	int isoc_altsetting;
+	int suspend_count;
+	u16 sco_handle;
+
+#if (CONFIG_BLUEDROID == 0)
+	int (*recv_bulk)(struct btusb_data *data, void *buffer, int count);
+#endif
+	struct notifier_block pm_notifier;
+	struct notifier_block reboot_notifier;
+
+#ifdef CONFIG_SCO_OVER_HCI
+	AIC_sco_card_t *sco_snd;
+#endif
+};
+
+#define QUEUE_SIZE 500
+
+/* Reserv for core and drivers use */
+#define BT_SKB_RESERVE 8
+
+/* BD Address */
+struct bdaddr_t {
+	__u8 b[6];
+} __packed;
+
+/* Skb helpers */
+struct bt_skb_cb {
+	__u8 pkt_type;
+	__u8 incoming;
+	__u16 expect;
+	__u16 tx_seq;
+	__u8 retries;
+	__u8 sar;
+	__u8 force_active;
+};
+
+#define bt_cb(skb) ((struct bt_skb_cb *)((skb)->cb))
+
+#define HCI_MAX_ACL_SIZE   1024
+#define HCI_MAX_SCO_SIZE   255
+#define HCI_MAX_EVENT_SIZE 260
+#define HCI_MAX_FRAME_SIZE (HCI_MAX_ACL_SIZE + 4)
+
+/* HCI bus types */
+#define HCI_VIRTUAL 0
+#define HCI_USB     1
+#define HCI_PCCARD  2
+#define HCI_UART    3
+#define HCI_RS232   4
+#define HCI_PCI     5
+#define HCI_SDIO    6
+
+/* HCI controller types */
+#define HCI_BREDR 0x00
+#define HCI_AMP   0x01
+
+/* HCI device flags */
+enum {
+	HCI_UP,
+	HCI_INIT,
+	HCI_RUNNING,
+
+	HCI_PSCAN,
+	HCI_ISCAN,
+	HCI_AUTH,
+	HCI_ENCRYPT,
+	HCI_INQUIRY,
+
+	HCI_RAW,
+
+	HCI_RESET,
+};
+
+/*
+ * BR/EDR and/or LE controller flags: the flags defined here should represent
+ * states from the controller.
+ */
+enum {
+	HCI_SETUP,
+	HCI_AUTO_OFF,
+	HCI_MGMT,
+	HCI_PAIRABLE,
+	HCI_SERVICE_CACHE,
+	HCI_LINK_KEYS,
+	HCI_DEBUG_KEYS,
+	HCI_UNREGISTER,
+
+	HCI_LE_SCAN,
+	HCI_SSP_ENABLED,
+	HCI_HS_ENABLED,
+	HCI_LE_ENABLED,
+	HCI_CONNECTABLE,
+	HCI_DISCOVERABLE,
+	HCI_LINK_SECURITY,
+	HCI_PENDING_CLASS,
+};
+
+/* HCI data types */
+#define HCI_COMMAND_PKT 0x01
+#define HCI_ACLDATA_PKT 0x02
+#define HCI_SCODATA_PKT 0x03
+#define HCI_EVENT_PKT   0x04
+#define HCI_VENDOR_PKT  0xff
+
+#define HCI_MAX_NAME_LENGTH 248
+#define HCI_MAX_EIR_LENGTH  240
+
+#define HCI_OP_READ_LOCAL_VERSION 0x1001
+struct hci_rp_read_local_version {
+	__u8 status;
+	__u8 hci_ver;
+	__le16 hci_rev;
+	__u8 lmp_ver;
+	__le16 manufacturer;
+	__le16 lmp_subver;
+} __packed;
+
+#define HCI_EV_CMD_COMPLETE 0x0e
+struct hci_ev_cmd_complete {
+	__u8 ncmd;
+	__le16 opcode;
+} __packed;
+
+/* ---- HCI Packet structures ---- */
+#define HCI_COMMAND_HDR_SIZE 3
+#define HCI_EVENT_HDR_SIZE   2
+#define HCI_ACL_HDR_SIZE     4
+#define HCI_SCO_HDR_SIZE     3
+
+struct hci_command_hdr {
+	__le16 opcode; /* OCF & OGF */
+	__u8 plen;
+} __packed;
+
+struct hci_event_hdr {
+	__u8 evt;
+	__u8 plen;
+} __packed;
+
+struct hci_acl_hdr {
+	__le16 handle; /* Handle & Flags(PB, BC) */
+	__le16 dlen;
+} __packed;
+
+struct hci_sco_hdr {
+	__le16 handle;
+	__u8 dlen;
+} __packed;
+
+static inline struct hci_event_hdr *hci_event_hdr(const struct sk_buff *skb)
+{
+	return (struct hci_event_hdr *)skb->data;
+}
+
+static inline struct hci_acl_hdr *hci_acl_hdr(const struct sk_buff *skb)
+{
+	return (struct hci_acl_hdr *)skb->data;
+}
+
+static inline struct hci_sco_hdr *hci_sco_hdr(const struct sk_buff *skb)
+{
+	return (struct hci_sco_hdr *)skb->data;
+}
+
+/* ---- HCI Ioctl requests structures ---- */
+struct hci_dev_stats {
+	__u32 err_rx;
+	__u32 err_tx;
+	__u32 cmd_tx;
+	__u32 evt_rx;
+	__u32 acl_tx;
+	__u32 acl_rx;
+	__u32 sco_tx;
+	__u32 sco_rx;
+	__u32 byte_rx;
+	__u32 byte_tx;
+};
+
+struct hci_conn_hash {
+	struct list_head list;
+	unsigned int acl_num;
+	unsigned int sco_num;
+	unsigned int le_num;
+};
+
+#define HCI_MAX_SHORT_NAME_LENGTH 10
+
+#define NUM_REASSEMBLY 4
+struct hci_dev {
+	/* mutex for hic dev */
+	struct mutex lock;
+
+	char name[8];
+	unsigned long flags;
+	__u16 id;
+	__u8 bus;
+	__u8 dev_type;
+
+	struct sk_buff *reassembly[NUM_REASSEMBLY];
+
+	struct hci_conn_hash conn_hash;
+
+	struct hci_dev_stats stat;
+
+	atomic_t promisc;
+
+	struct device *parent;
+	struct device dev;
+
+	unsigned long dev_flags;
+
+	int (*open)(struct hci_dev *hdev);
+	int (*close)(struct hci_dev *hdev);
+	int (*flush)(struct hci_dev *hdev);
+	int (*send)(struct sk_buff *skb);
+	__u16 voice_setting;
+	void (*notify)(struct hci_dev *hdev, unsigned int evt);
+	int (*ioctl)(struct hci_dev *hdev, unsigned int cmd, unsigned long arg);
+	u8 *align_data;
+};
+
+static inline void *hci_get_drvdata(struct hci_dev *hdev)
+{
+	return dev_get_drvdata(&hdev->dev);
+}
+
+static inline void hci_set_drvdata(struct hci_dev *hdev, void *data)
+{
+	dev_set_drvdata(&hdev->dev, data);
+}
+
+#define SET_HCIDEV_DEV(hdev, pdev) ((hdev)->parent = (pdev))
+
+/* ---- HCI Packet structures ---- */
+#define HCI_COMMAND_HDR_SIZE 3
+#define HCI_EVENT_HDR_SIZE   2
+#define HCI_ACL_HDR_SIZE     4
+#define HCI_SCO_HDR_SIZE     3
+
+/* -----  HCI Commands ---- */
+#define HCI_OP_INQUIRY                                 0x0401
+#define HCI_OP_INQUIRY_CANCEL                          0x0402
+#define HCI_OP_EXIT_PERIODIC_INQ                       0x0404
+#define HCI_OP_CREATE_CONN                             0x0405
+#define HCI_OP_DISCONNECT                              0x0406
+#define HCI_OP_ADD_SCO                                 0x0407
+#define HCI_OP_CREATE_CONN_CANCEL                      0x0408
+#define HCI_OP_ACCEPT_CONN_REQ                         0x0409
+#define HCI_OP_REJECT_CONN_REQ                         0x040a
+#define HCI_OP_LINK_KEY_REPLY                          0x040b
+#define HCI_OP_LINK_KEY_NEG_REPLY                      0x040c
+#define HCI_OP_PIN_CODE_REPLY                          0x040d
+#define HCI_OP_PIN_CODE_NEG_REPLY                      0x040e
+#define HCI_OP_CHANGE_CONN_PTYPE                       0x040f
+#define HCI_OP_AUTH_REQUESTED                          0x0411
+#define HCI_OP_SET_CONN_ENCRYPT                        0x0413
+#define HCI_OP_CHANGE_CONN_LINK_KEY                    0x0415
+#define HCI_OP_REMOTE_NAME_REQ                         0x0419
+#define HCI_OP_REMOTE_NAME_REQ_CANCEL                  0x041a
+#define HCI_OP_READ_REMOTE_FEATURES                    0x041b
+#define HCI_OP_READ_REMOTE_EXT_FEATURES                0x041c
+#define HCI_OP_READ_REMOTE_VERSION                     0x041d
+#define HCI_OP_SETUP_SYNC_CONN                         0x0428
+#define HCI_OP_ACCEPT_SYNC_CONN_REQ                    0x0429
+#define HCI_OP_REJECT_SYNC_CONN_REQ                    0x042a
+#define HCI_OP_SNIFF_MODE                              0x0803
+#define HCI_OP_EXIT_SNIFF_MODE                         0x0804
+#define HCI_OP_ROLE_DISCOVERY                          0x0809
+#define HCI_OP_SWITCH_ROLE                             0x080b
+#define HCI_OP_READ_LINK_POLICY                        0x080c
+#define HCI_OP_WRITE_LINK_POLICY                       0x080d
+#define HCI_OP_READ_DEF_LINK_POLICY                    0x080e
+#define HCI_OP_WRITE_DEF_LINK_POLICY                   0x080f
+#define HCI_OP_SNIFF_SUBRATE                           0x0811
+#define HCI_OP_Write_Link_Policy_Settings              0x080d
+#define HCI_OP_SET_EVENT_MASK                          0x0c01
+#define HCI_OP_RESET                                   0x0c03
+#define HCI_OP_SET_EVENT_FLT                           0x0c05
+#define HCI_OP_Write_Extended_Inquiry_Response         0x0c52
+#define HCI_OP_Write_Simple_Pairing_Mode               0x0c56
+#define HCI_OP_Read_Buffer_Size                        0x1005
+#define HCI_OP_Host_Buffer_Size                        0x0c33
+#define HCI_OP_Read_Local_Version_Information          0x1001
+#define HCI_OP_Read_BD_ADDR                            0x1009
+#define HCI_OP_Read_Local_Supported_Commands           0x1002
+#define HCI_OP_Write_Scan_Enable                       0x0c1a
+#define HCI_OP_Write_Current_IAC_LAP                   0x0c3a
+#define HCI_OP_Write_Inquiry_Scan_Activity             0x0c1e
+#define HCI_OP_Write_Class_of_Device                   0x0c24
+#define HCI_OP_LE_Rand                                 0x2018
+#define HCI_OP_LE_Set_Random_Address                   0x2005
+#define HCI_OP_LE_Set_Extended_Scan_Enable             0x2042
+#define HCI_OP_LE_Set_Extended_Scan_Parameters         0x2041
+#define HCI_OP_Set_Event_Filter                        0x0c05
+#define HCI_OP_Write_Voice_Setting                     0x0c26
+#define HCI_OP_Change_Local_Name                       0x0c13
+#define HCI_OP_Read_Local_Name                         0x0c14
+#define HCI_OP_Wirte_Page_Timeout                      0x0c18
+#define HCI_OP_LE_Clear_Resolving_List                 0x0c29
+#define HCI_OP_LE_Set_Addres_Resolution_Enable_Command 0x0c2e
+#define HCI_OP_Write_Inquiry_mode                      0x0c45
+#define HCI_OP_Write_Page_Scan_Type                    0x0c47
+#define HCI_OP_Write_Inquiry_Scan_Type                 0x0c43
+
+#define HCI_OP_Delete_Stored_Link_Key           0x0c12
+#define HCI_OP_LE_Read_Local_Resolvable_Address 0x202d
+#define HCI_OP_LE_Extended_Create_Connection    0x2043
+#define HCI_OP_Read_Remote_Version_Information  0x041d
+#define HCI_OP_LE_Start_Encryption              0x2019
+#define HCI_OP_LE_Add_Device_to_Resolving_List  0x2027
+#define HCI_OP_LE_Set_Privacy_Mode              0x204e
+#define HCI_OP_LE_Connection_Update             0x2013
+
+/* -----  HCI events---- */
+#define HCI_OP_DISCONNECT               0x0406
+#define HCI_EV_INQUIRY_COMPLETE         0x01
+#define HCI_EV_INQUIRY_RESULT           0x02
+#define HCI_EV_CONN_COMPLETE            0x03
+#define HCI_EV_CONN_REQUEST             0x04
+#define HCI_EV_DISCONN_COMPLETE         0x05
+#define HCI_EV_AUTH_COMPLETE            0x06
+#define HCI_EV_REMOTE_NAME              0x07
+#define HCI_EV_ENCRYPT_CHANGE           0x08
+#define HCI_EV_CHANGE_LINK_KEY_COMPLETE 0x09
+
+#define HCI_EV_REMOTE_FEATURES    0x0b
+#define HCI_EV_REMOTE_VERSION     0x0c
+#define HCI_EV_QOS_SETUP_COMPLETE 0x0d
+#define HCI_EV_CMD_COMPLETE       0x0e
+#define HCI_EV_CMD_STATUS         0x0f
+
+#define HCI_EV_ROLE_CHANGE     0x12
+#define HCI_EV_NUM_COMP_PKTS   0x13
+#define HCI_EV_MODE_CHANGE     0x14
+#define HCI_EV_PIN_CODE_REQ    0x16
+#define HCI_EV_LINK_KEY_REQ    0x17
+#define HCI_EV_LINK_KEY_NOTIFY 0x18
+#define HCI_EV_CLOCK_OFFSET    0x1c
+#define HCI_EV_PKT_TYPE_CHANGE 0x1d
+#define HCI_EV_PSCAN_REP_MODE  0x20
+
+#define HCI_EV_INQUIRY_RESULT_WITH_RSSI 0x22
+#define HCI_EV_REMOTE_EXT_FEATURES      0x23
+#define HCI_EV_SYNC_CONN_COMPLETE       0x2c
+#define HCI_EV_SYNC_CONN_CHANGED        0x2d
+#define HCI_EV_SNIFF_SUBRATE            0x2e
+#define HCI_EV_EXTENDED_INQUIRY_RESULT  0x2f
+#define HCI_EV_IO_CAPA_REQUEST          0x31
+#define HCI_EV_SIMPLE_PAIR_COMPLETE     0x36
+#define HCI_EV_REMOTE_HOST_FEATURES     0x3d
+#define HCI_EV_LE_Meta                  0x3e
+
+/* ULP Event sub code */
+#define HCI_BLE_CONN_COMPLETE_EVT              0x01
+#define HCI_BLE_ADV_PKT_RPT_EVT                0x02
+#define HCI_BLE_LL_CONN_PARAM_UPD_EVT          0x03
+#define HCI_BLE_READ_REMOTE_FEAT_CMPL_EVT      0x04
+#define HCI_BLE_LTK_REQ_EVT                    0x05
+#define HCI_BLE_RC_PARAM_REQ_EVT               0x06
+#define HCI_BLE_DATA_LENGTH_CHANGE_EVT         0x07
+#define HCI_BLE_ENHANCED_CONN_COMPLETE_EVT     0x0a
+#define HCI_BLE_DIRECT_ADV_EVT                 0x0b
+#define HCI_BLE_PHY_UPDATE_COMPLETE_EVT        0x0c
+#define HCI_LE_EXTENDED_ADVERTISING_REPORT_EVT 0x0D
+#define HCI_BLE_PERIODIC_ADV_SYNC_EST_EVT      0x0E
+#define HCI_BLE_PERIODIC_ADV_REPORT_EVT        0x0F
+#define HCI_BLE_PERIODIC_ADV_SYNC_LOST_EVT     0x10
+#define HCI_BLE_SCAN_TIMEOUT_EVT               0x11
+#define HCI_LE_ADVERTISING_SET_TERMINATED_EVT  0x12
+#define HCI_BLE_SCAN_REQ_RX_EVT                0x13
+#define HCI_BLE_CIS_EST_EVT                    0x19
+#define HCI_BLE_CIS_REQ_EVT                    0x1a
+#define HCI_BLE_CREATE_BIG_CPL_EVT             0x1b
+#define HCI_BLE_TERM_BIG_CPL_EVT               0x1c
+#define HCI_BLE_BIG_SYNC_EST_EVT               0x1d
+#define HCI_BLE_BIG_SYNC_LOST_EVT              0x1e
+#define HCI_BLE_REQ_PEER_SCA_CPL_EVT           0x1f
+
+#define HCI_VENDOR_SPECIFIC_EVT 0xFF /* Vendor specific events */
+
+#define CONFIG_MAC_OFFSET_GEN_1_2                                              \
+	(0x3C) // MAC's OFFSET in config/efuse for aic generation 1~2 bluetooth chip
+#define CONFIG_MAC_OFFSET_GEN_3PLUS                                            \
+	(0x44) // MAC's OFFSET in config/efuse for aic generation 3+ bluetooth chip
+
+// Define ioctl cmd the same as HCIDEVUP in the kernel
+#define DOWN_FW_CFG      _IOW('E', 176, int)
+#define SET_ISO_CFG      _IOW('E', 177, int)
+#define RESET_CONTROLLER _IOW('E', 178, int)
+#define DWFW_CMPLT       _IOW('E', 179, int)
+
+#define GET_USB_INFO _IOR('E', 180, int)
+
+void bt_data_dump(char *tag, void *data, unsigned long len);
+int aic_enqueue(struct sk_buff *skb);
+int aic_queue_cnt(void);
+int bt_sdio_recv(u8 *data, u32 data_len);
+
+int btchr_init(void);
+void btchr_exit(void);
+int hdev_init(void);
+void hdev_exit(void);
+
+struct hci_dev *hci_dev_get(int index);
+int hci_recv_fragment(struct hci_dev *hdev, int type, void *data, int count);
+
+#endif //_AICWF_SDIO_BT_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..7e47a1529a872
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.c
@@ -0,0 +1,1967 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief private command definition
+ *
+ ******************************************************************************
+ */
+
+#include "aic_priv_cmd.h"
+#include "aicwf_genl.h"
+#include "aicwf_sdio.h"
+#include "rwnx_debugfs.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_l func;
+	u8_l cnt;
+	u8_l reserved[2];
+	u32_l 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] = 0;
+			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] = 0;
+			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_tx(struct rwnx_hw *rwnx_hw, int argc, char *argv[],
+			       char *command)
+{
+	u8_l enable = 0;
+	struct cmd_rf_settx settx_param;
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+	s8 max_pwr;
+	struct txpwr_loss_conf txpwr_loss_tmp;
+	struct txpwr_loss_conf *txpwr_loss;
+
+	txpwr_loss = &txpwr_loss_tmp;
+#endif
+
+	if (argc < 6)
+		return -EINVAL;
+
+	settx_param.chan = command_strtoul(argv[1], NULL, 10);
+	settx_param.bw = command_strtoul(argv[2], NULL, 10);
+	settx_param.mode = command_strtoul(argv[3], NULL, 10);
+	settx_param.rate = command_strtoul(argv[4], NULL, 10);
+	settx_param.length = command_strtoul(argv[5], NULL, 10);
+	if (argc > 6)
+		settx_param.tx_intv_us = command_strtoul(argv[6], NULL, 10);
+	else
+		settx_param.tx_intv_us = 10000; // set default val 10ms
+	settx_param.max_pwr = POWER_LEVEL_INVALID_VAL;
+	AICWFDBG(LOGINFO, "txparam:%d,%d,%d,%d,%d,%d\n", settx_param.chan,
+		 settx_param.bw, settx_param.mode, settx_param.rate,
+			 settx_param.length, settx_param.tx_intv_us);
+
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+	txpwr_loss = &txpwr_loss_tmp;
+	get_userconfig_txpwr_loss(txpwr_loss);
+	if (txpwr_loss->loss_enable_2g4 == 1)
+		AICWFDBG(LOGINFO, "%s:loss_value_2g4: %d\r\n", __func__,
+			 txpwr_loss->loss_value_2g4);
+	if (txpwr_loss->loss_enable_5g == 1)
+		AICWFDBG(LOGINFO, "%s:loss_value_5g: %d\r\n", __func__,
+			 txpwr_loss->loss_value_5g);
+	max_pwr = get_powerlimit_by_chnum(settx_param.chan, &enable);
+	if (settx_param.chan >= 36) {
+		if (txpwr_loss->loss_enable_5g == 1)
+			max_pwr -= txpwr_loss->loss_value_5g;
+	} else {
+		if (txpwr_loss->loss_enable_2g4 == 1)
+			max_pwr -= txpwr_loss->loss_value_2g4;
+	}
+	if (enable)
+		settx_param.max_pwr = max_pwr;
+	AICWFDBG(LOGINFO, "max_pwr:%d\n", settx_param.max_pwr);
+#endif
+
+	rwnx_send_rftest_req(rwnx_hw, SET_TX, sizeof(struct cmd_rf_settx),
+			     (u8_l *)&settx_param, NULL);
+	return 0;
+}
+
+static int aic_priv_cmd_set_txstop(struct rwnx_hw *rwnx_hw, int argc,
+				   char *argv[], char *command)
+{
+	rwnx_send_rftest_req(rwnx_hw, SET_TXSTOP, 0, NULL, NULL);
+	return 0;
+}
+
+static int aic_priv_cmd_set_rx(struct rwnx_hw *rwnx_hw, int argc, char *argv[],
+			       char *command)
+{
+	struct cmd_rf_rx setrx_param;
+
+	if (argc < 3)
+		return -EINVAL;
+	setrx_param.chan = command_strtoul(argv[1], NULL, 10);
+	setrx_param.bw = command_strtoul(argv[2], NULL, 10);
+	rwnx_send_rftest_req(rwnx_hw, SET_RX, sizeof(struct cmd_rf_rx),
+			     (u8_l *)&setrx_param, NULL);
+	return 0;
+}
+
+static int aic_priv_cmd_get_rx_result(struct rwnx_hw *rwnx_hw, int argc,
+				      char *argv[], char *command)
+{
+	rwnx_send_rftest_req(rwnx_hw, GET_RX_RESULT, 0, NULL, &cfm);
+	memcpy(command, &cfm.rftest_result[0], 8);
+	return 8;
+}
+
+static int aic_priv_cmd_set_rxstop(struct rwnx_hw *rwnx_hw, int argc,
+				   char *argv[], char *command)
+{
+	rwnx_send_rftest_req(rwnx_hw, SET_RXSTOP, 0, NULL, NULL);
+	return 0;
+}
+
+static int aic_priv_cmd_set_tx_tone(struct rwnx_hw *rwnx_hw, int argc,
+				    char *argv[], char *command)
+{
+	u8_l func = 0;
+	u8_l buf[2];
+	s8_l freq_ = 0;
+
+	AICWFDBG(LOGINFO, "%s argc:%d\n", argv[0], argc);
+	if (argc == 2 || argc == 3) {
+		AICWFDBG(LOGINFO, "argv 1:%s\n", argv[1]);
+		func = (u8_l)command_strtoul(argv[1], NULL, 16);
+		if (argc == 3) {
+			AICWFDBG(LOGINFO, "argv 2:%s\n", argv[2]);
+			freq_ = (u8_l)command_strtoul(argv[2], NULL, 10);
+		} else {
+			freq_ = 0;
+		};
+		buf[0] = func;
+		buf[1] = (u8_l)freq_;
+		rwnx_send_rftest_req(rwnx_hw, SET_TXTONE, argc - 1, buf, NULL);
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int aic_priv_cmd_set_rx_meter(struct rwnx_hw *rwnx_hw, int argc,
+				     char *argv[], char *command)
+{
+	s8_l freq = 0;
+
+	freq = (int)command_strtoul(argv[1], NULL, 10);
+	rwnx_send_rftest_req(rwnx_hw, SET_RX_METER, sizeof(freq), (u8_l *)&freq,
+			     NULL);
+	return 0;
+}
+
+static int aic_priv_cmd_set_set_power(struct rwnx_hw *rwnx_hw, int argc,
+				      char *argv[], char *command)
+{
+	return aic_chip_priv_cmd_set_power(rwnx_hw, argc, argv, command);
+}
+
+static int aic_priv_cmd_set_xtal_cap(struct rwnx_hw *rwnx_hw, int argc,
+				     char *argv[], char *command)
+{
+	u8_l xtal_cap;
+
+	if (argc < 2)
+		return -EINVAL;
+
+	xtal_cap = command_strtoul(argv[1], NULL, 10);
+	AICWFDBG(LOGINFO, "xtal_cap =%x\r\n", xtal_cap);
+	rwnx_send_rftest_req(rwnx_hw, SET_XTAL_CAP, sizeof(xtal_cap),
+			     (u8_l *)&xtal_cap, &cfm);
+	memcpy(command, &cfm.rftest_result[0], 4);
+	return 4;
+}
+
+static int aic_priv_cmd_set_xtal_cap_fine(struct rwnx_hw *rwnx_hw, int argc,
+					  char *argv[], char *command)
+{
+	u8_l xtal_cap_fine;
+
+	if (argc < 2)
+		return -EINVAL;
+
+	xtal_cap_fine = command_strtoul(argv[1], NULL, 10);
+	AICWFDBG(LOGINFO, "xtal_cap_fine =%x\r\n", xtal_cap_fine);
+	rwnx_send_rftest_req(rwnx_hw, SET_XTAL_CAP_FINE, sizeof(xtal_cap_fine),
+			     (u8_l *)&xtal_cap_fine, &cfm);
+	memcpy(command, &cfm.rftest_result[0], 4);
+	return 4;
+}
+
+static int aic_priv_cmd_get_efuse_block(struct rwnx_hw *rwnx_hw, int argc,
+					char *argv[], char *command)
+{
+	struct cmd_rf_getefuse getefuse_param;
+
+	if (argc < 2)
+		return -EINVAL;
+
+	getefuse_param.block = command_strtoul(argv[1], NULL, 10);
+	rwnx_send_rftest_req(rwnx_hw, GET_EFUSE_BLOCK, sizeof(struct cmd_rf_getefuse),
+			     (u8_l *)&getefuse_param, &cfm);
+	AICWFDBG(LOGINFO, "get val=%x\r\n", cfm.rftest_result[0]);
+	memcpy(command, &cfm.rftest_result[0], 4);
+	return 4;
+}
+
+static int aic_priv_cmd_set_freq_cal(struct rwnx_hw *rwnx_hw, int argc,
+				     char *argv[], char *command)
+{
+	struct cmd_rf_setfreq cmd_setfreq;
+
+	if (argc < 2)
+		return -EINVAL;
+
+	cmd_setfreq.val = command_strtoul(argv[1], NULL, 16);
+	AICWFDBG(LOGINFO, "param:%x\r\n", cmd_setfreq.val);
+	rwnx_send_rftest_req(rwnx_hw, SET_FREQ_CAL, sizeof(struct cmd_rf_setfreq),
+			     (u8_l *)&cmd_setfreq, &cfm);
+	memcpy(command, &cfm.rftest_result[0], 4);
+	return 4;
+}
+
+static int aic_priv_cmd_set_freq_cal_fine(struct rwnx_hw *rwnx_hw, int argc,
+					  char *argv[], char *command)
+{
+	struct cmd_rf_setfreq cmd_setfreq;
+
+	if (argc < 2)
+		return -EINVAL;
+
+	cmd_setfreq.val = command_strtoul(argv[1], NULL, 16);
+	AICWFDBG(LOGINFO, "param:%x\r\n", cmd_setfreq.val);
+	rwnx_send_rftest_req(rwnx_hw, SET_FREQ_CAL_FINE, sizeof(struct cmd_rf_setfreq),
+			     (u8_l *)&cmd_setfreq, &cfm);
+	memcpy(command, &cfm.rftest_result[0], 4);
+	return 4;
+}
+
+static int aic_priv_cmd_get_freq_cal(struct rwnx_hw *rwnx_hw, int argc,
+				     char *argv[], char *command)
+{
+	aic_chip_priv_cmd_get_freq_cal(rwnx_hw, argc, argv, command);
+	return 4;
+}
+
+static int aic_priv_cmd_set_mac_addr(struct rwnx_hw *rwnx_hw, int argc,
+				     char *argv[], char *command)
+{
+	u8_l 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_l *)&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_l 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_l *)&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;
+}
+
+static int aic_priv_cmd_set_vendor_info(struct rwnx_hw *rwnx_hw, int argc,
+					char *argv[], char *command)
+{
+	return aic_chip_priv_cmd_set_vendor_info(rwnx_hw, argc, argv, command);
+}
+
+static int aic_priv_cmd_get_vendor_info(struct rwnx_hw *rwnx_hw, int argc,
+					char *argv[], char *command)
+{
+	return aic_chip_priv_cmd_get_vendor_info(rwnx_hw, argc, argv, command);
+}
+
+static int aic_priv_cmd_rdwr_pwrmm(struct rwnx_hw *rwnx_hw, int argc,
+				   char *argv[], char *command)
+{
+	if (argc <= 1) { // read cur
+		rwnx_send_rftest_req(rwnx_hw, RDWR_PWRMM, 0, NULL, &cfm);
+	} else { // write
+		u8_l pwrmm = (u8_l)command_strtoul(argv[1], NULL, 16);
+
+		pwrmm = (pwrmm) ? 1 : 0;
+		AICWFDBG(LOGINFO, "set pwrmm = %x\r\n", pwrmm);
+		rwnx_send_rftest_req(rwnx_hw, RDWR_PWRMM, sizeof(pwrmm), (u8_l *)&pwrmm,
+				     &cfm);
+	}
+	memcpy(command, &cfm.rftest_result[0], 4);
+	return 4;
+}
+
+static int aic_priv_cmd_rdwr_pwridx(struct rwnx_hw *rwnx_hw, int argc,
+				    char *argv[], char *command)
+{
+	u8_l func = 0;
+
+	if (!rwnx_hw->chip_ops->support_priv_cmd_rdwr_pwridx) {
+		AICWFDBG(LOGERROR, "unsupported cmd\n");
+		return -EINVAL;
+	}
+	if (argc > 1)
+		func = (u8_l)command_strtoul(argv[1], NULL, 16);
+	if (func == 0) { // read cur
+		rwnx_send_rftest_req(rwnx_hw, RDWR_PWRIDX, 0, NULL, &cfm);
+	} else if (func <= 2) { // write 2.4g/5g pwr idx
+		if (argc > 3) {
+			u8_l type = (u8_l)command_strtoul(argv[2], NULL, 16);
+			u8_l pwridx = (u8_l)command_strtoul(argv[3], NULL, 10);
+			u8_l buf[3] = {func, type, pwridx};
+
+			AICWFDBG(LOGINFO, "set pwridx:[%x][%x]=%x\r\n", func, type, pwridx);
+			rwnx_send_rftest_req(rwnx_hw, RDWR_PWRIDX, sizeof(buf), buf, &cfm);
+		} else {
+			return -EINVAL;
+		}
+	} else {
+		return -EINVAL;
+	}
+	memcpy(command, &cfm.rftest_result[0], 9);
+	return 9;
+}
+
+static int aic_priv_cmd_rdwr_pwrlvl(struct rwnx_hw *rwnx_hw, int argc,
+				    char *argv[], char *command)
+{
+	u8_l func = 0;
+
+	if (!rwnx_hw->chip_ops->support_priv_cmd_rdwr_pwrlvl) {
+		AICWFDBG(LOGERROR, "unsupported cmd\n");
+		return -EINVAL;
+	}
+	if (argc > 1)
+		func = (u8_l)command_strtoul(argv[1], NULL, 16);
+	if (func == 0) { // read cur
+		rwnx_send_rftest_req(rwnx_hw, RDWR_PWRLVL, 0, NULL, &cfm);
+	} else if (func <= 2) { // write 2.4g/5g pwr lvl
+		if (argc > 4) {
+			u8_l grp = (u8_l)command_strtoul(argv[2], NULL, 16);
+			u8_l idx, size;
+			u8_l buf[14] = {
+				func,
+				grp,
+			};
+			if (argc > 12) { // set all grp
+				AICWFDBG(LOGINFO,
+					 "set pwrlvl %s:\n"
+						 "  [%x] =",
+						 (func == 1) ? "2.4g" : "5g", grp);
+				if (grp == 1) { // TXPWR_LVL_GRP_11N_11AC
+					size = 10;
+				} else {
+					size = 12;
+				}
+				for (idx = 0; idx < size; idx++) {
+					s8_l pwrlvl =
+						(s8_l)command_strtoul(argv[3 + idx], NULL, 10);
+					buf[2 + idx] = (u8_l)pwrlvl;
+					if (idx && !(idx & 0x3))
+						AICWFDBG(LOGINFO, " ");
+					AICWFDBG(LOGINFO, " %2d", pwrlvl);
+				}
+				AICWFDBG(LOGINFO, "\n");
+				size += 2;
+			} else { // set grp[idx]
+				u8_l idx = (u8_l)command_strtoul(argv[3], NULL, 10);
+				s8_l pwrlvl = (s8_l)command_strtoul(argv[4], NULL, 10);
+
+				buf[2] = idx;
+				buf[3] = (u8_l)pwrlvl;
+				size = 4;
+				AICWFDBG(LOGINFO,
+					 "set pwrlvl %s:\n"
+						 "  [%x][%d] = %d\n",
+						 (func == 1) ? "2.4g" : "5g", grp, idx, pwrlvl);
+			}
+			rwnx_send_rftest_req(rwnx_hw, RDWR_PWRLVL, size, buf, &cfm);
+		} else {
+			AICWFDBG(LOGERROR, "wrong args\n");
+			return -EINVAL;
+		}
+	} else {
+		AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+		return -EINVAL;
+	}
+
+	memcpy(command, &cfm.rftest_result[0],  (rwnx_hw->chip_ops->pwrlvl_result_copy_len) * 12);
+	return ((rwnx_hw->chip_ops->pwrlvl_result_copy_len) * 12);
+}
+
+static int aic_priv_cmd_rdwr_pwrofst(struct rwnx_hw *rwnx_hw, int argc,
+				     char *argv[], char *command)
+{
+	return aic_chip_priv_cmd_rdwr_pwrofst(rwnx_hw, argc, argv, command);
+}
+
+static int aic_priv_cmd_rdwr_pwrofstfine(struct rwnx_hw *rwnx_hw, int argc,
+					 char *argv[], char *command)
+{
+	u8_l func = 0;
+
+	if (argc > 1)
+		func = (u8_l)command_strtoul(argv[1], NULL, 16);
+	if (func == 0) { // read cur
+		rwnx_send_rftest_req(rwnx_hw, RDWR_PWROFSTFINE, 0, NULL, &cfm);
+	} else if (func <= 2) { // write 2.4g/5g pwr ofst
+		if (argc > 3) {
+			u8_l chgrp = (u8_l)command_strtoul(argv[2], NULL, 16);
+			s8_l pwrofst = (u8_l)command_strtoul(argv[3], NULL, 10);
+			u8_l buf[3] = {func, chgrp, (u8_l)pwrofst};
+
+			AICWFDBG(LOGINFO, "set pwrofstfine:[%x][%x]=%d\r\n", func, chgrp,
+				 pwrofst);
+			rwnx_send_rftest_req(rwnx_hw, RDWR_PWROFSTFINE, sizeof(buf), buf,
+					     &cfm);
+		} else {
+			AICWFDBG(LOGERROR, "wrong args\n");
+			return -EINVAL;
+		}
+	} else {
+		AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+		return -EINVAL;
+	}
+	memcpy(command, &cfm.rftest_result[0], 7);
+	return 7;
+}
+
+static int aic_priv_cmd_rdwr_drvibit(struct rwnx_hw *rwnx_hw, int argc,
+				     char *argv[], char *command)
+{
+	u8_l func = 0;
+
+	if (argc > 1)
+		func = (u8_l)command_strtoul(argv[1], NULL, 16);
+	if (func == 0) { // read cur
+		rwnx_send_rftest_req(rwnx_hw, RDWR_DRVIBIT, 0, NULL, &cfm);
+	} else if (func == 1) { // write 2.4g pa drv_ibit
+		if (argc > 2) {
+			u8_l ibit = (u8_l)command_strtoul(argv[2], NULL, 16);
+			u8_l buf[2] = {func, ibit};
+
+			AICWFDBG(LOGINFO, "set drvibit:[%x]=%x\r\n", func, ibit);
+			rwnx_send_rftest_req(rwnx_hw, RDWR_DRVIBIT, sizeof(buf), buf, &cfm);
+		} else {
+			AICWFDBG(LOGERROR, "wrong args\n");
+			return -EINVAL;
+		}
+	} else {
+		AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+		return -EINVAL;
+	}
+	memcpy(command, &cfm.rftest_result[0], 16);
+	return 16;
+}
+
+static int aic_priv_cmd_rdwr_efuse_pwrofst(struct rwnx_hw *rwnx_hw, int argc,
+					   char *argv[], char *command)
+{
+	return aic_chip_priv_cmd_rdwr_efuse_pwrofst(rwnx_hw, argc, argv, command);
+}
+
+static int aic_priv_cmd_rdwr_efuse_pwrofstfine(struct rwnx_hw *rwnx_hw,
+					       int argc, char *argv[],
+					       char *command)
+{
+	u8_l func = 0;
+
+	if (argc > 1)
+		func = (u8_l)command_strtoul(argv[1], NULL, 16);
+	if (func == 0) { // read cur
+		rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_PWROFSTFINE, 0, NULL, &cfm);
+	} else if (func <= 2) { // write 2.4g/5g pwr ofst
+		if (argc > 3) {
+			u8_l chgrp = (u8_l)command_strtoul(argv[2], NULL, 16);
+			s8_l pwrofst = (u8_l)command_strtoul(argv[3], NULL, 10);
+			u8_l buf[3] = {func, chgrp, (u8_l)pwrofst};
+
+			AICWFDBG(LOGINFO, "set pwrofstfine:[%x][%x]=%d\r\n", func, chgrp,
+				 pwrofst);
+			rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_PWROFSTFINE, sizeof(buf),
+					     buf, &cfm);
+		} else {
+			AICWFDBG(LOGERROR, "wrong args\n");
+			return -EINVAL;
+		}
+	} else {
+		AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+		return -EINVAL;
+	}
+	memcpy(command, &cfm.rftest_result[0], 7);
+	return 7;
+}
+
+static int aic_priv_cmd_rdwr_efuse_drvibit(struct rwnx_hw *rwnx_hw, int argc,
+					   char *argv[], char *command)
+{
+	u8_l func = 0;
+
+	if (argc > 1)
+		func = (u8_l)command_strtoul(argv[1], NULL, 16);
+	if (func == 0) { // read cur
+		rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_DRVIBIT, 0, NULL, &cfm);
+	} else if (func == 1) { // write 2.4g pa drv_ibit
+		if (argc > 2) {
+			u8_l ibit = (u8_l)command_strtoul(argv[2], NULL, 16);
+			u8_l buf[2] = {func, ibit};
+
+			AICWFDBG(LOGINFO, "set efuse drvibit:[%x]=%x\r\n", func, ibit);
+			rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_DRVIBIT, sizeof(buf), buf,
+					     &cfm);
+		} else {
+			AICWFDBG(LOGERROR, "wrong args\n");
+			return -EINVAL;
+		}
+	} else {
+		AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+		return -EINVAL;
+	}
+	memcpy(command, &cfm.rftest_result[0], 4);
+	return 4;
+}
+
+static int aic_priv_cmd_rdwr_efuse_usrdata(struct rwnx_hw *rwnx_hw, int argc,
+					   char *argv[], char *command)
+{
+	struct cmd_ef_usrdata cmd_ef_usrdata;
+
+	if (argc <= 1) { // read all
+		cmd_ef_usrdata.func = 0;
+		cmd_ef_usrdata.cnt = 3;
+	} else if (argc >= 2) { // read/write
+		cmd_ef_usrdata.func = (u8_l)command_strtoul(argv[1], NULL, 10);
+		cmd_ef_usrdata.cnt = (u8_l)command_strtoul(argv[2], NULL, 10);
+		if (cmd_ef_usrdata.func == 1) {
+			int idx;
+
+			for (idx = 0; idx < cmd_ef_usrdata.cnt; idx++) {
+				cmd_ef_usrdata.usrdata[idx] =
+					(u32_l)command_strtoul(argv[3 + idx], NULL, 16);
+			}
+		}
+	} else {
+		AICWFDBG(LOGERROR, "wrong argc: %x\n", argc);
+		return -EINVAL;
+	}
+	rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_USRDATA, sizeof(cmd_ef_usrdata),
+			     (u8_l *)&cmd_ef_usrdata, &cfm);
+	memcpy(command, &cfm.rftest_result[0], 12);
+	return 12;
+}
+
+static int aic_priv_cmd_rdwr_efuse_sdiocfg(struct rwnx_hw *rwnx_hw, int argc,
+					   char *argv[], char *command)
+{
+	u8_l func = 0;
+
+	if (argc > 1)
+		func = (u8_l)command_strtoul(argv[1], NULL, 16);
+	if (func == 0) { // read cur
+		rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_SDIOCFG, 0, NULL, &cfm);
+	} else if (func == 1) { // write sdiocfg
+		if (argc > 2) {
+			u8_l ibit = (u8_l)command_strtoul(argv[2], NULL, 16);
+			u8_l buf[2] = {func, ibit};
+
+			AICWFDBG(LOGINFO, "set efuse sdiocfg:[%x]=%x\r\n", func, ibit);
+			rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_SDIOCFG, sizeof(buf), buf,
+					     &cfm);
+		} else {
+			AICWFDBG(LOGERROR, "wrong args\n");
+			return -EINVAL;
+		}
+	} else {
+		AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+		return -EINVAL;
+	}
+	memcpy(command, &cfm.rftest_result[0], 4);
+	return 4;
+}
+
+static int aic_priv_cmd_rdwr_efuse_usbvidpid(struct rwnx_hw *rwnx_hw, int argc,
+					     char *argv[], char *command)
+{
+	u8_l func = 0;
+
+	AICWFDBG(LOGINFO, "read/write usb vid/pid into efuse\n");
+	if (argc > 1)
+		func = (u8_l)command_strtoul(argv[1], NULL, 16);
+	if (func == 0) { // read cur
+		rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_USBVIDPID, 0, NULL, &cfm);
+	} else if (func == 1) { // write USB vid+pid
+		if (argc > 2) {
+			u32_l usb_id = (u32_l)command_strtoul(argv[2], NULL, 16);
+			u8_l buf[5] = {func, (u8_l)usb_id, (u8_l)(usb_id >> 8),
+						   (u8_l)(usb_id >> 16), (u8_l)(usb_id >> 24)};
+			AICWFDBG(LOGINFO, "set efuse usb vid/pid:[%x]=%x\r\n", func,
+				 usb_id);
+			rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_USBVIDPID, sizeof(buf),
+					     buf, &cfm);
+		} else {
+			AICWFDBG(LOGERROR, "wrong args\n");
+			return -EINVAL;
+		}
+	} else {
+		AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+		return -EINVAL;
+	}
+	memcpy(command, &cfm.rftest_result[0], 4);
+	return 4;
+}
+
+static int aic_priv_cmd_rdwr_efuse_he_off(struct rwnx_hw *rwnx_hw, int argc,
+					  char *argv[], char *command)
+{
+	u8_l func = 0;
+
+	func = command_strtoul(argv[1], NULL, 10);
+	AICWFDBG(LOGINFO, "set he off: %d\n", func);
+	if (func == 1 || func == 0) {
+		rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_HE_OFF, sizeof(func),
+				     (u8_l *)&func, &cfm);
+		AICWFDBG(LOGINFO, "he_off cfm: %d\n", cfm.rftest_result[0]);
+		memcpy(command, &cfm.rftest_result[0], 4);
+		return 4;
+	}
+	return 0;
+}
+
+static int aic_priv_cmd_set_cal_xtal(struct rwnx_hw *rwnx_hw, int argc,
+				     char *argv[], char *command)
+{
+	rwnx_send_rftest_req(rwnx_hw, SET_CAL_XTAL, 0, NULL, NULL);
+	return 0;
+}
+
+static int aic_priv_cmd_get_cal_xtal_res(struct rwnx_hw *rwnx_hw, int argc,
+					 char *argv[], char *command)
+{
+	rwnx_send_rftest_req(rwnx_hw, GET_CAL_XTAL_RES, 0, NULL, &cfm);
+	memcpy(command, &cfm.rftest_result[0], 4);
+	AICWFDBG(LOGINFO, "cap=0x%x, cap_fine=0x%x\n",
+		 cfm.rftest_result[0] & 0x0000ffff,
+			 (cfm.rftest_result[0] >> 16) & 0x0000ffff);
+	return 4;
+}
+
+static int aic_priv_cmd_set_cob_cal(struct rwnx_hw *rwnx_hw, int argc,
+				    char *argv[], char *command)
+{
+	struct cmd_rf_setcobcal setcob_cal;
+
+	if (argc < 3)
+		return -EINVAL;
+	setcob_cal.dutid = command_strtoul(argv[1], NULL, 10);
+	setcob_cal.chip_num = command_strtoul(argv[2], NULL, 10);
+	setcob_cal.dis_xtal = command_strtoul(argv[3], NULL, 10);
+	rwnx_send_rftest_req(rwnx_hw, SET_COB_CAL, sizeof(struct cmd_rf_setcobcal),
+			     (u8_l *)&setcob_cal, NULL);
+	return 0;
+}
+
+static int aic_priv_cmd_get_cob_cal_res(struct rwnx_hw *rwnx_hw, int argc,
+					char *argv[], char *command)
+{
+	u8_l state;
+	struct cob_result_ptr *cob_result_ptr;
+
+	rwnx_send_rftest_req(rwnx_hw, GET_COB_CAL_RES, 0, NULL, &cfm);
+	state = (cfm.rftest_result[0] >> 16) & 0x000000ff;
+	if (!state) {
+		AICWFDBG(LOGINFO, "cap= 0x%x, cap_fine= 0x%x, freq_ofst= %d Hz\n",
+			 cfm.rftest_result[0] & 0x000000ff,
+				 (cfm.rftest_result[0] >> 8) & 0x000000ff,
+				 cfm.rftest_result[1]);
+		cob_result_ptr = (struct cob_result_ptr *)&cfm.rftest_result[2];
+		AICWFDBG(LOGINFO,
+			 "golden_rcv_dut= %d , tx_rssi= %d dBm, snr = %d dB\ndut_rcv_godlden= %d , rx_rssi= %d dBm",
+			 cob_result_ptr->golden_rcv_dut_num,
+			 cob_result_ptr->rssi_static, cob_result_ptr->snr_static,
+			 cob_result_ptr->dut_rcv_golden_num,
+			 cob_result_ptr->dut_rssi_static);
+		memcpy(command, &cfm.rftest_result, 16);
+		return 16;
+	}
+	AICWFDBG(LOGERROR, "cob not idle\n");
+	return -EINVAL;
+}
+
+static int aic_priv_cmd_do_cob_test(struct rwnx_hw *rwnx_hw, int argc,
+				    char *argv[], char *command)
+{
+	u8_l state;
+	struct cmd_rf_setcobcal setcob_cal;
+	struct cob_result_ptr *cob_result_ptr;
+
+	setcob_cal.dutid = 1;
+	setcob_cal.chip_num = 1;
+	setcob_cal.dis_xtal = 0;
+	if (argc > 1)
+		setcob_cal.dis_xtal = command_strtoul(argv[1], NULL, 10);
+	rwnx_send_rftest_req(rwnx_hw, SET_COB_CAL, sizeof(struct cmd_rf_setcobcal),
+			     (u8_l *)&setcob_cal, NULL);
+	msleep(2000);
+	rwnx_send_rftest_req(rwnx_hw, GET_COB_CAL_RES, 0, NULL, &cfm);
+	state = (cfm.rftest_result[0] >> 16) & 0x000000ff;
+	if (!state) {
+		AICWFDBG(LOGINFO, "cap= 0x%x, cap_fine= 0x%x, freq_ofst= %d Hz\n",
+			 cfm.rftest_result[0] & 0x000000ff,
+				 (cfm.rftest_result[0] >> 8) & 0x000000ff,
+				 cfm.rftest_result[1]);
+		cob_result_ptr = (struct cob_result_ptr *)&cfm.rftest_result[2];
+		AICWFDBG(LOGINFO,
+			 "golden_rcv_dut= %d , tx_rssi= %d dBm, snr = %d dB\ndut_rcv_godlden= %d , rx_rssi= %d dBm",
+			 cob_result_ptr->golden_rcv_dut_num,
+			 cob_result_ptr->rssi_static, cob_result_ptr->snr_static,
+			 cob_result_ptr->dut_rcv_golden_num,
+			 cob_result_ptr->dut_rssi_static);
+		memcpy(command, &cfm.rftest_result, 16);
+		return 16;
+	}
+	AICWFDBG(LOGERROR, "cob not idle\n");
+	return -EINVAL;
+}
+
+static int aic_priv_cmd_set_papr(struct rwnx_hw *rwnx_hw, int argc,
+				 char *argv[], char *command)
+{
+	u8_l func = 0;
+
+	if (argc > 1) {
+		func = command_strtoul(argv[1], NULL, 10);
+		AICWFDBG(LOGINFO, "papr %d\r\n", func);
+		rwnx_send_rftest_req(rwnx_hw, SET_PAPR, sizeof(func), (u8_l *)&func,
+				     NULL);
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int aic_priv_cmd_set_notch(struct rwnx_hw *rwnx_hw, int argc,
+				  char *argv[], char *command)
+{
+	u8_l func = 0;
+
+	if (argc > 1) {
+		func = command_strtoul(argv[1], NULL, 10);
+		AICWFDBG(LOGINFO, "notch %d\r\n", func);
+		rwnx_send_rftest_req(rwnx_hw, SET_NOTCH, sizeof(func), (u8_l *)&func,
+				     NULL);
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int aic_priv_cmd_set_srrc(struct rwnx_hw *rwnx_hw, int argc,
+				 char *argv[], char *command)
+{
+	u8_l func = 0;
+
+	if (argc > 1) {
+		func = command_strtoul(argv[1], NULL, 10);
+		AICWFDBG(LOGINFO, "srrc %d\r\n", func);
+		rwnx_send_rftest_req(rwnx_hw, SET_SRRC, sizeof(func), (u8_l *)&func,
+				     NULL);
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int aic_priv_cmd_set_fss(struct rwnx_hw *rwnx_hw, int argc, char *argv[],
+				char *command)
+{
+	u8_l func = 0;
+
+	if (argc > 1) {
+		func = command_strtoul(argv[1], NULL, 10);
+		AICWFDBG(LOGINFO, "fss %d\r\n", func);
+		rwnx_send_rftest_req(rwnx_hw, SET_FSS, sizeof(func), (u8_l *)&func,
+				     NULL);
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int aic_priv_cmd_set_usb_off(struct rwnx_hw *rwnx_hw, int argc,
+				    char *argv[], char *command)
+{
+	rwnx_send_rftest_req(rwnx_hw, SET_USB_OFF, 0, NULL, NULL);
+	return 0;
+}
+
+static int aic_priv_cmd_set_pll_test(struct rwnx_hw *rwnx_hw, int argc,
+				     char *argv[], char *command)
+{
+	u8_l func = 0, tx_pwr = 0xc;
+	s8_l freq = 0;
+
+	if (argc > 1)
+		func = command_strtoul(argv[1], NULL, 16);
+	if (argc > 3) {
+		freq = (s8_l)command_strtoul(argv[2], NULL, 10);
+		tx_pwr = command_strtoul(argv[3], NULL, 16);
+	}
+	if (func <= 1) {
+		u8_l buf[3] = {func, (u8_l)freq, tx_pwr};
+
+		AICWFDBG(LOGINFO, "set pll_test %d: freq=%d, tx_pwr=0x%x\n", func, freq,
+			 tx_pwr);
+		rwnx_send_rftest_req(rwnx_hw, SET_PLL_TEST, sizeof(buf), buf, &cfm);
+	} else {
+		AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int aic_priv_cmd_get_txpwr(struct rwnx_hw *rwnx_hw, int argc,
+				  char *argv[], char *command)
+{
+	s8_l power = 0;
+
+	power = get_txpwr_max(power);
+	memcpy(command, &power, 1);
+	return 1;
+}
+
+static int aic_priv_cmd_set_txpwr_loss(struct rwnx_hw *rwnx_hw, int argc,
+				       char *argv[], char *command)
+{
+	s8_l func;
+
+	if (argc > 1) {
+		func = (s8_l)command_strtoul(argv[1], NULL, 10);
+		pr_info("set txpwr loss: %d\n", func);
+		if (g_rwnx_plat->sdiodev->rwnx_hw->chip_ops->support_priv_cmd_set_txpwr_loss) {
+			set_txpwr_loss_ofst(func);
+			rwnx_send_txpwr_lvl_v3_req(g_rwnx_plat->sdiodev->rwnx_hw, 0);
+		} else {
+			AICWFDBG(LOGINFO, "error:don't support ,now only support D40 D80");
+		}
+	} else {
+		pr_info("wrong args\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int aic_priv_cmd_rdwr_pwradd2x(struct rwnx_hw *rwnx_hw, int argc,
+				      char *argv[], char *command)
+{
+	u8_l func = 0;
+	u8_l buf[2];
+	s8 pwradd2x_in = 0;
+
+	if (argc > 1)
+		func = (u8_l)command_strtoul(argv[1], NULL, 10);
+
+	if (func > 0 && argc > 2)
+		pwradd2x_in = (int8_t)command_strtoul(argv[2], NULL, 10);
+
+	if (func == 0) { // read cur
+		rwnx_send_rftest_req(rwnx_hw, RDWR_PWRADD2X, 0, NULL, &cfm);
+	} else if (func == 1 || func == 2) { // write pwradd2x
+		AICWFDBG(LOGINFO, "set pwradd2x_%s %d\r\n",
+			 (func == 1) ? "2g4" : "5g", pwradd2x_in);
+		if (pwradd2x_in < -15 || pwradd2x_in > 15) {
+			AICWFDBG(LOGERROR,
+				 "wrong params %d,  pwradd2x: -15 ~ 15\n",
+				 pwradd2x_in);
+			return -EINVAL;
+		}
+
+		buf[0] = func;
+		buf[1] = (u8_l)pwradd2x_in;
+		rwnx_send_rftest_req(rwnx_hw, RDWR_PWRADD2X,
+				     sizeof(buf), buf, &cfm);
+	} else {
+		AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+		return -EINVAL;
+	}
+	memcpy(command, &cfm.rftest_result[0], 2);
+	return 2;
+}
+
+static int aic_priv_cmd_rdwr_efuse_pwradd2x(struct rwnx_hw *rwnx_hw, int argc,
+					    char *argv[], char *command)
+{
+	u8_l func = 0;
+	u8_l buf[2];
+	s8 pwradd2x_in = 0;
+
+	if (argc > 1)
+		func = (u8_l)command_strtoul(argv[1], NULL, 10);
+
+	if (func > 0 && argc > 2)
+		pwradd2x_in = (int8_t)command_strtoul(argv[2], NULL, 10);
+
+	if (func == 0) { // read cur
+		rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_PWRADD2X, 0, NULL, &cfm);
+	} else if (func == 1 || func == 2) { // write pwradd2x
+		AICWFDBG(LOGINFO, "set efuse pwradd2x_%s %d\r\n",
+			 (func == 1) ? "2g4" : "5g", pwradd2x_in);
+		if (pwradd2x_in < -15 || pwradd2x_in > 15) {
+			AICWFDBG(LOGERROR,
+				 "wrong params %d,  pwradd2x: -15 ~ 15\n",
+				 pwradd2x_in);
+			return -EINVAL;
+		}
+
+		buf[0] = func;
+		buf[1] = (u8_l)pwradd2x_in;
+		rwnx_send_rftest_req(rwnx_hw, RDWR_EFUSE_PWRADD2X,
+				     sizeof(buf), buf, &cfm);
+	} else {
+		AICWFDBG(LOGERROR, "wrong func: %x\n", func);
+		return -EINVAL;
+	}
+	memcpy(command, &cfm.rftest_result[0], 3);
+	return 3;
+}
+
+#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 = 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)
+{
+	u8_l buf[3];
+	int bytes_written = 0;
+
+	buf[0] = rwnx_hw->wiphy->regd->alpha2[0];
+	buf[1] = rwnx_hw->wiphy->regd->alpha2[1];
+	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_l func = 0;
+	int bytes_written = 0;
+	s8 tp_res[4];
+
+	if (argc < 2) {
+		AICWFDBG(LOGINFO, "%s param err\n", __func__);
+		return -1;
+	}
+
+	func = (u8_l)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_l func = 0;
+	int bytes_written = 0;
+
+	if (argc < 3) {
+		AICWFDBG(LOGINFO, "%s param err\n", __func__);
+		return -1;
+	}
+
+	func = (u8_l)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_l func = 0;
+	int bytes_written = 0;
+
+	if (argc < 2) {
+		AICWFDBG(LOGINFO, "%s param err\n", __func__);
+		return -1;
+	}
+	func = (u8_l)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_l func = 0;
+	int bytes_written = 0;
+
+	if (argc < 3) {
+		AICWFDBG(LOGERROR, "%s param err\n", __func__);
+		return -1;
+	}
+	func = (u8_l)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_l func = 0;
+	int bytes_written = 0;
+
+	if (argc < 2) {
+		AICWFDBG(LOGERROR, "%s param err\n", __func__);
+		return -1;
+	}
+	func = (u8_l)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
+#ifdef CONFIG_AIC8800_GENL
+static int aic_priv_cmd_pktft_set(struct rwnx_hw *rwnx_hw, int argc,
+				  char *argv[], char *command)
+{
+	int ret = 0;
+
+	if (argc < 6) {
+		AICWFDBG(LOGINFO, "%s param err\n", __func__);
+		return -1;
+	}
+	ret = cmd_pktfilter_set(rwnx_hw, &argv[1], argc - 1, NULL, NULL);
+	return ret;
+}
+
+static int aic_priv_cmd_pktft_del(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;
+	}
+	ret = cmd_pktfilter_del(rwnx_hw, &argv[1], argc - 1, NULL, NULL);
+	return ret;
+}
+
+static int aic_priv_cmd_pktft_delall(struct rwnx_hw *rwnx_hw, int argc,
+				     char *argv[], char *command)
+{
+	int ret = 0;
+
+	ret = cmd_pktfilter_delall(rwnx_hw, argv, 0, NULL, NULL);
+	return ret;
+}
+
+static int aic_priv_cmd_pktft_enable(struct rwnx_hw *rwnx_hw, int argc,
+				     char *argv[], char *command)
+{
+	int ret;
+
+	if (argc < 3) {
+		AICWFDBG(LOGINFO, "%s param err\n", __func__);
+		return -1;
+	}
+	ret = cmd_pktfilter_enable(rwnx_hw, &argv[1], argc - 1, NULL, NULL);
+	return ret;
+}
+
+static int aic_priv_cmd_pktft_list(struct rwnx_hw *rwnx_hw, int argc,
+				   char *argv[], char *command)
+{
+	unsigned long r_len = 0;
+	int bytes_written = 0;
+
+	if (argc < 2) {
+		AICWFDBG(LOGINFO, "%s param err\n", __func__);
+		return -1;
+	}
+	cmd_pktfilter_list(rwnx_hw, &argv[1], argc - 1, command, &r_len);
+	bytes_written = r_len;
+
+	return bytes_written;
+}
+#endif
+
+static int aic_priv_cmd_set_suspend(struct rwnx_hw *rwnx_hw, int argc,
+				    char *argv[], char *command)
+{
+	u8_l func = 0;
+	s8_l 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_l)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_l 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_tx", aic_priv_cmd_set_tx,
+	 "<chan> <bw> <mode> <rate> <length> <interval>"},
+	{"set_txstop", aic_priv_cmd_set_txstop, "= stop tx "},
+	{"set_rx", aic_priv_cmd_set_rx, "<chan_num> <bw> "},
+	{"get_rx_result", aic_priv_cmd_get_rx_result,
+	 "= display rx fcsok/total pkt num"},
+	{"set_rxstop", aic_priv_cmd_set_rxstop, "= stop rx "},
+	{"set_txtone", aic_priv_cmd_set_tx_tone, "<val> val = 0/off"},
+	{"set_rx_meter", aic_priv_cmd_set_rx_meter, "= set rx meter "},
+	{"set_power", aic_priv_cmd_set_set_power, "<dec val> "},
+	{"set_xtal_cap", aic_priv_cmd_set_xtal_cap, "<dec val> [0 ~ 31]"},
+	{"set_xtal_cap_fine", aic_priv_cmd_set_xtal_cap_fine, "<dec val> [0 ~ 63]"},
+	{"get_efuse_block", aic_priv_cmd_get_efuse_block, "<val>"},
+	{"set_freq_cal", aic_priv_cmd_set_freq_cal, "<hex val>"},
+	{"set_freq_cal_fine", aic_priv_cmd_set_freq_cal_fine, "<hex val>"},
+	{"get_freq_cal", aic_priv_cmd_get_freq_cal, "= display cap & cap fine"},
+	{"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"},
+	{"set_vendor_info", aic_priv_cmd_set_vendor_info,
+	 "= write vendor info into efuse or flash is allowed only once"},
+	{"get_vendor_info", aic_priv_cmd_get_vendor_info,
+	 "= display vendor info stored in efuse or flash"},
+	{"rdwr_pwrmm", aic_priv_cmd_rdwr_pwrmm,
+	 "<val> = 0/rdwr_pwrlvl, 1/set_power = read/write txpwr manul mode"},
+	{"rdwr_pwridx", aic_priv_cmd_rdwr_pwridx, "<band> <mod> <idx>"},
+	{"rdwr_pwrlvl", aic_priv_cmd_rdwr_pwrlvl, "<band> <mod> <idx>"},
+	{"rdwr_pwrofst", aic_priv_cmd_rdwr_pwrofst, "<band> <rate> <ch> <ofst>"},
+	{"rdwr_pwrofstfine", aic_priv_cmd_rdwr_pwrofstfine,
+	 "<band> <rate> <ch> <ofstfine>"},
+	{"rdwr_drvibit", aic_priv_cmd_rdwr_drvibit,
+	 "<func> <val> read/write 8800D pa drvibit"},
+	{"set_cal_xtal", aic_priv_cmd_set_cal_xtal, "= set cal xtal"},
+	{"get_cal_xtal_res", aic_priv_cmd_get_cal_xtal_res,
+	 "= get cal xtal result cap & cap_fine"},
+	{"set_cob_cal", aic_priv_cmd_set_cob_cal,
+	 "<dutid> <chip_num> <disxtal> = dut cob test"},
+	{"get_cob_cal_res", aic_priv_cmd_get_cob_cal_res, "= get cob cal result"},
+	{"do_cob_test", aic_priv_cmd_do_cob_test,
+	 "<func> = 0/xtal, 1/dis_xtal, 2/only_xtal"},
+	{"rdwr_efuse_pwrofst", aic_priv_cmd_rdwr_efuse_pwrofst,
+	 "<band> <rate> <ch> <ofst> limited to a maximum of two times"},
+	{"rdwr_efuse_pwrofstfine", aic_priv_cmd_rdwr_efuse_pwrofstfine,
+	 "<band> <rate> <ch> <ofstfine> limited to a maximum of two times"},
+	{"rdwr_efuse_drvibit", aic_priv_cmd_rdwr_efuse_drvibit,
+	 "<func> <val> = read/write 8800D efuse pa drvibitis allowed only once"},
+	{"rdwr_efuse_usrdata", aic_priv_cmd_rdwr_efuse_usrdata,
+	 "<func> <val> = read/write efuse usrdata"},
+	{"rdwr_efuse_sdiocfg", aic_priv_cmd_rdwr_efuse_sdiocfg,
+	 "<func> <val> = read/write sdiocfg_bit into efuse"},
+	{"rdwr_efuse_usbvidpid", aic_priv_cmd_rdwr_efuse_usbvidpid,
+	 "<func> <val> = read/write usb vid/pid into efuse"},
+	{"rdwr_efuse_he_off", aic_priv_cmd_rdwr_efuse_he_off,
+	 "<func> = read/write he_off into efuse"},
+	{"set_papr", aic_priv_cmd_set_papr,
+	 "<val> = configure papr filter to optimize sideband suppression"},
+	{"set_notch", aic_priv_cmd_set_notch,
+	 "<val> = configure filter to optimize sideband suppression"},
+	{"set_srrc", aic_priv_cmd_set_srrc,
+	 "<func> = disable/enable sideband suppression for SRRC"},
+	{"set_fss", aic_priv_cmd_set_fss,
+	 "<func> = disable/enable treatment of spurious emissions and burrs"},
+	{"set_usb_off", aic_priv_cmd_set_usb_off,
+	 "= off usb configure before usb disconnect"},
+	{"set_pll_test", aic_priv_cmd_set_pll_test,
+	 "<func> <freq> <tx_pwr> = use pll test to measure saturation power"},
+	{"get_txpwr", aic_priv_cmd_get_txpwr, "= get userconfig max txpwr"},
+	{"set_txpwr_loss", aic_priv_cmd_set_txpwr_loss,
+	 "<val> = txpwr will change ,val can be negative"},
+	{"rdwr_pwradd2x", aic_priv_cmd_rdwr_pwradd2x,
+	  "a value is added for both 2.4G and 5G to achieve overall power adjustment of the band"},
+	{"rdwr_efuse_pwradd2x", aic_priv_cmd_rdwr_efuse_pwradd2x,
+	  "add power offset for both 2.4G and 5G, write to efuse"},
+
+	/* 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
+#ifdef CONFIG_AIC8800_GENL
+	{"pkt_filter_set", aic_priv_cmd_pktft_set,
+	 "<code> <offset> <length> <mask> <pattern>"},
+	{"pkt_filter_del", aic_priv_cmd_pktft_del, "<id>"},
+	{"pkt_filter_delall", aic_priv_cmd_pktft_delall, "no param"},
+	{"pkt_filter_enable", aic_priv_cmd_pktft_enable, "<id> <val>"},
+	{"pkt_filter_list", aic_priv_cmd_pktft_list, "<val>"},
+#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;
+}
+
+#define RWNX_COUNTRY_CODE_LEN 2
+#define CMD_SET_COUNTRY       "COUNTRY"
+#define CMD_SET_VENDOR_EX_IE  "SET_VENDOR_EX_IE"
+#define CMD_SET_AP_WPS_P2P_IE "SET_AP_WPS_P2P_IE"
+#define CMD_SETSUSPENDMODE    "SETSUSPENDMODE"
+
+#ifdef CONFIG_AIC8800_SET_VENDOR_EXTENSION_IE
+
+static void set_vendor_extension_ie(char *command)
+{
+	char databyte[3] = {0x00, 0x00, 0x00};
+	int skip = strlen(CMD_SET_VENDOR_EX_IE) + 1;
+	int command_index = skip;
+	int data_index = 0;
+
+	memset(vendor_extension_data, 0, 256);
+	vendor_extension_len = 0;
+	memcpy(databyte, command + command_index, 2);
+	vendor_extension_len = command_strtoul(databyte, NULL, 16);
+	pr_info("%s len:%d \r\n", __func__, vendor_extension_len);
+
+	// parser command and save data in vendor_extension_data
+	for (data_index = 0; data_index < vendor_extension_len; data_index++) {
+		command_index = command_index + 3;
+		memcpy(databyte, command + command_index, 2);
+		vendor_extension_data[data_index] = command_strtoul(databyte, NULL, 16);
+	}
+}
+#endif // CONFIG_AIC8800_SET_VENDOR_EXTENSION_IE
+
+int android_priv_cmd(struct net_device *net, struct ifreq *ifr, int cmd)
+{
+#define PRIVATE_COMMAND_MAX_LEN 8192
+#define PRIVATE_COMMAND_DEF_LEN 6144
+
+	int ret = 0;
+	char *command = NULL;
+	int bytes_written = 0;
+	struct android_wifi_priv_cmd priv_cmd;
+	int buf_size = 0;
+#ifdef ANDROID_PLATFORM
+	struct rwnx_vif *vif = netdev_priv(net);
+	int skip = 0;
+	char *country = NULL;
+	int setsusp_mode;
+	struct ieee80211_regdomain *regdomain;
+#endif
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	if (!ifr->ifr_data) {
+		ret = -EINVAL;
+		goto exit;
+	}
+
+#ifdef CONFIG_COMPAT
+	if (in_compat_syscall()) {
+		struct compat_android_wifi_priv_cmd compat_priv_cmd;
+
+		if (copy_from_user(&compat_priv_cmd, ifr->ifr_data,
+				   sizeof(struct compat_android_wifi_priv_cmd))) {
+			ret = -EFAULT;
+			goto exit;
+		}
+		priv_cmd.buf = compat_ptr(compat_priv_cmd.buf);
+		priv_cmd.used_len = compat_priv_cmd.used_len;
+		priv_cmd.total_len = compat_priv_cmd.total_len;
+	} else {
+#endif /* CONFIG_COMPAT */
+		if (copy_from_user(&priv_cmd, ifr->ifr_data,
+				   sizeof(struct android_wifi_priv_cmd))) {
+			ret = -EFAULT;
+			goto exit;
+		}
+	}
+	if (priv_cmd.total_len > PRIVATE_COMMAND_MAX_LEN ||
+	    priv_cmd.total_len < 0) {
+		pr_err("%s: buf length invalid:%d\n", __func__, priv_cmd.total_len);
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	buf_size = max(priv_cmd.total_len, PRIVATE_COMMAND_DEF_LEN);
+	command =
+		kmalloc((buf_size + 1), GFP_KERNEL);
+	if (!command) {
+		pr_err("%s: failed to allocate memory\n", __func__);
+		ret = -ENOMEM;
+		goto exit;
+	}
+	if (copy_from_user(command, priv_cmd.buf, priv_cmd.total_len)) {
+		ret = -EFAULT;
+		goto exit;
+	}
+	command[priv_cmd.total_len] = '\0';
+
+	AICWFDBG(LOGINFO, "%s: Android private cmd \"%s\" on %s\n", __func__,
+		 command, ifr->ifr_name);
+
+#ifdef ANDROID_PLATFORM
+	if (!strncasecmp(command, CMD_SET_COUNTRY, strlen(CMD_SET_COUNTRY))) {
+		skip = strlen(CMD_SET_COUNTRY) + 1;
+		country = command + skip;
+		if (!country || strlen(country) < RWNX_COUNTRY_CODE_LEN) {
+			pr_err("%s: invalid country code\n", __func__);
+			ret = -EINVAL;
+			goto exit;
+		}
+
+		AICWFDBG(LOGINFO, "%s country code:%c%c\n", __func__,
+			 toupper(country[0]), toupper(country[1]));
+		regdomain = get_regdomain_from_rwnx_db(vif->rwnx_hw->wiphy, country);
+		ret = regulatory_set_wiphy_regd(vif->rwnx_hw->wiphy, regdomain);
+		if (ret)
+			pr_warn("regulatory_set_wiphy_regd fail \r\n");
+#ifdef CONFIG_AIC8800_POWER_LIMIT
+		aic_chip_powerlimit_load(vif->rwnx_hw);
+		if (!rwnx_hw->testmode)
+			rwnx_send_me_chan_config_req(vif->rwnx_hw);
+#endif
+	}
+#ifdef CONFIG_AIC8800_SET_VENDOR_EXTENSION_IE
+	else if (!strncasecmp(command, CMD_SET_VENDOR_EX_IE,
+			      strlen(CMD_SET_VENDOR_EX_IE))) {
+		set_vendor_extension_ie(command);
+	}
+#endif // CONFIG_AIC8800_SET_VENDOR_EXTENSION_IE
+	else if (!strncasecmp(command, CMD_SET_AP_WPS_P2P_IE,
+			      strlen(CMD_SET_AP_WPS_P2P_IE))) {
+		ret = 0;
+		goto exit;
+	} else if (!strncasecmp(command, CMD_SETSUSPENDMODE,
+							strlen(CMD_SETSUSPENDMODE))) {
+#ifdef AICWF_SDIO_SUPPORT
+#if defined(CONFIG_GPIO_WAKEUP) && !defined(CONFIG_AIC8800_AUTO_POWERSAVE)
+		skip = strlen(CMD_SETSUSPENDMODE) + 1;
+		setsusp_mode = command_strtoul(command + skip, NULL, 10);
+#ifdef AICWF_LATENCY_MODE
+		if (setsusp_mode)
+			rwnx_send_me_set_lp_level(g_rwnx_plat->sdiodev->rwnx_hw, setsusp_mode, 0);
+		else
+			rwnx_send_me_set_lp_level(g_rwnx_plat->sdiodev->rwnx_hw, 1, 1);
+#else
+		rwnx_send_me_set_lp_level(g_rwnx_plat->sdiodev->rwnx_hw,
+					  setsusp_mode, !setsusp_mode);
+#endif
+
+	AICWFDBG(LOGINFO, "set suspend mode %d\n", setsusp_mode);
+#endif // CONFIG_GPIO_WAKEUP
+#endif
+		goto exit;
+	}
+#endif // Handle Android command
+
+	bytes_written = handle_private_cmd(net, command, priv_cmd.total_len);
+	if (bytes_written >= 0) {
+		if (bytes_written == 0 && priv_cmd.total_len > 0)
+			command[0] = '\0';
+		if (bytes_written >= priv_cmd.total_len) {
+			pr_info("%s: err. bytes_written:%d >= buf_size:%d\n", __func__,
+				bytes_written, buf_size);
+			goto exit;
+		}
+		bytes_written++;
+		priv_cmd.used_len = bytes_written;
+		if (copy_to_user(priv_cmd.buf, command, bytes_written)) {
+			pr_err("%s: failed to copy data to user buffer\n", __func__);
+			ret = -EFAULT;
+		}
+	} else {
+		/* Propagate the error */
+		ret = bytes_written;
+	}
+
+exit:
+	kfree(command);
+	return ret;
+}
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..1c28f52cc4d1f
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_priv_cmd.h
@@ -0,0 +1,127 @@
+/* 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_l chan;
+	u8_l bw;
+	u8_l mode;
+	u8_l rate;
+	u16_l length;
+	u16_l tx_intv_us;
+	s8_l max_pwr;
+};
+
+struct cmd_rf_setfreq {
+	u8_l val;
+};
+
+struct cmd_rf_rx {
+	u8_l chan;
+	u8_l bw;
+};
+
+struct cmd_rf_getefuse {
+	u8_l block;
+};
+
+struct cmd_rf_setcobcal {
+	u8_l dutid;
+	u8_l chip_num;
+	u8_l dis_xtal;
+};
+
+struct cob_result_ptr {
+	u16_l dut_rcv_golden_num;
+	u8_l golden_rcv_dut_num;
+	s8_l rssi_static;
+	s8_l snr_static;
+	s8_l dut_rssi_static;
+	u16_l reserved;
+};
+
+struct android_wifi_priv_cmd {
+	char *buf;
+
+	int used_len;
+
+	int total_len;
+
+};
+
+#ifdef CONFIG_COMPAT
+struct compat_android_wifi_priv_cmd {
+	compat_caddr_t buf;
+
+	int used_len;
+
+	int total_len;
+
+};
+
+#endif /* CONFIG_COMPAT */
+
+extern int reg_regdb_size;
+extern u8_l vendor_extension_data[256];
+extern int vendor_extension_len;
+
+int android_priv_cmd(struct net_device *net, struct ifreq *ifr, int cmd);
+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/aic_vendor.c b/drivers/net/wireless/aic/aic8800_fdrv/aic_vendor.c
new file mode 100644
index 0000000000000..077eb9b68f36c
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_vendor.c
@@ -0,0 +1,950 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief vendor interface function
+ *
+ ******************************************************************************
+ */
+
+#include "aic_vendor.h"
+#include "rwnx_defs.h"
+#include "rwnx_version_gen.h"
+#include <linux/inetdevice.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <net/netlink.h>
+
+static struct wifi_ring_buffer_status ring_buffer[] = {
+	{
+		.name = "aicwf_ring_buffer0",
+		.flags = 0,
+		.ring_id = 0,
+		.verbose_level = 0,
+		.written_bytes = 0,
+		.read_bytes = 0,
+		.written_records = 0,
+	},
+};
+
+static struct wlan_driver_wake_reason_cnt_t wake_reason_cnt = {
+	.total_cmd_event_wake = 10,
+};
+
+enum apf_attributes {
+	APF_ATTRIBUTE_VERSION,
+	APF_ATTRIBUTE_MAX_LEN,
+	APF_ATTRIBUTE_PROGRAM,
+	APF_ATTRIBUTE_PROGRAM_LEN,
+	APF_ATTRIBUTE_MAX
+};
+
+int aic_dev_start_mkeep_alive(struct rwnx_hw *rwnx_hw,
+			      struct rwnx_vif *rwnx_vif, u8 mkeep_alive_id,
+							  u8 *ip_pkt, u16 ip_pkt_len, u8 *src_mac,
+							  u8 *dst_mac, u32 period_msec)
+{
+	u8 *data, *pos;
+
+	data = kzalloc(ip_pkt_len + 14, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	pos = data;
+	memcpy(pos, dst_mac, 6);
+	pos += 6;
+	memcpy(pos, src_mac, 6);
+	pos += 6;
+	/* Mapping Ethernet type (ETHERTYPE_IP: 0x0800) */
+	*(pos++) = 0x08;
+	*(pos++) = 0x00;
+
+	/* Mapping IP pkt */
+	memcpy(pos, ip_pkt, ip_pkt_len);
+	pos += ip_pkt_len;
+
+	// add send 802.3 pkt(raw data)
+	kfree(data);
+
+	return 0;
+}
+
+int aic_dev_stop_mkeep_alive(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+			     u8 mkeep_alive_id)
+{
+	int res = -1;
+
+	/*
+	 * The mkeep_alive packet is for STA interface only; if the bss is
+	 * configured as AP, dongle shall reject a mkeep_alive request.
+	 */
+	if (rwnx_vif->wdev.iftype != NL80211_IFTYPE_STATION)
+		return res;
+
+	pr_info("%s execution\n", __func__);
+
+	// add send stop keep alive
+	res = 0;
+	return res;
+}
+
+static int aicwf_vendor_start_mkeep_alive(struct wiphy *wiphy,
+					  struct wireless_dev *wdev,
+					  const void *data, int len)
+{
+	/* max size of IP packet for keep alive */
+	const int MKEEP_ALIVE_IP_PKT_MAX = 256;
+
+	int ret = 0, rem, type;
+	u8 mkeep_alive_id = 0;
+	u8 *ip_pkt = NULL;
+	u16 ip_pkt_len = 0;
+	u8 src_mac[6];
+	u8 dst_mac[6];
+	u32 period_msec = 0;
+	const struct nlattr *iter;
+	struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+	struct rwnx_vif *rwnx_vif = container_of(wdev, struct rwnx_vif, wdev);
+	gfp_t kflags = in_interrupt() ? GFP_ATOMIC : GFP_KERNEL;
+
+	nla_for_each_attr(iter, data, len, rem) {
+		type = nla_type(iter);
+		switch (type) {
+		case MKEEP_ALIVE_ATTRIBUTE_ID:
+			mkeep_alive_id = nla_get_u8(iter);
+			break;
+		case MKEEP_ALIVE_ATTRIBUTE_IP_PKT_LEN:
+			ip_pkt_len = nla_get_u16(iter);
+			if (ip_pkt_len > MKEEP_ALIVE_IP_PKT_MAX) {
+				ret = -EINVAL;
+				goto exit;
+			}
+			break;
+		case MKEEP_ALIVE_ATTRIBUTE_IP_PKT:
+			if (!ip_pkt_len) {
+				ret = -EINVAL;
+				pr_err("ip packet length is 0\n");
+				goto exit;
+			}
+			ip_pkt = kzalloc(ip_pkt_len, kflags);
+			if (!ip_pkt) {
+				ret = -ENOMEM;
+				pr_err("Failed to allocate mem for ip packet\n");
+				goto exit;
+			}
+			memcpy(ip_pkt, (u8 *)nla_data(iter), ip_pkt_len);
+			break;
+		case MKEEP_ALIVE_ATTRIBUTE_SRC_MAC_ADDR:
+			memcpy(src_mac, nla_data(iter), 6);
+			break;
+		case MKEEP_ALIVE_ATTRIBUTE_DST_MAC_ADDR:
+			memcpy(dst_mac, nla_data(iter), 6);
+			break;
+		case MKEEP_ALIVE_ATTRIBUTE_PERIOD_MSEC:
+			period_msec = nla_get_u32(iter);
+			break;
+		default:
+			pr_err("%s(%d), Unknown type: %d\n", __func__, __LINE__, type);
+			ret = -EINVAL;
+			goto exit;
+		}
+	}
+
+	if (!ip_pkt) {
+		ret = -EINVAL;
+		pr_err("ip packet is NULL\n");
+		goto exit;
+	}
+
+	ret = aic_dev_start_mkeep_alive(rwnx_hw, rwnx_vif, mkeep_alive_id, ip_pkt,
+					ip_pkt_len, src_mac, dst_mac, period_msec);
+	if (ret < 0)
+		pr_err("start_mkeep_alive is failed ret: %d\n", ret);
+
+exit:
+	kfree(ip_pkt);
+
+	return ret;
+}
+
+static int aicwf_vendor_stop_mkeep_alive(struct wiphy *wiphy,
+					 struct wireless_dev *wdev,
+					 const void *data, int len)
+{
+	int ret = 0, rem, type;
+	u8 mkeep_alive_id = 0;
+	const struct nlattr *iter;
+	struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+	struct rwnx_vif *rwnx_vif = container_of(wdev, struct rwnx_vif, wdev);
+
+	nla_for_each_attr(iter, data, len, rem) {
+		type = nla_type(iter);
+		switch (type) {
+		case MKEEP_ALIVE_ATTRIBUTE_ID:
+			mkeep_alive_id = nla_get_u8(iter);
+			break;
+		default:
+			pr_err("%s(%d), Unknown type: %d\n", __func__, __LINE__, type);
+			ret = -EINVAL;
+			break;
+		}
+	}
+
+	ret = aic_dev_stop_mkeep_alive(rwnx_hw, rwnx_vif, mkeep_alive_id);
+	if (ret < 0)
+		pr_err("stop_mkeep_alive is failed ret: %d\n", ret);
+
+	return ret;
+}
+
+static int aicwf_vendor_get_ver(struct wiphy *wiphy, struct wireless_dev *wdev,
+				const void *data, int len)
+{
+	int ret = 0, rem, type;
+	const struct nlattr *iter;
+	int payload = 0;
+	char version[128];
+	int attr = -1;
+	struct sk_buff *reply;
+
+	nla_for_each_attr(iter, data, len, rem) {
+		type = nla_type(iter);
+		switch (type) {
+		case LOGGER_ATTRIBUTE_DRIVER_VER:
+			memcpy(version, RWNX_VERS_BANNER, sizeof(RWNX_VERS_BANNER));
+			payload = strlen(version);
+			attr = LOGGER_ATTRIBUTE_DRIVER_VER;
+			break;
+		case LOGGER_ATTRIBUTE_FW_VER:
+			memcpy(version, wiphy->fw_version, sizeof(wiphy->fw_version));
+			payload = strlen(version);
+			attr = LOGGER_ATTRIBUTE_FW_VER;
+			break;
+		default:
+			AICWFDBG(LOGERROR, "%s(%d), Unknown type: %d\n", __func__, __LINE__,
+				 type);
+			return -EINVAL;
+		}
+	}
+
+	if (attr < 0)
+		return -EINVAL;
+
+	reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, payload);
+
+	if (!reply)
+		return -ENOMEM;
+
+	if (nla_put(reply, attr, payload, version)) {
+		wiphy_err(wiphy, "%s put version error\n", __func__);
+		goto out_put_fail;
+	}
+
+	ret = cfg80211_vendor_cmd_reply(reply);
+	if (ret)
+		wiphy_err(wiphy, "%s reply cmd error\n", __func__);
+	return ret;
+
+out_put_fail:
+	kfree_skb(reply);
+	return -EMSGSIZE;
+}
+
+static int aicwf_vendor_subcmd_get_channel_list(struct wiphy *wiphy,
+						struct wireless_dev *wdev,
+						const void *data, int len)
+{
+	int ret = 0, rem, type;
+	const struct nlattr *iter;
+	struct sk_buff *reply;
+	int num_channels = 0;
+	int *channel_list = NULL;
+	int payload;
+	int i = 0;
+	struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+	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];
+
+	num_channels += rwnx_band_2ghz->n_channels;
+	num_channels += (rwnx_hw->band_5g_support) ? rwnx_band_5ghz->n_channels : 0;
+
+	channel_list = kcalloc(num_channels, sizeof(int), GFP_KERNEL);
+	if (!channel_list)
+		return -ENOMEM;
+
+	for (i = 0; i < rwnx_band_2ghz->n_channels; i++)
+		channel_list[i] = rwnx_band_2ghz->channels[i].center_freq;
+
+	for (; rwnx_hw->band_5g_support && i < num_channels; i++)
+		channel_list[i] = rwnx_band_5ghz->channels[i].center_freq;
+
+	payload = sizeof(num_channels) + sizeof(int) * num_channels + 4;
+
+	nla_for_each_attr(iter, data, len, rem) {
+		type = nla_type(iter);
+		switch (type) {
+		case GSCAN_ATTRIBUTE_BAND:
+			reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, payload);
+
+			if (!reply)
+				return -ENOMEM;
+
+			if (nla_put_u32(reply, GSCAN_ATTRIBUTE_NUM_CHANNELS, num_channels))
+				goto out_put_fail;
+
+			if (nla_put(reply, GSCAN_ATTRIBUTE_CHANNEL_LIST,
+				    sizeof(int) * num_channels, channel_list))
+				goto out_put_fail;
+
+			ret = cfg80211_vendor_cmd_reply(reply);
+			if (ret)
+				wiphy_err(wiphy, "%s reply cmd error\n", __func__);
+			break;
+		default:
+			pr_err("%s(%d), Unknown type: %d\n", __func__, __LINE__, type);
+			return -EINVAL;
+		}
+	}
+
+	kfree(channel_list);
+	return ret;
+
+out_put_fail:
+	kfree(channel_list);
+	kfree_skb(reply);
+	return -EMSGSIZE;
+}
+
+static int aicwf_vendor_subcmd_set_country_code(struct wiphy *wiphy,
+						struct wireless_dev *wdev,
+						const void *data, int len)
+{
+	int ret = 0, rem, type;
+	const struct nlattr *iter;
+
+	nla_for_each_attr(iter, data, len, rem) {
+		type = nla_type(iter);
+		switch (type) {
+		case ANDR_WIFI_ATTRIBUTE_COUNTRY:
+			pr_info("%s(%d), ANDR_WIFI_ATTRIBUTE_COUNTRY: %s\n", __func__,
+				__LINE__, (char *)nla_data(iter));
+			break;
+		default:
+			pr_err("%s(%d), Unknown type: %d\n", __func__, __LINE__, type);
+			return -EINVAL;
+		}
+	}
+
+	return ret;
+}
+
+static int aicwf_vendor_logger_trigger_memory_dump(struct wiphy *wiphy,
+						   struct wireless_dev *wdev,
+						   const void *data, int len)
+{
+	/* TODO
+	 * Add handle in the future!
+	 */
+	return 0;
+}
+
+static int aicwf_vendor_subcmd_get_feature_set(struct wiphy *wiphy,
+					       struct wireless_dev *wdev,
+					       const void *data, int len)
+{
+	int ret;
+	struct sk_buff *reply;
+	u32 feature = 0, payload;
+	struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+
+	payload = sizeof(feature);
+	reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, payload);
+
+	if (!reply)
+		return -ENOMEM;
+
+	/* TODO
+	 * Add handle in the future!
+	 */
+	/*bit 1:Basic infrastructure mode */
+	if (wiphy->interface_modes & BIT(NL80211_IFTYPE_STATION))
+		feature |= WIFI_FEATURE_INFRA;
+
+	/*bit 2:Support for 5 GHz Band */
+	if (rwnx_hw->band_5g_support)
+		feature |= WIFI_FEATURE_INFRA_5G;
+
+	/*bit3:HOTSPOT is a supplicant feature, enable it by default */
+	feature |= WIFI_FEATURE_HOTSPOT;
+
+	/*bit 4:P2P */
+	if ((wiphy->interface_modes & BIT(NL80211_IFTYPE_P2P_CLIENT)) &&
+	    (wiphy->interface_modes & BIT(NL80211_IFTYPE_P2P_GO)))
+		feature |= WIFI_FEATURE_P2P;
+
+	/*bit 5:soft AP feature supported */
+	if (wiphy->interface_modes & BIT(NL80211_IFTYPE_AP))
+		feature |= WIFI_FEATURE_SOFT_AP;
+
+	/*bit 18:WiFi Logger */
+	feature |= WIFI_FEATURE_LOGGER;
+
+	/*bit 21:WiFi mkeep_alive */
+	feature |= WIFI_FEATURE_MKEEP_ALIVE;
+
+#ifdef AICWF_LATENCY_MODE
+	feature |= WIFI_FEATURE_SET_LATENCY_MODE;
+#endif
+
+	if (nla_put_u32(reply, ANDR_WIFI_ATTRIBUTE_NUM_FEATURE_SET, feature)) {
+		wiphy_err(wiphy, "%s put u32 error\n", __func__);
+		goto out_put_fail;
+	}
+
+	ret = cfg80211_vendor_cmd_reply(reply);
+	if (ret)
+		wiphy_err(wiphy, "%s reply cmd error\n", __func__);
+
+	return ret;
+
+out_put_fail:
+	kfree_skb(reply);
+	return -EMSGSIZE;
+}
+
+static int aicwf_vendor_logger_get_feature(struct wiphy *wiphy,
+					   struct wireless_dev *wdev,
+					   const void *data, int len)
+{
+	int ret;
+	struct sk_buff *reply;
+	u32 feature = 0, payload;
+
+	payload = sizeof(feature);
+	reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, payload);
+
+	if (!reply)
+		return -ENOMEM;
+
+	feature |= WIFI_LOGGER_MEMORY_DUMP_SUPPORTED;
+	feature |= WIFI_LOGGER_CONNECT_EVENT_SUPPORTED;
+
+	/*vts will test wake reason state function */
+	feature |= WIFI_LOGGER_WAKE_LOCK_SUPPORTED;
+
+	if (nla_put_u32(reply, ANDR_WIFI_ATTRIBUTE_NUM_FEATURE_SET, feature)) {
+		wiphy_err(wiphy, "put skb u32 failed\n");
+		goto out_put_fail;
+	}
+
+	ret = cfg80211_vendor_cmd_reply(reply);
+	if (ret)
+		wiphy_err(wiphy, "reply cmd error\n");
+
+	return ret;
+
+out_put_fail:
+	kfree_skb(reply);
+	return -EMSGSIZE;
+}
+
+static int aicwf_vendor_logger_get_ring_status(struct wiphy *wiphy,
+					       struct wireless_dev *wdev,
+					       const void *data, int len)
+{
+	int ret;
+	struct sk_buff *reply;
+	u32 payload;
+	u32 ring_buffer_nums = ARRAY_SIZE(ring_buffer);
+
+	payload = sizeof(ring_buffer_nums) + sizeof(ring_buffer);
+	reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, payload);
+
+	if (!reply)
+		return -ENOMEM;
+
+	if (nla_put_u32(reply, LOGGER_ATTRIBUTE_RING_NUM, ring_buffer_nums)) {
+		wiphy_err(wiphy, "put skb u32 failed\n");
+		goto out_put_fail;
+	}
+
+	if (nla_put(reply, LOGGER_ATTRIBUTE_RING_STATUS, sizeof(ring_buffer),
+		    ring_buffer)) {
+		wiphy_err(wiphy, "put skb failed\n");
+		goto out_put_fail;
+	}
+
+	ret = cfg80211_vendor_cmd_reply(reply);
+	if (ret)
+		wiphy_err(wiphy, "reply cmd error\n");
+
+	return ret;
+
+out_put_fail:
+	kfree_skb(reply);
+	return -EMSGSIZE;
+}
+
+static int aicwf_vendor_logger_start_logging(struct wiphy *wiphy,
+					     struct wireless_dev *wdev,
+					     const void *data, int len)
+{
+	int ret = 0, rem, type, i;
+	const struct nlattr *iter;
+	struct wifi_ring_buffer_status rb = {};
+
+	nla_for_each_attr(iter, data, len, rem) {
+		type = nla_type(iter);
+		switch (type) {
+		case LOGGER_ATTRIBUTE_LOG_LEVEL:
+			rb.verbose_level = nla_get_u32(iter);
+			break;
+		case LOGGER_ATTRIBUTE_RING_FLAGS:
+			rb.flags = nla_get_u32(iter);
+			break;
+		case LOGGER_ATTRIBUTE_LOG_TIME_INTVAL:
+			nla_get_u32(iter);
+			break;
+		case LOGGER_ATTRIBUTE_LOG_MIN_DATA_SIZE:
+			nla_get_u32(iter);
+			break;
+		case LOGGER_ATTRIBUTE_RING_NAME:
+			strscpy(rb.name, nla_data(iter), sizeof(rb.name));
+			break;
+		default:
+			AICWFDBG(LOGERROR, "%s(%d), Unknown type: %d\n", __func__, __LINE__,
+				 type);
+			return -EINVAL;
+		}
+	}
+
+	ret = -EINVAL;
+	for (i = 0; i < ARRAY_SIZE(ring_buffer); i++) {
+		if (strcmp(rb.name, ring_buffer[i].name) == 0) {
+			ret = 0;
+			break;
+		}
+	}
+
+	/* TODO
+	 * Add handle in the future
+	 */
+
+	return ret;
+}
+
+static int aicwf_vendor_logger_get_ring_data(struct wiphy *wiphy,
+					     struct wireless_dev *wdev,
+					     const void *data, int len)
+{
+	int ret = 0, rem, type, i;
+	const struct nlattr *iter;
+	struct wifi_ring_buffer_status rb = {};
+
+	nla_for_each_attr(iter, data, len, rem) {
+		type = nla_type(iter);
+		switch (type) {
+		case LOGGER_ATTRIBUTE_RING_NAME:
+			strscpy(rb.name, nla_data(iter), sizeof(rb.name));
+			break;
+		default:
+			pr_err("%s(%d), Unknown type: %d\n", __func__, __LINE__, type);
+			return -EINVAL;
+		}
+	}
+
+	ret = -EINVAL;
+	for (i = 0; i < ARRAY_SIZE(ring_buffer); i++) {
+		if (strcmp(rb.name, ring_buffer[i].name) == 0) {
+			ret = 0;
+			break;
+		}
+	}
+
+	/* TODO
+	 * Add handle in the future
+	 */
+
+	return ret;
+}
+
+static int aicwf_vendor_logger_get_wake_reason_stats(struct wiphy *wiphy,
+						     struct wireless_dev *wdev,
+						     const void *data, int len)
+{
+	int ret;
+	struct sk_buff *reply;
+	u32 payload;
+
+	payload = sizeof(wake_reason_cnt.total_cmd_event_wake);
+	reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, payload);
+
+	if (!reply)
+		return -ENOMEM;
+
+	/* TODO
+	 * Add handle in the future
+	 */
+	if (nla_put_u32(reply, WAKE_STAT_ATTRIBUTE_TOTAL_CMD_EVENT,
+			wake_reason_cnt.total_cmd_event_wake))
+		goto out_put_fail;
+
+	ret = cfg80211_vendor_cmd_reply(reply);
+	if (ret)
+		wiphy_err(wiphy, "reply cmd error\n");
+
+	return ret;
+
+out_put_fail:
+	kfree_skb(reply);
+	return -EMSGSIZE;
+}
+
+static int aicwf_vendor_logger_get_tx_pkt_fates(struct wiphy *wiphy,
+						struct wireless_dev *wdev,
+						const void *data, int len)
+{
+	/* TODO
+	 * Add handle in the future
+	 */
+	return 0;
+}
+
+static int aicwf_vendor_logger_get_rx_pkt_fates(struct wiphy *wiphy,
+						struct wireless_dev *wdev,
+						const void *data, int len)
+{
+	/* TODO
+	 * Add handle in the future
+	 */
+	return 0;
+}
+
+#ifdef AICWF_LATENCY_MODE
+static int aicwf_vendor_subcmd_set_latency_mode(struct wiphy *wiphy,
+						struct wireless_dev *wdev,
+						const void *data, int len)
+{
+	int err = 0, rem, type;
+	u32 latency_mode;
+	const struct nlattr *iter;
+#ifndef CONFIG_AIC8800_AUTO_POWERSAVE
+	struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+#endif
+
+	nla_for_each_attr(iter, data, len, rem) {
+		type = nla_type(iter);
+		switch (type) {
+		case ANDR_WIFI_ATTRIBUTE_LATENCY_MODE:
+			latency_mode = nla_get_u32(iter);
+			pr_info("%s,Setting latency mode %d\n", __func__, latency_mode);
+			#ifdef AICWF_SDIO_SUPPORT
+#ifndef CONFIG_AIC8800_AUTO_POWERSAVE
+			if (latency_mode)
+				rwnx_send_me_set_lp_level(rwnx_hw, 0, 1);
+			else
+				rwnx_send_me_set_lp_level(rwnx_hw, 1, 0);
+#endif
+			#endif
+			break;
+		default:
+			pr_err("%s(%d), Unknown type: %d\n", __func__, __LINE__, type);
+			return err;
+		}
+	}
+
+	return err;
+}
+#endif
+
+static int aicwf_vendor_logger_start_pkt_fate_monitoring(struct wiphy *wiphy,
+							 struct wireless_dev *wdev,
+							 const void *data, int len)
+{
+	/* TODO
+	 * Add handle in the future
+	 */
+	return 0;
+}
+
+static int aicwf_vendor_apf_subcmd_get_capabilities(struct wiphy *wiphy,
+						    struct wireless_dev *wdev,
+						    const void *data, int len)
+{
+	int ver, max_len;
+	int ret = 0;
+	struct sk_buff *skb = NULL;
+
+	/* APF version */
+	ver = 4;
+	/* APF memory size limit */
+	max_len = 2048;
+
+	skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, max_len);
+
+	ret = nla_put_u32(skb, APF_ATTRIBUTE_VERSION, ver);
+	if (ret < 0) {
+		AICWFDBG(LOGERROR, "Failed to put APF_ATTRIBUTE_VERSION, ret:%d\n",
+			 ret);
+		goto fail;
+	}
+	ret = nla_put_u32(skb, APF_ATTRIBUTE_MAX_LEN, max_len);
+	if (ret < 0) {
+		AICWFDBG(LOGERROR, "Failed to put APF_ATTRIBUTE_MAX_LEN, ret:%d\n",
+			 ret);
+		goto fail;
+	}
+
+	ret = cfg80211_vendor_cmd_reply(skb);
+	if (unlikely(ret))
+		AICWFDBG(LOGERROR, "vendor command reply failed, ret=%d\n", ret);
+
+fail:
+	/* TODO
+	 * Add handle in the future
+	 */
+	return ret;
+}
+
+static int aicwf_vendor_sub_cmd_set_mac(struct wiphy *wiphy,
+					struct wireless_dev *wdev,
+					const void *data, int len)
+{
+	int ret = 0, rem, type;
+	const struct nlattr *iter;
+	u8 mac[ETH_ALEN];
+
+	nla_for_each_attr(iter, data, len, rem) {
+		type = nla_type(iter);
+		switch (type) {
+		case WIFI_VENDOR_ATTR_DRIVER_MAC_ADDR:
+			memcpy(mac, nla_data(iter), ETH_ALEN);
+			pr_info("%s, %02X:%02X:%02X:%02X:%02X:%02X\n", __func__, mac[0],
+				mac[1], mac[2], mac[3], mac[4], mac[5]);
+			break;
+		default:
+			pr_err("%s(%d), Unknown type: %d\n", __func__, __LINE__, type);
+			return -EINVAL;
+		}
+	}
+
+	/* TODO
+	 * Add handle in the future
+	 */
+
+	return ret;
+}
+
+static const struct nla_policy
+	aicwf_cfg80211_mkeep_alive_policy[MKEEP_ALIVE_ATTRIBUTE_MAX + 1] = {
+		[0] = {.type = NLA_UNSPEC},
+		[MKEEP_ALIVE_ATTRIBUTE_ID] = {.type = NLA_U8},
+		[MKEEP_ALIVE_ATTRIBUTE_IP_PKT] = {.type = NLA_MSECS},
+		[MKEEP_ALIVE_ATTRIBUTE_IP_PKT_LEN] = {.type = NLA_U16},
+		[MKEEP_ALIVE_ATTRIBUTE_SRC_MAC_ADDR] = {.type = NLA_MSECS,
+							.len = ETH_ALEN},
+		[MKEEP_ALIVE_ATTRIBUTE_DST_MAC_ADDR] = {.type = NLA_MSECS,
+							.len = ETH_ALEN},
+		[MKEEP_ALIVE_ATTRIBUTE_PERIOD_MSEC] = {.type = NLA_U32},
+};
+
+static const struct nla_policy
+	aicwf_cfg80211_logger_policy[LOGGER_ATTRIBUTE_MAX + 1] = {
+		[0] = {.type = NLA_UNSPEC},
+		[LOGGER_ATTRIBUTE_DRIVER_VER] = {.type = NLA_BINARY},
+		[LOGGER_ATTRIBUTE_FW_VER] = {.type = NLA_BINARY},
+		[LOGGER_ATTRIBUTE_LOG_LEVEL] = {.type = NLA_U32},
+		[LOGGER_ATTRIBUTE_RING_FLAGS] = {.type = NLA_U32},
+		[LOGGER_ATTRIBUTE_LOG_TIME_INTVAL] = {.type = NLA_U32},
+		[LOGGER_ATTRIBUTE_LOG_MIN_DATA_SIZE] = {.type = NLA_U32},
+		[LOGGER_ATTRIBUTE_RING_NAME] = {.type = NLA_STRING},
+};
+
+static const struct nla_policy
+	aicwf_cfg80211_subcmd_policy[GSCAN_ATTRIBUTE_MAX + 1] = {
+		[0] = {.type = NLA_UNSPEC},
+		[GSCAN_ATTRIBUTE_BAND] = {.type = NLA_U32},
+};
+
+static const struct nla_policy
+	aicwf_cfg80211_andr_wifi_policy[ANDR_WIFI_ATTRIBUTE_MAX + 1] = {
+		[0] = {.type = NLA_UNSPEC},
+		[ANDR_WIFI_ATTRIBUTE_COUNTRY] = {.type = NLA_STRING},
+};
+
+static const struct nla_policy
+	aicwf_cfg80211_subcmd_set_mac_policy[WIFI_VENDOR_ATTR_DRIVER_MAX + 1] = {
+		[0] = {.type = NLA_UNSPEC},
+		[WIFI_VENDOR_ATTR_DRIVER_MAC_ADDR] = {.type = NLA_MSECS,
+						      .len = ETH_ALEN},
+};
+
+static int aicwf_dump_interface(struct wiphy *wiphy, struct wireless_dev *wdev,
+				struct sk_buff *skb, const void *data,
+				int data_len, unsigned long *storage)
+{
+	return 0;
+}
+
+const struct wiphy_vendor_command aicwf_vendor_cmd[] = {
+	{{.vendor_id = GOOGLE_OUI, .subcmd = WIFI_OFFLOAD_SUBCMD_START_MKEEP_ALIVE},
+	 .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+	 .doit = aicwf_vendor_start_mkeep_alive,
+	 .dumpit = aicwf_dump_interface,
+	 .policy = aicwf_cfg80211_mkeep_alive_policy,
+	 .maxattr = MKEEP_ALIVE_ATTRIBUTE_MAX
+	},
+	{{.vendor_id = GOOGLE_OUI, .subcmd = WIFI_OFFLOAD_SUBCMD_STOP_MKEEP_ALIVE},
+	 .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+	 .doit = aicwf_vendor_stop_mkeep_alive,
+	 .dumpit = aicwf_dump_interface,
+	 .policy = aicwf_cfg80211_mkeep_alive_policy,
+	 .maxattr = MKEEP_ALIVE_ATTRIBUTE_MAX
+	},
+	{{.vendor_id = GOOGLE_OUI, .subcmd = LOGGER_GET_VER},
+	 .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+	 .doit = aicwf_vendor_get_ver,
+	 .dumpit = aicwf_dump_interface,
+	 .policy = aicwf_cfg80211_logger_policy,
+	 .maxattr = LOGGER_ATTRIBUTE_MAX
+	},
+	{{.vendor_id = GOOGLE_OUI, .subcmd = GSCAN_SUBCMD_GET_CHANNEL_LIST},
+	 .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+	 .doit = aicwf_vendor_subcmd_get_channel_list,
+	 .dumpit = aicwf_dump_interface,
+	 .policy = aicwf_cfg80211_subcmd_policy,
+	 .maxattr = GSCAN_ATTRIBUTE_MAX
+	},
+	{{.vendor_id = GOOGLE_OUI, .subcmd = WIFI_SUBCMD_SET_COUNTRY_CODE},
+	 .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+	 .doit = aicwf_vendor_subcmd_set_country_code,
+	 .dumpit = aicwf_dump_interface,
+	 .policy = aicwf_cfg80211_andr_wifi_policy,
+	 .maxattr = ANDR_WIFI_ATTRIBUTE_MAX
+	},
+	{
+		{.vendor_id = GOOGLE_OUI, .subcmd = LOGGER_TRIGGER_MEM_DUMP},
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+		.doit = aicwf_vendor_logger_trigger_memory_dump,
+		.dumpit = aicwf_dump_interface,
+		.policy = VENDOR_CMD_RAW_DATA,
+	},
+	{
+		{.vendor_id = GOOGLE_OUI, .subcmd = WIFI_SUBCMD_GET_FEATURE_SET},
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+		.doit = aicwf_vendor_subcmd_get_feature_set,
+		.dumpit = aicwf_dump_interface,
+		.policy = VENDOR_CMD_RAW_DATA,
+	},
+	{
+		{.vendor_id = GOOGLE_OUI, .subcmd = LOGGER_GET_FEATURE},
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+		.doit = aicwf_vendor_logger_get_feature,
+		.dumpit = aicwf_dump_interface,
+		.policy = VENDOR_CMD_RAW_DATA,
+	},
+	{
+		{.vendor_id = GOOGLE_OUI, .subcmd = LOGGER_GET_RING_STATUS},
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+		.doit = aicwf_vendor_logger_get_ring_status,
+		.dumpit = aicwf_dump_interface,
+		.policy = VENDOR_CMD_RAW_DATA,
+	},
+	{{.vendor_id = GOOGLE_OUI, .subcmd = LOGGER_START_LOGGING},
+	 .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+	 .doit = aicwf_vendor_logger_start_logging,
+	 .dumpit = aicwf_dump_interface,
+	 .policy = aicwf_cfg80211_logger_policy,
+	 .maxattr = LOGGER_ATTRIBUTE_MAX
+	},
+	{{.vendor_id = GOOGLE_OUI, .subcmd = LOGGER_GET_RING_DATA},
+	 .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+	 .doit = aicwf_vendor_logger_get_ring_data,
+	 .dumpit = aicwf_dump_interface,
+	 .policy = aicwf_cfg80211_logger_policy,
+	 .maxattr = LOGGER_ATTRIBUTE_MAX
+	},
+	{
+		{.vendor_id = GOOGLE_OUI, .subcmd = LOGGER_GET_WAKE_REASON_STATS},
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+		.doit = aicwf_vendor_logger_get_wake_reason_stats,
+		.dumpit = aicwf_dump_interface,
+		.policy = VENDOR_CMD_RAW_DATA,
+	},
+	{
+		{.vendor_id = GOOGLE_OUI, .subcmd = APF_SUBCMD_GET_CAPABILITIES},
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+		.doit = aicwf_vendor_apf_subcmd_get_capabilities,
+		.dumpit = aicwf_dump_interface,
+		.policy = VENDOR_CMD_RAW_DATA,
+	},
+	{
+		{.vendor_id = GOOGLE_OUI, .subcmd = LOGGER_START_PKT_FATE_MONITORING},
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+		.doit = aicwf_vendor_logger_start_pkt_fate_monitoring,
+		.dumpit = aicwf_dump_interface,
+		.policy = VENDOR_CMD_RAW_DATA,
+	},
+	{
+		{.vendor_id = GOOGLE_OUI, .subcmd = LOGGER_GET_TX_PKT_FATES},
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+		.doit = aicwf_vendor_logger_get_tx_pkt_fates,
+		.dumpit = aicwf_dump_interface,
+		.policy = VENDOR_CMD_RAW_DATA,
+	},
+	{
+		{.vendor_id = GOOGLE_OUI, .subcmd = LOGGER_GET_RX_PKT_FATES},
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+		.doit = aicwf_vendor_logger_get_rx_pkt_fates,
+		.dumpit = aicwf_dump_interface,
+		.policy = VENDOR_CMD_RAW_DATA,
+	},
+	{
+		{.vendor_id = GOOGLE_OUI, .subcmd = VENDOR_NL80211_SUBCMD_SET_MAC},
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_RUNNING,
+		.doit = aicwf_vendor_sub_cmd_set_mac,
+		.dumpit = aicwf_dump_interface,
+		.policy = aicwf_cfg80211_subcmd_set_mac_policy,
+		.maxattr = WIFI_VENDOR_ATTR_DRIVER_MAX,
+	},
+	{
+		{.vendor_id = BRCM_OUI, .subcmd = VENDOR_NL80211_SUBCMD_SET_MAC},
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_RUNNING,
+		.doit = aicwf_vendor_sub_cmd_set_mac,
+		.dumpit = aicwf_dump_interface,
+		.policy = aicwf_cfg80211_subcmd_set_mac_policy,
+		.maxattr = WIFI_VENDOR_ATTR_DRIVER_MAX,
+	},
+
+#ifdef AICWF_LATENCY_MODE
+	{
+		{
+			.vendor_id = GOOGLE_OUI,
+			.subcmd = WIFI_SUBCMD_SET_LATENCY_MODE
+		},
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+		.doit = aicwf_vendor_subcmd_set_latency_mode,
+		.policy = aicwf_cfg80211_andr_wifi_policy,
+		.maxattr = ANDR_WIFI_ATTRIBUTE_MAX
+	},
+#endif
+};
+
+static const struct nl80211_vendor_cmd_info aicwf_vendor_events[] = {};
+
+int aicwf_vendor_init(struct wiphy *wiphy)
+{
+	wiphy->vendor_commands = aicwf_vendor_cmd;
+	wiphy->n_vendor_commands = ARRAY_SIZE(aicwf_vendor_cmd);
+	wiphy->vendor_events = aicwf_vendor_events;
+	wiphy->n_vendor_events = ARRAY_SIZE(aicwf_vendor_events);
+	return 0;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aic_vendor.h b/drivers/net/wireless/aic/aic8800_fdrv/aic_vendor.h
new file mode 100644
index 0000000000000..fd0851e8f75a3
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aic_vendor.h
@@ -0,0 +1,370 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AIC_VENDOR_H
+#define _AIC_VENDOR_H
+
+#include <linux/types.h>
+#include "rwnx_defs.h"
+
+#define GOOGLE_OUI 0x001A11
+#define BRCM_OUI   0x001018
+
+enum get_cmd_type {
+	START_MKEEP_ALIVE,
+	STOP_MKEEP_ALIVE,
+};
+
+enum android_vendor_sub_command {
+	/* don't use 0 as a valid subcommand */
+	VENDOR_NL80211_SUBCMD_UNSPECIFIED,
+
+	/* define all vendor startup commands between 0x0 and 0x0FFF */
+	VENDOR_NL80211_SUBCMD_RANGE_START = 0x0001,
+	VENDOR_NL80211_SUBCMD_RANGE_END = 0x0FFF,
+
+	/* define all GScan related commands between 0x1000 and 0x10FF */
+	ANDROID_NL80211_SUBCMD_GSCAN_RANGE_START = 0x1000,
+	ANDROID_NL80211_SUBCMD_GSCAN_RANGE_END = 0x10FF,
+
+	/* define all NearbyDiscovery related commands between 0x1100 and 0x11FF */
+	ANDROID_NL80211_SUBCMD_NBD_RANGE_START = 0x1100,
+	ANDROID_NL80211_SUBCMD_NBD_RANGE_END = 0x11FF,
+
+	/* define all RTT related commands between 0x1100 and 0x11FF */
+	ANDROID_NL80211_SUBCMD_RTT_RANGE_START = 0x1100,
+	ANDROID_NL80211_SUBCMD_RTT_RANGE_END = 0x11FF,
+
+	ANDROID_NL80211_SUBCMD_LSTATS_RANGE_START = 0x1200,
+	ANDROID_NL80211_SUBCMD_LSTATS_RANGE_END = 0x12FF,
+
+	/* define all Logger related commands between 0x1400 and 0x14FF */
+	ANDROID_NL80211_SUBCMD_DEBUG_RANGE_START = 0x1400,
+	ANDROID_NL80211_SUBCMD_DEBUG_RANGE_END = 0x14FF,
+
+	/* define all wifi offload related commands between 0x1600 and 0x16FF */
+	ANDROID_NL80211_SUBCMD_WIFI_OFFLOAD_RANGE_START = 0x1600,
+	ANDROID_NL80211_SUBCMD_WIFI_OFFLOAD_RANGE_END = 0x16FF,
+
+	/* define all NAN related commands between 0x1700 and 0x17FF */
+	ANDROID_NL80211_SUBCMD_NAN_RANGE_START = 0x1700,
+	ANDROID_NL80211_SUBCMD_NAN_RANGE_END = 0x17FF,
+
+	/* define all Android Packet Filter related commands between 0x1800 and
+	 *  0x18FF
+	 */
+	ANDROID_NL80211_SUBCMD_PKT_FILTER_RANGE_START = 0x1800,
+	ANDROID_NL80211_SUBCMD_PKT_FILTER_RANGE_END = 0x18FF,
+
+	/* This is reserved for future usage */
+
+};
+
+enum wifi_offload_sub_command {
+	WIFI_OFFLOAD_SUBCMD_START_MKEEP_ALIVE =
+		ANDROID_NL80211_SUBCMD_WIFI_OFFLOAD_RANGE_START,
+	WIFI_OFFLOAD_SUBCMD_STOP_MKEEP_ALIVE,
+};
+
+enum mkeep_alive_attributes {
+	MKEEP_ALIVE_ATTRIBUTE_ID = 0x1,
+	MKEEP_ALIVE_ATTRIBUTE_IP_PKT,
+	MKEEP_ALIVE_ATTRIBUTE_IP_PKT_LEN,
+	MKEEP_ALIVE_ATTRIBUTE_SRC_MAC_ADDR,
+	MKEEP_ALIVE_ATTRIBUTE_DST_MAC_ADDR,
+	MKEEP_ALIVE_ATTRIBUTE_PERIOD_MSEC,
+	MKEEP_ALIVE_ATTRIBUTE_AFTER_LAST,
+	MKEEP_ALIVE_ATTRIBUTE_MAX = MKEEP_ALIVE_ATTRIBUTE_AFTER_LAST - 1
+};
+
+enum debug_sub_command {
+	LOGGER_START_LOGGING = ANDROID_NL80211_SUBCMD_DEBUG_RANGE_START,
+	LOGGER_TRIGGER_MEM_DUMP,
+	LOGGER_GET_MEM_DUMP,
+	LOGGER_GET_VER,
+	LOGGER_GET_RING_STATUS,
+	LOGGER_GET_RING_DATA,
+	LOGGER_GET_FEATURE,
+	LOGGER_RESET_LOGGING,
+	LOGGER_TRIGGER_DRIVER_MEM_DUMP,
+	LOGGER_GET_DRIVER_MEM_DUMP,
+	LOGGER_START_PKT_FATE_MONITORING,
+	LOGGER_GET_TX_PKT_FATES,
+	LOGGER_GET_RX_PKT_FATES,
+	LOGGER_GET_WAKE_REASON_STATS,
+	LOGGER_DEBUG_GET_DUMP,
+	LOGGER_FILE_DUMP_DONE_IND,
+	LOGGER_SET_HAL_START,
+	LOGGER_HAL_STOP,
+	LOGGER_SET_HAL_PID,
+};
+
+enum logger_attributes {
+	LOGGER_ATTRIBUTE_INVALID = 0,
+	LOGGER_ATTRIBUTE_DRIVER_VER,
+	LOGGER_ATTRIBUTE_FW_VER,
+	LOGGER_ATTRIBUTE_RING_ID,
+	LOGGER_ATTRIBUTE_RING_NAME,
+	LOGGER_ATTRIBUTE_RING_FLAGS,
+	LOGGER_ATTRIBUTE_LOG_LEVEL,
+	LOGGER_ATTRIBUTE_LOG_TIME_INTVAL,
+	LOGGER_ATTRIBUTE_LOG_MIN_DATA_SIZE,
+	LOGGER_ATTRIBUTE_FW_DUMP_LEN,
+	LOGGER_ATTRIBUTE_FW_DUMP_DATA,
+	// LOGGER_ATTRIBUTE_FW_ERR_CODE,
+	LOGGER_ATTRIBUTE_RING_DATA,
+	LOGGER_ATTRIBUTE_RING_STATUS,
+	LOGGER_ATTRIBUTE_RING_NUM,
+	LOGGER_ATTRIBUTE_DRIVER_DUMP_LEN,
+	LOGGER_ATTRIBUTE_DRIVER_DUMP_DATA,
+	LOGGER_ATTRIBUTE_PKT_FATE_NUM,
+	LOGGER_ATTRIBUTE_PKT_FATE_DATA,
+	LOGGER_ATTRIBUTE_AFTER_LAST,
+	LOGGER_ATTRIBUTE_MAX = LOGGER_ATTRIBUTE_AFTER_LAST - 1,
+};
+
+enum wifi_sub_command {
+	GSCAN_SUBCMD_GET_CAPABILITIES = ANDROID_NL80211_SUBCMD_GSCAN_RANGE_START,
+	GSCAN_SUBCMD_SET_CONFIG,                    /* 0x1001 */
+	GSCAN_SUBCMD_SET_SCAN_CONFIG,               /* 0x1002 */
+	GSCAN_SUBCMD_ENABLE_GSCAN,                  /* 0x1003 */
+	GSCAN_SUBCMD_GET_SCAN_RESULTS,              /* 0x1004 */
+	GSCAN_SUBCMD_SCAN_RESULTS,                  /* 0x1005 */
+	GSCAN_SUBCMD_SET_HOTLIST,                   /* 0x1006 */
+	GSCAN_SUBCMD_SET_SIGNIFICANT_CHANGE_CONFIG, /* 0x1007 */
+	GSCAN_SUBCMD_ENABLE_FULL_SCAN_RESULTS,      /* 0x1008 */
+	GSCAN_SUBCMD_GET_CHANNEL_LIST,              /* 0x1009 */
+	WIFI_SUBCMD_GET_FEATURE_SET,                /* 0x100A */
+	WIFI_SUBCMD_GET_FEATURE_SET_MATRIX,         /* 0x100B */
+	WIFI_SUBCMD_SET_PNO_RANDOM_MAC_OUI,         /* 0x100C */
+	WIFI_SUBCMD_NODFS_SET,                      /* 0x100D */
+	WIFI_SUBCMD_SET_COUNTRY_CODE,               /* 0x100E */
+	/* Add more sub commands here */
+	GSCAN_SUBCMD_SET_EPNO_SSID,      /* 0x100F */
+	WIFI_SUBCMD_SET_SSID_WHITE_LIST, /* 0x1010 */
+	WIFI_SUBCMD_SET_ROAM_PARAMS,     /* 0x1011 */
+	WIFI_SUBCMD_ENABLE_LAZY_ROAM,    /* 0x1012 */
+	WIFI_SUBCMD_SET_BSSID_PREF,      /* 0x1013 */
+	WIFI_SUBCMD_SET_BSSID_BLACKLIST, /* 0x1014 */
+	GSCAN_SUBCMD_ANQPO_CONFIG,       /* 0x1015 */
+	WIFI_SUBCMD_SET_RSSI_MONITOR,    /* 0x1016 */
+	WIFI_SUBCMD_CONFIG_ND_OFFLOAD,   /* 0x1017 */
+	WIFI_SUBCMD_SET_LATENCY_MODE,    /* 0x1018 */
+	/* Add more sub commands here */
+	GSCAN_SUBCMD_MAX,
+	APF_SUBCMD_GET_CAPABILITIES = ANDROID_NL80211_SUBCMD_PKT_FILTER_RANGE_START,
+	APF_SUBCMD_SET_FILTER,
+};
+
+enum gscan_attributes {
+	GSCAN_ATTRIBUTE_NUM_BUCKETS = 10,
+	GSCAN_ATTRIBUTE_BASE_PERIOD,
+	GSCAN_ATTRIBUTE_BUCKETS_BAND,
+	GSCAN_ATTRIBUTE_BUCKET_ID,
+	GSCAN_ATTRIBUTE_BUCKET_PERIOD,
+	GSCAN_ATTRIBUTE_BUCKET_NUM_CHANNELS,
+	GSCAN_ATTRIBUTE_BUCKET_CHANNELS,
+	GSCAN_ATTRIBUTE_NUM_AP_PER_SCAN,
+	GSCAN_ATTRIBUTE_REPORT_THRESHOLD,
+	GSCAN_ATTRIBUTE_NUM_SCANS_TO_CACHE,
+	GSCAN_ATTRIBUTE_BAND = GSCAN_ATTRIBUTE_BUCKETS_BAND,
+
+	GSCAN_ATTRIBUTE_ENABLE_FEATURE = 20,
+	GSCAN_ATTRIBUTE_SCAN_RESULTS_COMPLETE, /* indicates no more results */
+	GSCAN_ATTRIBUTE_FLUSH_FEATURE,         /* Flush all the configs */
+	GSCAN_ENABLE_FULL_SCAN_RESULTS,
+	GSCAN_ATTRIBUTE_REPORT_EVENTS,
+
+	/* remaining reserved for additional attributes */
+	GSCAN_ATTRIBUTE_NUM_OF_RESULTS = 30,
+	GSCAN_ATTRIBUTE_FLUSH_RESULTS,
+	GSCAN_ATTRIBUTE_SCAN_RESULTS, /* flat array of wifi_scan_result */
+	GSCAN_ATTRIBUTE_SCAN_ID,      /* indicates scan number */
+	GSCAN_ATTRIBUTE_SCAN_FLAGS,   /* indicates if scan was aborted */
+	GSCAN_ATTRIBUTE_AP_FLAGS,     /* flags on significant change event */
+	GSCAN_ATTRIBUTE_NUM_CHANNELS,
+	GSCAN_ATTRIBUTE_CHANNEL_LIST,
+	GSCAN_ATTRIBUTE_CH_BUCKET_BITMASK,
+
+	GSCAN_ATTRIBUTE_AFTER_LAST,
+	GSCAN_ATTRIBUTE_MAX = GSCAN_ATTRIBUTE_AFTER_LAST - 1,
+};
+
+enum andr_wifi_attributes {
+	ANDR_WIFI_ATTRIBUTE_NUM_FEATURE_SET,
+	ANDR_WIFI_ATTRIBUTE_FEATURE_SET,
+	ANDR_WIFI_ATTRIBUTE_PNO_RANDOM_MAC_OUI,
+	ANDR_WIFI_ATTRIBUTE_NODFS_SET,
+	ANDR_WIFI_ATTRIBUTE_COUNTRY,
+	ANDR_WIFI_ATTRIBUTE_ND_OFFLOAD_VALUE,
+	ANDR_WIFI_ATTRIBUTE_LATENCY_MODE,
+	// Add more attribute here
+	ANDR_WIFI_ATTRIBUTE_AFTER_LAST,
+	ANDR_WIFI_ATTRIBUTE_MAX = ANDR_WIFI_ATTRIBUTE_AFTER_LAST - 1,
+};
+
+enum wifi_support_feature {
+	/* Feature enums */
+	WIFI_FEATURE_INFRA = 0x0001,          /* Basic infrastructure mode        */
+	WIFI_FEATURE_INFRA_5G = 0x0002,       /* Support for 5, GHz Band          */
+	WIFI_FEATURE_HOTSPOT = 0x0004,        /* Support for GAS/ANQP             */
+	WIFI_FEATURE_P2P = 0x0008,            /* Wifi-Direct                      */
+	WIFI_FEATURE_SOFT_AP = 0x0010,        /* Soft AP                          */
+	WIFI_FEATURE_GSCAN = 0x0020,          /* Google-Scan APIs                 */
+	WIFI_FEATURE_NAN = 0x0040,            /* Neighbor Awareness Networking    */
+	WIFI_FEATURE_D2D_RTT = 0x0080,        /* Device-to-device RTT             */
+	WIFI_FEATURE_D2AP_RTT = 0x0100,       /* Device-to-AP RTT                 */
+	WIFI_FEATURE_BATCH_SCAN = 0x0200,     /* Batched Scan (legacy)            */
+	WIFI_FEATURE_PNO = 0x0400,            /* Preferred network offload        */
+	WIFI_FEATURE_ADDITIONAL_STA = 0x0800, /* Support for two STAs             */
+	WIFI_FEATURE_TDLS = 0x1000,           /* Tunnel directed link setup       */
+	WIFI_FEATURE_TDLS_OFFCHANNEL = 0x2000, /* Support for TDLS off channel */
+	WIFI_FEATURE_EPR = 0x4000,    /* Enhanced power reporting         */
+	WIFI_FEATURE_AP_STA = 0x8000, /* Support for AP STA Concurrency   */
+	WIFI_FEATURE_LINK_LAYER_STATS = 0x10000, /* Support for Linkstats */
+	WIFI_FEATURE_LOGGER = 0x20000,       /* WiFi Logger                      */
+	WIFI_FEATURE_HAL_EPNO = 0x40000,     /* WiFi PNO enhanced                */
+	WIFI_FEATURE_RSSI_MONITOR = 0x80000, /* RSSI Monitor                     */
+	WIFI_FEATURE_MKEEP_ALIVE = 0x100000, /* WiFi mkeep_alive                 */
+	WIFI_FEATURE_CONFIG_NDO = 0x200000,  /* ND offload configure             */
+	WIFI_FEATURE_TX_TRANSMIT_POWER =
+		0x400000, /* Capture Tx transmit power levels */
+	WIFI_FEATURE_CONTROL_ROAMING =
+		0x800000, /* Enable/Disable firmware roaming  */
+	WIFI_FEATURE_IE_WHITELIST = 0x1000000, /* Support Probe IE white listing */
+	WIFI_FEATURE_SCAN_RAND =
+		0x2000000, /* Support MAC & Probe Sequence Number randomization */
+	WIFI_FEATURE_SET_LATENCY_MODE = 0x40000000,  /* Support Latency mode setting     */
+	WIFI_FEATURE_INVALID = 0xFFFFFFFF, /* Invalid Feature                  */
+};
+
+enum wifi_logger_feature {
+	WIFI_LOGGER_MEMORY_DUMP_SUPPORTED = (1 << (0)), // Memory dump of FW
+	WIFI_LOGGER_PER_PACKET_TX_RX_STATUS_SUPPORTED = (1 << (1)), // PKT status
+	WIFI_LOGGER_CONNECT_EVENT_SUPPORTED = (1 << (2)), // Connectivity event
+	WIFI_LOGGER_POWER_EVENT_SUPPORTED = (1 << (3)),   // POWER of Driver
+	WIFI_LOGGER_WAKE_LOCK_SUPPORTED = (1 << (4)),     // WAKE LOCK of Driver
+	WIFI_LOGGER_VERBOSE_SUPPORTED = (1 << (5)),       // verbose log of FW
+	WIFI_LOGGER_WATCHDOG_TIMER_SUPPORTED =
+		(1 << (6)),                                 // monitor the health of FW
+	WIFI_LOGGER_DRIVER_DUMP_SUPPORTED = (1 << (7)), // dumps driver state
+	WIFI_LOGGER_PACKET_FATE_SUPPORTED =
+		(1 << (8)), // tracks connection packets' fate
+};
+
+enum wake_stats_attributes {
+	WAKE_STAT_ATTRIBUTE_TOTAL_CMD_EVENT,
+	WAKE_STAT_ATTRIBUTE_CMD_EVENT_WAKE,
+	WAKE_STAT_ATTRIBUTE_CMD_EVENT_COUNT,
+	WAKE_STAT_ATTRIBUTE_CMD_COUNT_USED,
+	WAKE_STAT_ATTRIBUTE_TOTAL_DRIVER_FW,
+	WAKE_STAT_ATTRIBUTE_DRIVER_FW_WAKE,
+	WAKE_STAT_ATTRIBUTE_DRIVER_FW_COUNT,
+	WAKE_STAT_ATTRIBUTE_DRIVER_FW_COUNT_USED,
+	WAKE_STAT_ATTRIBUTE_TOTAL_RX_DATA_WAKE,
+	WAKE_STAT_ATTRIBUTE_RX_UNICAST_COUNT,
+	WAKE_STAT_ATTRIBUTE_RX_MULTICAST_COUNT,
+	WAKE_STAT_ATTRIBUTE_RX_BROADCAST_COUNT,
+	WAKE_STAT_ATTRIBUTE_RX_ICMP_PKT,
+	WAKE_STAT_ATTRIBUTE_RX_ICMP6_PKT,
+	WAKE_STAT_ATTRIBUTE_RX_ICMP6_RA,
+	WAKE_STAT_ATTRIBUTE_RX_ICMP6_NA,
+	WAKE_STAT_ATTRIBUTE_RX_ICMP6_NS,
+	WAKE_STAT_ATTRIBUTE_IPV4_RX_MULTICAST_ADD_CNT,
+	WAKE_STAT_ATTRIBUTE_IPV6_RX_MULTICAST_ADD_CNT,
+	WAKE_STAT_ATTRIBUTE_OTHER__RX_MULTICAST_ADD_CNT,
+	WAKE_STAT_ATTRIBUTE_RX_MULTICAST_PKT_INFO,
+	WAKE_STAT_ATTRIBUTE_AFTER_LAST,
+	WAKE_STAT_ATTRIBUTE_MAX = WAKE_STAT_ATTRIBUTE_AFTER_LAST - 1,
+};
+
+enum vendor_nl80211_subcmd {
+	/* copied from wpa_supplicant brcm definations */
+	VENDOR_NL80211_SUBCMD_UNSPEC = 0,
+	VENDOR_NL80211_SUBCMD_SET_PMK = 4,
+	VENDOR_NL80211_SUBCMD_SET_MAC = 6,
+	VENDOR_NL80211_SCMD_ACS = 9,
+	VENDOR_NL80211_SCMD_MAX = 10,
+};
+
+enum nl80211_vendor_subcmd_attributes {
+	WIFI_VENDOR_ATTR_DRIVER_CMD = 0,
+	WIFI_VENDOR_ATTR_DRIVER_KEY_PMK = 1,
+	WIFI_VENDOR_ATTR_DRIVER_MAC_ADDR = 3,
+	WIFI_VENDOR_ATTR_DRIVER_AFTER_LAST = 5,
+	WIFI_VENDOR_ATTR_DRIVER_MAX = WIFI_VENDOR_ATTR_DRIVER_AFTER_LAST - 1,
+};
+
+struct wifi_ring_buffer_status {
+	u8 name[32];
+	u32 flags;
+	int ring_id;
+	u32 ring_buffer_byte_size;
+	u32 verbose_level;
+	u32 written_bytes;
+	u32 read_bytes;
+	u32 written_records;
+};
+
+struct rx_data_cnt_details_t {
+	int rx_unicast_cnt;   /*Total rx unicast packet which woke up host */
+	int rx_multicast_cnt; /*Total rx multicast packet which woke up host */
+	int rx_broadcast_cnt; /*Total rx broadcast packet which woke up host */
+};
+
+struct rx_wake_pkt_type_classification_t {
+	int icmp_pkt;  /*wake icmp packet count */
+	int icmp6_pkt; /*wake icmp6 packet count */
+	int icmp6_ra;  /*wake icmp6 RA packet count */
+	int icmp6_na;  /*wake icmp6 NA packet count */
+	int icmp6_ns;  /*wake icmp6 NS packet count */
+				   // ToDo: Any more interesting classification to add?
+};
+
+struct rx_multicast_cnt_t {
+	int ipv4_rx_multicast_addr_cnt;  /*Rx wake packet was ipv4 multicast */
+	int ipv6_rx_multicast_addr_cnt;  /*Rx wake packet was ipv6 multicast */
+	int other_rx_multicast_addr_cnt; /*Rx wake packet was non-ipv4 and non-ipv6 */
+};
+
+struct wlan_driver_wake_reason_cnt_t {
+	int total_cmd_event_wake; /* Total count of cmd event wakes */
+	int *cmd_event_wake_cnt; /* Individual wake count array, each index a reason */
+	int cmd_event_wake_cnt_sz;   /* Max number of cmd event wake reasons */
+	int cmd_event_wake_cnt_used; /* Number of cmd event wake reasons specific to the driver */
+
+	int total_driver_fw_local_wake; /* Total count of drive/fw wakes, for local reasons */
+	int *driver_fw_local_wake_cnt;  /* Individual wake count array, each index a reason */
+	int driver_fw_local_wake_cnt_sz;   /* Max number of local driver/fw wake reasons */
+	int driver_fw_local_wake_cnt_used; /* Number of local driver/fw wake reasons
+					    * specific to the driver
+					    */
+
+	int total_rx_data_wake; /* total data rx packets, that woke up host */
+	struct rx_data_cnt_details_t rx_wake_details;
+	struct rx_wake_pkt_type_classification_t rx_wake_pkt_classification_info;
+	struct rx_multicast_cnt_t rx_multicast_wake_pkt_info;
+};
+
+struct wl_mkeep_alive_pkt {
+	u16 version;     /* Version for mkeep_alive */
+	u16 length;      /* length of fixed parameters in the structure */
+	u32 period_msec; /* high bit on means immediate send */
+	u16 len_bytes;
+	u8 keep_alive_id; /* 0 - 3 for N = 4 */
+	u8 data[];
+};
+
+int aic_dev_start_mkeep_alive(struct rwnx_hw *rwnx_hw,
+			      struct rwnx_vif *rwnx_vif, u8 mkeep_alive_id,
+			      u8 *ip_pkt, u16 ip_pkt_len, u8 *src_mac,
+			      u8 *dst_mac, u32 period_msec);
+
+int aic_dev_start_mkeep_alive(struct rwnx_hw *rwnx_hw,
+			      struct rwnx_vif *rwnx_vif, u8 mkeep_alive_id,
+			      u8 *ip_pkt, u16 ip_pkt_len, u8 *src_mac,
+			      u8 *dst_mac, u32 period_msec);
+
+int aicwf_vendor_init(struct wiphy *wiphy);
+int aic_dev_stop_mkeep_alive(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
+			     u8 mkeep_alive_id);
+
+#endif /* _AIC_VENDOR_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..39f107c029e94
--- /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_l ana_pwr;
+	u8_l 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_l *)&pwr, NULL);
+	return 0;
+}
+
+static int aic8800d80_priv_cmd_get_freq_cal(struct rwnx_hw *rwnx_hw, int argc,
+					    char *argv[], char *command)
+{
+	u8_l 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_l 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_l 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_l 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_l func = 0;
+	int res_len = 0;
+	struct dbg_rftest_cmd_cfm cfm = {{
+		0,
+	}};
+
+	if (argc > 1)
+		func = (u8_l)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_l type = (u8_l)command_strtoul(argv[2], NULL, 16);
+			u8_l chgrp = (u8_l)command_strtoul(argv[3], NULL, 16);
+			s8_l pwrofst = (u8_l)command_strtoul(argv[4], NULL, 10);
+			u8_l buf[4] = {func, type, chgrp, (u8_l)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_l func = 0;
+	int res_len = 0;
+	struct dbg_rftest_cmd_cfm cfm = {{
+		0,
+	}};
+
+	if (argc > 1)
+		func = (u8_l)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_l type = (u8_l)command_strtoul(argv[2], NULL, 16);
+			u8_l chgrp = (u8_l)command_strtoul(argv[3], NULL, 16);
+			s8_l pwrofst = (u8_l)command_strtoul(argv[4], NULL, 10);
+			u8_l buf[4] = {func, type, chgrp, (u8_l)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..3d55925f4df4e
--- /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_l ana_pwr;
+	u8_l 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_l *)&pwr, NULL);
+	return 0;
+}
+
+static int aic8800dc_priv_cmd_get_freq_cal(struct rwnx_hw *rwnx_hw, int argc,
+					   char *argv[], char *command)
+{
+	u8_l 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_l 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_l 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_l 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_l func = 0;
+	int res_len = 0;
+	struct dbg_rftest_cmd_cfm cfm = {{
+		0,
+	}};
+
+	if (argc > 1)
+		func = (u8_l)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_l chgrp = (u8_l)command_strtoul(argv[2], NULL, 16);
+			s8_l pwrofst = (u8_l)command_strtoul(argv[3], NULL, 10);
+			u8_l buf[3] = {func, chgrp, (u8_l)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_l func = 0;
+	int res_len = 0;
+	struct dbg_rftest_cmd_cfm cfm = {{
+		0,
+	}};
+
+	if (argc > 1)
+		func = (u8_l)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_l chgrp = (u8_l)command_strtoul(argv[2], NULL, 16);
+			s8_l pwrofst = (u8_l)command_strtoul(argv[3], NULL, 10);
+			u8_l buf[3] = {func, chgrp, (u8_l)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..817fca95f55b1
--- /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_l ana_pwr;
+	u8_l dig_pwr;
+	u8_l 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_l *)&pwr, NULL);
+	return 0;
+}
+
+static int aic8801_priv_cmd_get_freq_cal(struct rwnx_hw *rwnx_hw, int argc,
+					 char *argv[], char *command)
+{
+	u8_l 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_l 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_l 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_l 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_l func = 0;
+	int res_len = 0;
+	struct dbg_rftest_cmd_cfm cfm = {{
+		0,
+	}};
+
+	if (argc > 1)
+		func = (u8_l)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_l chgrp = (u8_l)command_strtoul(argv[2], NULL, 16);
+			s8_l pwrofst = (u8_l)command_strtoul(argv[3], NULL, 10);
+			u8_l buf[3] = {func, chgrp, (u8_l)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_l func = 0;
+	int res_len = 0;
+	struct dbg_rftest_cmd_cfm cfm = {{
+		0,
+	}};
+
+	if (argc > 1)
+		func = (u8_l)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_l chgrp = (u8_l)command_strtoul(argv[2], NULL, 16);
+			s8_l pwrofst = (u8_l)command_strtoul(argv[3], NULL, 10);
+			u8_l buf[3] = {func, chgrp, (u8_l)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..78d1a59cf98bb
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_ops.c
@@ -0,0 +1,267 @@
+// 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_l vid, u16_l did, u16_l *chipid)
+{
+	if (vid == SDIO_VENDOR_ID_AIC8801 && did == SDIO_DEVICE_ID_AIC8801) {
+		*chipid = PRODUCT_ID_AIC8801;
+		AICWFDBG(LOGINFO, "%s USE AIC8801\r\n", __func__);
+		return 0;
+	} else if (vid == SDIO_VENDOR_ID_AIC8800DC &&
+		   did == SDIO_DEVICE_ID_AIC8800DC) {
+		*chipid = PRODUCT_ID_AIC8800DC;
+		AICWFDBG(LOGINFO, "%s USE AIC8800DC\r\n", __func__);
+		return 0;
+	} else if (vid == SDIO_VENDOR_ID_AIC8800D80 &&
+		   did == SDIO_DEVICE_ID_AIC8800D80) {
+		*chipid = PRODUCT_ID_AIC8800D80;
+		AICWFDBG(LOGINFO, "%s USE AIC8800D80\r\n", __func__);
+		return 0;
+	}
+	return -ENODEV;
+}
+
+const struct aic_sdio_chip_hw *aic_sdio_get_props(u16_l 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)
+{
+	switch (chipid) {
+	case PRODUCT_ID_AIC8801:
+		return &aic_chip_aic8801_ops;
+	case PRODUCT_ID_AIC8800DC:
+	case PRODUCT_ID_AIC8800DW:
+		return &aic_chip_aic8800dc_ops;
+	case PRODUCT_ID_AIC8800D80:
+		return &aic_chip_aic8800d80_ops;
+	default:
+		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..126825ba11658
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_chip_ops.h
@@ -0,0 +1,186 @@
+/* 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_l 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_l vendor, u16_l device, u16_l *chipid);
+
+/* Get SDIO hardware properties for a given chip product ID */
+const struct aic_sdio_chip_hw *aic_sdio_get_props(u16_l 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_aic8801_ops;
+extern const struct aic_chip_ops aic_chip_aic8800dc_ops;
+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..5305927d0124a
--- /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..5897f2b207926
--- /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"
+
+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};
+
+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,
+};
+
+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,
+};
+
+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,
+};
+
+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};
+
+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_l *)wifi_txgain_table_24g_8800dcdw_h,
+						      128);
+			if (ret)
+				return -1;
+
+			ret = rwnx_send_rf_config_req(rwnx_hw, 16, 1,
+						      (u8_l *)wifi_txgain_table_24g_1_8800dcdw_h,
+						      128);
+			if (ret)
+				return -1;
+		} else {
+			ret = rwnx_send_rf_config_req(rwnx_hw, 0, 1,
+						      (u8_l *)wifi_txgain_table_24g_8800dcdw,
+						      128);
+			if (ret)
+				return -1;
+
+			ret = rwnx_send_rf_config_req(rwnx_hw, 16, 1,
+						      (u8_l *)wifi_txgain_table_24g_1_8800dcdw,
+						      128);
+			if (ret)
+				return -1;
+		}
+
+		ret = rwnx_send_rf_config_req(rwnx_hw, 0, 0,
+					      (u8_l *)wifi_rxgain_table_24g_20m_8800dcdw,
+					      256);
+		if (ret)
+			return -1;
+
+		ret = rwnx_send_rf_config_req(rwnx_hw, 32, 0,
+					      (u8_l *)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..1d5ff8d1c8a5c
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_genl.c
@@ -0,0 +1,932 @@
+// 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 pkt_filter_list pkt_list;
+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 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_l *value)
+{
+	u8 parsed;
+	int ret;
+
+	if (!str || !str[0])
+		return -EINVAL;
+
+	ret = kstrtou8(str, base, &parsed);
+	if (ret)
+		return ret;
+
+	*value = parsed;
+	return 0;
+}
+
+static int genl_parse_u16(const char *str, unsigned int base, u16_l *value)
+{
+	u16 parsed;
+	int ret;
+
+	if (!str || !str[0])
+		return -EINVAL;
+
+	ret = kstrtou16(str, base, &parsed);
+	if (ret)
+		return ret;
+
+	*value = parsed;
+	return 0;
+}
+
+int cmd_pktfilter_set(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+		      char *r_buf, unsigned long *r_len)
+{
+	struct pkt_filter filter = {};
+	u16_l param[256] = {0};
+	int expected_len, i, ret;
+
+	if (len < 4) {
+		genl_debug(LOG_INFO, "%s param err\n", __func__);
+		return -EINVAL;
+	}
+
+	ret = genl_parse_u16(s_buf[0], 16, &filter.id);
+	if (ret)
+		return ret;
+	ret = genl_parse_u16(s_buf[1], 16, &filter.offset);
+	if (ret)
+		return ret;
+	ret = genl_parse_u16(s_buf[2], 16, &filter.length);
+	if (ret)
+		return ret;
+
+	if (filter.id >= AICWF_PKT_FILTER_MAX || !filter.length ||
+	    filter.length > AICWF_PKT_FILTER_PATTERN_MAX)
+		return -ERANGE;
+
+	expected_len = 4 + 2 * filter.length;
+	if (len != expected_len || expected_len > ARRAY_SIZE(param))
+		return -EINVAL;
+
+	param[0] = filter.id;
+	param[1] = filter.offset;
+	param[2] = filter.length;
+	for (i = 0; i < filter.length; i++) {
+		ret = genl_parse_u8(s_buf[3 + i], 16, &filter.mask[i]);
+		if (ret)
+			return ret;
+		param[3 + i] = filter.mask[i];
+	}
+	for (i = 0; i < filter.length; i++) {
+		ret = genl_parse_u8(s_buf[3 + filter.length + i], 16,
+				    &filter.pattern[i]);
+		if (ret)
+			return ret;
+		param[3 + filter.length + i] = filter.pattern[i];
+	}
+	i = 3 + 2 * filter.length;
+	ret = genl_parse_u16(s_buf[i], 16, &filter.total_len);
+	if (ret)
+		return ret;
+	param[i] = filter.total_len;
+
+	if (filter.total_len && filter.total_len < filter.length) {
+		genl_debug(LOG_ERROR, "%s total_len param err\n", __func__);
+		return -EINVAL;
+	}
+
+	filter.enable = 1;
+	ret = rwnx_send_set_pkt_filter_req(rwnx_hw, param);
+	if (!ret)
+		pkt_list.filter[filter.id] = filter;
+
+	return ret;
+}
+
+int cmd_pktfilter_del(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+		      char *r_buf, unsigned long *r_len)
+{
+	u8_l id;
+	int ret;
+
+	if (len != 1) {
+		genl_debug(LOG_INFO, "%s param err\n", __func__);
+		return -EINVAL;
+	}
+
+	ret = genl_parse_u8(s_buf[0], 16, &id);
+	if (ret)
+		return ret;
+	if (id >= AICWF_PKT_FILTER_MAX)
+		return -ERANGE;
+
+	if (pkt_list.filter[id].length == 0) {
+		genl_debug(LOG_INFO, "%s, id does not exist\n", __func__);
+		return -ENOENT;
+	}
+
+	ret = rwnx_send_del_pkt_filter_req(rwnx_hw, id);
+	if (!ret)
+		memset(&pkt_list.filter[id], 0, sizeof(pkt_list.filter[id]));
+
+	return ret;
+}
+
+int cmd_pktfilter_delall(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+			 char *r_buf, unsigned long *r_len)
+{
+	int i, ret;
+
+	if (len)
+		return -EINVAL;
+
+	for (i = 0; i < AICWF_PKT_FILTER_MAX; i++)
+		if (pkt_list.filter[i].length)
+			break;
+
+	if (i == AICWF_PKT_FILTER_MAX)
+		return 0;
+
+	ret = rwnx_send_del_pkt_filter_req(rwnx_hw, 0xffff);
+	if (!ret)
+		memset(&pkt_list, 0, sizeof(pkt_list));
+
+	return ret;
+}
+
+int cmd_pktfilter_list(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+		       char *r_buf, unsigned long *r_len)
+{
+	u16_l num = 0;
+	size_t filter_len;
+	u8_l val;
+	int i, ret;
+
+	filter_len = sizeof(pkt_list.filter[0]);
+
+	if (len != 1 || !r_buf || !r_len) {
+		genl_debug(LOG_INFO, "%s param err\n", __func__);
+		return -EINVAL;
+	}
+
+	ret = genl_parse_u8(s_buf[0], 10, &val);
+	if (ret)
+		return ret;
+	if (val > 1)
+		return -ERANGE;
+
+	for (i = 0; i < AICWF_PKT_FILTER_MAX; i++) {
+		if (pkt_list.filter[i].length != 0 &&
+		    pkt_list.filter[i].enable == val) {
+			memcpy(r_buf + sizeof(num) + filter_len * num,
+			       &pkt_list.filter[i],
+			       filter_len);
+			num++;
+		}
+	}
+	memcpy(r_buf, &num, sizeof(num));
+	*r_len = sizeof(num) + filter_len * num;
+
+	return 0;
+}
+
+int cmd_pktfilter_enable(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+			 char *r_buf, unsigned long *r_len)
+{
+	u8_l id, val;
+	u16_l param[256] = {0};
+	int i, ret;
+
+	if (len != 2) {
+		genl_debug(LOG_INFO, "%s param err\n", __func__);
+		return -EINVAL;
+	}
+
+	ret = genl_parse_u8(s_buf[0], 16, &id);
+	if (ret)
+		return ret;
+	ret = genl_parse_u8(s_buf[1], 16, &val);
+	if (ret)
+		return ret;
+	if (id >= AICWF_PKT_FILTER_MAX || val > 1)
+		return -ERANGE;
+
+	if (pkt_list.filter[id].length == 0) {
+		genl_debug(LOG_INFO, "%s, id does not exist\n", __func__);
+		return -ENOENT;
+	}
+	if (val == 1) {
+		if (pkt_list.filter[id].enable == 1) {
+			genl_debug(LOG_INFO, "%s, id already enable\n", __func__);
+			return 0;
+		}
+		param[0] = pkt_list.filter[id].id;
+		param[1] = pkt_list.filter[id].offset;
+		param[2] = pkt_list.filter[id].length;
+		for (i = 0; i < param[2]; i++)
+			param[3 + i] = pkt_list.filter[id].mask[i];
+		for (i = 0; i < param[2]; i++)
+			param[3 + param[2] + i] = pkt_list.filter[id].pattern[i];
+		param[3 + 2 * param[2]] = pkt_list.filter[id].total_len;
+
+		ret = rwnx_send_set_pkt_filter_req(rwnx_hw, param);
+		if (!ret)
+			pkt_list.filter[id].enable = 1;
+
+	} else if (val == 0) {
+		if (pkt_list.filter[id].enable == 0) {
+			genl_debug(LOG_INFO, "%s, id already disable\n", __func__);
+			return 0;
+		}
+		ret = rwnx_send_del_pkt_filter_req(rwnx_hw, pkt_list.filter[id].id);
+		if (!ret)
+			pkt_list.filter[id].enable = 0;
+	}
+
+	return ret;
+}
+
+#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 =
+		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)
+{
+	int ret = 0;
+	u8_l buf[3];
+
+	if (len || !rwnx_hw->wiphy->regd)
+		return -EINVAL;
+
+	buf[0] = rwnx_hw->wiphy->regd->alpha2[0];
+	buf[1] = rwnx_hw->wiphy->regd->alpha2[1];
+	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;
+
+	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 {
+		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_l 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_l 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_l)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_l 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_l *)&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_l 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_l 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_l *)&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_l 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[] = {
+	{
+		.name = "pkt_filter_set",
+		.func = cmd_pktfilter_set,
+	},
+	{
+		.name = "pkt_filter_del",
+		.func = cmd_pktfilter_del,
+	},
+	{
+		.name = "pkt_filter_delall",
+		.func = cmd_pktfilter_delall,
+	},
+	{
+		.name = "pkt_filter_list",
+		.func = cmd_pktfilter_list,
+	},
+	{
+		.name = "pkt_filter_enable",
+		.func = cmd_pktfilter_enable,
+	},
+#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, sizeof(pkt_list), 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",
+				sizeof(pkt_list));
+			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", sizeof(pkt_list));
+		r_len = strlen(r_buf) + 1;
+	}
+	if (r_len > sizeof(pkt_list)) {
+		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;
+
+	memset(&pkt_list, 0, sizeof(pkt_list));
+	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);
+	memset(&pkt_list, 0, sizeof(pkt_list));
+	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..edb262e354569
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_genl.h
@@ -0,0 +1,112 @@
+/* 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")
+
+#define AICWF_PKT_FILTER_MAX         20
+#define AICWF_PKT_FILTER_PATTERN_MAX 100
+
+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)
+
+struct pkt_filter {
+	u16_l id;
+	u16_l offset;
+	u16_l length;
+	u16_l enable;
+	u8_l mask[AICWF_PKT_FILTER_PATTERN_MAX];
+	u8_l pattern[AICWF_PKT_FILTER_PATTERN_MAX];
+	u16_l total_len;
+};
+
+struct pkt_filter_list {
+	u16_l num;
+	struct pkt_filter filter[AICWF_PKT_FILTER_MAX];
+};
+
+typedef int (*func_cmd)(struct rwnx_hw *, char *[], int, char *,
+						unsigned long *);
+struct cmd_table_ops {
+	const char *name;
+	func_cmd func;
+};
+
+int cmd_pktfilter_set(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+		      char *r_buf, unsigned long *r_len);
+int cmd_pktfilter_del(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+		      char *r_buf, unsigned long *r_len);
+int cmd_pktfilter_delall(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+			 char *r_buf, unsigned long *r_len);
+int cmd_pktfilter_list(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+		       char *r_buf, unsigned long *r_len);
+int cmd_pktfilter_enable(struct rwnx_hw *rwnx_hw, char *s_buf[], int len,
+			 char *r_buf, unsigned long *r_len);
+#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..75441b78d9483
--- /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;
+
+int aic_rxbuff_num_max = 30;
+
+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..bc7a03cacd94f
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_rx_prealloc.h
@@ -0,0 +1,32 @@
+/* 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;
+};
+
+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..ef4eaf24f84df
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_sdio.c
@@ -0,0 +1,2813 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief SDIO function declarations
+ *
+ ******************************************************************************
+ */
+
+#include <linux/completion.h>
+#include <linux/debugfs.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/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 "rwnx_wakelock.h"
+
+#include "aic_bsp_export.h"
+#include "aicwf_chip_ops.h"
+
+static const struct aic_sdio_chip_hw *aic_sdio_hw;
+
+#ifdef CONFIG_GPIO_WAKEUP
+//extern int rwnx_send_me_set_lp_level(struct rwnx_hw *rwnx_hw, u8 lp_level, u8 disable_filter);
+
+#ifdef CONFIG_AIC8800_WIFI_SUSPEND_FOR_LINUX
+#include <linux/proc_fs.h>
+struct proc_dir_entry *wifi_suspend_node;
+#endif // CONFIG_AIC8800_WIFI_SUSPEND_FOR_LINUX
+
+#endif // CONFIG_GPIO_WAKEUP
+
+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
+
+#ifdef CONFIG_AIC8800_WIFI_SUSPEND_FOR_LINUX
+void rwnx_set_wifi_suspend(char onoff)
+{
+#ifndef CONFIG_AIC8800_AUTO_POWERSAVE
+	int ret = 0;
+
+	if (onoff == '0') {
+		pr_info("%s resume \r\n", __func__);
+		rwnx_send_me_set_lp_level(g_rwnx_plat->sdiodev->rwnx_hw, 0, 1);
+	} else {
+		pr_info("%s suspend \r\n", __func__);
+		ret = rwnx_send_me_set_lp_level(g_rwnx_plat->sdiodev->rwnx_hw, 1, 0);
+	}
+#endif
+}
+
+static ssize_t rwnx_wifi_suspend_write_proc(struct file *file,
+					    const char __user *buffer,
+					    size_t count, loff_t *pos)
+{
+	char onoff;
+
+	if (count < 1)
+		return -EINVAL;
+
+	if (copy_from_user(&onoff, buffer, 1))
+		return -EFAULT;
+
+	rwnx_set_wifi_suspend(onoff);
+
+	return count;
+}
+
+static const struct file_operations wifi_suspend_fops = {
+	.owner = THIS_MODULE,
+	.write = rwnx_wifi_suspend_write_proc,
+};
+
+void rwnx_init_wifi_suspend_node(void)
+{
+	struct proc_dir_entry *ent;
+
+	wifi_suspend_node = proc_mkdir("wifi_suspend", NULL);
+	if (!wifi_suspend_node)
+		pr_warn("Unable to create /proc/wifi_suspend directory");
+
+	ent = proc_create("suspend", 0660, wifi_suspend_node, &wifi_suspend_fops);
+	if (!ent)
+		pr_warn("Unable to create /proc/wifi_suspend/suspend");
+}
+
+void rwnx_deinit_wifi_suspend_node(void)
+{
+	remove_proc_entry("suspend", wifi_suspend_node);
+	remove_proc_entry("wifi_suspend", 0);
+}
+#endif // CONFIG_AIC8800_WIFI_SUSPEND_FOR_LINUX
+
+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_l value = sdiodev->tp_ctrl.cur_temp;
+	u8_l current_state = sdiodev->tp_ctrl.cur_stat;
+	s8_l thd_1 = sdiodev->tp_ctrl.tp_thd_1;
+	s8_l 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
+
+#ifdef CONFIG_GPIO_WAKEUP
+void rwnx_set_wifi_suspend(char onoff);
+#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++;
+
+	rwnx_wakeup_lock_timeout(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
+
+#ifdef CONFIG_AIC8800_WIFI_SUSPEND_FOR_LINUX
+	rwnx_init_wifi_suspend_node();
+#endif // CONFIG_AIC8800_WIFI_SUSPEND_FOR_LINUX
+#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);
+#ifdef CONFIG_AIC8800_WIFI_SUSPEND_FOR_LINUX
+	rwnx_deinit_wifi_suspend_node();
+#endif // CONFIG_AIC8800_WIFI_SUSPEND_FOR_LINUX
+}
+
+void aicwf_sdio_remove_(struct sdio_func *func)
+{
+	aicwf_sdio_remove(func);
+}
+
+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 ws_cnt = 0;
+	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
+
+	while (aicwf_wakeup_lock_status(sdiodev->rwnx_hw)) {
+		msleep(30);
+		ws_cnt += 1;
+		if (ws_cnt >= 20) {
+			sus_abort = 1;
+			break;
+		}
+	}
+	if (sus_abort) {
+#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)
+{
+	int ret;
+	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);
+#ifdef CONFIG_AIC8800_WIFI_SUSPEND_FOR_LINUX
+	rwnx_set_wifi_suspend('0');
+#endif // CONFIG_AIC8800_WIFI_SUSPEND_FOR_LINUX
+
+#ifdef CONFIG_PLATFORM_EXTERNAL
+	disable_wakeup_irq(sdiodev);
+#endif
+
+	return 0;
+}
+
+static const struct sdio_device_id aicwf_sdmmc_ids[] = {
+	{SDIO_DEVICE(SDIO_VENDOR_ID_AIC8801, SDIO_DEVICE_ID_AIC8801)},
+	{SDIO_DEVICE(SDIO_VENDOR_ID_AIC8800DC, SDIO_DEVICE_ID_AIC8800DC)},
+	{SDIO_DEVICE(SDIO_VENDOR_ID_AIC8800D80, SDIO_DEVICE_ID_AIC8800D80)},
+	{},
+};
+
+MODULE_DEVICE_TABLE(sdio, aicwf_sdmmc_ids);
+
+static const struct dev_pm_ops aicwf_sdio_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(aicwf_sdio_suspend, aicwf_sdio_resume)};
+
+#ifndef CONFIG_FDRV_NO_REG_SDIO
+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"
+
+int bustx_thread_prio = 1;
+module_param_named(bustx_thread_prio, bustx_thread_prio, int, 0644);
+// module_param(bustx_thread_prio, int, 0);
+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
+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;
+
+			rwnx_wakeup_lock(sdiodev->rwnx_hw->ws_tx);
+			if ((int)(atomic_read(&sdiodev->tx_priv->tx_pktcnt) > 0) ||
+			    sdiodev->tx_priv->cmd_txstate)
+				aicwf_sdio_tx_process(sdiodev);
+			rwnx_wakeup_unlock(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;
+			rwnx_wakeup_lock(rx_priv->sdiodev->rwnx_hw->ws_rx);
+			aicwf_process_rxframes(rx_priv);
+			rwnx_wakeup_unlock(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;
+
+			rwnx_wakeup_lock(sdiodev->rwnx_hw->ws_pwrctrl);
+
+			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);
+
+			rwnx_wakeup_unlock(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;
+
+	rwnx_wakeup_lock(sdiodev->rwnx_hw->ws_irqrx);
+
+	if (!bus_if || bus_if->state == BUS_DOWN_ST) {
+		sdio_err("bus err\n");
+		rwnx_wakeup_unlock(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__);
+		rwnx_wakeup_unlock(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);
+	}
+
+	rwnx_wakeup_unlock(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);
+	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..c0e736658404a
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_sdio.h
@@ -0,0 +1,264 @@
+/* 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>
+
+#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;
+
+#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_l 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_l cur_stat; // 0--normal temp, 1/2--buffering temp
+	s8_l tp_thd_1; // temperature threshold 1
+	s8_l 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;
+	/* 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);
+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..72a9296a327aa
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_tcp_ack.c
@@ -0,0 +1,572 @@
+// 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(struct msg_buf *msg)
+{
+	int len = sizeof(struct msg_buf);
+
+	msg =
+		kzalloc(len, /*GFP_KERNEL */ GFP_ATOMIC);
+	if (!msg)
+		pr_err("AICWF %s: alloc failed\n", __func__);
+	memset(msg, 0, len);
+	return msg;
+}
+
+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..aafa7b7c2243e
--- /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 {
+	u16 source;
+	u16 dest;
+	s32 saddr;
+	s32 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(struct msg_buf *msg);
+
+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..e3be2989aee42
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_txrxif.c
@@ -0,0 +1,753 @@
+// 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/debugfs.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_l *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/aicwf_wext_linux.c b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_wext_linux.c
new file mode 100644
index 0000000000000..0f39545971cfa
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_wext_linux.c
@@ -0,0 +1,1137 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/etherdevice.h>
+#include <linux/netdevice.h>
+#include <net/netlink.h>
+#include <linux/wireless.h>
+#include <linux/nl80211.h>
+#include <net/iw_handler.h>
+#include <uapi/linux/if_arp.h>
+#include <linux/vmalloc.h>
+#include "aicwf_debug.h"
+#include "rwnx_msg_tx.h"
+#include "aicwf_wext_linux.h"
+#include "rwnx_defs.h"
+
+#define WLAN_CAPABILITY_ESS		BIT(0)
+#define WLAN_CAPABILITY_IBSS	BIT(1)
+#define WLAN_CAPABILITY_PRIVACY BIT(4)
+
+#define IEEE80211_HT_CAP_SGI_20			0x0020
+#define IEEE80211_HT_CAP_SGI_40			0x0040
+
+#define IEEE80211_HE_CH_BW_SET_160_80P80BIT(4)
+#define IEEE80211_HE_CH_BW_SET_160_IN_5GBIT(3)
+#define IEEE80211_HE_CH_BW_SET_40_AND_80_IN_5GBIT(2)
+#define IEEE80211_HE_CH_BW_SET_40_IN_2_4GBIT(1)
+
+#if WIRELESS_EXT < 17
+	#define IW_QUAL_QUAL_INVALID   0x10
+	#define IW_QUAL_LEVEL_INVALID  0x20
+	#define IW_QUAL_NOISE_INVALID  0x40
+	#define IW_QUAL_QUAL_UPDATED   0x1
+	#define IW_QUAL_LEVEL_UPDATED  0x2
+	#define IW_QUAL_NOISE_UPDATED  0x4
+#endif
+
+#define MAX_WPA_IE_LEN (256)
+
+/*				20/40/80,	ShortGI,	MCS Rate  */
+const u16 VHT_MCS_DATA_RATE[3][2][30] = {
+	{
+		{
+			13, 26, 39, 52, 78, 104, 117, 130, 156, 156,
+			26, 52, 78, 104, 156, 208, 234, 260, 312, 312,
+			39, 78, 117, 156, 234, 312, 351, 390, 468, 520
+		},	/* Long GI, 20MHz */
+		{
+			14, 29, 43, 58, 87, 116, 130, 144, 173, 173,
+			29, 58, 87, 116, 173, 231, 260, 289, 347, 347,
+			43,	87, 130, 173, 260, 347, 390,	433,	520, 578
+		}
+	},		/* Short GI, 20MHz */
+	{
+		{
+			27, 54, 81, 108, 162, 216, 243, 270, 324, 360,
+			54, 108, 162, 216, 324, 432, 486, 540, 648, 720,
+			81, 162, 243, 324, 486, 648, 729, 810, 972, 1080
+		},	/* Long GI, 40MHz */
+		{
+			30, 60, 90, 120, 180, 240, 270, 300, 360, 400,
+			60, 120, 180, 240, 360, 480, 540, 600, 720, 800,
+			90, 180, 270, 360, 540, 720, 810, 900, 1080, 1200
+		}
+	},		/* Short GI, 40MHz */
+	{
+		{
+			59, 117,  176, 234, 351, 468, 527, 585, 702, 780,
+			117, 234, 351, 468, 702, 936, 1053, 1170, 1404, 1560,
+			176, 351, 527, 702, 1053, 1404, 1580, 1755, 2106, 2340
+		},	/* Long GI, 80MHz */
+		{
+			65, 130, 195, 260, 390, 520, 585, 650, 780, 867,
+			130, 260, 390, 520, 780, 1040, 1170, 1300, 1560, 1734,
+			195, 390, 585, 780, 1170, 1560, 1755, 1950, 2340, 2600
+		}	/* Short GI, 80MHz */
+	}
+};
+
+/*HE 20/40/80,MCS Rate  */
+const u16 HE_MCS_DATA_RATE[3][30] = {
+	{
+		9, 17, 26, 34, 52, 69, 77, 86, 103, 115,
+		129, 143, 0, 0, 0, 0, 0, 0, 0, 0,
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+	},	/* 20MHz */
+	{
+		17, 34, 52, 69, 103, 138, 155, 172, 207, 229,
+		258, 286, 0, 0, 0, 0, 0, 0, 0, 0,
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+	},	/* 40MHz */
+	{
+		36, 72,  108, 144, 216, 288, 324, 360, 432, 480,
+		540, 601, 0, 0, 0, 0, 0, 0, 0, 0,
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+	},	/* 80MHz */
+
+};
+
+#if WIRELESS_EXT >= 17
+struct iw_statistics iwstats;
+
+static struct iw_statistics *aicwf_get_wireless_stats(struct net_device *dev)
+{
+	int tmp_level = -100;
+	int tmp_qual = 0;
+	int tmp_noise = 0;
+
+	struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+	struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+	struct rwnx_sta *sta = NULL;
+
+	union rwnx_rate_ctrl_info *rate_info;
+	struct mm_get_sta_info_cfm cfm;
+
+	if (rwnx_vif->sta.ap) {
+		sta = rwnx_vif->sta.ap;
+		rwnx_send_get_sta_info_req(rwnx_hw, sta->sta_idx, &cfm);
+			rate_info = (union rwnx_rate_ctrl_info *)&cfm.rate_info;
+			tmp_level = cfm.rssi;
+	}
+
+	iwstats.qual.level = tmp_level;
+	iwstats.qual.qual = tmp_qual;
+	iwstats.qual.noise = tmp_noise;
+	iwstats.qual.updated = 0x07;
+	iwstats.qual.updated = iwstats.qual.updated | IW_QUAL_DBM;
+
+	return &iwstats;
+}
+#endif
+
+static int aicwf_get_name(struct net_device *dev,
+			  struct iw_request_info *info,
+			  union iwreq_data *wrqu, char *extra)
+{
+	struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+	struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+	struct rwnx_sta *sta = NULL;
+
+	union rwnx_rate_ctrl_info *rate_info;
+	struct mm_get_sta_info_cfm cfm;
+
+	if (rwnx_vif->sta.ap) {
+		sta = rwnx_vif->sta.ap;
+		rwnx_send_get_sta_info_req(rwnx_hw, sta->sta_idx, &cfm);
+			rate_info = (union rwnx_rate_ctrl_info *)&cfm.rate_info;
+
+			switch (rate_info->format_mod_tx) {
+			case FORMATMOD_NON_HT:
+			case FORMATMOD_NON_HT_DUP_OFDM:
+				snprintf(wrqu->name, IFNAMSIZ, "IEEE 802.11bg");
+				break;
+			case FORMATMOD_HT_MF:
+			case FORMATMOD_HT_GF:
+				snprintf(wrqu->name, IFNAMSIZ, "IEEE 802.11n");
+				break;
+			case FORMATMOD_VHT:
+				snprintf(wrqu->name, IFNAMSIZ, "IEEE 802.11ac");
+				break;
+			case FORMATMOD_HE_MU:
+			case FORMATMOD_HE_SU:
+			case FORMATMOD_HE_ER:
+				snprintf(wrqu->name, IFNAMSIZ, "IEEE 802.11ax");
+				break;
+			}
+
+	} else {
+		snprintf(wrqu->name, IFNAMSIZ, "unassociated");
+	}
+
+	return 0;
+}
+
+static int aicwf_get_freq(struct net_device *dev,
+			  struct iw_request_info *info,
+			   union iwreq_data *wrqu, char *extra)
+{
+	struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+
+	if (rwnx_vif->sta.ap) {
+		wrqu->freq.m = rwnx_vif->sta.ap->center_freq * 100000;
+		wrqu->freq.e = 1;
+		wrqu->freq.i = ieee80211_frequency_to_channel(rwnx_vif->sta.ap->center_freq);
+	} else {
+		wrqu->freq.m = 2412 * 100000;
+		wrqu->freq.e = 1;
+		wrqu->freq.i = 1;
+	}
+
+	return 0;
+}
+
+static int aicwf_get_mode(struct net_device *dev, struct iw_request_info *a,
+			  union iwreq_data *wrqu, char *b)
+{
+	wrqu->mode = IW_MODE_AUTO;
+
+	return 0;
+}
+
+static int aicwf_get_range(struct net_device *dev,
+			   struct iw_request_info *info,
+			    union iwreq_data *wrqu, char *extra)
+{
+	struct iw_range *range = (struct iw_range *)extra;
+	u16 val = 0;
+
+	wrqu->data.length = sizeof(*range);
+	memset(range, 0, sizeof(*range));
+
+	/* Let's try to keep this struct in the same order as in
+	 * linux/include/wireless.h
+	 */
+
+	/* TODO: See what values we can set, and remove the ones we can't
+	 * set, or fill them with some default data.
+	 */
+
+	/* ~5 Mb/s real (802.11b) */
+	range->throughput = 5 * 1000 * 1000;
+
+	/* TODO: Not used in 802.11b?
+	 *	range->min_nwid;	 Minimal NWID we are able to set
+	 */
+	/* TODO: Not used in 802.11b?
+	 *	range->max_nwid;	 Maximal NWID we are able to set
+	 */
+
+	/* Old Frequency (backward compat - moved lower ) */
+	/*	range->old_num_channels;
+	 *	range->old_num_frequency;
+	 *	range->old_freq[6];  Filler to keep "version" at the same offset
+	 */
+
+	/* signal level threshold range */
+
+	/* Quality of link & SNR stuff */
+	/* Quality range (link, level, noise)
+	 * If the quality is absolute, it will be in the range [0 ; max_qual],
+	 * if the quality is dBm, it will be in the range [max_qual ; 0].
+	 * Don't forget that we use 8 bit arithmetics...
+	 *
+	 * If percentage range is 0~100
+	 * Signal strength dbm range logical is -100 ~ 0
+	 * but usually value is -90 ~ -20
+	 */
+	range->max_qual.qual = 100;
+#ifdef CONFIG_SIGNAL_DISPLAY_DBM
+	range->max_qual.level = (u8)-100;
+	range->max_qual.noise = (u8)-100;
+	range->max_qual.updated = IW_QUAL_ALL_UPDATED; /* Updated all three */
+	range->max_qual.updated |= IW_QUAL_DBM;
+#else /* !CONFIG_SIGNAL_DISPLAY_DBM */
+	/* percent values between 0 and 100. */
+	range->max_qual.level = 100;
+	range->max_qual.noise = 100;
+	range->max_qual.updated = IW_QUAL_ALL_UPDATED; /* Updated all three */
+#endif /* !CONFIG_SIGNAL_DISPLAY_DBM */
+
+	/* This should contain the average/typical values of the quality
+	 * indicator. This should be the threshold between a "good" and
+	 * a "bad" link (example : monitor going from green to orange).
+	 * Currently, user space apps like quality monitors don't have any
+	 * way to calibrate the measurement. With this, they can split
+	 * the range between 0 and max_qual in different quality level
+	 * (using a geometric subdivision centered on the average).
+	 * I expect that people doing the user space apps will feedback
+	 * us on which value we need to put in each driver...
+	 */
+	range->avg_qual.qual = 92; /* > 8% missed beacons is 'bad' */
+#ifdef CONFIG_SIGNAL_DISPLAY_DBM
+	/* TODO: Find real 'good' to 'bad' threshold value for RSSI */
+	range->avg_qual.level = (u8)-70;
+	range->avg_qual.noise = 0;
+	range->avg_qual.updated = IW_QUAL_ALL_UPDATED; /* Updated all three */
+	range->avg_qual.updated |= IW_QUAL_DBM;
+#else /* !CONFIG_SIGNAL_DISPLAY_DBM */
+	/* TODO: Find real 'good' to 'bad' threshol value for RSSI */
+	range->avg_qual.level = 30;
+	range->avg_qual.noise = 100;
+	range->avg_qual.updated = IW_QUAL_ALL_UPDATED; /* Updated all three */
+#endif /* !CONFIG_SIGNAL_DISPLAY_DBM */
+
+	range->pm_capa = 0;
+
+	range->we_version_compiled = WIRELESS_EXT;
+	range->we_version_source = 16;
+
+	/*	range->retry_capa;	 What retry options are supported
+	 *	range->retry_flags;	 How to decode max/min retry limit
+	 *	range->r_time_flags;	 How to decode max/min retry life
+	 *	range->min_retry;	 Minimal number of retries
+	 *	range->max_retry;	 Maximal number of retries
+	 *	range->min_r_time;	 Minimal retry lifetime
+	 *	range->max_r_time;	 Maximal retry lifetime
+	 */
+	range->num_channels = val;
+	range->num_frequency = val;
+
+	/* Commented by Albert 2009/10/13
+	 * The following code will proivde the security capability to network manager.
+	 * If the driver doesn't provide this capability to network manager,
+	 * the WPA/WPA2 routers can't be chosen in the network manager.
+	 */
+
+	/*
+	 * #define IW_SCAN_CAPA_NONE		0x00
+	 * #define IW_SCAN_CAPA_ESSID		0x01
+	 * #define IW_SCAN_CAPA_BSSID		0x02
+	 * #define IW_SCAN_CAPA_CHANNEL	0x04
+	 * #define IW_SCAN_CAPA_MODE		0x08
+	 * #define IW_SCAN_CAPA_RATE		0x10
+	 * #define IW_SCAN_CAPA_TYPE		0x20
+	 * #define IW_SCAN_CAPA_TIME		0x40
+	 */
+
+#if WIRELESS_EXT > 17
+	range->enc_capa = IW_ENC_CAPA_WPA | IW_ENC_CAPA_WPA2 |
+			  IW_ENC_CAPA_CIPHER_TKIP | IW_ENC_CAPA_CIPHER_CCMP;
+#endif
+
+#ifdef IW_SCAN_CAPA_ESSID /* WIRELESS_EXT > 21 */
+	range->scan_capa = IW_SCAN_CAPA_ESSID | IW_SCAN_CAPA_TYPE | IW_SCAN_CAPA_BSSID |
+		   IW_SCAN_CAPA_CHANNEL | IW_SCAN_CAPA_MODE | IW_SCAN_CAPA_RATE;
+#endif
+
+	return 0;
+}
+
+static char *aicwf_get_iwe_stream_mac_addr(struct rwnx_hw *rwnx_hw,
+					   struct iw_request_info *info,
+					   struct scanu_result_wext *scan_re,
+					   char *start, char *stop,
+					   struct iw_event *iwe)
+{
+	/*  AP MAC address */
+	iwe->cmd = SIOCGIWAP;
+	iwe->u.ap_addr.sa_family = ARPHRD_ETHER;
+
+	if (scan_re->bss && &scan_re->bss->bssid[0])
+		memcpy(iwe->u.ap_addr.sa_data, scan_re->bss->bssid, ETH_ALEN);
+
+	start = iwe_stream_add_event(info, start, stop, iwe, IW_EV_ADDR_LEN);
+	return start;
+}
+
+static inline char *aicwf_get_iwe_stream_essid(struct rwnx_hw *rwnx_hw,
+					       struct iw_request_info *info,
+					       struct scanu_result_wext *scan_re,
+					       char *start, char *stop,
+					       struct iw_event *iwe)
+{
+	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)scan_re->payload;
+	const u8 *ie = mgmt->u.beacon.variable;
+	u8 *ssid;
+	int ssid_len = 0;
+
+	//get ssid len form ie
+	ssid_len = ie[1];
+
+	//get ssid form ie
+	ssid = vmalloc(sizeof(char) * (ssid_len + 1));
+	memset(ssid, 0, (ssid_len + 1));
+	memcpy(ssid, &ie[2], ssid_len);
+
+	//AICWFDBG(LOGDEBUG, "%s len:%d ssid:%s\r\n", __func__, ssid_len, ssid);
+
+	/* Add the ESSID */
+	iwe->cmd = SIOCGIWESSID;
+	iwe->u.data.flags = 1;
+	iwe->u.data.length = min_t(u16, ssid_len, 32);
+	start = iwe_stream_add_point(info, start, stop, iwe, ssid);
+
+	vfree(ssid);
+
+	return start;
+}
+
+static inline char *aicwf_get_iwe_stream_protocol(struct rwnx_hw *rwnx_hw,
+						  struct iw_request_info *info,
+						  struct scanu_result_wext *scan_re,
+						  char *start, char *stop,
+						  struct iw_event *iwe)
+{
+	u16 ht_cap = false;
+	u16 vht_cap = false;
+	u16 he_cap = false;
+	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)scan_re->payload;
+	u8 *payload = mgmt->u.beacon.variable;
+	const u8 *ie_content;
+
+	/* parsing HT_CAP_IE	 */
+	ie_content = NULL;
+	ie_content = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, payload, scan_re->ind->length);
+	if (ie_content)
+		ht_cap = true;
+
+	/* parsing VHT_CAP_IE	 */
+	ie_content = NULL;
+	ie_content = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, payload, scan_re->ind->length);
+	if (ie_content)
+		vht_cap = true;
+
+	/* parsing HE_CAP_IE	 */
+	ie_content = NULL;
+	ie_content = cfg80211_find_ie(WLAN_EID_EXTENSION, payload, scan_re->ind->length);
+	if (ie_content && ie_content[2] == WLAN_EID_EXT_HE_CAPABILITY)
+		he_cap = true;
+
+	/* Add the protocol name */
+	iwe->cmd = SIOCGIWNAME;
+
+	if (ieee80211_frequency_to_channel(scan_re->ind->center_freq) > 14) {
+		if (!he_cap) {
+			snprintf(iwe->u.name, IFNAMSIZ, "IEEE 802.11ax");
+		} else if (!vht_cap) {
+			snprintf(iwe->u.name, IFNAMSIZ, "IEEE 802.11ac");
+		} else {
+			if (!ht_cap)
+				snprintf(iwe->u.name, IFNAMSIZ, "IEEE 802.11an");
+			else
+				snprintf(iwe->u.name, IFNAMSIZ, "IEEE 802.11a");
+		}
+	} else {
+		if (!he_cap)
+			snprintf(iwe->u.name, IFNAMSIZ, "IEEE 802.11ax");
+		else if (!ht_cap)
+			snprintf(iwe->u.name, IFNAMSIZ, "IEEE 802.11bgn");
+		else
+			snprintf(iwe->u.name, IFNAMSIZ, "IEEE 802.11bg");
+	}
+
+	start = iwe_stream_add_event(info, start, stop, iwe, IW_EV_CHAR_LEN);
+	return start;
+}
+
+static inline char *aicwf_get_iwe_stream_rssi(struct rwnx_hw *rwnx_hw,
+					      struct iw_request_info *info,
+					      struct scanu_result_wext *scan_re,
+					      char *start, char *stop,
+					      struct iw_event *iwe)
+{
+	iwe->cmd = IWEVQUAL;
+
+	iwe->u.qual.updated = IW_QUAL_QUAL_UPDATED | IW_QUAL_LEVEL_UPDATED
+		| IW_QUAL_NOISE_INVALID
+		| IW_QUAL_DBM;
+
+	iwe->u.qual.level = (u8)scan_re->ind->rssi;
+	iwe->u.qual.qual = 100;
+	iwe->u.qual.noise = -92;//0;
+
+	start = iwe_stream_add_event(info, start, stop, iwe, IW_EV_QUAL_LEN);
+
+	return start;
+}
+
+static inline char *aicwf_get_iwe_stream_chan(struct rwnx_hw *rwnx_hw,
+					      struct iw_request_info *info,
+					      struct scanu_result_wext *scan_re,
+					      char *start, char *stop,
+					      struct iw_event *iwe)
+{
+	/* Add frequency/channel */
+	iwe->cmd = SIOCGIWFREQ;
+	iwe->u.freq.m = scan_re->ind->center_freq * 100000;
+	iwe->u.freq.e = 1;
+	iwe->u.freq.i = ieee80211_frequency_to_channel(scan_re->ind->center_freq);
+	start = iwe_stream_add_event(info, start, stop, iwe, IW_EV_FREQ_LEN);
+
+	return start;
+}
+
+static inline char *aicwf_get_iwe_stream_mode(struct rwnx_hw *rwnx_hw,
+					      struct iw_request_info *info,
+					      struct scanu_result_wext *scan_re,
+					      char *start, char *stop,
+					      struct iw_event *iwe)
+{
+	u16 cap = scan_re->bss->capability;
+	/* Add mode */
+	if (cap & (WLAN_CAPABILITY_IBSS | WLAN_CAPABILITY_ESS)) {
+		iwe->cmd = SIOCGIWMODE;
+		if (cap & WLAN_CAPABILITY_ESS)
+			iwe->u.mode = IW_MODE_MASTER;
+		else
+			iwe->u.mode = IW_MODE_ADHOC;
+
+		start = iwe_stream_add_event(info, start, stop, iwe, IW_EV_UINT_LEN);
+	}
+	return start;
+}
+
+static inline char *aicwf_get_iwe_stream_encryption(struct rwnx_hw *rwnx_hw,
+						    struct iw_request_info *info,
+						    struct scanu_result_wext *scan_re,
+						    char *start, char *stop,
+						    struct iw_event *iwe)
+{
+	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)scan_re->payload;
+	const u8 *ie = mgmt->u.beacon.variable;
+	u16 cap = scan_re->bss->capability;
+	u8 *ssid;
+	int ssid_len = 0;
+
+	//get ssid len form ie
+	ssid_len = ie[1];
+
+	//get ssid form ie
+	ssid = vmalloc(sizeof(char) * (ssid_len + 1));
+	memset(ssid, 0, (ssid_len + 1));
+	memcpy(ssid, &ie[2], ssid_len);
+
+	/* Add encryption capability */
+	iwe->cmd = SIOCGIWENCODE;
+	if (cap & WLAN_CAPABILITY_PRIVACY)
+		iwe->u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
+	else
+		iwe->u.data.flags = IW_ENCODE_DISABLED;
+	iwe->u.data.length = 0;
+	start = iwe_stream_add_point(info, start, stop, iwe, ssid);
+	vfree(ssid);
+
+	return start;
+}
+
+static inline char *aicwf_get_iwe_stream_rate(struct rwnx_hw *rwnx_hw,
+					      struct iw_request_info *info,
+					      struct scanu_result_wext *scan_re,
+					      char *start, char *stop,
+					      struct iw_event *iwe)
+{
+	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)scan_re->payload;
+	u8 *payload = mgmt->u.beacon.variable;
+	const u8 *ie_content;
+
+	u16 mcs_rate = 0;
+	u8 bw_40mhz = 0;
+	u8 short_gi = 0;
+	u16 max_rate = 0;
+	u8 bw_160mhz = 0;
+
+	u16 ht_cap = false;
+	struct ieee80211_ht_cap *ht_capie;
+
+	u16 vht_cap = false;
+	u8 tx_mcs_map[2];
+	u8 tx_mcs_index = 0;
+	u16 vht_data_rate = 0;
+
+	u16 he_cap = false;
+	u8 he_ch_width_set = 0;
+	u8 he_bw = 0;
+
+	/* parsing HT_CAP_IE	 */
+	ie_content = NULL;
+	ie_content = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, payload, scan_re->ind->length);
+	if (ie_content) {
+		ht_cap = true;
+		ht_capie = (struct ieee80211_ht_cap *)(ie_content + 2);
+		bw_40mhz = (ht_capie->cap_info & NL80211_CHAN_WIDTH_40) ? 1 : 0;
+		short_gi = (ht_capie->cap_info &
+			    (IEEE80211_HT_CAP_SGI_20 | IEEE80211_HT_CAP_SGI_40)) ? 1 : 0;
+		memcpy(&mcs_rate, ht_capie->mcs.rx_mask, 2);
+	}
+
+	/* parsing VHT_CAP_IE	 */
+	ie_content = NULL;
+	ie_content = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, payload, scan_re->ind->length);
+	if (ie_content) {
+		vht_cap = true;
+		bw_160mhz = ((*(u8 *)(ie_content + 2)) >> 2) & ((u8)(0xFF >> (8 - (2))));
+		if (bw_160mhz)
+			short_gi = ((*(u8 *)(ie_content + 2)) >> 6) & ((u8)(0xFF >> (8 - (1))));
+		else
+			short_gi = ((*(u8 *)(ie_content + 2)) >> 5) & ((u8)(0xFF >> (8 - (1))));
+
+		memcpy(tx_mcs_map, ((ie_content + 2) + 8), 2);
+
+		tx_mcs_index = (tx_mcs_map[0] & 0x0F) - 1;
+		if (ieee80211_frequency_to_channel(scan_re->ind->center_freq) > 14)
+			vht_data_rate = VHT_MCS_DATA_RATE[2][short_gi][tx_mcs_index];
+		else
+			vht_data_rate = VHT_MCS_DATA_RATE[1][short_gi][tx_mcs_index];
+		//TO DO:
+		//need to counter antenna number for AC
+	}
+
+	/* parsing HE_CAP_IE	 */
+	ie_content = NULL;
+	/*0xFF+len+WLAN_EID_EXTENSION+playload */
+	ie_content = cfg80211_find_ie(WLAN_EID_EXTENSION, payload, scan_re->ind->length);
+	if (ie_content && ie_content[2] == WLAN_EID_EXT_HE_CAPABILITY) {
+		he_cap = true;
+		he_ch_width_set = ie_content[8];
+		if (he_ch_width_set & IEEE80211_HE_CH_BW_SET_160_80P80)
+			he_bw = NL80211_CHAN_WIDTH_80;
+		else if (he_ch_width_set & IEEE80211_HE_CH_BW_SET_160_IN_5G)
+			he_bw = NL80211_CHAN_WIDTH_160;
+		else if (he_ch_width_set & IEEE80211_HE_CH_BW_SET_40_AND_80_IN_5G)
+			he_bw = NL80211_CHAN_WIDTH_40;
+		else if (he_ch_width_set & IEEE80211_HE_CH_BW_SET_40_IN_2_4G)
+			he_bw = NL80211_CHAN_WIDTH_40;
+		else
+			he_bw = NL80211_CHAN_WIDTH_20;
+		//TO DO:
+		//need to counter antenna number for AX
+	}
+
+	if (he_cap) {
+		if (he_bw == NL80211_CHAN_WIDTH_20)
+			max_rate = 144;
+		else if (he_bw == NL80211_CHAN_WIDTH_40)
+			max_rate = 287;
+		else if (he_bw == NL80211_CHAN_WIDTH_80)
+			max_rate = 601;
+		else if (he_bw == NL80211_CHAN_WIDTH_160)
+			max_rate = 1147;
+		max_rate = max_rate * 2;
+	} else if (vht_cap) {
+		max_rate = vht_data_rate;
+	} else if (ht_cap) {
+		if (mcs_rate & 0x8000) /* MCS15 */
+			max_rate = (bw_40mhz) ? ((short_gi) ? 300 : 270) : ((short_gi) ? 144 : 130);
+
+		else if (mcs_rate & 0x0080) {
+			/* MCS7 */
+			max_rate = (bw_40mhz) ? ((short_gi) ? 150 : 135) : ((short_gi) ? 72 : 65);
+		} else {
+			/* default MCS7 */
+			max_rate = (bw_40mhz) ? ((short_gi) ? 150 : 135) : ((short_gi) ? 72 : 65);
+		}
+	}
+
+	iwe->cmd = SIOCGIWRATE;
+	iwe->u.bitrate.disabled = 0;
+	iwe->u.bitrate.fixed = iwe->u.bitrate.disabled;
+	iwe->u.bitrate.value = max_rate * 1000000;
+	start = iwe_stream_add_event(info, start, stop, iwe, IW_EV_PARAM_LEN);
+	return start;
+}
+
+int aic_get_sec_ie(u8 *in_ie, uint in_len, u8 *rsn_ie, u16 *rsn_len, u8 *wpa_ie, u16 *wpa_len)
+{
+	u8 authmode;
+	u8 sec_idx;
+	u8 wpa_oui[4] = {0x00, 0x50, 0xf2, 0x01};
+	uint cnt = 0;
+
+	/* Search required WPA or WPA2 IE and copy to sec_ie[ ] */
+
+	cnt = 0;
+
+	sec_idx = 0;
+
+	while (cnt < in_len) {
+		authmode = in_ie[cnt];
+
+		if ((authmode == 0xdd/*_WPA_IE_ID_*/) &&
+		    (memcmp(&in_ie[cnt + 2], &wpa_oui[0], 4) == 0)) {
+			if (wpa_ie)
+				memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt + 1] + 2);
+
+			*wpa_len = in_ie[cnt + 1] + 2;
+			cnt += in_ie[cnt + 1] + 2; /* get next */
+		} else {
+			if (authmode == 0x30/*_WPA2_IE_ID_*/) {
+				if (rsn_ie)
+					memcpy(rsn_ie, &in_ie[cnt], in_ie[cnt + 1] + 2);
+
+				*rsn_len = in_ie[cnt + 1] + 2;
+				cnt += in_ie[cnt + 1] + 2; /* get next */
+			} else {
+				cnt += in_ie[cnt + 1] + 2; /* get next */
+			}
+		}
+	}
+
+	return *rsn_len + *wpa_len;
+}
+
+static inline char *aicwf_get_iwe_stream_wpa_wpa2(struct rwnx_hw *rwnx_hw,
+						  struct iw_request_info *info,
+						  struct scanu_result_wext *scan_re,
+						  char *start, char *stop,
+						  struct iw_event *iwe)
+{
+	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)scan_re->payload;
+	u8 *payload = mgmt->u.beacon.variable;
+	int buf_size = MAX_WPA_IE_LEN * 2;
+	u8 *pbuf;
+
+	u8 wpa_ie[255] = {0}, rsn_ie[255] = {0};
+	u16 i, wpa_len = 0, rsn_len = 0;
+	u8 *p;
+	int out_len = 0;
+
+	pbuf = vmalloc(sizeof(u8) * buf_size);
+
+	if (pbuf) {
+		p = pbuf;
+
+		/* parsing WPA/WPA2 IE */
+			out_len = aic_get_sec_ie(payload,
+						 scan_re->ind->length,
+						 rsn_ie, &rsn_len,
+						 wpa_ie, &wpa_len);
+
+			if (wpa_len > 0) {
+				memset(pbuf, 0, buf_size);
+				p += sprintf(p, "wpa_ie=");
+				for (i = 0; i < wpa_len; i++)
+					p += sprintf(p, "%02x", wpa_ie[i]);
+
+				if (wpa_len > 100) {
+					pr_info("-----------------Len %d----------------\n",
+						wpa_len);
+					for (i = 0; i < wpa_len; i++)
+						pr_info("%02x ", wpa_ie[i]);
+					pr_info("\n");
+					pr_info("-----------------Len %d----------------\n",
+						wpa_len);
+				}
+
+				memset(iwe, 0, sizeof(*iwe));
+				iwe->cmd = IWEVCUSTOM;
+				iwe->u.data.length = strlen(pbuf);
+				start = iwe_stream_add_point(info, start, stop, iwe, pbuf);
+
+				memset(iwe, 0, sizeof(*iwe));
+				iwe->cmd = IWEVGENIE;
+				iwe->u.data.length = wpa_len;
+				start = iwe_stream_add_point(info, start, stop, iwe, wpa_ie);
+			}
+			if (rsn_len > 0) {
+				memset(pbuf, 0, buf_size);
+				p += sprintf(p, "rsn_ie=");
+				for (i = 0; i < rsn_len; i++)
+					p += sprintf(p, "%02x", rsn_ie[i]);
+				memset(iwe, 0, sizeof(*iwe));
+				iwe->cmd = IWEVCUSTOM;
+				iwe->u.data.length = strlen(pbuf);
+				start = iwe_stream_add_point(info, start, stop, iwe, pbuf);
+
+				memset(iwe, 0, sizeof(*iwe));
+				iwe->cmd = IWEVGENIE;
+				iwe->u.data.length = rsn_len;
+				start = iwe_stream_add_point(info, start, stop, iwe, rsn_ie);
+			}
+
+		vfree(pbuf);
+	}
+	return start;
+}
+
+u8 aicwf_get_is_wps_ie(u8 *ie_ptr, uint *wps_ielen)
+{
+	u8 match = false;
+	u8 eid, wps_oui[4] = {0x0, 0x50, 0xf2, 0x04};
+
+	if (!ie_ptr)
+		return match;
+
+	eid = ie_ptr[0];
+
+	if ((eid == 0xdd/*_WPA_IE_ID_*/) && (memcmp(&ie_ptr[2], wps_oui, 4) == 0)) {
+		*wps_ielen = ie_ptr[1] + 2;
+		match = true;
+	}
+	return match;
+}
+
+static inline char *aicwf_get_iwe_stream_wps(struct rwnx_hw *rwnx_hw,
+					     struct iw_request_info *info,
+					     struct scanu_result_wext *scan_re,
+					     char *start, char *stop,
+					     struct iw_event *iwe)
+{
+	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)scan_re->payload;
+	u8 *payload = mgmt->u.beacon.variable;
+
+	/* parsing WPS IE */
+	uint cnt = 0, total_ielen;
+	u8 *wpsie_ptr = NULL;
+	uint wps_ielen = 0;
+	u8 *ie_ptr = payload;
+
+	total_ielen = scan_re->ind->length;
+
+	while (cnt < total_ielen) {
+		if (aicwf_get_is_wps_ie(&ie_ptr[cnt], &wps_ielen) && wps_ielen > 2) {
+			wpsie_ptr = &ie_ptr[cnt];
+			iwe->cmd = IWEVGENIE;
+			iwe->u.data.length = (u16)wps_ielen;
+			start = iwe_stream_add_point(info, start, stop, iwe, wpsie_ptr);
+		}
+		cnt += ie_ptr[cnt + 1] + 2; /* goto next */
+	}
+	return start;
+}
+
+static char *translate_scan(struct rwnx_hw *rwnx_hw,
+			    struct iw_request_info *info, struct scanu_result_wext *scan_re,
+		char *start, char *stop)
+{
+	struct iw_event iwe;
+
+	memset(&iwe, 0, sizeof(iwe));
+
+	if (scan_re->bss) {
+		start = aicwf_get_iwe_stream_mac_addr(rwnx_hw, info, scan_re, start, stop, &iwe);
+		start = aicwf_get_iwe_stream_essid(rwnx_hw, info, scan_re, start, stop, &iwe);
+		start = aicwf_get_iwe_stream_protocol(rwnx_hw, info, scan_re, start, stop, &iwe);
+		start = aicwf_get_iwe_stream_chan(rwnx_hw, info, scan_re, start, stop, &iwe);
+		start = aicwf_get_iwe_stream_mode(rwnx_hw, info, scan_re, start, stop, &iwe);
+		start = aicwf_get_iwe_stream_encryption(rwnx_hw, info, scan_re, start, stop, &iwe);
+		start = aicwf_get_iwe_stream_rate(rwnx_hw, info, scan_re, start, stop, &iwe);
+		start = aicwf_get_iwe_stream_wpa_wpa2(rwnx_hw, info, scan_re, start, stop, &iwe);
+		start = aicwf_get_iwe_stream_wps(rwnx_hw, info, scan_re, start, stop, &iwe);
+		start = aicwf_get_iwe_stream_rssi(rwnx_hw, info, scan_re, start, stop, &iwe);
+		return start;
+	} else {
+		return start;
+	}
+}
+
+static int aicwf_get_wap(struct net_device *dev,
+			 struct iw_request_info *info,
+			  union iwreq_data *wrqu, char *extra)
+{
+	struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+
+	wrqu->ap_addr.sa_family = ARPHRD_ETHER;
+
+	memset(wrqu->ap_addr.sa_data, 0, ETH_ALEN);
+
+	if (rwnx_vif->sta.ap)
+		memcpy(wrqu->ap_addr.sa_data, rwnx_vif->sta.ap->mac_addr, ETH_ALEN);
+	else
+		memset(wrqu->ap_addr.sa_data, 0, ETH_ALEN);
+
+	return 0;
+}
+
+static int aicwf_set_scan(struct net_device *dev, struct iw_request_info *a,
+			  union iwreq_data *wrqu, char *extra)
+{
+	int ret = 0;
+	struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+	struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+	struct cfg80211_scan_request *request;
+	int index = 0;
+	struct wiphy *wiphy = priv_to_wiphy(rwnx_hw);
+	unsigned long wext_scan_timeout;
+
+	if (!wiphy)
+		pr_err("aic_wiphy error \r\n");
+
+	if (rwnx_hw->wext_scan || rwnx_hw->scanning) {
+		AICWFDBG(LOGINFO, "is scanning, abort\n");
+		ret =  rwnx_send_scanu_cancel_req(rwnx_hw, NULL);
+		if (ret)
+			return ret;
+		msleep(150);
+	}
+
+	rwnx_hw->wext_scan = 1;
+
+	request = vmalloc(sizeof(*request));
+
+	request->n_channels = rwnx_hw->support_freqs_number;
+	request->n_ssids = 0;
+	request->no_cck = false;
+	request->ie = NULL;
+	request->ie_len = 0;
+
+	for (index = 0; index < rwnx_hw->support_freqs_number; index++) {
+		request->channels[index] = ieee80211_get_channel(wiphy,
+								 rwnx_hw->support_freqs[index]);
+		if (!request->channels[index]) {
+			AICWFDBG(LOGERROR, "%s ERROR!!! channels is NULL", __func__);
+			continue;
+		}
+	}
+
+#if WIRELESS_EXT >= 17
+		if (wrqu->data.length == sizeof(struct iw_scan_req)) {
+			struct iw_scan_req *req = (struct iw_scan_req *)extra;
+
+			if (wrqu->data.flags & IW_SCAN_THIS_ESSID) {
+				int len = min_t(int, req->essid_len, 32);
+
+				request->ssids = kmalloc_obj(*request->ssids, GFP_KERNEL);
+				if (!request->ssids) {
+					AICWFDBG(LOGERROR, "%s Failed to alloc memory for ssids",
+						 __func__);
+					return -ENOMEM;
+				}
+				memset(request->ssids, 0, sizeof(struct cfg80211_ssid));
+				memcpy(request->ssids[0].ssid, req->essid, len);
+				request->ssids[0].ssid_len = len;
+				request->n_ssids = 1;
+				AICWFDBG(LOGDEBUG, "IW_SCAN_THIS_ESSID, ssid=%s, len=%d\n",
+					 req->essid, req->essid_len);
+			} else if (req->scan_type == IW_SCAN_TYPE_PASSIVE) {
+				AICWFDBG(LOGDEBUG,
+					 "aic_set_scan, req->scan_type == IW_SCAN_TYPE_PASSIVE\n");
+			}
+		}
+#endif
+	ret = rwnx_send_scanu_req(rwnx_hw, rwnx_vif, request);
+	if (ret)
+		return ret;
+
+	rwnx_vif->rwnx_hw->scan_request = request;
+	wext_scan_timeout = msecs_to_jiffies(5000);
+
+	if (!wait_for_completion_killable_timeout(&rwnx_hw->wext_scan_com,
+						  wext_scan_timeout)) {
+		AICWFDBG(LOGERROR, "%s WEXT scan timeout", __func__);
+	}
+	return 0;
+}
+
+static int aicwf_get_scan(struct net_device *dev, struct iw_request_info *a,
+			  union iwreq_data *wrqu, char *extra)
+{
+	int ret = 0;
+	struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+	struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+	struct scanu_result_wext *scan_re;
+	struct scanu_result_wext *tmp;
+	char *start = extra;
+	char *stop = start + wrqu->data.length;
+
+	AICWFDBG(LOGDEBUG, "%s Enter %p %p len:%d \r\n", __func__, start, stop, wrqu->data.length);
+
+	//TODO: spinlock
+	list_for_each_entry_safe(scan_re, tmp, &rwnx_hw->wext_scanre_list, scanu_re_list) {
+		start = translate_scan(rwnx_hw, a, scan_re, start, stop);
+		if ((stop - start) < 768)
+			return -E2BIG;
+	}
+
+	list_for_each_entry_safe(scan_re, tmp, &rwnx_hw->wext_scanre_list, scanu_re_list) {
+		list_del(&scan_re->scanu_re_list);
+		vfree(scan_re->payload);
+		vfree(scan_re->ind);
+		vfree(scan_re);
+		scan_re = NULL;
+	}
+
+	wrqu->data.length = start - extra;
+	wrqu->data.flags = 0;
+
+	return ret;
+}
+
+static int aicwf_get_essid(struct net_device *dev,
+			   struct iw_request_info *a,
+			    union iwreq_data *wrqu, char *extra)
+{
+	int ret = 0;
+	struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+	struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+
+	wrqu->essid.length = strlen(rwnx_hw->wext_essid);
+	memcpy(extra, rwnx_hw->wext_essid, strlen(rwnx_hw->wext_essid));
+	wrqu->essid.flags = 1;
+
+	return ret;
+}
+
+static int aicwf_get_nick(struct net_device *dev,
+			  struct iw_request_info *info,
+			   union iwreq_data *wrqu, char *extra)
+{
+	if (extra) {
+		wrqu->data.length = 8;
+		wrqu->data.flags = 1;
+		memcpy(extra, "AIC@8800", 8);
+	}
+
+	return 0;
+}
+
+static int aicwf_get_rate(struct net_device *dev,
+			  struct iw_request_info *info,
+			   union iwreq_data *wrqu, char *extra)
+{
+	u16 max_rate = 0;
+	struct rwnx_vif *rwnx_vif = netdev_priv(dev);
+	struct rwnx_hw *rwnx_hw = rwnx_vif->rwnx_hw;
+	struct rwnx_sta *sta = NULL;
+
+	union rwnx_rate_ctrl_info *rate_info;
+	struct mm_get_sta_info_cfm cfm;
+	u16 tmp = 0;
+
+	if (rwnx_vif->sta.ap) {
+		sta = rwnx_vif->sta.ap;
+		rwnx_send_get_sta_info_req(rwnx_hw, sta->sta_idx, &cfm);
+			rate_info = (union rwnx_rate_ctrl_info *)&cfm.rate_info;
+			ht_tmp = VHT_MCS_DATA_RATE[rate_info->bw_tx][1][rate_info->mcs_index_tx];
+			he_tmp = HE_MCS_DATA_RATE[rate_info->bw_tx][rate_info->mcs_index_tx];
+
+			switch (rate_info->format_mod_tx) {
+			case FORMATMOD_NON_HT:
+			case FORMATMOD_NON_HT_DUP_OFDM:
+				//get bg mode datarate
+				max_rate = tx_legrates_lut_rate[rate_info->mcs_index_tx] / 10;
+				break;
+			case FORMATMOD_HT_MF:
+			case FORMATMOD_HT_GF:
+			case FORMATMOD_VHT:
+				//get bg mode MCS index
+				max_rate = tmp / 2;
+				break;
+			case FORMATMOD_HE_MU:
+			case FORMATMOD_HE_SU:
+			case FORMATMOD_HE_ER:
+				//get bg mode MCS index
+				max_rate = he_tmp;
+				break;
+			}
+	}
+
+	wrqu->bitrate.fixed = 0;	/* no auto select */
+	wrqu->bitrate.value = max_rate * 1000000;
+
+	return 0;
+}
+
+static int aicwf_set_txpower(struct net_device *dev,
+			     struct iw_request_info *info,
+			  union iwreq_data *wrqu, char *keybuf)
+{
+	/* Not currently supported */
+
+	return 0;
+}
+
+static int aicwf_get_txpower(struct net_device *dev,
+			     struct iw_request_info *info,
+			  union iwreq_data *wrqu, char *keybuf)
+{
+	/* Not currently supported */
+
+	return 0;
+}
+
+static int aicwf_get_enc(struct net_device *dev,
+			 struct iw_request_info *info,
+			  union iwreq_data *wrqu, char *keybuf)
+{
+	int ret = 0;
+	struct iw_point *erq = &wrqu->encoding;
+
+	erq->length = 0;
+	erq->flags |= IW_ENCODE_ENABLED;
+
+	return ret;
+}
+
+static iw_handler aic_handlers[] = {
+	NULL,					/* SIOCSIWCOMMIT */
+	aicwf_get_name,			/* SIOCGIWNAME */
+	NULL,					/* SIOCSIWNWID */
+	NULL,					/* SIOCGIWNWID */
+	NULL,					/* SIOCSIWFREQ */
+	aicwf_get_freq,			/* SIOCGIWFREQ */
+	NULL,					/* SIOCSIWMODE */
+	aicwf_get_mode,			/* SIOCGIWMODE */
+	NULL,					/* SIOCSIWSENS */
+	NULL,					/* SIOCGIWSENS */
+	NULL,					/* SIOCSIWRANGE */
+	aicwf_get_range,		/* SIOCGIWRANGE */
+	NULL,					/* SIOCSIWPRIV */
+	NULL,					/* SIOCGIWPRIV */
+	NULL,					/* SIOCSIWSTATS */
+	NULL,					/* SIOCGIWSTATS */
+	NULL,					/* SIOCSIWSPY */
+	NULL,					/* SIOCGIWSPY */
+	NULL,					/* SIOCGIWTHRSPY */
+	NULL,					/* SIOCWIWTHRSPY */
+	NULL,					/* SIOCSIWAP */
+	aicwf_get_wap,			/* SIOCGIWAP */
+	NULL,					/* request MLME operation; uses struct iw_mlme */
+	NULL,					/* SIOCGIWAPLIST -- depricated */
+	aicwf_set_scan,			/* SIOCSIWSCAN */
+	aicwf_get_scan,			/* SIOCGIWSCAN */
+	NULL,					/* SIOCSIWESSID */
+	aicwf_get_essid,		/* SIOCGIWESSID */
+	NULL,					/* SIOCSIWNICKN */
+	aicwf_get_nick,			/* SIOCGIWNICKN */
+	NULL,					/* -- hole -- */
+	NULL,					/* -- hole -- */
+	NULL,					/* SIOCSIWRATE */
+	aicwf_get_rate,			/* SIOCGIWRATE */
+	NULL,					/* SIOCSIWRTS */
+	NULL,					/* SIOCGIWRTS */
+	NULL,					/* SIOCSIWFRAG */
+	NULL,					/* SIOCGIWFRAG */
+	aicwf_set_txpower,		/* SIOCSIWTXPOW */
+	aicwf_get_txpower,		/* SIOCGIWTXPOW */
+	NULL,					/* SIOCSIWRETRY */
+	NULL,					/* SIOCGIWRETRY */
+	NULL,					/* SIOCSIWENCODE */
+	aicwf_get_enc,			/* SIOCGIWENCODE */
+	NULL,					/* SIOCSIWPOWER */
+	NULL,					/* SIOCGIWPOWER */
+	NULL,					/*---hole---*/
+	NULL,					/*---hole---*/
+	NULL,					/* SIOCSIWGENIE */
+	NULL,					/* SIOCGWGENIE */
+	NULL,					/* SIOCSIWAUTH */
+	NULL,					/* SIOCGIWAUTH */
+	NULL,					/* SIOCSIWENCODEEXT */
+	NULL,					/* SIOCGIWENCODEEXT */
+	NULL,					/* SIOCSIWPMKSA */
+	NULL,					/*---hole---*/
+};
+
+struct iw_handler_def aic_handlers_def = {
+		.standard = aic_handlers,
+		.num_standard = sizeof(aic_handlers) / sizeof(iw_handler),
+#if WIRELESS_EXT >= 17
+		.get_wireless_stats = aicwf_get_wireless_stats,
+#endif
+};
+
+void aicwf_set_wireless_ext(struct net_device *ndev, struct rwnx_hw *rwnx_hw)
+{
+	init_completion(&rwnx_hw->wext_scan_com);
+	INIT_LIST_HEAD(&rwnx_hw->wext_scanre_list);
+
+	ndev->wireless_handlers = (struct iw_handler_def *)&aic_handlers_def;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/aicwf_wext_linux.h b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_wext_linux.h
new file mode 100644
index 0000000000000..eafd2a9d80b56
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/aicwf_wext_linux.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AICWF_WEXT_LINUX_H_
+#define _AICWF_WEXT_LINUX_H_
+
+struct scanu_result_wext {
+	struct list_head scanu_re_list;
+	struct cfg80211_bss *bss;
+	struct scanu_result_ind *ind;
+	u32_l *payload;
+};
+
+void aicwf_set_wireless_ext(struct net_device *ndev, struct rwnx_hw *rwnx_hw);
+void aicwf_scan_complete_event(struct net_device *dev);
+
+#endif /* _AICWF_WEXT_LINUX_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..c904afabfb91a
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/regdb.c
@@ -0,0 +1,2770 @@
+// 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>
+
+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 *reg_regdb[] = {
+	&regdom_00, &regdom_AD, &regdom_AE, &regdom_AF, &regdom_AI, &regdom_AL,
+	&regdom_AM, &regdom_AN, &regdom_AR, &regdom_AS, &regdom_AT, &regdom_AU,
+	&regdom_AW, &regdom_AZ, &regdom_BA, &regdom_BB, &regdom_BD, &regdom_BE,
+	&regdom_BF, &regdom_BG, &regdom_BH, &regdom_BL, &regdom_BM, &regdom_BN,
+	&regdom_BO, &regdom_BR, &regdom_BS, &regdom_BT, &regdom_BY, &regdom_BZ,
+	&regdom_CA, &regdom_CF, &regdom_CH, &regdom_CI, &regdom_CL, &regdom_CN,
+	&regdom_CO, &regdom_CR, &regdom_CX, &regdom_CY, &regdom_CZ, &regdom_DE,
+	&regdom_DK, &regdom_DM, &regdom_DO, &regdom_DZ, &regdom_EC, &regdom_EE,
+	&regdom_EG, &regdom_ES, &regdom_ET, &regdom_FI, &regdom_FM, &regdom_FR,
+	&regdom_GB, &regdom_GD, &regdom_GE, &regdom_GF, &regdom_GH, &regdom_GL,
+	&regdom_GP, &regdom_GR, &regdom_GT, &regdom_GU, &regdom_GY, &regdom_HK,
+	&regdom_HN, &regdom_HR, &regdom_HT, &regdom_HU, &regdom_ID, &regdom_IE,
+	&regdom_IL, &regdom_IN, &regdom_IR, &regdom_IS, &regdom_IT, &regdom_JM,
+	&regdom_JO, &regdom_JP, &regdom_KE, &regdom_KH, &regdom_KN, &regdom_KP,
+	&regdom_KR, &regdom_KW, &regdom_KY, &regdom_KZ, &regdom_LB, &regdom_LC,
+	&regdom_LI, &regdom_LK, &regdom_LS, &regdom_LT, &regdom_LU, &regdom_LV,
+	&regdom_MA, &regdom_MC, &regdom_MD, &regdom_ME, &regdom_MF, &regdom_MH,
+	&regdom_MK, &regdom_MN, &regdom_MO, &regdom_MP, &regdom_MQ, &regdom_MR,
+	&regdom_MT, &regdom_MU, &regdom_MW, &regdom_MX, &regdom_MY, &regdom_NI,
+	&regdom_NL, &regdom_NO, &regdom_NP, &regdom_NZ, &regdom_OM, &regdom_PA,
+	&regdom_PE, &regdom_PF, &regdom_PG, &regdom_PH, &regdom_PK, &regdom_PL,
+	&regdom_PM, &regdom_PR, &regdom_PT, &regdom_PW, &regdom_PY, &regdom_QA,
+	&regdom_RE, &regdom_RO, &regdom_RS, &regdom_RU, &regdom_RW, &regdom_SA,
+	&regdom_SE, &regdom_SG, &regdom_SI, &regdom_SK, &regdom_SN, &regdom_SR,
+	&regdom_SV, &regdom_SY, &regdom_TC, &regdom_TD, &regdom_TG, &regdom_TH,
+	&regdom_TN, &regdom_TR, &regdom_TT, &regdom_TW, &regdom_UA, &regdom_UG,
+	&regdom_US, &regdom_UY, &regdom_UZ, &regdom_VC, &regdom_VE, &regdom_VI,
+	&regdom_VN, &regdom_VU, &regdom_WF, &regdom_YE, &regdom_YT, &regdom_ZA,
+	&regdom_ZW, &regdom_BW, &regdom_GI, &regdom_IQ, &regdom_KG, &regdom_LY,
+	&regdom_MV, &regdom_NA, &regdom_NG, &regdom_SM, &regdom_TJ, &regdom_TM,
+	&regdom_FK, &regdom_FO, &regdom_ZM, &regdom_CU, &regdom_TZ, &regdom_WS,
+	&regdom_XK, &regdom_WW, &regdom_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..db089e09acd16
--- /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..f875603314fa8
--- /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..44235182295e6
--- /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_debugfs.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_debugfs.c
new file mode 100644
index 0000000000000..b5eba38e540b0
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_debugfs.c
@@ -0,0 +1,2585 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief Definition of debugfs entries
+ *
+ ******************************************************************************
+ */
+
+#include <linux/debugfs.h>
+#include <linux/kernel.h>
+#include <linux/kmod.h>
+#include <linux/sort.h>
+#include <linux/string.h>
+#include <linux/vmalloc.h>
+#include "lmac_types.h"
+
+#include "rwnx_debugfs.h"
+#include "rwnx_msg_tx.h"
+#include "rwnx_radar.h"
+#include "rwnx_tx.h"
+
+#ifdef CONFIG_RWNX_DEBUGFS
+#ifdef CONFIG_RWNX_FULLMAC
+static ssize_t rwnx_dbgfs_stats_read(struct file *file, char __user *user_buf,
+				     size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char *buf;
+	int ret;
+	int i, skipped;
+	ssize_t read;
+	int bufsz = (NX_TXQ_CNT) * 20 +
+			(ARRAY_SIZE(priv->stats.amsdus_rx) + 1) * 40 +
+			(ARRAY_SIZE(priv->stats.ampdus_tx) * 30);
+
+	if (*ppos)
+		return 0;
+
+	buf = kmalloc(bufsz, GFP_ATOMIC);
+	if (!buf)
+		return 0;
+
+	ret = scnprintf(buf, bufsz, "TXQs CFM balances ");
+	for (i = 0; i < NX_TXQ_CNT; i++)
+		ret += scnprintf(&buf[ret], bufsz - ret, "  [%1d]:%3d", i,
+				 priv->stats.cfm_balance[i]);
+
+	ret += scnprintf(&buf[ret], bufsz - ret, "\n");
+
+#ifdef CONFIG_RWNX_SPLIT_TX_BUF
+	ret += scnprintf(&buf[ret], bufsz - ret,
+			 "\nAMSDU[len]       done         failed   received\n");
+	for (i = skipped = 0; i < NX_TX_PAYLOAD_MAX; i++) {
+		if (priv->stats.amsdus[i].done) {
+			per = DIV_ROUND_UP((priv->stats.amsdus[i].failed) * 100,
+					   priv->stats.amsdus[i].done);
+		} else if (priv->stats.amsdus_rx[i]) {
+			per = 0;
+		} else {
+			per = 0;
+			skipped = 1;
+			continue;
+		}
+		if (skipped) {
+			ret += scnprintf(&buf[ret], bufsz - ret, "   ...\n");
+			skipped = 0;
+		}
+
+		ret += scnprintf(&buf[ret], bufsz - ret, "   [%2d]    %10d %8d(%3d%%) %10d\n",
+				 i ? i + 1 : i, priv->stats.amsdus[i].done,
+				 priv->stats.amsdus[i].failed, per, priv->stats.amsdus_rx[i]);
+	}
+
+	for (; i < ARRAY_SIZE(priv->stats.amsdus_rx); i++) {
+		if (!priv->stats.amsdus_rx[i]) {
+			skipped = 1;
+			continue;
+		}
+		if (skipped) {
+			ret += scnprintf(&buf[ret], bufsz - ret, "   ...\n");
+			skipped = 0;
+		}
+
+		ret += scnprintf(&buf[ret], bufsz - ret,
+				 "   [%2d]                              %10d\n", i + 1,
+				 priv->stats.amsdus_rx[i]);
+	}
+#else
+	ret += scnprintf(&buf[ret], bufsz - ret, "\nAMSDU[len]   received\n");
+	for (i = skipped = 0; i < ARRAY_SIZE(priv->stats.amsdus_rx); i++) {
+		if (!priv->stats.amsdus_rx[i]) {
+			skipped = 1;
+			continue;
+		}
+		if (skipped) {
+			ret += scnprintf(&buf[ret], bufsz - ret, "   ...\n");
+			skipped = 0;
+		}
+
+		ret += scnprintf(&buf[ret], bufsz - ret, "   [%2d]    %10d\n", i + 1,
+				 priv->stats.amsdus_rx[i]);
+	}
+
+#endif /* CONFIG_RWNX_SPLIT_TX_BUF */
+
+	ret +=
+		scnprintf(&buf[ret], bufsz - ret, "\nAMPDU[len]     done  received\n");
+	for (i = skipped = 0; i < ARRAY_SIZE(priv->stats.ampdus_tx); i++) {
+		if (!priv->stats.ampdus_tx[i] && !priv->stats.ampdus_rx[i]) {
+			skipped = 1;
+			continue;
+		}
+		if (skipped) {
+			ret += scnprintf(&buf[ret], bufsz - ret, "    ...\n");
+			skipped = 0;
+		}
+
+		ret += scnprintf(&buf[ret], bufsz - ret, "   [%2d]   %9d %9d\n",
+				 i ? i + 1 : i, priv->stats.ampdus_tx[i],
+				 priv->stats.ampdus_rx[i]);
+	}
+
+	ret += scnprintf(&buf[ret], bufsz - ret, "#mpdu missed        %9d\n",
+			 priv->stats.ampdus_rx_miss);
+	read = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+	kfree(buf);
+
+	return read;
+}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+static ssize_t rwnx_dbgfs_stats_write(struct file *file,
+				      const char __user *user_buf, size_t count,
+				      loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+
+	/* Prevent from interrupt preemption as these statistics are updated under
+	 * interrupt
+	 */
+	spin_lock_bh(&priv->tx_lock);
+
+	memset(&priv->stats, 0, sizeof(priv->stats));
+
+	spin_unlock_bh(&priv->tx_lock);
+
+	return count;
+}
+
+DEBUGFS_READ_FUNC(stats);
+DEBUGFS_WRITE_FUNC(stats);
+static const struct file_operations rwnx_dbgfs_stats_ops = {
+	.write = rwnx_dbgfs_stats_write,
+	.read = rwnx_dbgfs_stats_read,
+	.open = simple_open,
+	.llseek = generic_file_llseek,
+}
+
+#define TXQ_STA_PREF     "tid|"
+#define TXQ_STA_PREF_FMT "%3d|"
+
+#ifdef CONFIG_RWNX_FULLMAC
+#define TXQ_VIF_PREF     "type|"
+#define TXQ_VIF_PREF_FMT "%4s|"
+#else
+#define TXQ_VIF_PREF     "AC|"
+#define TXQ_VIF_PREF_FMT "%2s|"
+#endif /* CONFIG_RWNX_FULLMAC */
+
+#define TXQ_HDR     "idx| status|credit|ready|retry"
+#define TXQ_HDR_FMT "%3d|%s%s%s%s%s%s%s|%6d|%5d|%5d"
+
+#ifdef CONFIG_RWNX_AMSDUS_TX
+#ifdef CONFIG_RWNX_FULLMAC
+#define TXQ_HDR_SUFF     "|amsdu"
+#define TXQ_HDR_SUFF_FMT "|%5d"
+#else
+#define TXQ_HDR_SUFF     "|amsdu-ht|amdsu-vht"
+#define TXQ_HDR_SUFF_FMT "|%8d|%9d"
+#endif /* CONFIG_RWNX_FULLMAC */
+#else
+#define TXQ_HDR_SUFF    ""
+#define TXQ_HDR_SUF_FMT ""
+#endif /* CONFIG_RWNX_AMSDUS_TX */
+
+#define TXQ_HDR_MAX_LEN                                                        \
+	(sizeof(TXQ_STA_PREF) + sizeof(TXQ_HDR) + sizeof(TXQ_HDR_SUFF) + 1)
+
+#ifdef CONFIG_RWNX_FULLMAC
+#define PS_HDR        "Legacy PS: ready=%d, sp=%d / UAPSD: ready=%d, sp=%d"
+#define PS_HDR_LEGACY "Legacy PS: ready=%d, sp=%d"
+#define PS_HDR_UAPSD  "UAPSD: ready=%d, sp=%d"
+#define PS_HDR_MAX_LEN                                                         \
+	sizeof("Legacy PS: ready=xxx, sp=xxx / UAPSD: ready=xxx, sp=xxx\n")
+#else
+#define PS_HDR         ""
+#define PS_HDR_MAX_LEN 0
+#endif /* CONFIG_RWNX_FULLMAC */
+
+#define STA_HDR "** STA %d (%pM)\n"
+#define STA_HDR_MAX_LEN                                                        \
+	(sizeof("- STA xx (xx:xx:xx:xx:xx:xx)\n") + PS_HDR_MAX_LEN)
+
+#ifdef CONFIG_RWNX_FULLMAC
+#define VIF_HDR         "* VIF [%d] %s\n"
+#define VIF_HDR_MAX_LEN (sizeof(VIF_HDR) + IFNAMSIZ)
+#else
+#define VIF_HDR         "* VIF [%d]\n"
+#define VIF_HDR_MAX_LEN sizeof(VIF_HDR)
+#endif
+
+#ifdef CONFIG_RWNX_AMSDUS_TX
+
+#ifdef CONFIG_RWNX_FULLMAC
+#define VIF_SEP "---------------------------------------\n"
+#else
+#define VIF_SEP "----------------------------------------------------\n"
+#endif /* CONFIG_RWNX_FULLMAC */
+
+#else /* ! CONFIG_RWNX_AMSDUS_TX */
+#define VIF_SEP "---------------------------------\n"
+#endif /* CONFIG_RWNX_AMSDUS_TX */
+
+#define VIF_SEP_LEN sizeof(VIF_SEP)
+
+#define CAPTION                                                                \
+	"status: L=in hwq list, F=stop full, P=stop sta PS, V=stop vif PS, "       \
+	"C=stop channel, S=stop CSA, M=stop MU"
+#define CAPTION_LEN sizeof(CAPTION)
+
+#define STA_TXQ 0
+#define VIF_TXQ 1
+
+static int rwnx_dbgfs_txq(char *buf, size_t size, struct rwnx_txq *txq,
+			  int type, int tid, char *name)
+{
+	int res, idx = 0;
+
+	if (type == STA_TXQ) {
+		res = scnprintf(&buf[idx], size, TXQ_STA_PREF_FMT, tid);
+		idx += res;
+		size -= res;
+	} else {
+		res = scnprintf(&buf[idx], size, TXQ_VIF_PREF_FMT, name);
+		idx += res;
+		size -= res;
+	}
+
+	res = scnprintf(&buf[idx], size, TXQ_HDR_FMT, txq->idx,
+			(txq->status & RWNX_TXQ_IN_HWQ_LIST) ? "L" : " ",
+			(txq->status & RWNX_TXQ_STOP_FULL) ? "F" : " ",
+			(txq->status & RWNX_TXQ_STOP_STA_PS) ? "P" : " ",
+			(txq->status & RWNX_TXQ_STOP_VIF_PS) ? "V" : " ",
+			(txq->status & RWNX_TXQ_STOP_CHAN) ? "C" : " ",
+			(txq->status & RWNX_TXQ_STOP_CSA) ? "S" : " ",
+			(txq->status & RWNX_TXQ_STOP_MU_POS) ? "M" : " ",
+			txq->credits, skb_queue_len(&txq->sk_list), txq->nb_retry);
+	idx += res;
+	size -= res;
+
+#ifdef CONFIG_RWNX_AMSDUS_TX
+	if (type == STA_TXQ) {
+		res = scnprintf(&buf[idx], size, TXQ_HDR_SUFF_FMT,
+				#ifdef CONFIG_RWNX_FULLMAC
+				txq->amsdu_len
+				#else
+				txq->amsdu_ht_len_cap, txq->amsdu_vht_len_cap
+				#endif /* CONFIG_RWNX_FULLMAC */
+		);
+		idx += res;
+		size -= res;
+	}
+#endif
+
+	res = scnprintf(&buf[idx], size, "\n");
+	idx += res;
+	size -= res;
+
+	return idx;
+}
+
+static int rwnx_dbgfs_txq_sta(char *buf, size_t size, struct rwnx_sta *rwnx_sta,
+			      struct rwnx_hw *rwnx_hw)
+{
+	int tid, res, idx = 0;
+	struct rwnx_txq *txq;
+
+	res = scnprintf(&buf[idx], size, "\n" STA_HDR, rwnx_sta->sta_idx,
+			#ifdef CONFIG_RWNX_FULLMAC
+			rwnx_sta->mac_addr
+			#endif /* CONFIG_RWNX_FULLMAC */
+	);
+	idx += res;
+	size -= res;
+
+#ifdef CONFIG_RWNX_FULLMAC
+	if (rwnx_sta->ps.active) {
+		if (rwnx_sta->uapsd_tids &&
+		    rwnx_sta->uapsd_tids == ((1 << NX_NB_TXQ_PER_STA) - 1))
+			res = scnprintf(&buf[idx], size, PS_HDR_UAPSD "\n",
+					rwnx_sta->ps.pkt_ready[UAPSD_ID],
+					rwnx_sta->ps.sp_cnt[UAPSD_ID]);
+		else if (rwnx_sta->uapsd_tids)
+			res = scnprintf(&buf[idx], size, PS_HDR "\n",
+					rwnx_sta->ps.pkt_ready[LEGACY_PS_ID],
+					rwnx_sta->ps.sp_cnt[LEGACY_PS_ID],
+					rwnx_sta->ps.pkt_ready[UAPSD_ID],
+					rwnx_sta->ps.sp_cnt[UAPSD_ID]);
+		else
+			res = scnprintf(&buf[idx], size, PS_HDR_LEGACY "\n",
+					rwnx_sta->ps.pkt_ready[LEGACY_PS_ID],
+					rwnx_sta->ps.sp_cnt[LEGACY_PS_ID]);
+		idx += res;
+		size -= res;
+	} else {
+		res = scnprintf(&buf[idx], size, "\n");
+		idx += res;
+		size -= res;
+	}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+	res = scnprintf(&buf[idx], size, TXQ_STA_PREF TXQ_HDR TXQ_HDR_SUFF "\n");
+	idx += res;
+	size -= res;
+#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
+	{
+		res = rwnx_dbgfs_txq(&buf[idx], size, txq, STA_TXQ, tid, NULL);
+		idx += res;
+		size -= res;
+	}
+
+	return idx;
+}
+
+static int rwnx_dbgfs_txq_vif(char *buf, size_t size, struct rwnx_vif *rwnx_vif,
+			      struct rwnx_hw *rwnx_hw)
+{
+	int res, idx = 0;
+	struct rwnx_txq *txq;
+	struct rwnx_sta *rwnx_sta;
+
+#ifdef CONFIG_RWNX_FULLMAC
+	res = scnprintf(&buf[idx], size, VIF_HDR, rwnx_vif->vif_index,
+			rwnx_vif->ndev->name);
+	idx += res;
+	size -= res;
+	if (!rwnx_vif->up || !rwnx_vif->ndev)
+		return idx;
+
+#else
+	int ac;
+	char ac_name[2] = {'0', '\0'};
+
+	res = scnprintf(&buf[idx], size, VIF_HDR, rwnx_vif->vif_index);
+	idx += res;
+	size -= res;
+#endif /* CONFIG_RWNX_FULLMAC */
+
+#ifdef CONFIG_RWNX_FULLMAC
+	if (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_AP ||
+	    RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_GO ||
+	    RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_MESH_POINT) {
+		res = scnprintf(&buf[idx], size, TXQ_VIF_PREF TXQ_HDR "\n");
+		idx += res;
+		size -= res;
+		txq = rwnx_txq_vif_get(rwnx_vif, NX_UNK_TXQ_TYPE);
+		res = rwnx_dbgfs_txq(&buf[idx], size, txq, VIF_TXQ, 0, "UNK");
+		idx += res;
+		size -= res;
+		txq = rwnx_txq_vif_get(rwnx_vif, NX_BCMC_TXQ_TYPE);
+		res = rwnx_dbgfs_txq(&buf[idx], size, txq, VIF_TXQ, 0, "BCMC");
+		idx += res;
+		size -= res;
+		rwnx_sta = &rwnx_hw->sta_table[rwnx_vif->ap.bcmc_index];
+		if (rwnx_sta->ps.active) {
+			res = scnprintf(&buf[idx], size, PS_HDR_LEGACY "\n",
+					rwnx_sta->ps.sp_cnt[LEGACY_PS_ID],
+					rwnx_sta->ps.sp_cnt[LEGACY_PS_ID]);
+			idx += res;
+			size -= res;
+		} else {
+			res = scnprintf(&buf[idx], size, "\n");
+			idx += res;
+			size -= res;
+		}
+
+		list_for_each_entry(rwnx_sta, &rwnx_vif->ap.sta_list, list) {
+			res = rwnx_dbgfs_txq_sta(&buf[idx], size, rwnx_sta, rwnx_hw);
+			idx += res;
+			size -= res;
+		}
+	} else if (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_STATION ||
+		   RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_P2P_CLIENT) {
+		if (rwnx_vif->sta.ap) {
+			res = rwnx_dbgfs_txq_sta(&buf[idx], size, rwnx_vif->sta.ap, rwnx_hw);
+			idx += res;
+			size -= res;
+		}
+	}
+
+#else
+	res = scnprintf(&buf[idx], size, TXQ_VIF_PREF TXQ_HDR "\n");
+	idx += res;
+	size -= res;
+
+#ifdef CONFIG_MAC80211_TXQ
+	for (ac = RWNX_HWQ_BK, txq = rwnx_txq_vif_get(rwnx_vif, ac);
+	     ac < NX_NB_TXQ_PER_VIF; ac++, txq = rwnx_txq_vif_get(rwnx_vif, ac))
+#else
+	for (ac = RWNX_HWQ_BK, txq = &(rwnx_vif)->txqs[0]; ac < NX_NB_TXQ_PER_VIF;
+	     ac++, txq++)
+#endif
+	{
+		ac_name[0]++;
+		res = rwnx_dbgfs_txq(&buf[idx], size, txq, VIF_TXQ, 0, ac_name);
+		idx += res;
+		size -= res;
+	}
+
+	list_for_each_entry(rwnx_sta, &rwnx_vif->stations, list) {
+		res = rwnx_dbgfs_txq_sta(&buf[idx], size, rwnx_sta, rwnx_hw);
+		idx += res;
+		size -= res;
+	}
+#endif /* CONFIG_RWNX_FULLMAC */
+	return idx;
+}
+
+static ssize_t rwnx_dbgfs_txq_read(struct file *file, char __user *user_buf,
+				   size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *rwnx_hw = file->private_data;
+	struct rwnx_vif *vif;
+	char *buf;
+	int idx, res;
+	ssize_t read;
+	size_t bufsz =
+		((NX_VIRT_DEV_MAX * (VIF_HDR_MAX_LEN + 2 * VIF_SEP_LEN)) +
+		 (NX_REMOTE_STA_MAX * STA_HDR_MAX_LEN) +
+		 ((NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX + NX_NB_TXQ) * TXQ_HDR_MAX_LEN) +
+		 CAPTION_LEN);
+
+	/* everything is read in one go */
+	if (*ppos)
+		return 0;
+
+	bufsz = min_t(size_t, bufsz, count);
+	buf = kmalloc(bufsz, GFP_ATOMIC);
+	if (!buf)
+		return 0;
+
+	bufsz--;
+	idx = 0;
+
+	res = scnprintf(&buf[idx], bufsz, CAPTION);
+	idx += res;
+	bufsz -= res;
+
+	// spin_lock_bh(&rwnx_hw->tx_lock);
+	list_for_each_entry(vif, &rwnx_hw->vifs, list) {
+		res = scnprintf(&buf[idx], bufsz, "\n" VIF_SEP);
+		idx += res;
+		bufsz -= res;
+		res = rwnx_dbgfs_txq_vif(&buf[idx], bufsz, vif, rwnx_hw);
+		idx += res;
+		bufsz -= res;
+		res = scnprintf(&buf[idx], bufsz, VIF_SEP);
+		idx += res;
+		bufsz -= res;
+	}
+	// spin_unlock_bh(&rwnx_hw->tx_lock);
+
+	read = simple_read_from_buffer(user_buf, count, ppos, buf, idx);
+	kfree(buf);
+
+	return read;
+}
+
+DEBUGFS_READ_FILE_OPS(txq);
+
+static ssize_t rwnx_dbgfs_acsinfo_read(struct file *file, char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+#ifdef CONFIG_RWNX_FULLMAC
+	struct wiphy *wiphy = priv->wiphy;
+#endif // CONFIG_RWNX_FULLMAC
+	int survey_cnt = 0;
+	int len = 0;
+	int band, chan_cnt;
+	int band_max = NL80211_BAND_5GHZ;
+	char *buf = vmalloc((SCAN_CHANNEL_MAX + 1) * 43);
+	ssize_t size;
+
+	if (!buf)
+		return 0;
+
+	if (priv->band_5g_support)
+		band_max = NL80211_BAND_5GHZ + 1;
+
+	mutex_lock(&priv->dbgdump_elem.mutex);
+
+	len += scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count),
+			 "FREQ    TIME(ms)    BUSY(ms)    NOISE(dBm)\n");
+
+	for (band = NL80211_BAND_2GHZ; band < band_max; band++) {
+		for (chan_cnt = 0; chan_cnt < wiphy->bands[band]->n_channels;
+			 chan_cnt++) {
+			struct rwnx_survey_info *p_survey_info = &priv->survey[survey_cnt];
+			struct ieee80211_channel *p_chan =
+				&wiphy->bands[band]->channels[chan_cnt];
+
+			if (p_survey_info->filled) {
+				len += scnprintf(&buf[len],
+						 min_t(size_t, sizeof(buf) - len - 1, count),
+						 "%d    %03d         %03d         %d\n",
+						 p_chan->center_freq,
+						 p_survey_info->chan_time_ms,
+						 p_survey_info->chan_time_busy_ms,
+						 p_survey_info->noise_dbm);
+			} else {
+				len += scnprintf(&buf[len],
+						 min_t(size_t, sizeof(buf) - len - 1, count),
+						 "%d    NOT AVAILABLE\n", p_chan->center_freq);
+			}
+
+			survey_cnt++;
+		}
+	}
+
+	mutex_unlock(&priv->dbgdump_elem.mutex);
+
+	size = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+	vfree(buf);
+
+	return size;
+}
+
+DEBUGFS_READ_FILE_OPS(acsinfo);
+
+static ssize_t rwnx_dbgfs_fw_dbg_read(struct file *file, char __user *user_buf,
+				      size_t count, loff_t *ppos)
+{
+	char help[] =
+	"usage: [MOD:<ALL|KE|DBG|IPC|DMA|MM|TX|RX|PHY>]* [DBG:<NONE|CRT|ERR|WRN|INF|VRB>]\n";
+
+	return simple_read_from_buffer(user_buf, count, ppos, help, sizeof(help));
+}
+
+static ssize_t rwnx_dbgfs_fw_dbg_write(struct file *file,
+				       const char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[32];
+	int idx = 0;
+	u32 mod = 0;
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+	buf[len] = '\0';
+
+#define RWNX_MOD_TOKEN(str, val)						\
+	do {									\
+		if (strncmp(&buf[idx], str, sizeof(str) - 1) == 0) {		\
+			idx += sizeof(str) - 1;					\
+			mod |= val;						\
+			continue;						\
+		}								\
+	} while (0)
+
+	while ((idx + 4) < len) {
+		if (strncmp(&buf[idx], "MOD:", 4) == 0) {
+			idx += 4;
+			RWNX_MOD_TOKEN("ALL", 0xffffffff);
+			RWNX_MOD_TOKEN("KE", BIT(0));
+			RWNX_MOD_TOKEN("DBG", BIT(1));
+			RWNX_MOD_TOKEN("IPC", BIT(2));
+			RWNX_MOD_TOKEN("DMA", BIT(3));
+			RWNX_MOD_TOKEN("MM", BIT(4));
+			RWNX_MOD_TOKEN("TX", BIT(5));
+			RWNX_MOD_TOKEN("RX", BIT(6));
+			RWNX_MOD_TOKEN("PHY", BIT(7));
+			idx++;
+		} else if (strncmp(&buf[idx], "DBG:", 4) == 0) {
+			u32 dbg = 0;
+
+			idx += 4;
+			if (strncmp(&buf[idx], "NONE", sizeof("NONE") - 1) == 0) {
+				idx += sizeof("NONE") - 1;
+				dbg = 0;
+				goto dbg_done;
+			}
+			if (strncmp(&buf[idx], "CRT", sizeof("CRT") - 1) == 0) {
+				idx += sizeof("CRT") - 1;
+				dbg = 1;
+				goto dbg_done;
+			}
+			if (strncmp(&buf[idx], "ERR", sizeof("ERR") - 1) == 0) {
+				idx += sizeof("ERR") - 1;
+				dbg = 2;
+				goto dbg_done;
+			}
+			if (strncmp(&buf[idx], "WRN", sizeof("WRN") - 1) == 0) {
+				idx += sizeof("WRN") - 1;
+				dbg = 3;
+				goto dbg_done;
+			}
+			if (strncmp(&buf[idx], "INF", sizeof("INF") - 1) == 0) {
+				idx += sizeof("INF") - 1;
+				dbg = 4;
+				goto dbg_done;
+			}
+			if (strncmp(&buf[idx], "VRB", sizeof("VRB") - 1) == 0) {
+				idx += sizeof("VRB") - 1;
+				dbg = 5;
+				goto dbg_done;
+			}
+			idx++;
+			continue;
+dbg_done:
+	rwnx_send_dbg_set_sev_filter_req(priv, dbg);
+		} else {
+			idx++;
+		}
+	}
+
+	if (mod)
+		rwnx_send_dbg_set_mod_filter_req(priv, mod);
+
+	return count;
+}
+
+DEBUGFS_READ_FUNC(fw_dbg);
+DEBUGFS_WRITE_FUNC(fw_dbg);
+static const struct file_operations rwnx_dbgfs_fw_dbg_ops = {
+	.write = rwnx_dbgfs_fw_dbg_write,
+	.read = rwnx_dbgfs_fw_dbg_read,
+	.open = simple_open,
+	.llseek = generic_file_llseek,
+}
+
+static ssize_t rwnx_dbgfs_sys_stats_read(struct file *file,
+					 char __user *user_buf, size_t count,
+					 loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[3 * 64];
+	int len = 0;
+	ssize_t read;
+	int error = 0;
+	struct dbg_get_sys_stat_cfm cfm;
+	u32 sleep_int, sleep_frac, doze_int, doze_frac;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Get the information from the FW */
+	error = rwnx_send_dbg_get_sys_stat_req(priv, &cfm);
+	if (error)
+		return error;
+
+	if (cfm.stats_time == 0)
+		return 0;
+
+	sleep_int = ((cfm.cpu_sleep_time * 100) / cfm.stats_time);
+	sleep_frac =
+		(((cfm.cpu_sleep_time * 100) % cfm.stats_time) * 10) / cfm.stats_time;
+	doze_int = ((cfm.doze_time * 100) / cfm.stats_time);
+	doze_frac =
+		(((cfm.doze_time * 100) % cfm.stats_time) * 10) / cfm.stats_time;
+
+	len += scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count),
+			 "\nSystem statistics:\n");
+	len += scnprintf(&buf[len], min_t(size_t, sizeof(buf) - 1, count),
+			 "  CPU sleep [%%]: %d.%d\n", sleep_int, sleep_frac);
+	len += scnprintf(&buf[len], min_t(size_t, sizeof(buf) - 1, count),
+			 "  Doze      [%%]: %d.%d\n", doze_int, doze_frac);
+
+	read = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+
+	return read;
+}
+
+DEBUGFS_READ_FILE_OPS(sys_stats);
+
+#ifdef CONFIG_RWNX_MUMIMO_TX
+static ssize_t rwnx_dbgfs_mu_group_read(struct file *file,
+					char __user *user_buf, size_t count,
+					loff_t *ppos)
+{
+	struct rwnx_hw *rwnx_hw = file->private_data;
+	struct rwnx_mu_info *mu = &rwnx_hw->mu;
+	struct rwnx_mu_group *group;
+	size_t bufsz = NX_MU_GROUP_MAX * sizeof("xx = (xx - xx - xx - xx)\n") + 50;
+	char *buf;
+	int j, res, idx = 0;
+
+	if (*ppos)
+		return 0;
+
+	buf = kmalloc(bufsz, GFP_ATOMIC);
+	if (!buf)
+		return 0;
+
+	res =
+		scnprintf(&buf[idx], bufsz, "MU Group list (%d groups, %d users max)\n",
+			  NX_MU_GROUP_MAX, CONFIG_USER_MAX);
+	idx += res;
+	bufsz -= res;
+
+	list_for_each_entry(group, &mu->active_groups, list) {
+		if (group->user_cnt) {
+			res = scnprintf(&buf[idx], bufsz, "%2d = (", group->group_id);
+			idx += res;
+			bufsz -= res;
+			for (j = 0; j < (CONFIG_USER_MAX - 1); j++) {
+				if (group->users[j])
+					res = scnprintf(&buf[idx], bufsz, "%2d - ",
+							group->users[j]->sta_idx);
+				else
+					res = scnprintf(&buf[idx], bufsz, ".. - ");
+
+				idx += res;
+				bufsz -= res;
+			}
+
+			if (group->users[j])
+				res = scnprintf(&buf[idx], bufsz, "%2d)\n",
+						group->users[j]->sta_idx);
+			else
+				res = scnprintf(&buf[idx], bufsz, "..)\n");
+
+			idx += res;
+			bufsz -= res;
+		}
+	}
+
+	res = simple_read_from_buffer(user_buf, count, ppos, buf, idx);
+	kfree(buf);
+
+	return res;
+}
+
+DEBUGFS_READ_FILE_OPS(mu_group);
+#endif
+
+#ifdef CONFIG_RWNX_P2P_DEBUGFS
+static ssize_t rwnx_dbgfs_oppps_write(struct file *file,
+				      const char __user *user_buf, size_t count,
+				      loff_t *ppos)
+{
+	struct rwnx_hw *rw_hw = file->private_data;
+	struct rwnx_vif *rw_vif;
+	char buf[32];
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+	int ctw;
+
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+	buf[len] = '\0';
+
+	/* Read the written CT Window (provided in ms) value */
+	if (sscanf(buf, "ctw=%d", &ctw) > 0) {
+		/* Check if at least one VIF is configured as P2P GO */
+		list_for_each_entry(rw_vif, &rw_hw->vifs, list) {
+#ifdef CONFIG_RWNX_FULLMAC
+			if (RWNX_VIF_TYPE(rw_vif) == NL80211_IFTYPE_P2P_GO) {
+#endif /* CONFIG_RWNX_FULLMAC */
+				struct mm_set_p2p_oppps_cfm cfm;
+				/* Forward request to the embedded and wait for confirmation */
+				rwnx_send_p2p_oppps_req(rw_hw, rw_vif, (u8)ctw, &cfm);
+				break;
+			}
+		}
+	}
+
+	return count;
+}
+
+DEBUGFS_WRITE_FILE_OPS(oppps);
+
+static ssize_t rwnx_dbgfs_noa_write(struct file *file,
+				    const char __user *user_buf, size_t count,
+				    loff_t *ppos)
+{
+	struct rwnx_hw *rw_hw = file->private_data;
+	struct rwnx_vif *rw_vif;
+	char buf[64];
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+	int noa_count, interval, duration, dyn_noa;
+
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+	buf[len] = '\0';
+
+	/* Read the written NOA information */
+	if (sscanf(buf, "count=%d interval=%d duration=%d dyn=%d", &noa_count,
+		   &interval, &duration, &dyn_noa) > 0) {
+		/* Check if at least one VIF is configured as P2P GO */
+		list_for_each_entry(rw_vif, &rw_hw->vifs, list) {
+#ifdef CONFIG_RWNX_FULLMAC
+			if (RWNX_VIF_TYPE(rw_vif) == NL80211_IFTYPE_P2P_GO) {
+#endif /* CONFIG_RWNX_FULLMAC */
+				struct mm_set_p2p_noa_cfm cfm;
+
+				/* Forward request to the embedded and wait for confirmation */
+				rwnx_send_p2p_noa_req(rw_hw, rw_vif, noa_count, interval,
+						      duration, (dyn_noa > 0), &cfm);
+
+				break;
+			}
+		}
+	}
+	return count;
+}
+
+DEBUGFS_WRITE_FILE_OPS(noa);
+#endif /* CONFIG_RWNX_P2P_DEBUGFS */
+
+static char fw_log_buffer[FW_LOG_SIZE];
+
+static ssize_t rwnx_dbgfs_fw_log_read(struct file *file, char __user *user_buf,
+				      size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	size_t not_cpy;
+	size_t nb_cpy;
+	char *log = fw_log_buffer;
+
+	AICWFDBG(LOGDEBUG, "%s, %d, %p, %p\n", __func__, priv->debugfs.fw_log.buf.size,
+		 priv->debugfs.fw_log.buf.start, priv->debugfs.fw_log.buf.dataend);
+
+	if ((priv->debugfs.fw_log.buf.start + priv->debugfs.fw_log.buf.size) >=
+	    priv->debugfs.fw_log.buf.dataend) {
+		memcpy(log, priv->debugfs.fw_log.buf.start,
+		       priv->debugfs.fw_log.buf.dataend - priv->debugfs.fw_log.buf.start);
+		not_cpy = copy_to_user(user_buf, log,
+				       priv->debugfs.fw_log.buf.dataend -
+				       priv->debugfs.fw_log.buf.start);
+		nb_cpy = priv->debugfs.fw_log.buf.dataend -
+				 priv->debugfs.fw_log.buf.start - not_cpy;
+		priv->debugfs.fw_log.buf.start = priv->debugfs.fw_log.buf.data;
+	} else {
+		memcpy(log, priv->debugfs.fw_log.buf.start,
+		       priv->debugfs.fw_log.buf.size);
+		not_cpy = copy_to_user(user_buf, log, priv->debugfs.fw_log.buf.size);
+		nb_cpy = priv->debugfs.fw_log.buf.size - not_cpy;
+		priv->debugfs.fw_log.buf.start = priv->debugfs.fw_log.buf.start +
+							priv->debugfs.fw_log.buf.size -
+							not_cpy;
+	}
+
+	priv->debugfs.fw_log.buf.size -= nb_cpy;
+
+	AICWFDBG(LOGDEBUG, "nb_cpy=%lu, not_cpy=%lu, start=%p, end=%p\n",
+		 (unsigned long)nb_cpy, (unsigned long)not_cpy,
+		 priv->debugfs.fw_log.buf.start, priv->debugfs.fw_log.buf.end);
+	return nb_cpy;
+}
+
+static ssize_t rwnx_dbgfs_fw_log_write(struct file *file,
+				       const char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	AICWFDBG(LOGDEBUG, "%s function_show\n", __func__);
+	return count;
+}
+
+DEBUGFS_READ_FUNC(fw_log);
+DEBUGFS_WRITE_FUNC(fw_log);
+static const struct file_operations rwnx_dbgfs_fw_log_ops = {
+	.write = rwnx_dbgfs_fw_log_write,
+	.read = rwnx_dbgfs_fw_log_read,
+	.open = simple_open,
+	.llseek = generic_file_llseek,
+}
+
+#ifdef CONFIG_RWNX_RADAR
+static ssize_t rwnx_dbgfs_pulses_read(struct file *file, char __user *user_buf,
+				      size_t count, loff_t *ppos, int rd_idx)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char *buf;
+	int len = 0;
+	int bufsz;
+	int i;
+	int index;
+	struct rwnx_radar_pulses *p = &priv->radar.pulses[rd_idx];
+	ssize_t read;
+
+	if (*ppos != 0)
+		return 0;
+
+	/* Prevent from interrupt preemption */
+	spin_lock_bh(&priv->radar.lock);
+	bufsz = p->count * 34 + 51;
+	bufsz += rwnx_radar_dump_pattern_detector(NULL, 0, &priv->radar, rd_idx);
+	buf = kmalloc(bufsz, GFP_ATOMIC);
+	if (!buf) {
+		spin_unlock_bh(&priv->radar.lock);
+		return 0;
+	}
+
+	if (p->count) {
+		len += scnprintf(&buf[len], bufsz - len,
+				 " PRI     WIDTH     FOM     FREQ\n");
+		index = p->index;
+		for (i = 0; i < p->count; i++) {
+			struct radar_pulse *pulse;
+
+			if (index > 0)
+				index--;
+			else
+				index = RWNX_RADAR_PULSE_MAX - 1;
+
+			pulse = (struct radar_pulse *)&p->buffer[index];
+
+			len += scnprintf(&buf[len], bufsz - len,
+					 "%05dus  %03dus     %2d%%    %+3dMHz\n",
+					 pulse->rep, 2 * pulse->len,
+					 6 * pulse->fom, 2 * pulse->freq);
+		}
+	}
+
+	len += rwnx_radar_dump_pattern_detector(&buf[len], bufsz - len,
+						&priv->radar, rd_idx);
+
+	spin_unlock_bh(&priv->radar.lock);
+
+	read = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+
+	kfree(buf);
+
+	return read;
+}
+
+static ssize_t rwnx_dbgfs_pulses_prim_read(struct file *file,
+					   char __user *user_buf, size_t count,
+					   loff_t *ppos)
+{
+	return rwnx_dbgfs_pulses_read(file, user_buf, count, ppos, 0);
+}
+
+DEBUGFS_READ_FILE_OPS(pulses_prim);
+
+static ssize_t rwnx_dbgfs_pulses_sec_read(struct file *file,
+					  char __user *user_buf, size_t count,
+					  loff_t *ppos)
+{
+	return rwnx_dbgfs_pulses_read(file, user_buf, count, ppos, 1);
+}
+
+DEBUGFS_READ_FILE_OPS(pulses_sec);
+
+static ssize_t rwnx_dbgfs_detected_read(struct file *file,
+					char __user *user_buf, size_t count,
+					loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char *buf;
+	int bufsz, len = 0;
+	ssize_t read;
+
+	if (*ppos != 0)
+		return 0;
+
+	bufsz = 5; // RIU:\n
+	bufsz +=
+		rwnx_radar_dump_radar_detected(NULL, 0, &priv->radar, RWNX_RADAR_RIU);
+
+	if (priv->phy.cnt > 1) {
+		bufsz += 5; // FCU:\n
+		bufsz += rwnx_radar_dump_radar_detected(NULL, 0, &priv->radar,
+							RWNX_RADAR_FCU);
+	}
+
+	buf = kmalloc(bufsz, GFP_KERNEL);
+	if (!buf)
+		return 0;
+
+	len = scnprintf(&buf[len], bufsz, "RIU:\n");
+	len += rwnx_radar_dump_radar_detected(&buf[len], bufsz - len, &priv->radar,
+					      RWNX_RADAR_RIU);
+
+	if (priv->phy.cnt > 1) {
+		len += scnprintf(&buf[len], bufsz - len, "FCU:\n");
+		len += rwnx_radar_dump_radar_detected(&buf[len], bufsz - len,
+						      &priv->radar, RWNX_RADAR_FCU);
+	}
+
+	read = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+
+	kfree(buf);
+
+	return read;
+}
+
+DEBUGFS_READ_FILE_OPS(detected);
+
+static ssize_t rwnx_dbgfs_enable_read(struct file *file, char __user *user_buf,
+				      size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[32];
+	int ret;
+	ssize_t read;
+
+	ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count),
+			"RIU=%d FCU=%d\n", priv->radar.dpd[RWNX_RADAR_RIU]->enabled,
+			priv->radar.dpd[RWNX_RADAR_FCU]->enabled);
+
+	read = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+	return read;
+}
+
+static ssize_t rwnx_dbgfs_enable_write(struct file *file,
+				       const char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[32];
+	int val;
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+
+	if (sscanf(buf, "RIU=%d", &val) > 0)
+		rwnx_radar_detection_enable(&priv->radar, val, RWNX_RADAR_RIU);
+
+	if (sscanf(buf, "FCU=%d", &val) > 0)
+		rwnx_radar_detection_enable(&priv->radar, val, RWNX_RADAR_FCU);
+
+	return count;
+}
+
+DEBUGFS_READ_FUNC(enable);
+DEBUGFS_WRITE_FUNC(enable);
+static const struct file_operations rwnx_dbgfs_enable_ops = {
+	.write = rwnx_dbgfs_enable_write,
+	.read = rwnx_dbgfs_enable_read,
+	.open = simple_open,
+	.llseek = generic_file_llseek,
+}
+
+static ssize_t rwnx_dbgfs_band_read(struct file *file, char __user *user_buf,
+				    size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[32];
+	int ret;
+	ssize_t read;
+
+	ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), "BAND=%d\n",
+			priv->phy.sec_chan.band);
+
+	read = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+	return read;
+}
+
+static ssize_t rwnx_dbgfs_band_write(struct file *file,
+				     const char __user *user_buf, size_t count,
+				     loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[32];
+	int val;
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+	int band_max = NL80211_BAND_5GHZ;
+
+	if (priv->band_5g_support)
+		band_max = NL80211_BAND_5GHZ + 1;
+
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+
+	if (kstrtoint(buf, 10, &val) == 0 && val >= 0 && val < band_max)
+		priv->phy.sec_chan.band = val;
+
+	return count;
+}
+
+DEBUGFS_READ_FUNC(band);
+DEBUGFS_WRITE_FUNC(band);
+static const struct file_operations rwnx_dbgfs_band_ops = {
+	.write = rwnx_dbgfs_band_write,
+	.read = rwnx_dbgfs_band_read,
+	.open = simple_open,
+	.llseek = generic_file_llseek,
+}
+
+static ssize_t rwnx_dbgfs_type_read(struct file *file, char __user *user_buf,
+				    size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[32];
+	int ret;
+	ssize_t read;
+
+	ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), "TYPE=%d\n",
+			priv->phy.sec_chan.type);
+
+	read = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+	return read;
+}
+
+static ssize_t rwnx_dbgfs_type_write(struct file *file,
+				     const char __user *user_buf, size_t count,
+				     loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[32];
+	int val;
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+
+	if (kstrtoint(buf, 10, &val) == 0 && val >= PHY_CHNL_BW_20 &&
+	    val <= PHY_CHNL_BW_80P80)
+		priv->phy.sec_chan.type = val;
+
+	return count;
+}
+
+DEBUGFS_READ_FUNC(type);
+DEBUGFS_WRITE_FUNC(type);
+static const struct file_operations rwnx_dbgfs_type_ops = {
+	.write = rwnx_dbgfs_type_write,
+	.read = rwnx_dbgfs_type_read,
+	.open = simple_open,
+	.llseek = generic_file_llseek,
+}
+
+static ssize_t rwnx_dbgfs_prim20_read(struct file *file, char __user *user_buf,
+				      size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[32];
+	int ret;
+	ssize_t read;
+
+	ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count),
+			"PRIM20=%dMHz\n", priv->phy.sec_chan.prim20_freq);
+
+	read = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+	return read;
+}
+
+static ssize_t rwnx_dbgfs_prim20_write(struct file *file,
+				       const char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[32];
+	int val;
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+
+	if (kstrtoint(buf, 10, &val) == 0)
+		priv->phy.sec_chan.prim20_freq = val;
+
+	return count;
+}
+
+DEBUGFS_READ_FUNC(prim20);
+DEBUGFS_WRITE_FUNC(prim20);
+static const struct file_operations rwnx_dbgfs_prim20_ops = {
+	.write = rwnx_dbgfs_prim20_write,
+	.read = rwnx_dbgfs_prim20_read,
+	.open = simple_open,
+	.llseek = generic_file_llseek,
+}
+
+static ssize_t rwnx_dbgfs_center1_read(struct file *file, char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[32];
+	int ret;
+	ssize_t read;
+
+	ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count),
+			"CENTER1=%dMHz\n", priv->phy.sec_chan.center_freq1);
+
+	read = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+	return read;
+}
+
+static ssize_t rwnx_dbgfs_center1_write(struct file *file,
+					const char __user *user_buf,
+					size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[32];
+	int val;
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+
+	if (kstrtoint(buf, 10, &val) == 0)
+		priv->phy.sec_chan.center_freq1 = val;
+
+	return count;
+}
+
+DEBUGFS_READ_FUNC(center1);
+DEBUGFS_WRITE_FUNC(center1);
+static const struct file_operations rwnx_dbgfs_center1_ops = {
+	.write = rwnx_dbgfs_center1_write,
+	.read = rwnx_dbgfs_center1_read,
+	.open = simple_open,
+	.llseek = generic_file_llseek,
+}
+
+static ssize_t rwnx_dbgfs_center2_read(struct file *file, char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[32];
+	int ret;
+	ssize_t read;
+
+	ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count),
+			"CENTER2=%dMHz\n", priv->phy.sec_chan.center_freq2);
+
+	read = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+	return read;
+}
+
+static ssize_t rwnx_dbgfs_center2_write(struct file *file,
+					const char __user *user_buf,
+					size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[32];
+	int val;
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+
+	if (kstrtoint(buf, 10, &val) == 0)
+		priv->phy.sec_chan.center_freq2 = val;
+
+	return count;
+}
+
+DEBUGFS_READ_FUNC(center2);
+DEBUGFS_WRITE_FUNC(center2);
+static const struct file_operations rwnx_dbgfs_center2_ops = {
+	.write = rwnx_dbgfs_center2_write,
+	.read = rwnx_dbgfs_center2_read,
+	.open = simple_open,
+	.llseek = generic_file_llseek,
+}
+
+static ssize_t rwnx_dbgfs_set_read(struct file *file, char __user *user_buf,
+				   size_t count, loff_t *ppos)
+{
+	return 0;
+}
+
+static ssize_t rwnx_dbgfs_set_write(struct file *file,
+				    const char __user *user_buf, size_t count,
+				    loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+
+	rwnx_send_set_channel(priv, 1, NULL);
+	rwnx_radar_detection_enable(&priv->radar, RWNX_RADAR_DETECT_ENABLE,
+				    RWNX_RADAR_FCU);
+
+	return count;
+}
+
+DEBUGFS_READ_FUNC(set);
+DEBUGFS_WRITE_FUNC(set);
+static const struct file_operations rwnx_dbgfs_set_ops = {
+	.write = rwnx_dbgfs_set_write,
+	.read = rwnx_dbgfs_set_read,
+	.open = simple_open,
+	.llseek = generic_file_llseek,
+}
+#endif /* CONFIG_RWNX_RADAR */
+
+static ssize_t rwnx_dbgfs_regdbg_write(struct file *file,
+				       const char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[32];
+	u32 addr, val, oper;
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+	struct dbg_mem_read_cfm mem_read_cfm;
+	int ret;
+
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+
+	if (sscanf(buf, "%x %x %x", &oper, &addr, &val) > 0)
+		AICWFDBG(LOGDEBUG, "addr=%x, val=%x,oper=%d\n", addr, val, oper);
+
+	if (oper == 0) {
+		ret = rwnx_send_dbg_mem_read_req(priv, addr, &mem_read_cfm);
+		AICWFDBG(LOGDEBUG, "[0x%x] = [0x%x]\n",
+			 mem_read_cfm.memaddr, mem_read_cfm.memdata);
+	}
+
+	return count;
+}
+
+DEBUGFS_WRITE_FILE_OPS(regdbg);
+
+static ssize_t rwnx_dbgfs_vendor_hwconfig_write(struct file *file,
+						const char __user *user_buf,
+						size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[64];
+	s32_l addr[13];
+	s32_l addr_out[12];
+	u32_l hwconfig_id;
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+	int ret;
+
+	AICWFDBG(LOGDEBUG, "%s function_show\n", __func__);
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+	ret = sscanf(buf, "%x %x %x %x %x %x %x %x %x %x %x %x %x %x", &hwconfig_id,
+		     &addr[0], &addr[1], &addr[2], &addr[3], &addr[4], &addr[5],
+		     &addr[6], &addr[7], &addr[8], &addr[9], &addr[10], &addr[11],
+		     &addr[12]);
+	if (ret > 14) {
+		AICWFDBG(LOGDEBUG, "param error > 14\n");
+	} else {
+		switch (hwconfig_id) {
+		case 0:
+			if (ret != 5) {
+				AICWFDBG(LOGDEBUG, "param error  != 5\n");
+				break;
+			}
+			ret = rwnx_send_vendor_hwconfig_req(priv, hwconfig_id, addr, NULL);
+			AICWFDBG(LOGDEBUG, "ACS_TXOP_REQ bk:0x%x be:0x%x vi:0x%x vo:0x%x\n",
+				 addr[0], addr[1], addr[2], addr[3]);
+			break;
+		case 1:
+			if (ret != 14) {
+				AICWFDBG(LOGDEBUG, "param error  != 14\n");
+				break;
+			}
+			addr[12] = ~addr[12] + 1;
+			ret = rwnx_send_vendor_hwconfig_req(priv, hwconfig_id, addr, NULL);
+			AICWFDBG(LOGDEBUG,
+				 "CHANNEL_ACCESS_REQ edca:%x,%x,%x,%x, vif:%x, retry_cnt:%x, rts:%x, long_nav:%x, cfe:%x, rc_retry_cnt:%x:%x:%x ccademod_th %x\n",
+				 addr[0], addr[1], addr[2], addr[3], addr[4], addr[5],
+				 addr[6], addr[7], addr[8], addr[9], addr[10], addr[11],
+				 addr[12]);
+			break;
+		case 2:
+			if (ret != 7) {
+				AICWFDBG(LOGDEBUG, "param error  != 7\n");
+				break;
+			}
+			ret = rwnx_send_vendor_hwconfig_req(priv, hwconfig_id, addr, NULL);
+			AICWFDBG(LOGDEBUG,
+				 "MAC_TIMESCALE_REQ sifsA:%x,sifsB:%x,slot:%x,ofdm_delay:%x,long_delay:%x,short_delay:%x\n",
+				 addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+			break;
+		case 3:
+			if (ret != 6) {
+				AICWFDBG(LOGDEBUG, "param error  != 6\n");
+				break;
+			}
+			addr[1] = ~addr[1] + 1;
+			addr[2] = ~addr[2] + 1;
+			addr[3] = ~addr[3] + 1;
+			addr[4] = ~addr[4] + 1;
+			ret = rwnx_send_vendor_hwconfig_req(priv, hwconfig_id, addr, NULL);
+			AICWFDBG(LOGDEBUG,
+				 "CCA_THRESHOLD_REQ auto_cca:%d, cca20p_rise:%d cca20s_rise:%d cca20p_fail:%d cca20s_fail:%d\n",
+				 addr[0], addr[1], addr[2], addr[3], addr[4]);
+			break;
+		case 4: // BWMODE_REQ
+			if (ret != 2) {
+				AICWFDBG(LOGDEBUG, "param error != 2\n");
+			} else {
+				ret = rwnx_send_vendor_hwconfig_req(priv, hwconfig_id, addr,
+								    NULL);
+				AICWFDBG(LOGDEBUG, "BWMODE_REQ md=%d\n", addr[0]);
+			}
+			break;
+		case 5: // CHIP_TEMP_GET_REQ
+			if (ret != 1) {
+				AICWFDBG(LOGDEBUG, "param error != 1\n");
+			} else {
+				ret = rwnx_send_vendor_hwconfig_req(priv, hwconfig_id, addr,
+								    addr_out);
+				AICWFDBG(LOGDEBUG, "CHIP_TEMP_GET_REQ degree=%d\n", addr_out[0]);
+			}
+			break;
+		case 6: // AP_PS_LEVEL_SET_REQ
+			if (ret != 2) {
+				AICWFDBG(LOGDEBUG, "param error != 2\n");
+			} else {
+				ret = rwnx_send_vendor_hwconfig_req(priv, hwconfig_id, addr,
+								    NULL);
+				AICWFDBG(LOGDEBUG, "AP_PS_LEVEL_SET_REQ lvl=%d\n", addr[0]);
+			}
+			break;
+		default:
+			AICWFDBG(LOGDEBUG, "param error\n");
+			break;
+		}
+		if (ret)
+			AICWFDBG(LOGDEBUG, "rwnx_send_vendor_hwconfig_req fail: %x\n", ret);
+	}
+
+	return count;
+}
+
+DEBUGFS_WRITE_FILE_OPS(vendor_hwconfig);
+
+static ssize_t rwnx_dbgfs_vendor_swconfig_write(struct file *file,
+						const char __user *user_buf,
+						size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[64];
+	s32_l addr[12];
+	s32_l addr_out[12];
+	u32_l swconfig_id;
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+	int ret;
+
+	AICWFDBG(LOGDEBUG, "%s function_show\n", __func__);
+
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+	ret = sscanf(buf, "%x %x %x", &swconfig_id, &addr[0], &addr[1]);
+	if (ret > 3) {
+		AICWFDBG(LOGDEBUG, "param error > 3\n");
+	} else {
+		switch (swconfig_id) {
+		case 0: // BCN_CFG_REQ
+			if (ret != 2) {
+				AICWFDBG(LOGDEBUG, "param error != 2\n");
+			} else {
+				ret = rwnx_send_vendor_swconfig_req(priv, swconfig_id, addr,
+								    addr_out);
+				AICWFDBG(LOGDEBUG, "BCN_CFG_REQ set_en=%d, get_en=%d\n",
+					 addr[0], addr_out[0]);
+			}
+			break;
+
+		case 1: // TEMP_COMP_SET_REQ
+			if (ret != 3) {
+				AICWFDBG(LOGDEBUG, "param error != 3\n");
+			} else {
+				ret = rwnx_send_vendor_swconfig_req(priv, swconfig_id, addr,
+								    addr_out);
+				AICWFDBG(LOGDEBUG,
+					 "TEMP_COMP_SET_REQ set_en=%d, tmr=%dms, get_st=%d\n",
+					 addr[0], addr[1], addr_out[0]);
+			}
+			break;
+
+		case 2: // TEMP_COMP_GET_REQ
+			if (ret != 1) {
+				AICWFDBG(LOGDEBUG, "param error != 1\n");
+			} else {
+				ret = rwnx_send_vendor_swconfig_req(priv, swconfig_id, addr,
+								    addr_out);
+				AICWFDBG(LOGDEBUG, "TEMP_COMP_GET_REQ get_st=%d, degree=%d\n",
+					 addr_out[0], addr_out[1]);
+			}
+			break;
+
+		case 3: // EXT_FLAGS_SET_REQ
+			if (ret != 2) {
+				AICWFDBG(LOGDEBUG, "param error != 2\n");
+			} else {
+				ret = rwnx_send_vendor_swconfig_req(priv, swconfig_id, addr,
+								    addr_out);
+				AICWFDBG(LOGDEBUG,
+					 "EXT_FLAGS_SET_REQ set ext_flags=0x%x, get ext_flags=0x%x\n",
+					 addr[0], addr_out[0]);
+			}
+			break;
+
+		case 4: // EXT_FLAGS_GET_REQ
+			if (ret != 1) {
+				AICWFDBG(LOGDEBUG, "param error != 1\n");
+			} else {
+				ret = rwnx_send_vendor_swconfig_req(priv, swconfig_id, addr,
+								    addr_out);
+				AICWFDBG(LOGDEBUG, "EXT_FLAGS_GET_REQ get ext_flags=0x%x\n",
+					 addr_out[0]);
+			}
+			break;
+
+		case 5: // EXT_FLAGS_MASK_SET_REQ
+			if (ret != 3) {
+				AICWFDBG(LOGDEBUG, "param error != 3\n");
+			} else {
+				ret = rwnx_send_vendor_swconfig_req(priv, swconfig_id, addr,
+								    addr_out);
+				AICWFDBG(LOGDEBUG,
+					 "EXT_FLAGS_MASK_SET_REQ set ext_flags mask=0x%x, val=0x%x, get ext_flags=0x%x\n",
+					 addr[0], addr[1], addr_out[0]);
+			}
+			break;
+
+		default:
+			AICWFDBG(LOGDEBUG, "param error\n");
+			break;
+		}
+
+		if (ret)
+			AICWFDBG(LOGDEBUG, "rwnx_send_vendor_swconfig_req fail: %x\n", ret);
+	}
+
+	return count;
+}
+
+DEBUGFS_WRITE_FILE_OPS(vendor_swconfig);
+
+static ssize_t rwnx_dbgfs_agg_disable_write(struct file *file,
+					    const char __user *user_buf,
+					    size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	char buf[64];
+	int agg_disable, agg_disable_rx, sta_idx;
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+	int ret;
+
+	AICWFDBG(LOGDEBUG, "%s function_show\n", __func__);
+
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+	ret = sscanf(buf, "%d %d %d", &agg_disable, &agg_disable_rx, &sta_idx);
+	if (ret > 3 || ret < 2) {
+		AICWFDBG(LOGDEBUG, "param error: cnt=%d\n", ret);
+	} else {
+		if (ret < 3)
+			sta_idx = RWNX_INVALID_STA;
+		AICWFDBG(LOGDEBUG, "disable_agg: T=%d, R=%d, staidx=%d\n", agg_disable,
+			 agg_disable_rx, sta_idx);
+		ret = rwnx_send_disable_agg_req(priv, (u8_l)agg_disable,
+						(u8_l)agg_disable_rx, (u8_l)sta_idx);
+		if (ret)
+			AICWFDBG(LOGDEBUG, "rwnx_send_disable_agg_req fail: %d\n", ret);
+	}
+
+	return count;
+}
+
+DEBUGFS_WRITE_FILE_OPS(agg_disable);
+
+static ssize_t rwnx_dbgfs_set_roc_write(struct file *file,
+					const char __user *user_buf,
+					size_t count, loff_t *ppos)
+{
+	struct rwnx_hw *priv = file->private_data;
+	struct rwnx_vif *vif = NULL;
+	char buf[64];
+	int roc_start, chan_freq, duration;
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+	int ret;
+	int if_type = NL80211_IFTYPE_STATION;
+
+	AICWFDBG(LOGDEBUG, "%s function_show\n", __func__);
+
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+	ret = sscanf(buf, "%d %d %d", &roc_start, &chan_freq, &duration);
+
+	list_for_each_entry(vif, &priv->vifs, list) {
+		if (RWNX_VIF_TYPE(vif) == if_type)
+			break;
+	}
+	if (ret > 3 || ret < 1) {
+		AICWFDBG(LOGDEBUG, "param error: cnt=%d\n", ret);
+	} else if (vif) {
+		struct ieee80211_channel chan_entry = {
+			0,
+		};
+		struct ieee80211_channel *chan = &chan_entry;
+		struct mm_remain_on_channel_cfm roc_cfm;
+
+		if (ret < 3) {
+			duration = 2000;
+			if (ret < 2)
+				chan_freq = 2412;
+		}
+		AICWFDBG(LOGDEBUG, "set_roc: start=%d, freq=%d\n", roc_start, chan_freq);
+		if (roc_start) {
+			if (chan_freq <= 2484)
+				chan->band = NL80211_BAND_2GHZ;
+			else
+				chan->band = NL80211_BAND_5GHZ;
+			chan->center_freq = chan_freq;
+			chan->max_power = 18;
+			ret = rwnx_send_roc(priv, vif, chan, duration, &roc_cfm);
+			if (ret)
+				AICWFDBG(LOGDEBUG, "rwnx_send_roc fail: %d\n", ret);
+			else
+				AICWFDBG(LOGDEBUG, "roc_cfm: opcode=%x, st=%d, idx=%d\n",
+					 roc_cfm.op_code,
+					 roc_cfm.status, roc_cfm.chan_ctxt_index);
+		} else {
+			ret = rwnx_send_cancel_roc(priv);
+			if (ret)
+				AICWFDBG(LOGDEBUG, "rwnx_send_cancel_roc fail: %d\n", ret);
+		}
+	}
+
+	return count;
+}
+
+DEBUGFS_WRITE_FILE_OPS(set_roc);
+
+#ifdef CONFIG_RWNX_FULLMAC
+
+#define LINE_MAX_SZ 150
+
+struct st {
+	char line[LINE_MAX_SZ + 1];
+	unsigned int r_idx;
+};
+
+static int compare_idx(const void *st1, const void *st2)
+{
+	int index1 = ((struct st *)st1)->r_idx;
+	int index2 = ((struct st *)st2)->r_idx;
+
+	if (index1 > index2)
+		return 1;
+	if (index1 < index2)
+		return -1;
+
+	return 0;
+}
+
+static const int ru_size[] = {26, 52, 106, 242, 484, 996};
+
+static int print_rate(char *buf, int size, int format, int nss, int mcs, int bw,
+		      int sgi, int pre, int *r_idx)
+{
+	int res = 0;
+	int bitrates_cck[4] = {10, 20, 55, 110};
+	int bitrates_ofdm[8] = {6, 9, 12, 18, 24, 36, 48, 54};
+	char he_gi[3][4] = {"0.8", "1.6", "3.2"};
+
+	if (format < FORMATMOD_HT_MF) {
+		if (mcs < 4) {
+			if (r_idx) {
+				*r_idx = (mcs * 2) + pre;
+				res = scnprintf(buf, size - res, "%3d ", *r_idx);
+			}
+			res += scnprintf(&buf[res], size - res,
+					 "L-CCK/%cP      %2u.%1uM    ", pre > 0 ? 'L' : 'S',
+					 bitrates_cck[mcs] / 10, bitrates_cck[mcs] % 10);
+		} else {
+			mcs -= 4;
+			if (r_idx) {
+				*r_idx = N_CCK + mcs;
+				res = scnprintf(buf, size - res, "%3d ", *r_idx);
+			}
+			res += scnprintf(&buf[res], size - res, "L-OFDM        %2u.0M    ",
+					 bitrates_ofdm[mcs]);
+		}
+	} else if (format < FORMATMOD_VHT) {
+		if (r_idx) {
+			*r_idx = N_CCK + N_OFDM + nss * 32 + mcs * 4 + bw * 2 + sgi;
+			res = scnprintf(buf, size - res, "%3d ", *r_idx);
+		}
+		mcs += nss * 8;
+		res += scnprintf(&buf[res], size - res, "HT%d/%cGI       MCS%-2d   ",
+				 20 * (1 << bw), sgi ? 'S' : 'L', mcs);
+	} else if (format == FORMATMOD_VHT) {
+		if (r_idx) {
+			*r_idx = N_CCK + N_OFDM + N_HT + nss * 80 + mcs * 8 + bw * 2 + sgi;
+			res = scnprintf(buf, size - res, "%3d ", *r_idx);
+		}
+		res += scnprintf(&buf[res], size - res, "VHT%d/%cGI%*cMCS%d/%1d  ",
+				 20 * (1 << bw), sgi ? 'S' : 'L', bw > 2 ? 5 : 6, ' ',
+				 mcs, nss + 1);
+	} else if (format == FORMATMOD_HE_SU) {
+		if (r_idx) {
+			*r_idx = N_CCK + N_OFDM + N_HT + N_VHT + nss * 144 + mcs * 12 +
+					bw * 3 + sgi;
+			res = scnprintf(buf, size - res, "%3d ", *r_idx);
+		}
+		res += scnprintf(&buf[res], size - res, "HE%d/GI%s%*cMCS%d/%1d%*c",
+				 20 * (1 << bw), he_gi[sgi], bw > 2 ? 4 : 5, ' ', mcs,
+				 nss + 1, mcs > 9 ? 1 : 2, ' ');
+	} else {
+		if (r_idx) {
+			*r_idx = N_CCK + N_OFDM + N_HT + N_VHT + N_HE_SU + nss * 216 +
+					mcs * 18 + bw * 3 + sgi;
+			res = scnprintf(buf, size - res, "%3d ", *r_idx);
+		}
+		res += scnprintf(&buf[res], size - res, "HEMU-%d/GI%s%*cMCS%d/%1d%*c",
+				 ru_size[bw], he_gi[sgi], bw > 1 ? 1 : 2, ' ', mcs,
+				 nss + 1, mcs > 9 ? 1 : 2, ' ');
+	}
+
+	return res;
+}
+
+static int print_rate_from_cfg(char *buf, int size, u32 rate_config, int *r_idx,
+			       int ru_size)
+{
+	union rwnx_rate_ctrl_info *r_cfg =
+		(union rwnx_rate_ctrl_info *)&rate_config;
+	union rwnx_mcs_index *mcs_index = (union rwnx_mcs_index *)&rate_config;
+	unsigned int ft, pre, gi, bw, nss, mcs, len;
+
+	ft = r_cfg->format_mod_tx;
+	pre = r_cfg->gi_and_pre_type_tx >> 1;
+	gi = r_cfg->gi_and_pre_type_tx;
+	bw = r_cfg->bw_tx;
+
+	if (ft == FORMATMOD_HE_MU) {
+		mcs = mcs_index->he.mcs;
+		nss = mcs_index->he.nss;
+		bw = ru_size;
+	} else if (ft == FORMATMOD_HE_SU) {
+		mcs = mcs_index->he.mcs;
+		nss = mcs_index->he.nss;
+	} else if (ft == FORMATMOD_VHT) {
+		mcs = mcs_index->vht.mcs;
+		nss = mcs_index->vht.nss;
+	} else if (ft >= FORMATMOD_HT_MF) {
+		mcs = mcs_index->ht.mcs;
+		nss = mcs_index->ht.nss;
+	} else {
+		mcs = mcs_index->legacy;
+		nss = 0;
+	}
+
+	len = print_rate(buf, size, ft, nss, mcs, bw, gi, pre, r_idx);
+	return len;
+}
+
+static void idx_to_rate_cfg(int idx, union rwnx_rate_ctrl_info *r_cfg,
+			    int *ru_size)
+{
+	r_cfg->value = 0;
+	if (idx < N_CCK) {
+		r_cfg->format_mod_tx = FORMATMOD_NON_HT;
+		r_cfg->gi_and_pre_type_tx = (idx & 1) << 1;
+		r_cfg->mcs_index_tx = idx / 2;
+	} else if (idx < (N_CCK + N_OFDM)) {
+		r_cfg->format_mod_tx = FORMATMOD_NON_HT;
+		r_cfg->mcs_index_tx = idx - N_CCK + 4;
+	} else if (idx < (N_CCK + N_OFDM + N_HT)) {
+		union rwnx_mcs_index *r = (union rwnx_mcs_index *)r_cfg;
+
+		idx -= (N_CCK + N_OFDM);
+		r_cfg->format_mod_tx = FORMATMOD_HT_MF;
+		r->ht.nss = idx / (8 * 2 * 2);
+		r->ht.mcs = (idx % (8 * 2 * 2)) / (2 * 2);
+		r_cfg->bw_tx = ((idx % (8 * 2 * 2)) % (2 * 2)) / 2;
+		r_cfg->gi_and_pre_type_tx = idx & 1;
+	} else if (idx < (N_CCK + N_OFDM + N_HT + N_VHT)) {
+		union rwnx_mcs_index *r = (union rwnx_mcs_index *)r_cfg;
+
+		idx -= (N_CCK + N_OFDM + N_HT);
+		r_cfg->format_mod_tx = FORMATMOD_VHT;
+		r->vht.nss = idx / (10 * 4 * 2);
+		r->vht.mcs = (idx % (10 * 4 * 2)) / (4 * 2);
+		r_cfg->bw_tx = ((idx % (10 * 4 * 2)) % (4 * 2)) / 2;
+		r_cfg->gi_and_pre_type_tx = idx & 1;
+	} else if (idx < (N_CCK + N_OFDM + N_HT + N_VHT + N_HE_SU)) {
+		union rwnx_mcs_index *r = (union rwnx_mcs_index *)r_cfg;
+
+		idx -= (N_CCK + N_OFDM + N_HT + N_VHT);
+		r_cfg->format_mod_tx = FORMATMOD_HE_SU;
+		r->vht.nss = idx / (12 * 4 * 3);
+		r->vht.mcs = (idx % (12 * 4 * 3)) / (4 * 3);
+		r_cfg->bw_tx = ((idx % (12 * 4 * 3)) % (4 * 3)) / 3;
+		r_cfg->gi_and_pre_type_tx = idx % 3;
+	} else {
+		union rwnx_mcs_index *r = (union rwnx_mcs_index *)r_cfg;
+
+		WARN_ON_ONCE(!ru_size);
+
+		idx -= (N_CCK + N_OFDM + N_HT + N_VHT + N_HE_SU);
+		r_cfg->format_mod_tx = FORMATMOD_HE_MU;
+		r->vht.nss = idx / (12 * 6 * 3);
+		r->vht.mcs = (idx % (12 * 6 * 3)) / (6 * 3);
+		*ru_size = ((idx % (12 * 6 * 3)) % (6 * 3)) / 3;
+		r_cfg->gi_and_pre_type_tx = idx % 3;
+		r_cfg->bw_tx = 0;
+	}
+}
+
+static void idx_to_rate_cfg1(unsigned int formatmod, unsigned int mcs,
+			     unsigned int nss, unsigned int bw_tx,
+			     unsigned int gi, union rwnx_rate_ctrl_info *r_cfg,
+			     int *ru_size)
+{
+	r_cfg->value = 0;
+
+	switch (formatmod) {
+	case FORMATMOD_NON_HT: {
+		r_cfg->format_mod_tx = formatmod;
+		r_cfg->gi_and_pre_type_tx = 1;
+		r_cfg->mcs_index_tx = mcs;
+		break;
+	}
+	case FORMATMOD_NON_HT_DUP_OFDM: {
+		r_cfg->format_mod_tx = formatmod;
+		r_cfg->gi_and_pre_type_tx = gi;
+		r_cfg->mcs_index_tx = mcs;
+		break;
+	}
+	case FORMATMOD_HT_MF: {
+		union rwnx_mcs_index *r = (union rwnx_mcs_index *)r_cfg;
+
+		r_cfg->formati_mod_tx = formatmod;
+		r->ht.nss = nss;
+		r->ht.mcs = mcs;
+		r_cfg->bw_tx = bw_tx;
+		r_cfg->gi_and_pre_type_tx = gi;
+		break;
+	}
+	case FORMATMOD_VHT:
+	case FORMATMOD_HE_SU: {
+		union rwnx_mcs_index *r = (union rwnx_mcs_index *)r_cfg;
+
+		r_cfg->format_mod_tx = formatmod;
+		r->vht.nss = nss;
+		r->vht.mcs = mcs;
+		r_cfg->bw_tx = bw_tx;
+		r_cfg->gi_and_pre_type_tx = gi;
+		break;
+	}
+	case FORMATMOD_HE_MU: {
+		union rwnx_mcs_index *r = (union rwnx_mcs_index *)r_cfg;
+
+		r_cfg->format_mod_tx = formatmod;
+		r->he.nss = nss;
+		r->he.mcs = mcs;
+		r_cfg->bw_tx = 0;
+		r_cfg->gi_and_pre_type_tx = gi;
+		break;
+	}
+	default:
+		AICWFDBG(LOGDEBUG, "Don't have the formatmod");
+	}
+}
+
+static ssize_t rwnx_dbgfs_rc_stats_read(struct file *file,
+					char __user *user_buf, size_t count,
+					loff_t *ppos)
+{
+	struct rwnx_sta *sta = NULL;
+	struct rwnx_hw *priv = file->private_data;
+	char *buf;
+	int bufsz, len = 0;
+	ssize_t read;
+	int i = 0;
+	int error = 0;
+	struct me_rc_stats_cfm me_rc_stats_cfm;
+	unsigned int no_samples;
+	struct st *st;
+	u8 mac[6];
+	int ret = 0;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* everything should fit in one call */
+	if (*ppos)
+		return 0;
+
+	/* Get the station index from MAC address */
+	ret = sscanf(file->f_path.dentry->d_parent->d_iname,
+		     "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac[0],
+		     &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
+	if (ret != 6)
+		pr_err("Invalid MAC format in d_iname: %s\n",
+		       file->f_path.dentry->d_parent->d_iname);
+
+	sta = rwnx_get_sta(priv, mac);
+	if (!sta)
+		return 0;
+
+	/* Forward the information to the LMAC */
+	error = rwnx_send_me_rc_stats(priv, sta->sta_idx, &me_rc_stats_cfm);
+	if (error)
+		return error;
+
+	no_samples = me_rc_stats_cfm.no_samples;
+	if (no_samples == 0)
+		return 0;
+
+	bufsz = no_samples * LINE_MAX_SZ + 500;
+
+	buf = kmalloc(bufsz + 1, GFP_ATOMIC);
+	if (!buf)
+		return 0;
+
+	st = kmalloc_array(no_samples, sizeof(*st), GFP_ATOMIC);
+	if (!st) {
+		kfree(buf);
+		return 0;
+	}
+
+	for (i = 0; i < no_samples; i++) {
+		unsigned int tp, eprob;
+
+		len = print_rate_from_cfg(st[i].line, LINE_MAX_SZ,
+					  me_rc_stats_cfm.rate_stats[i].rate_config,
+					  &st[i].r_idx, 0);
+
+		if (me_rc_stats_cfm.sw_retry_step != 0) {
+			len +=
+			scnprintf(&st[i].line[len], LINE_MAX_SZ - len, "%c",
+				  me_rc_stats_cfm.retry_step_idx[me_rc_stats_cfm.sw_retry_step] ==
+					i ? '*' : ' ');
+		} else {
+			len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, " ");
+		}
+		len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, "%c",
+				 me_rc_stats_cfm.retry_step_idx[0] == i ? 'T' : ' ');
+		len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, "%c",
+				 me_rc_stats_cfm.retry_step_idx[1] == i ? 't' : ' ');
+		len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, "%c ",
+				 me_rc_stats_cfm.retry_step_idx[2] == i ? 'P' : ' ');
+
+		tp = me_rc_stats_cfm.tp[i] / 10;
+		len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, " %4u.%1u",
+				 tp / 10, tp % 10);
+
+		eprob = ((me_rc_stats_cfm.rate_stats[i].probability * 1000) >> 16) + 1;
+		len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len,
+				 "  %4u.%1u %5u(%6u)  %6u", eprob / 10, eprob % 10,
+				 me_rc_stats_cfm.rate_stats[i].success,
+				 me_rc_stats_cfm.rate_stats[i].attempts,
+				 me_rc_stats_cfm.rate_stats[i].sample_skipped);
+	}
+	len = scnprintf(buf, bufsz,
+			"\nTX rate info for %02X:%02X:%02X:%02X:%02X:%02X:\n",
+			mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+
+	len += scnprintf(&buf[len], bufsz - len,
+			 " #  type           rate             tpt   eprob    ok(   tot)   skipped\n");
+
+	// add sorted statistics to the buffer
+	sort(st, no_samples, sizeof(st[0]), compare_idx, NULL);
+	for (i = 0; i < no_samples; i++)
+		len += scnprintf(&buf[len], bufsz - len, "%s\n", st[i].line);
+
+	// display HE TB statistics if any
+	if (me_rc_stats_cfm.rate_stats[RC_HE_STATS_IDX].rate_config != 0) {
+		unsigned int tp, eprob;
+		struct rc_rate_stats *rate_stats =
+			&me_rc_stats_cfm.rate_stats[RC_HE_STATS_IDX];
+		int ru_index = rate_stats->ru_and_length & 0x07;
+		int ul_length = rate_stats->ru_and_length >> 3;
+
+		len += scnprintf(&buf[len], bufsz - len, "\nHE TB rate info:\n");
+
+		len += scnprintf(&buf[len], bufsz - len,
+						 "    type           rate             tpt   eprob    ok(   tot)   ul_length\n    ");
+		len += print_rate_from_cfg(&buf[len], bufsz - len,
+					   rate_stats->rate_config, NULL, ru_index);
+
+		tp = me_rc_stats_cfm.tp[RC_HE_STATS_IDX] / 10;
+		len += scnprintf(&buf[len], bufsz - len, "      %4u.%1u", tp / 10,
+				 tp % 10);
+
+		eprob = ((rate_stats->probability * 1000) >> 16) + 1;
+		len += scnprintf(&buf[len], bufsz - len, "  %4u.%1u %5u(%6u)  %6u\n",
+				 eprob / 10, eprob % 10, rate_stats->success,
+				 rate_stats->attempts, ul_length);
+	}
+
+	len += scnprintf(&buf[len], bufsz - len, "\n MPDUs AMPDUs AvLen trialP");
+	len += scnprintf(&buf[len], bufsz - len, "\n%6u %6u %3d.%1d %6u\n",
+			 me_rc_stats_cfm.ampdu_len, me_rc_stats_cfm.ampdu_packets,
+			 me_rc_stats_cfm.avg_ampdu_len >> 16,
+			 ((me_rc_stats_cfm.avg_ampdu_len * 10) >> 16) % 10,
+			 me_rc_stats_cfm.sample_wait);
+
+	read = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+
+	kfree(buf);
+	kfree(st);
+
+	return read;
+}
+
+DEBUGFS_READ_FILE_OPS(rc_stats);
+
+static ssize_t rwnx_dbgfs_rc_fixed_rate_idx_write(struct file *file,
+						  const char __user *user_buf,
+						  size_t count, loff_t *ppos)
+{
+	struct rwnx_sta *sta = NULL;
+	struct rwnx_hw *priv = file->private_data;
+	u8 mac[6];
+	char buf[20];
+	int fixed_rate_idx = 1;
+	unsigned int formatmod, mcs, nss, bw_tx, gi;
+	union rwnx_rate_ctrl_info rate_config;
+	union rwnx_rate_ctrl_info *r_cfg = &rate_config;
+	int error = 0;
+	size_t len = min_t(size_t, count, sizeof(buf) - 1);
+	int ret = 0;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* Get the station index from MAC address */
+	ret = sscanf(file->f_path.dentry->d_parent->d_iname,
+		     "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac[0],
+		     &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
+	if (ret != 6)
+		pr_err("Invalid MAC address format: %s\n",
+		       file->f_path.dentry->d_parent->d_iname);
+	// if (mac == NULL)
+	//     return 0;
+	sta = rwnx_get_sta(priv, mac);
+	if (!sta)
+		return 0;
+
+	/* Get the content of the file */
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+	buf[len] = '\0';
+	// sscanf(buf, "%i\n", &fixed_rate_idx);
+	ret = sscanf(buf, "%u %u %u %u %u", &formatmod, &mcs, &nss, &bw_tx, &gi);
+	if (ret != 5)
+		pr_warn("Invalid input: expected 5 values, got %d\n", ret);
+
+	AICWFDBG(LOGDEBUG, "%u %u %u %u %u\n", formatmod, mcs, nss, bw_tx, gi);
+
+	if (formatmod > 6 || mcs > 11 || nss > 8 || bw_tx > 6 || gi > 3) {
+		AICWFDBG(LOGDEBUG, "error parameter");
+		return len;
+	}
+
+	/* Convert rate index into rate configuration */
+	if (fixed_rate_idx < 0 ||
+	    fixed_rate_idx >= (N_CCK + N_OFDM + N_HT + N_VHT + N_HE_SU))
+		// disable fixed rate
+		rate_config.value = (u32)-1;
+	else
+		idx_to_rate_cfg1(formatmod, mcs, nss, bw_tx, gi, &rate_config, NULL);
+
+	AICWFDBG(LOGDEBUG, "formatModTx=%u mcsIndexTx=%u bwTx=%u giAndPreTypeTx=%u\n",
+		 r_cfg->format_mod_tx, r_cfg->mcs_index_tx, r_cfg->bw_tx,
+		 r_cfg->gi_and_pre_type_tx);
+	// Forward the request to the LMAC
+	error = rwnx_send_me_rc_set_rate(priv, sta->sta_idx, (u16)rate_config.value);
+	if (error != 0)
+		return error;
+
+	AICWFDBG(LOGDEBUG, "send success\n");
+	priv->debugfs.rc_config[sta->sta_idx] = (int)rate_config.value;
+	return len;
+}
+
+DEBUGFS_WRITE_FILE_OPS(rc_fixed_rate_idx);
+
+static ssize_t rwnx_dbgfs_last_rx_read(struct file *file, char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct rwnx_sta *sta = NULL;
+	struct rwnx_hw *priv = file->private_data;
+	struct rwnx_rx_rate_stats *rate_stats;
+	char *buf;
+	int bufsz, i, len = 0;
+	ssize_t read;
+	unsigned int fmt, pre, bw, nss, mcs, gi;
+	u8 mac[6];
+	struct rx_vector_1 *last_rx;
+	char hist[] = "##################################################";
+	int hist_len = sizeof(hist) - 1;
+	u8 nrx;
+	int ret = 0;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	/* everything should fit in one call */
+	if (*ppos)
+		return 0;
+
+	/* Get the station index from MAC address */
+	ret = sscanf(file->f_path.dentry->d_parent->d_iname,
+		     "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac[0],
+		     &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
+	if (ret != 6)
+		pr_warn("Invalid MAC string: %s\n",
+			file->f_path.dentry->d_parent->d_iname);
+	sta = rwnx_get_sta(priv, mac);
+	if (!sta)
+		return 0;
+
+	rate_stats = &sta->stats.rx_rate;
+	bufsz = (rate_stats->rate_cnt * (50 + hist_len) + 200);
+	buf = kmalloc(bufsz + 1, GFP_ATOMIC);
+	if (!buf)
+		return 0;
+
+	// Get number of RX paths
+	nrx = (priv->version_cfm.version_phy_1 & MDM_NRX_MASK) >> MDM_NRX_LSB;
+
+	len += scnprintf(buf, bufsz,
+			 "\nRX rate info for %02X:%02X:%02X:%02X:%02X:%02X:\n",
+			 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+
+	// Display Statistics
+	for (i = 0; i < rate_stats->size; i++) {
+		if (rate_stats->table[i]) {
+			union rwnx_rate_ctrl_info rate_config;
+			int percent = (rate_stats->table[i] * 1000) / rate_stats->cpt;
+			int p = 0;
+			int ru_size = 0;
+
+			idx_to_rate_cfg(i, &rate_config, &ru_size);
+			len += print_rate_from_cfg(&buf[len], bufsz - len,
+						   rate_config.value, NULL, ru_size);
+			p = (percent * hist_len) / 1000;
+			len += scnprintf(&buf[len], bufsz - len, ": %6d(%3d.%1d%%)%.*s\n",
+					 rate_stats->table[i], percent / 10, percent % 10,
+					 p, hist);
+		}
+	}
+
+	// Display detailed info of the last received rate
+	last_rx = &sta->stats.last_rx.rx_vect1;
+
+	len += scnprintf(&buf[len], bufsz - len,
+			 "\nLast received rate\n  type         rate    LDPC STBC BEAMFM DCM DOPPLER %s\n",
+			 (nrx > 1) ? "rssi1(dBm) rssi2(dBm)" : "rssi(dBm)");
+
+	fmt = last_rx->format_mod;
+	bw = last_rx->ch_bw;
+	pre = last_rx->pre_type;
+	if (fmt >= FORMATMOD_HE_SU) {
+		mcs = last_rx->he.mcs;
+		nss = last_rx->he.nss;
+		gi = last_rx->he.gi_type;
+		if (fmt == FORMATMOD_HE_MU)
+			bw = last_rx->he.ru_size;
+	} else if (fmt == FORMATMOD_VHT) {
+		mcs = last_rx->vht.mcs;
+		nss = last_rx->vht.nss;
+		gi = last_rx->vht.short_gi;
+	} else if (fmt >= FORMATMOD_HT_MF) {
+		mcs = last_rx->ht.mcs % 8;
+		nss = last_rx->ht.mcs / 8;
+		gi = last_rx->ht.short_gi;
+	} else {
+		mcs = legrates_lut[last_rx->leg_rate].idx;
+		WARN_ON_ONCE(mcs == -1);
+		nss = 0;
+		gi = 0;
+	}
+
+	len += print_rate(&buf[len], bufsz - len, fmt, nss, mcs, bw, gi, pre, NULL);
+
+	/* flags for HT/VHT/HE */
+	if (fmt >= FORMATMOD_HE_SU) {
+		len += scnprintf(&buf[len], bufsz - len, "  %c    %c     %c    %c     %c",
+				 last_rx->he.fec ? 'L' : ' ', last_rx->he.stbc ? 'S' : ' ',
+				 last_rx->he.beamformed ? 'B' : ' ', last_rx->he.dcm ? 'D' : ' ',
+				 last_rx->he.doppler ? 'D' : ' ');
+	} else if (fmt == FORMATMOD_VHT) {
+		len += scnprintf(&buf[len], bufsz - len, "  %c    %c     %c           ",
+				 last_rx->vht.fec ? 'L' : ' ',
+				 last_rx->vht.stbc ? 'S' : ' ',
+				 last_rx->vht.beamformed ? 'B' : ' ');
+	} else if (fmt >= FORMATMOD_HT_MF) {
+		len += scnprintf(&buf[len], bufsz - len, "  %c    %c                  ",
+				 last_rx->ht.fec ? 'L' : ' ',
+				 last_rx->ht.stbc ? 'S' : ' ');
+	} else {
+		len += scnprintf(&buf[len], bufsz - len, "                         ");
+	}
+	if (nrx > 1) {
+		len += scnprintf(&buf[len], bufsz - len, "       %-4d       %d\n",
+						 last_rx->rssi1, last_rx->rssi1);
+	} else {
+		len += scnprintf(&buf[len], bufsz - len, "      %d\n", last_rx->rssi1);
+	}
+
+	read = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+
+	kfree(buf);
+	return read;
+}
+
+static ssize_t rwnx_dbgfs_last_rx_write(struct file *file,
+					const char __user *user_buf,
+					size_t count, loff_t *ppos)
+{
+	struct rwnx_sta *sta = NULL;
+	struct rwnx_hw *priv = file->private_data;
+	u8 mac[6];
+	int ret = 0;
+
+	/* Get the station index from MAC address */
+	ret = sscanf(file->f_path.dentry->d_parent->d_iname,
+		     "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac[0],
+		     &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
+	if (ret != 6)
+		pr_warn("Invalid MAC string: %s\n",
+			file->f_path.dentry->d_parent->d_iname);
+	sta = rwnx_get_sta(priv, mac);
+	if (!sta)
+		return 0;
+
+	/* Prevent from interrupt preemption as these statistics are updated under
+	 * interrupt
+	 */
+	spin_lock_bh(&priv->tx_lock);
+	memset(sta->stats.rx_rate.table, 0,
+	       sta->stats.rx_rate.size * sizeof(sta->stats.rx_rate.table[0]));
+	sta->stats.rx_rate.cpt = 0;
+	sta->stats.rx_rate.rate_cnt = 0;
+	spin_unlock_bh(&priv->tx_lock);
+
+	return count;
+}
+
+DEBUGFS_READ_FUNC(last_rx);
+DEBUGFS_WRITE_FUNC(last_rx);
+static const struct file_operations rwnx_dbgfs_last_rx_ops = {
+	.write = rwnx_dbgfs_last_rx_write,
+	.read = rwnx_dbgfs_last_rx_read,
+	.open = simple_open,
+	.llseek = generic_file_llseek,
+}
+
+#endif /* CONFIG_RWNX_FULLMAC */
+
+#ifdef CONFIG_RWNX_FULLMAC
+static void rwnx_rc_stat_work(struct work_struct *ws)
+{
+	struct rwnx_debugfs *rwnx_debugfs =
+		container_of(ws, struct rwnx_debugfs, rc_stat_work);
+	struct rwnx_hw *rwnx_hw =
+		container_of(rwnx_debugfs, struct rwnx_hw, debugfs);
+	struct rwnx_sta *sta;
+	u8_l ridx, sta_idx;
+
+	ridx = rwnx_debugfs->rc_read;
+	sta_idx = rwnx_debugfs->rc_sta[ridx];
+	if (sta_idx > (NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX)) {
+		WARN(1, "Invalid sta index %d", sta_idx);
+		return;
+	}
+
+	rwnx_debugfs->rc_sta[ridx] = 0xFF;
+	ridx = (ridx + 1) % ARRAY_SIZE(rwnx_debugfs->rc_sta);
+	rwnx_debugfs->rc_read = ridx;
+	sta = &rwnx_hw->sta_table[sta_idx];
+	if (!sta) {
+		WARN(1, "Invalid sta %d", sta_idx);
+		return;
+	}
+
+	if (!rwnx_debugfs->dir_sta[sta_idx]) {
+		/* register the sta */
+		struct dentry *dir_rc = rwnx_debugfs->dir_rc;
+		struct dentry *dir_sta;
+		struct dentry *file;
+		char sta_name[18];
+		struct rwnx_rx_rate_stats *rate_stats = &sta->stats.rx_rate;
+		int nb_rx_rate = N_CCK + N_OFDM;
+		struct rwnx_rc_config_save *rc_cfg, *next;
+
+		if (sta->sta_idx >= NX_REMOTE_STA_MAX)
+			scnprintf(sta_name, sizeof(sta_name), "bc_mc");
+		else
+			scnprintf(sta_name, sizeof(sta_name), "%pM", sta->mac_addr);
+
+		dir_sta = debugfs_create_dir(sta_name, dir_rc);
+		if (!dir_sta)
+			goto error;
+
+		rwnx_debugfs->dir_sta[sta->sta_idx] = dir_sta;
+
+		file = debugfs_create_file("stats", 0400, dir_sta, rwnx_hw,
+					   &rwnx_dbgfs_rc_stats_ops);
+		if (IS_ERR_OR_NULL(file))
+			goto error_after_dir;
+
+		file = debugfs_create_file("fixed_rate_idx", 0200, dir_sta, rwnx_hw,
+					   &rwnx_dbgfs_rc_fixed_rate_idx_ops);
+		if (IS_ERR_OR_NULL(file))
+			goto error_after_dir;
+
+		file = debugfs_create_file("rx_rate", 0600, dir_sta,
+					   rwnx_hw, &rwnx_dbgfs_last_rx_ops);
+		if (IS_ERR_OR_NULL(file))
+			goto error_after_dir;
+
+		if (rwnx_hw->mod_params->ht_on)
+			nb_rx_rate += N_HT;
+
+		if (rwnx_hw->mod_params->vht_on)
+			nb_rx_rate += N_VHT;
+
+		if (rwnx_hw->mod_params->he_on)
+			nb_rx_rate += N_HE_SU + N_HE_MU;
+
+		rate_stats->table =
+			kcalloc(nb_rx_rate, sizeof(rate_stats->table[0]), GFP_KERNEL);
+		if (!rate_stats->table)
+			goto error_after_dir;
+
+		rate_stats->size = nb_rx_rate;
+		rate_stats->cpt = 0;
+		rate_stats->rate_cnt = 0;
+
+		/* By default enable rate controller */
+		rwnx_debugfs->rc_config[sta_idx] = -1;
+
+		/* Unless we already fix the rate for this station */
+		list_for_each_entry_safe(rc_cfg, next, &rwnx_debugfs->rc_config_save,
+					 list) {
+			if (jiffies_to_msecs(jiffies - rc_cfg->timestamp) > RC_CONFIG_DUR) {
+				list_del(&rc_cfg->list);
+				kfree(rc_cfg);
+			} else if (!memcmp(rc_cfg->mac_addr, sta->mac_addr, ETH_ALEN)) {
+				rwnx_debugfs->rc_config[sta_idx] = rc_cfg->rate;
+				list_del(&rc_cfg->list);
+				kfree(rc_cfg);
+				break;
+			}
+		}
+
+		if (rwnx_debugfs->rc_config[sta_idx] >= 0 &&
+		    rwnx_send_me_rc_set_rate(rwnx_hw, sta_idx,
+					     (u16)rwnx_debugfs->rc_config[sta_idx]))
+			rwnx_debugfs->rc_config[sta_idx] = -1;
+	} else {
+		/* unregister the sta */
+		kfree(sta->stats.rx_rate.table);
+		sta->stats.rx_rate.table = NULL;
+		sta->stats.rx_rate.size = 0;
+		sta->stats.rx_rate.cpt = 0;
+		sta->stats.rx_rate.rate_cnt = 0;
+
+		/* If fix rate was set for this station, save the configuration in case
+		 * we reconnect to this station within RC_CONFIG_DUR msec
+		 */
+		if (rwnx_debugfs->rc_config[sta_idx] >= 0) {
+			struct rwnx_rc_config_save *rc_cfg;
+
+			rc_cfg = kmalloc_obj(*rc_cfg, GFP_KERNEL);
+			if (rc_cfg) {
+				rc_cfg->rate = rwnx_debugfs->rc_config[sta_idx];
+				rc_cfg->timestamp = jiffies;
+				memcpy(rc_cfg->mac_addr, sta->mac_addr, ETH_ALEN);
+				list_add_tail(&rc_cfg->list, &rwnx_debugfs->rc_config_save);
+			}
+		}
+
+		debugfs_remove_recursive(rwnx_debugfs->dir_sta[sta_idx]);
+		rwnx_debugfs->dir_sta[sta->sta_idx] = NULL;
+	}
+
+	return;
+
+error_after_dir:
+	debugfs_remove_recursive(rwnx_debugfs->dir_sta[sta_idx]);
+	rwnx_debugfs->dir_sta[sta->sta_idx] = NULL;
+error:
+	AICWFDBG(LOGDEBUG,
+		 "AICWF Error while (un)registering debug entry for sta %d\n", sta_idx);
+}
+
+void _rwnx_dbgfs_rc_stat_write(struct rwnx_debugfs *rwnx_debugfs,
+			       uint8_t sta_idx)
+{
+	u8_l widx = rwnx_debugfs->rc_write;
+
+	if (rwnx_debugfs->rc_sta[widx] != 0XFF)
+		WARN(1, "Overlap in debugfs rc_sta table\n");
+
+	if (rwnx_debugfs->unregistering)
+		return;
+
+	rwnx_debugfs->rc_sta[widx] = sta_idx;
+	widx = (widx + 1) % ARRAY_SIZE(rwnx_debugfs->rc_sta);
+	rwnx_debugfs->rc_write = widx;
+
+	schedule_work(&rwnx_debugfs->rc_stat_work);
+}
+
+void rwnx_dbgfs_register_rc_stat(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta)
+{
+	_rwnx_dbgfs_rc_stat_write(&rwnx_hw->debugfs, sta->sta_idx);
+}
+
+void rwnx_dbgfs_unregister_rc_stat(struct rwnx_hw *rwnx_hw,
+				   struct rwnx_sta *sta)
+{
+	_rwnx_dbgfs_rc_stat_write(&rwnx_hw->debugfs, sta->sta_idx);
+}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+int rwnx_dbgfs_register(struct rwnx_hw *rwnx_hw, const char *name)
+{
+#ifdef CONFIG_RWNX_FULLMAC
+	struct dentry *phyd = rwnx_hw->wiphy->debugfsdir;
+	struct dentry *dir_rc;
+#endif /* CONFIG_RWNX_FULLMAC */
+	struct rwnx_debugfs *rwnx_debugfs = &rwnx_hw->debugfs;
+	struct dentry *dir_drv, *dir_diags;
+
+	RWNX_DBG(RWNX_FN_ENTRY_STR);
+
+	dir_drv = debugfs_create_dir(name, phyd);
+	if (!dir_drv)
+		return -ENOMEM;
+
+	rwnx_debugfs->dir = dir_drv;
+	rwnx_debugfs->unregistering = false;
+
+	dir_diags = debugfs_create_dir("diags", dir_drv);
+	if (!dir_diags)
+		goto err;
+
+#ifdef CONFIG_RWNX_FULLMAC
+	dir_rc = debugfs_create_dir("rc", dir_drv);
+	if (!dir_rc)
+		goto err;
+	rwnx_debugfs->dir_rc = dir_rc;
+	INIT_WORK(&rwnx_debugfs->rc_stat_work, rwnx_rc_stat_work);
+	INIT_LIST_HEAD(&rwnx_debugfs->rc_config_save);
+	rwnx_debugfs->rc_read = 0;
+	rwnx_debugfs->rc_write = 0;
+	memset(rwnx_debugfs->rc_sta, 0xFF, sizeof(rwnx_debugfs->rc_sta));
+#endif
+	DEBUGFS_ADD_U32(tcp_pacing_shift, dir_drv, &rwnx_hw->tcp_pacing_shift,
+			0600);
+	DEBUGFS_ADD_FILE(stats, dir_drv, 0600);
+	DEBUGFS_ADD_FILE(sys_stats, dir_drv, 0400);
+	DEBUGFS_ADD_FILE(txq, dir_drv, 0400);
+	DEBUGFS_ADD_FILE(acsinfo, dir_drv, 0400);
+#ifdef CONFIG_RWNX_MUMIMO_TX
+	DEBUGFS_ADD_FILE(mu_group, dir_drv, 0400);
+#endif
+	DEBUGFS_ADD_FILE(regdbg, dir_drv, 0200);
+	DEBUGFS_ADD_FILE(vendor_hwconfig, dir_drv, 0200);
+	DEBUGFS_ADD_FILE(vendor_swconfig, dir_drv, 0200);
+	DEBUGFS_ADD_FILE(agg_disable, dir_drv, 0200);
+	DEBUGFS_ADD_FILE(set_roc, dir_drv, 0200);
+
+#ifdef CONFIG_RWNX_P2P_DEBUGFS
+	{
+		/* Create a p2p directory */
+		struct dentry *dir_p2p;
+
+		dir_p2p = debugfs_create_dir("p2p", dir_drv);
+		if (!dir_p2p)
+			goto err;
+
+		/* Add file allowing to control Opportunistic PS */
+		DEBUGFS_ADD_FILE(oppps, dir_p2p, 0400);
+		/* Add file allowing to control Notice of Absence */
+		DEBUGFS_ADD_FILE(noa, dir_p2p, 0400);
+	}
+#endif /* CONFIG_RWNX_P2P_DEBUGFS */
+
+	if (rwnx_hw->fwlog_en) {
+		rwnx_fw_log_init(&rwnx_hw->debugfs.fw_log);
+		DEBUGFS_ADD_FILE(fw_log, dir_drv, 0600);
+	}
+#ifdef CONFIG_RWNX_RADAR
+	{
+		struct dentry *dir_radar, *dir_sec;
+
+		dir_radar = debugfs_create_dir("radar", dir_drv);
+		if (!dir_radar)
+			goto err;
+
+		DEBUGFS_ADD_FILE(pulses_prim, dir_radar, 0400);
+		DEBUGFS_ADD_FILE(detected, dir_radar, 0400);
+		DEBUGFS_ADD_FILE(enable, dir_radar, 0400);
+
+		if (rwnx_hw->phy.cnt == 2) {
+			DEBUGFS_ADD_FILE(pulses_sec, dir_radar, 0400);
+
+			dir_sec = debugfs_create_dir("sec", dir_radar);
+			if (!dir_sec)
+				goto err;
+
+			DEBUGFS_ADD_FILE(band, dir_sec, 0600);
+			DEBUGFS_ADD_FILE(type, dir_sec, 0600);
+			DEBUGFS_ADD_FILE(prim20, dir_sec, 0600);
+			DEBUGFS_ADD_FILE(center1, dir_sec, 0600);
+			DEBUGFS_ADD_FILE(center2, dir_sec, 0600);
+			DEBUGFS_ADD_FILE(set, dir_sec, 0600);
+		}
+	}
+#endif /* CONFIG_RWNX_RADAR */
+	return 0;
+
+err:
+	rwnx_dbgfs_unregister(rwnx_hw);
+	return -ENOMEM;
+}
+
+void rwnx_dbgfs_unregister(struct rwnx_hw *rwnx_hw)
+{
+	struct rwnx_debugfs *rwnx_debugfs = &rwnx_hw->debugfs;
+#ifdef CONFIG_RWNX_FULLMAC
+	struct rwnx_rc_config_save *cfg, *next;
+#endif
+
+#ifdef CONFIG_RWNX_FULLMAC
+	list_for_each_entry_safe(cfg, next, &rwnx_debugfs->rc_config_save, list) {
+		list_del(&cfg->list);
+		kfree(cfg);
+	}
+#endif /* CONFIG_RWNX_FULLMAC */
+
+	if (rwnx_hw->fwlog_en)
+		rwnx_fw_log_deinit(&rwnx_hw->debugfs.fw_log);
+
+	if (!rwnx_hw->debugfs.dir)
+		return;
+
+	rwnx_debugfs->unregistering = true;
+#ifdef CONFIG_RWNX_FULLMAC
+	flush_work(&rwnx_debugfs->rc_stat_work);
+#endif
+	debugfs_remove_recursive(rwnx_hw->debugfs.dir);
+	rwnx_hw->debugfs.dir = NULL;
+}
+
+#endif /* CONFIG_RWNX_DEBUGFS */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_debugfs.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_debugfs.h
new file mode 100644
index 0000000000000..17656c96fdad6
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_debugfs.h
@@ -0,0 +1,134 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_DEBUGFS_H_
+#define _RWNX_DEBUGFS_H_
+#include <linux/version.h>
+
+#include "rwnx_fw_trace.h"
+#include <linux/if_ether.h>
+#include <linux/workqueue.h>
+
+struct rwnx_hw;
+struct rwnx_sta;
+
+/* some macros taken from iwlwifi */
+/* TODO: replace with generic read and fill read buffer in open to avoid double
+ * reads
+ */
+#define DEBUGFS_ADD_FILE(name, parent, mode)						\
+	do {										\
+		if (!debugfs_create_file(#name, mode, parent, rwnx_hw,			\
+					 &rwnx_dbgfs_##name##_ops))			\
+			goto err;							\
+	} while (0)
+
+#define DEBUGFS_ADD_U32(name, parent, ptr, mode)					\
+		debugfs_create_u32(#name, mode, parent, ptr)
+
+/* file operation */
+#define DEBUGFS_READ_FUNC(name)								\
+	static ssize_t rwnx_dbgfs_##name##_read(struct file *file,			\
+						char __user *user_buf,			\
+						size_t count,				\
+						loff_t *ppos)
+
+#define DEBUGFS_WRITE_FUNC(name)							\
+	static ssize_t rwnx_dbgfs_##name##_write(struct file *file,			\
+						 const char __user *user_buf,		\
+						 size_t count, loff_t *ppos)
+
+#define DEBUGFS_OPEN_FUNC(name)								\
+	static int rwnx_dbgfs_##name##_open(struct inode *inode, struct file *file)
+
+#define DEBUGFS_RELEASE_FUNC(name)							\
+	static int rwnx_dbgfs_##name##_release(struct inode *inode,			\
+					       struct file *file)
+
+#define DEBUGFS_READ_FILE_OPS(name)							\
+	DEBUGFS_READ_FUNC(name);							\
+	static const struct file_operations rwnx_dbgfs_##name##_ops = {			\
+		.read = rwnx_dbgfs_##name##_read,					\
+		.open = simple_open,							\
+		.llseek = generic_file_llseek,						\
+	}
+
+#define DEBUGFS_WRITE_FILE_OPS(name)							\
+	DEBUGFS_WRITE_FUNC(name);							\
+	static const struct file_operations rwnx_dbgfs_##name##_ops = {			\
+		.write = rwnx_dbgfs_##name##_write,					\
+		.open = simple_open,							\
+		.llseek = generic_file_llseek,						\
+	}
+
+#ifdef CONFIG_RWNX_DEBUGFS
+
+struct rwnx_debugfs {
+	unsigned long long rateidx;
+	struct dentry *dir;
+	bool trace_prst;
+
+	char helper_cmd[64];
+	// struct work_struct helper_work;
+	bool helper_scheduled;
+	spinlock_t umh_lock; //
+	bool unregistering;
+
+	struct rwnx_fw_log fw_log;
+
+#ifdef CONFIG_RWNX_FULLMAC
+	struct work_struct rc_stat_work;
+	u8_l rc_sta[NX_REMOTE_STA_MAX];
+	u8_l rc_write;
+	u8_l rc_read;
+	struct dentry *dir_rc;
+	struct dentry *dir_sta[NX_REMOTE_STA_MAX];
+	int rc_config[NX_REMOTE_STA_MAX];
+	struct list_head rc_config_save;
+#endif
+};
+
+#ifdef CONFIG_RWNX_FULLMAC
+
+// Max duration in msecs to save rate config for a sta after disconnection
+#define RC_CONFIG_DUR 600000
+
+struct rwnx_rc_config_save {
+	struct list_head list;
+	unsigned long timestamp;
+	int rate;
+	u8 mac_addr[ETH_ALEN];
+};
+#endif
+
+int rwnx_dbgfs_register(struct rwnx_hw *rwnx_hw, const char *name);
+void rwnx_dbgfs_unregister(struct rwnx_hw *rwnx_hw);
+#ifdef CONFIG_RWNX_FULLMAC
+void rwnx_dbgfs_register_rc_stat(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta);
+void rwnx_dbgfs_unregister_rc_stat(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta);
+#endif
+#else
+struct rwnx_debugfs {
+};
+
+static inline int rwnx_dbgfs_register(struct rwnx_hw *rwnx_hw, const char *name)
+{
+	return 0;
+}
+
+static inline void rwnx_dbgfs_unregister(struct rwnx_hw *rwnx_hw)
+{
+}
+
+#ifdef CONFIG_RWNX_FULLMAC
+static inline void rwnx_dbgfs_register_rc_stat(struct rwnx_hw *rwnx_hw,
+					       struct rwnx_sta *sta)
+{
+}
+
+static inline void rwnx_dbgfs_unregister_rc_stat(struct rwnx_hw *rwnx_hw,
+						 struct rwnx_sta *sta)
+{
+}
+#endif
+#endif /* CONFIG_RWNX_DEBUGFS */
+
+#endif /* _RWNX_DEBUGFS_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_fw_trace.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_fw_trace.c
new file mode 100644
index 0000000000000..7c619fcf99913
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_fw_trace.c
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief get fw log
+ *
+ ******************************************************************************
+ */
+#include "rwnx_fw_trace.h"
+#include "aicwf_debug.h"
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+
+int rwnx_fw_log_init(struct rwnx_fw_log *fw_log)
+{
+	u8 *buf = kmalloc(FW_LOG_SIZE, GFP_KERNEL);
+
+	if (!buf)
+		return -ENOMEM;
+
+	fw_log->buf.data = buf;
+	fw_log->buf.start = fw_log->buf.data;
+	fw_log->buf.size = 0;
+	fw_log->buf.end = fw_log->buf.data;
+	fw_log->buf.dataend = fw_log->buf.data + FW_LOG_SIZE;
+	spin_lock_init(&fw_log->lock);
+
+	AICWFDBG(LOGINFO, "fw_log_init: %lx, %lx\n",
+		 (unsigned long)fw_log->buf.start,
+		 (unsigned long)(fw_log->buf.dataend));
+	return 0;
+}
+
+void rwnx_fw_log_deinit(struct rwnx_fw_log *fw_log)
+{
+	if (!fw_log)
+		return;
+
+	kfree(fw_log->buf.data);
+	fw_log->buf.start = NULL;
+	fw_log->buf.end = NULL;
+	fw_log->buf.size = 0;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_fw_trace.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_fw_trace.h
new file mode 100644
index 0000000000000..9c9df4db93cc3
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_fw_trace.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RWNX_FW_TRACE_H_
+#define _RWNX_FW_TRACE_H_
+
+#include <linux/mutex.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include "lmac_types.h"
+
+#define FW_LOG_SIZE (10240)
+
+struct rwnx_fw_log_buf {
+	u8_l *data;
+	u8_l *start;
+	u8_l *end;
+	u8_l *dataend;
+	u32_l size;
+};
+
+struct rwnx_fw_log {
+	struct rwnx_fw_log_buf buf;
+	//
+	spinlock_t lock;
+};
+
+int rwnx_fw_log_init(struct rwnx_fw_log *fw_log);
+void rwnx_fw_log_deinit(struct rwnx_fw_log *fw_log);
+#endif /* _RWNX_FW_TRACE_H_ */
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_gki.c b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_gki.c
new file mode 100644
index 0000000000000..556519d6144f8
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_gki.c
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief for gki interface
+ *
+ ******************************************************************************
+ */
+
+#include <linux/version.h>
+#ifdef ANDROID_PLATFORM
+#include "net/wireless/core.h"
+#endif
+#include <include/linux/types.h>
+
+#undef NL80211_MCGRP_MLME
+#define NL80211_MCGRP_MLME 3
+
+static struct genl_family rwnx_nl80211_fam;
+
+static bool __rwnx_cfg80211_unexpected_frame(struct net_device *dev, u8 cmd,
+					     const u8 *addr, gfp_t gfp)
+{
+	struct wireless_dev *wdev = dev->ieee80211_ptr;
+	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
+	struct sk_buff *msg;
+	void *hdr;
+	u32 nlportid = READ_ONCE(wdev->ap_unexpected_nlportid);
+
+	if (!nlportid)
+		return false;
+
+	msg = nlmsg_new(100, gfp);
+	if (!msg)
+		return true;
+
+	hdr = genlmsg_put(msg, 0, 0, &rwnx_nl80211_fam, 0, cmd);
+	if (!hdr) {
+		nlmsg_free(msg);
+		return true;
+	}
+
+	if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
+	    nla_put_u32(msg, NL80211_ATTR_IFINDEX, dev->ifindex) ||
+	    nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, addr))
+		goto nla_put_failure;
+
+	genlmsg_end(msg, hdr);
+	genlmsg_unicast(wiphy_net(&rdev->wiphy), msg, nlportid);
+	return true;
+
+nla_put_failure:
+	nlmsg_free(msg);
+	return true;
+}
+
+bool rwnx_cfg80211_rx_spurious_frame(struct net_device *dev, const u8 *addr,
+				     gfp_t gfp)
+{
+	struct wireless_dev *wdev = dev->ieee80211_ptr;
+	bool ret;
+
+	if (WARN_ON(wdev->iftype != NL80211_IFTYPE_AP &&
+		    wdev->iftype != NL80211_IFTYPE_P2P_GO)) {
+		return false;
+	}
+	ret = __rwnx_cfg80211_unexpected_frame(dev, NL80211_CMD_UNEXPECTED_FRAME,
+					       addr, gfp);
+	return ret;
+}
+
+bool rwnx_cfg80211_rx_unexpected_4addr_frame(struct net_device *dev,
+					     const u8 *addr, gfp_t gfp)
+{
+	struct wireless_dev *wdev = dev->ieee80211_ptr;
+	bool ret;
+
+	if (WARN_ON(wdev->iftype != NL80211_IFTYPE_AP &&
+		    wdev->iftype != NL80211_IFTYPE_P2P_GO &&
+		    wdev->iftype != NL80211_IFTYPE_AP_VLAN)) {
+		return false;
+	}
+	ret = __rwnx_cfg80211_unexpected_frame(dev,
+					       NL80211_CMD_UNEXPECTED_4ADDR_FRAME,
+					       addr, gfp);
+	return ret;
+}
+
+void rwnx_cfg80211_notify_new_peer_candidate(struct net_device *dev,
+					     const u8 *addr, const u8 *ie,
+					     u8 ie_len, int sig_dbm, gfp_t gfp)
+{
+	struct wireless_dev *wdev = dev->ieee80211_ptr;
+	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
+	struct sk_buff *msg;
+	void *hdr;
+
+	if (WARN_ON(wdev->iftype != NL80211_IFTYPE_MESH_POINT))
+		return;
+
+	msg = nlmsg_new(100 + ie_len, gfp);
+	if (!msg)
+		return;
+
+	hdr = genlmsg_put(msg, 0, 0, &rwnx_nl80211_fam, 0,
+			  NL80211_CMD_NEW_PEER_CANDIDATE);
+	if (!hdr) {
+		nlmsg_free(msg);
+		return;
+	}
+
+	if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
+	    nla_put_u32(msg, NL80211_ATTR_IFINDEX, dev->ifindex) ||
+	    nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, addr) ||
+	    (ie_len && ie && nla_put(msg, NL80211_ATTR_IE, ie_len, ie)) ||
+	    (sig_dbm && nla_put_u32(msg, NL80211_ATTR_RX_SIGNAL_DBM, sig_dbm)))
+		goto nla_put_failure;
+
+	genlmsg_end(msg, hdr);
+
+#define NL80211_MCGRP_MLME 3
+	genlmsg_multicast_netns(&rwnx_nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
+				NL80211_MCGRP_MLME, gfp);
+	return;
+
+nla_put_failure:
+	nlmsg_free(msg);
+}
+
+void rwnx_cfg80211_report_obss_beacon(struct wiphy *wiphy, const u8 *frame,
+				      size_t len, int freq, int sig_dbm)
+{
+	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
+	struct sk_buff *msg;
+	void *hdr;
+	struct cfg80211_beacon_registration *reg;
+
+	spin_lock_bh(&rdev->beacon_registrations_lock);
+	list_for_each_entry(reg, &rdev->beacon_registrations, list) {
+		msg = nlmsg_new(len + 100, GFP_ATOMIC);
+		if (!msg) {
+			spin_unlock_bh(&rdev->beacon_registrations_lock);
+			return;
+		}
+
+		hdr = genlmsg_put(msg, 0, 0, &rwnx_nl80211_fam, 0, NL80211_CMD_FRAME);
+		if (!hdr)
+			goto nla_put_failure;
+
+		if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
+		    (freq && nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ, freq)) ||
+		    (sig_dbm && nla_put_u32(msg, NL80211_ATTR_RX_SIGNAL_DBM, sig_dbm)) ||
+		    nla_put(msg, NL80211_ATTR_FRAME, len, frame))
+			goto nla_put_failure;
+
+		genlmsg_end(msg, hdr);
+
+		genlmsg_unicast(wiphy_net(&rdev->wiphy), msg, reg->nlportid);
+	}
+	spin_unlock_bh(&rdev->beacon_registrations_lock);
+	return;
+
+nla_put_failure:
+	spin_unlock_bh(&rdev->beacon_registrations_lock);
+	nlmsg_free(msg);
+}
+
+static int rwnx_nl80211_send_chandef(struct sk_buff *msg,
+				     const struct cfg80211_chan_def *chandef)
+{
+	if (WARN_ON(!cfg80211_chandef_valid(chandef)))
+		return -EINVAL;
+
+	if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ, chandef->chan->center_freq))
+		return -ENOBUFS;
+	switch (chandef->width) {
+	case NL80211_CHAN_WIDTH_20_NOHT:
+	case NL80211_CHAN_WIDTH_20:
+	case NL80211_CHAN_WIDTH_40:
+		if (nla_put_u32(msg, NL80211_ATTR_WIPHY_CHANNEL_TYPE,
+				cfg80211_get_chandef_type(chandef)))
+			return -ENOBUFS;
+		break;
+	default:
+		break;
+	}
+	if (nla_put_u32(msg, NL80211_ATTR_CHANNEL_WIDTH, chandef->width))
+		return -ENOBUFS;
+	if (nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1, chandef->center_freq1))
+		return -ENOBUFS;
+	if (chandef->center_freq2 &&
+	    nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ2, chandef->center_freq2))
+		return -ENOBUFS;
+	return 0;
+}
+
+void rwnx_cfg80211_ch_switch_notify(struct cfg80211_registered_device *rdev,
+				    struct net_device *netdev,
+				    struct cfg80211_chan_def *chandef,
+				    gfp_t gfp, enum nl80211_commands notif,
+				    u8 count)
+{
+	struct sk_buff *msg;
+	void *hdr;
+
+	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
+	if (!msg)
+		return;
+
+	hdr = genlmsg_put(msg, 0, 0, &rwnx_nl80211_fam, 0, notif);
+	if (!hdr) {
+		nlmsg_free(msg);
+		return;
+	}
+
+	if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex))
+		goto nla_put_failure;
+
+	if (rwnx_nl80211_send_chandef(msg, chandef))
+		goto nla_put_failure;
+
+	if (notif == NL80211_CMD_CH_SWITCH_STARTED_NOTIFY &&
+	    nla_put_u32(msg, NL80211_ATTR_CH_SWITCH_COUNT, count))
+		goto nla_put_failure;
+
+	genlmsg_end(msg, hdr);
+
+	genlmsg_multicast_netns(&rwnx_nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
+				NL80211_MCGRP_MLME, gfp);
+	return;
+
+nla_put_failure:
+	nlmsg_free(msg);
+}
+
+void rwnx_cfg80211_ch_switch_started_notify(struct net_device *dev,
+					    struct cfg80211_chan_def *chandef,
+					    u8 count,
+					    bool quiet)
+{
+	struct wireless_dev *wdev = dev->ieee80211_ptr;
+	struct wiphy *wiphy = wdev->wiphy;
+	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
+
+	rwnx_cfg80211_ch_switch_notify(rdev, dev, chandef, GFP_KERNEL,
+				       NL80211_CMD_CH_SWITCH_STARTED_NOTIFY, count);
+}
+
+int rwnx_regulatory_set_wiphy_regd_sync_rtnl(struct wiphy *wiphy,
+					     struct ieee80211_regdomain *rd)
+{
+	wiphy_apply_custom_regulatory(wiphy, rd);
+	return 0;
+}
+
+void rwnx_skb_append(struct sk_buff *old, struct sk_buff *newsk,
+		     struct sk_buff_head *list)
+{
+	unsigned long flags;
+	struct sk_buff *prev = old;
+	struct sk_buff *next = prev->next;
+
+	spin_lock_irqsave(&list->lock, flags);
+	WRITE_ONCE(newsk->next, next);
+	WRITE_ONCE(newsk->prev, prev);
+	WRITE_ONCE(next->prev, newsk);
+	WRITE_ONCE(prev->next, newsk);
+	list->qlen++;
+	spin_unlock_irqrestore(&list->lock, flags);
+}
+
+bool rwnx_ieee80211_chandef_to_operating_class(struct cfg80211_chan_def *chandef, u8 *op_class)
+{
+	u8 vht_opclass;
+	u32 freq = chandef->center_freq1;
+
+	if (freq >= 2412 && freq <= 2472) {
+		if (chandef->width > NL80211_CHAN_WIDTH_40)
+			return false;
+
+		/* 2.407 GHz, channels 1..13 */
+		if (chandef->width == NL80211_CHAN_WIDTH_40) {
+			if (freq > chandef->chan->center_freq)
+				*op_class = 83; /* HT40+ */
+			else
+				*op_class = 84; /* HT40- */
+		} else {
+			*op_class = 81;
+		}
+
+		return true;
+	}
+
+	if (freq == 2484) {
+		/* channel 14 is only for IEEE 802.11b */
+		if (chandef->width != NL80211_CHAN_WIDTH_20_NOHT)
+			return false;
+
+		*op_class = 82; /* channel 14 */
+		return true;
+	}
+
+	switch (chandef->width) {
+	case NL80211_CHAN_WIDTH_80:
+		vht_opclass = 128;
+		break;
+	case NL80211_CHAN_WIDTH_160:
+		vht_opclass = 129;
+		break;
+	case NL80211_CHAN_WIDTH_80P80:
+		vht_opclass = 130;
+		break;
+	case NL80211_CHAN_WIDTH_10:
+	case NL80211_CHAN_WIDTH_5:
+		return false; /* unsupported for now */
+	default:
+		vht_opclass = 0;
+		break;
+	}
+
+	/* 5 GHz, channels 36..48 */
+	if (freq >= 5180 && freq <= 5240) {
+		if (vht_opclass) {
+			*op_class = vht_opclass;
+		} else if (chandef->width == NL80211_CHAN_WIDTH_40) {
+			if (freq > chandef->chan->center_freq)
+				*op_class = 116;
+			else
+				*op_class = 117;
+		} else {
+			*op_class = 115;
+		}
+
+		return true;
+	}
+
+	/* 5 GHz, channels 52..64 */
+	if (freq >= 5260 && freq <= 5320) {
+		if (vht_opclass) {
+			*op_class = vht_opclass;
+		} else if (chandef->width == NL80211_CHAN_WIDTH_40) {
+			if (freq > chandef->chan->center_freq)
+				*op_class = 119;
+			else
+				*op_class = 120;
+		} else {
+			*op_class = 118;
+		}
+
+		return true;
+	}
+
+	/* 5 GHz, channels 100..144 */
+	if (freq >= 5500 && freq <= 5720) {
+		if (vht_opclass) {
+			*op_class = vht_opclass;
+		} else if (chandef->width == NL80211_CHAN_WIDTH_40) {
+			if (freq > chandef->chan->center_freq)
+				*op_class = 122;
+			else
+				*op_class = 123;
+		} else {
+			*op_class = 121;
+		}
+
+		return true;
+	}
+
+	/* 5 GHz, channels 149..169 */
+	if (freq >= 5745 && freq <= 5845) {
+		if (vht_opclass) {
+			*op_class = vht_opclass;
+		} else if (chandef->width == NL80211_CHAN_WIDTH_40) {
+			if (freq > chandef->chan->center_freq)
+				*op_class = 126;
+			else
+				*op_class = 127;
+		} else if (freq <= 5805) {
+			*op_class = 124;
+		} else {
+			*op_class = 125;
+		}
+
+		return true;
+	}
+
+	/* 56.16 GHz, channel 1..4 */
+	if (freq >= 56160 + 2160 * 1 && freq <= 56160 + 2160 * 6) {
+		if (chandef->width >= NL80211_CHAN_WIDTH_40)
+			return false;
+
+		*op_class = 180;
+		return true;
+	}
+
+	/* not supported yet */
+	return false;
+}
+
+int rwnx_call_usermodehelper(const char *path, char **argv, char **envp,
+			     int wait)
+{
+	return -1;
+}
diff --git a/drivers/net/wireless/aic/aic8800_fdrv/rwnx_gki.h b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_gki.h
new file mode 100644
index 0000000000000..9abf8e6b1c0fe
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_gki.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __RWNX_GKI_H
+#define __RWNX_GKI_H
+
+#include <linux/version.h>
+
+#ifdef ANDROID_PLATFORM
+#include "net/wireless/core.h"
+#endif
+
+bool rwnx_cfg80211_rx_spurious_frame(struct net_device *dev, const u8 *addr,
+				     gfp_t gfp);
+
+bool rwnx_cfg80211_rx_unexpected_4addr_frame(struct net_device *dev,
+					     const u8 *addr, gfp_t gfp);
+
+void rwnx_cfg80211_notify_new_peer_candidate(struct net_device *dev,
+					     const u8 *addr, const u8 *ie,
+					     u8 ie_len, int sig_dbm, gfp_t gfp);
+
+void rwnx_cfg80211_report_obss_beacon(struct wiphy *wiphy, const u8 *frame,
+				      size_t len, int freq, int sig_dbm);
+
+void rwnx_cfg80211_ch_switch_notify(struct cfg80211_registered_device *rdev,
+				    struct net_device *netdev,
+				    struct cfg80211_chan_def *chandef,
+				    gfp_t gfp, enum nl80211_commands notif,
+				    u8 count);
+
+void rwnx_cfg80211_ch_switch_started_notify(struct net_device *dev,
+					    struct cfg80211_chan_def *chandef,
+					    u8 count,
+					    bool quiet);
+
+int rwnx_regulatory_set_wiphy_regd_sync_rtnl(struct wiphy *wiphy,
+					     struct ieee80211_regdomain *rd);
+
+void rwnx_skb_append(struct sk_buff *old, struct sk_buff *newsk,
+		     struct sk_buff_head *list);
+
+bool rwnx_ieee80211_chandef_to_operating_class(struct cfg80211_chan_def *chandef, u8 *op_class);
+
+int rwnx_call_usermodehelper(const char *path, char **argv, char **envp,
+			     int wait);
+
+#endif
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..4b747b9faa246
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_main.c
@@ -0,0 +1,5406 @@
+// 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_debugfs.h"
+#include "rwnx_defs.h"
+#include "rwnx_irqs.h"
+#include "rwnx_msg_tx.h"
+#include "rwnx_radar.h"
+#include "rwnx_version.h"
+#include "aic_vendor.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/random.h>
+#include <linux/vmalloc.h>
+#include <linux/wireless.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"
+#include "rwnx_version.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 "rwnx_wakelock.h"
+#ifdef CONFIG_SDIO_BT
+#include "aic_btsdio.h"
+#endif
+#include "aicwf_genl.h"
+#include "aicwf_chip_ops.h"
+#include "aicwf_txq_prealloc.h"
+
+#ifdef CONFIG_AIC8800_USE_WIRELESS_EXT
+#include "aicwf_wext_linux.h"
+#endif
+
+#ifndef CONFIG_AIC8800_USE_WIRELESS_EXT
+#ifdef CONFIG_WIRELESS_EXT
+#include <net/iw_handler.h>
+struct iw_handler_def aic_handlers_def;
+#endif
+#endif
+
+#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 = {
+	.types_mask = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP),
+	.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,
+	.iftype_data = &rwnx_he_capa,
+	.n_iftype_data = 1,
+};
+
+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,
+	.iftype_data = &rwnx_he_capa,
+	.n_iftype_data = 1,
+};
+
+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)),
+	}};
+
+/* 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)),
+		},
+		[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)),
+		},
+};
+
+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 -
+ *
+ * @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;
+}
+
+#ifdef CONFIG_AIC8800_BR_SUPPORT
+#define CONFIG_BR_SUPPORT_BRNAME "br0"
+void netdev_br_init(struct net_device *netdev)
+{
+	struct rwnx_vif *rwnx_vif = netdev_priv(netdev);
+
+	rcu_read_lock();
+	if (rcu_dereference(rwnx_vif->ndev->rx_handler_data)) {
+		struct net_device *br_netdev;
+
+		br_netdev = dev_get_by_name(&init_net, CONFIG_BR_SUPPORT_BRNAME);
+		if (br_netdev) {
+			memcpy(rwnx_vif->br_mac, br_netdev->dev_addr, ETH_ALEN);
+			dev_put(br_netdev);
+			AICWFDBG(LOGDEBUG, "AICWF " FUNC_NDEV_FMT
+				 " bind bridge dev " NDEV_FMT "(" MAC_FMT ")\n",
+				 FUNC_NDEV_ARG(netdev), NDEV_ARG(br_netdev),
+				 MAC_ARG(br_netdev->dev_addr));
+		} else {
+			AICWFDBG(LOGDEBUG, "AICWF " FUNC_NDEV_FMT
+				 " can't get bridge dev by name \"%s\"\n",
+				 FUNC_NDEV_ARG(netdev), CONFIG_BR_SUPPORT_BRNAME);
+		}
+	}
+	rwnx_vif->eth_br_ext_info.add_pppoe_tag = 1;
+	rcu_read_unlock();
+}
+#endif /* CONFIG_AIC8800_BR_SUPPORT */
+
+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);
+		}
+	}
+
+#ifdef CONFIG_AIC8800_BR_SUPPORT
+	netdev_br_init(dev);
+#endif /* CONFIG_AIC8800_BR_SUPPORT */
+
+	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);
+		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");
+		}
+
+#ifdef CONFIG_AIC8800_BR_SUPPORT
+		/* void nat25_db_cleanup(_adapter *priv); */
+		nat25_db_cleanup(rwnx_vif);
+#endif /* CONFIG_AIC8800_BR_SUPPORT */
+	}
+
+#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.
+ */
+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;
+}
+
+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,
+};
+
+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,
+};
+
+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;
+	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;
+
+	AICWFDBG(LOGINFO, "%s: %s, %d, %d\r\n", __func__, name, type,
+		 NL80211_IFTYPE_P2P_DEVICE);
+	// Look for an available VIF
+	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)
+		return NULL;
+
+#ifndef 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;
+
+#ifdef CONFIG_AIC8800_BR_SUPPORT
+	spin_lock_init(&vif->br_ext_lock);
+#endif /* CONFIG_AIC8800_BR_SUPPORT */
+
+	switch (type) {
+	case NL80211_IFTYPE_STATION:
+		vif->sta.ap = NULL;
+		vif->sta.tdls_sta = NULL;
+		vif->sta.external_auth = false;
+		break;
+	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;
+	default:
+		break;
+	}
+
+	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 {
+		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);
+	}
+
+	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;
+	}
+
+#ifdef CONFIG_AIC8800_USE_WIRELESS_EXT
+	aicwf_set_wireless_ext(ndev, rwnx_hw);
+#else
+#ifdef CONFIG_WIRELESS_EXT
+	memset(&aic_handlers_def, 0, sizeof(struct iw_handler_def));
+	ndev->wireless_handlers = (struct iw_handler_def *)&aic_handlers_def;
+#endif
+#endif
+	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_l 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)) {
+			case NL80211_IFTYPE_P2P_CLIENT:
+			case NL80211_IFTYPE_P2P_GO:
+				rwnx_hw->is_p2p_alive = 1;
+				p2p = 1;
+				break;
+			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 (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);
+
+#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) {
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_P2P_CLIENT:
+		vif->sta.ap = NULL;
+		vif->sta.tdls_sta = NULL;
+		vif->sta.external_auth = false;
+		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;
+		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;
+	default:
+		break;
+	}
+
+	vif->wdev.iftype = type;
+	if (params->use_4addr != -1)
+		vif->use_4addr = params->use_4addr;
+	if (type == NL80211_IFTYPE_P2P_CLIENT || type == NL80211_IFTYPE_P2P_GO)
+		p2p = true;
+
+	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 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 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);
+		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;
+	}
+
+	rwnx_wakeup_lock(rwnx_hw->ws_scan);
+
+	rwnx_hw->scan_request = request;
+	error = rwnx_send_scanu_req(rwnx_hw, rwnx_vif, request);
+	if (error) {
+		rwnx_wakeup_unlock(rwnx_hw->ws_scan);
+		return error;
+	}
+
+	return 0;
+}
+
+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_l 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;
+
+#ifdef CONFIG_AIC8800_USE_WIRELESS_EXT
+	memset(rwnx_hw->wext_essid, 0, 33);
+#endif
+		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 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);
+#ifdef CONFIG_RWNX_DEBUGFS
+		rwnx_dbgfs_register_rc_stat(rwnx_hw, sta);
+#endif
+
+		/* 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 */
+
+#ifdef CONFIG_RWNX_DEBUGFS
+			rwnx_dbgfs_unregister_rc_stat(rwnx_hw, cur);
+#endif
+		}
+
+		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 */
+
+#ifdef CONFIG_RWNX_DEBUGFS
+		rwnx_dbgfs_unregister_rc_stat(rwnx_hw, cur);
+#endif
+	} 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 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);
+#ifdef CONFIG_RWNX_DEBUGFS
+				rwnx_dbgfs_register_rc_stat(rwnx_hw, sta);
+#endif
+				/* 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_l 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 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;
+#if (defined CONFIG_AIC8800_HE_FOR_OLD_KERNEL) || (defined CONFIG_AIC8800_VHT_FOR_OLD_KERNEL)
+		rwnx_vif->ap.aic_index = 0;
+#endif
+		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 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 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)) {
+	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_STATION:
+	case NL80211_IFTYPE_P2P_CLIENT:
+	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 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_l rssi_thold,
+					     u32_l 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 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, &params->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, &params->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 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;
+#ifdef CONFIG_RWNX_DEBUGFS
+		rwnx_dbgfs_unregister_rc_stat(rwnx_hw, rwnx_vif->sta.tdls_sta);
+#endif
+		// 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 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 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 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_l 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_l)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_l 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 = le16_to_cpu(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 =
+			le32_to_cpu(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_l)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 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;
+				if (bss->beacon_ies && bss->beacon_ies->len > 0) {
+					tim = cfg80211_find_ie(WLAN_EID_TIM,
+							       bss->beacon_ies->data,
+							       bss->beacon_ies->len);
+					if (tim)
+						rwnx_vif->sta.dtim_peroid = *(tim + 3);
+				}
+			}
+			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 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 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 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 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 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 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 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 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 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 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 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,
+	.start_p2p_device = rwnx_cfgp2p_start_p2p_device,
+	.stop_p2p_device = rwnx_cfgp2p_stop_p2p_device,
+	.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,
+	.add_station = rwnx_cfg80211_add_station,
+	.del_station = rwnx_cfg80211_del_station_compat,
+	.change_station = rwnx_cfg80211_change_station,
+	.mgmt_tx = rwnx_cfg80211_mgmt_tx,
+	.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,
+	//  .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,
+	.start_radar_detection = rwnx_cfg80211_start_radar_detection,
+	.update_ft_ies = rwnx_cfg80211_update_ft_ies,
+	.set_cqm_rssi_config = rwnx_cfg80211_set_cqm_rssi_config,
+	.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,
+	.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);
+}
+
+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);
+}
+
+#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;
+}
+
+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_l 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;
+
+#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
+
+	aicwf_wakeup_lock_init(rwnx_hw);
+	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);
+
+	wiphy->bands[NL80211_BAND_2GHZ] = &rwnx_band_2ghz;
+	if (rwnx_hw->band_5g_support)
+		wiphy->bands[NL80211_BAND_5GHZ] = &rwnx_band_5ghz;
+
+	wiphy->interface_modes =
+		BIT(NL80211_IFTYPE_STATION) | 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);
+
+#if IS_ENABLED(CONFIG_PM)
+	/* Set WoWLAN flags */
+	wiphy->wowlan = &aic_wowlan_support;
+#endif
+	wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL |
+			WIPHY_FLAG_HAS_CHANNEL_SWITCH |
+			WIPHY_FLAG_4ADDR_STATION | WIPHY_FLAG_4ADDR_AP;
+	wiphy->max_num_csa_counters = BCN_MAX_CSA_CPT;
+
+#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_NEED_OBSS_SCAN |
+			   NL80211_FEATURE_SK_TX_STATUS |
+			   NL80211_FEATURE_VIF_TXPOWER |
+			   NL80211_FEATURE_ACTIVE_MONITOR |
+			   NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE | 0;
+	wiphy->features |= NL80211_FEATURE_SAE;
+
+	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;
+	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;
+
+	rwnx_enable_mesh(rwnx_hw);
+	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);
+
+	/* 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);
+	}
+
+	aicwf_vendor_init(wiphy);
+
+	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;
+
+#ifdef CONFIG_RWNX_DEBUGFS
+	ret = rwnx_dbgfs_register(rwnx_hw, "rwnx");
+	if (ret) {
+		wiphy_err(wiphy, "Failed to register debugfs entries");
+		goto err_debugfs;
+	}
+#endif
+	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);
+
+#ifdef CONFIG_SDIO_BT
+	btchr_init();
+	hdev_init();
+#endif
+
+#ifdef CONFIG_AIC8800_USE_P2P0
+
+	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_RWNX_DEBUGFS
+	rwnx_dbgfs_unregister(rwnx_hw);
+err_debugfs:
+#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:
+	aicwf_wakeup_lock_deinit(rwnx_hw);
+	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_RWNX_DEBUGFS
+	rwnx_dbgfs_unregister(rwnx_hw);
+#endif
+
+#ifdef CONFIG_SDIO_BT
+	btchr_exit();
+	hdev_exit();
+#endif
+#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
+	aicwf_wakeup_lock_deinit(rwnx_hw);
+	wiphy_free(rwnx_hw->wiphy);
+}
+
+static void aicsmac_driver_register(void)
+{
+#ifdef AICWF_SDIO_SUPPORT
+	aicwf_sdio_register();
+#endif
+}
+
+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_print_version();
+	rwnx_init_cmd_array();
+
+	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_VERSION(RWNX_VERS_MOD);
+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..8ee08b978ec9c
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_main.h
@@ -0,0 +1,34 @@
+/* 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;
+
+#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..e955debeeee66
--- /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..c32d949851c12
--- /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..01e8c9b26d503
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mod_params.c
@@ -0,0 +1,1295 @@
+// 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_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, true)
+	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, true)
+	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");
+
+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: 1)");
+
+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)");
+
+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)");
+
+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)");
+
+module_param_named(mesh, rwnx_mod_params.mesh, bool, 0444);
+MODULE_PARM_DESC(mesh, "Enable Meshing Capability (Default: 1-Enabled)");
+
+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");
+
+module_param_named(tdls, rwnx_mod_params.tdls, bool, 0444);
+MODULE_PARM_DESC(tdls, "Enable TDLS (Default: 1-Enabled)");
+
+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
+char default_ccode[4] = "XX";
+#else
+char default_ccode[4] = "00";
+#endif
+
+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))
+
+char ccode_channels[200];
+int index_for_channel_list;
+module_param_string(ccode_channels, ccode_channels, 200, 0600);
+
+void rwnx_get_countrycode_channels(struct wiphy *wiphy,
+				   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];
+#ifdef CONFIG_AIC8800_USE_WIRELESS_EXT
+	struct rwnx_hw *rwnx_hw = wiphy_priv(wiphy);
+	int support_freqs_counter = 0;
+#endif
+
+	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) {
+#ifdef CONFIG_AIC8800_USE_WIRELESS_EXT
+					rwnx_hw->support_freqs[support_freqs_counter++] =
+						center_freq;
+#endif
+					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;
+				}
+			}
+		}
+	}
+
+#ifdef CONFIG_AIC8800_USE_WIRELESS_EXT
+	rwnx_hw->support_freqs_number = support_freqs_counter;
+#endif
+	AICWFDBG(LOGDEBUG, "%s support channel:%s\r\n", __func__, ccode_channels);
+}
+
+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];
+}
+
+struct ieee80211_regdomain *get_regdomain_from_rwnx_db(struct wiphy *wiphy,
+						       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
+}
+
+static void rwnx_set_vht_capa(struct rwnx_hw *rwnx_hw, struct wiphy *wiphy)
+{
+#ifdef CONFIG_AIC8800_VHT_FOR_OLD_KERNEL
+#ifdef AIC8800_USE_5G
+	struct ieee80211_supported_band *band_5ghz =
+		wiphy->bands[NL80211_BAND_5GHZ];
+#endif
+	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;
+
+	rwnx_hw->vht_cap_2G.vht_supported = true;
+	if (rwnx_hw->mod_params->sgi80)
+		rwnx_hw->vht_cap_2G.cap |= IEEE80211_VHT_CAP_SHORT_GI_80;
+	if (rwnx_hw->mod_params->stbc_on)
+		rwnx_hw->vht_cap_2G.cap |= IEEE80211_VHT_CAP_RXSTBC_1;
+	if (rwnx_hw->mod_params->ldpc_on)
+		rwnx_hw->vht_cap_2G.cap |= IEEE80211_VHT_CAP_RXLDPC;
+	if (rwnx_hw->mod_params->bfmee) {
+		rwnx_hw->vht_cap_2G.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
+		rwnx_hw->vht_cap_2G.cap |= 3 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT;
+	}
+	if (nss > 1)
+		rwnx_hw->vht_cap_2G.cap |= IEEE80211_VHT_CAP_TXSTBC;
+
+	// Update the AMSDU max RX size (not shifted as located at offset 0 of the
+	// VHT cap)
+	rwnx_hw->vht_cap_2G.cap |= rwnx_hw->mod_params->amsdu_rx_max;
+
+	if (rwnx_hw->mod_params->bfmer) {
+		rwnx_hw->vht_cap_2G.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE;
+		/* Set number of sounding dimensions */
+		rwnx_hw->vht_cap_2G.cap |=
+			(nss - 1) << IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT;
+	}
+	if (rwnx_hw->mod_params->murx)
+		rwnx_hw->vht_cap_2G.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
+	if (rwnx_hw->mod_params->mutx)
+		rwnx_hw->vht_cap_2G.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);
+
+	rwnx_hw->vht_cap_2G.vht_mcs.rx_mcs_map = cpu_to_le16(0);
+	for (i = 0; i < nss; i++) {
+		rwnx_hw->vht_cap_2G.vht_mcs.rx_mcs_map |=
+			cpu_to_le16(mcs_map << (i * 2));
+		rwnx_hw->vht_cap_2G.vht_mcs.rx_highest =
+			MAX_VHT_RATE(mcs_map, nss, bw_max);
+		mcs_map = IEEE80211_VHT_MCS_SUPPORT_0_7;
+	}
+	for (; i < 8; i++) {
+		rwnx_hw->vht_cap_2G.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);
+	rwnx_hw->vht_cap_2G.vht_mcs.tx_mcs_map = cpu_to_le16(0);
+	for (i = 0; i < nss; i++) {
+		rwnx_hw->vht_cap_2G.vht_mcs.tx_mcs_map |=
+			cpu_to_le16(mcs_map << (i * 2));
+		rwnx_hw->vht_cap_2G.vht_mcs.tx_highest =
+			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++) {
+		rwnx_hw->vht_cap_2G.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
+		rwnx_hw->vht_cap_2G.cap |= IEEE80211_VHT_CAP_NOT_SUP_WIDTH_80;
+#endif // CONFIG_VENDOR_RWNX_VHT_NO80
+		rwnx_hw->vht_cap_2G.cap &= ~IEEE80211_VHT_CAP_SHORT_GI_80;
+	}
+
+	rwnx_hw->vht_cap_2G.cap |=
+		IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK;
+	AICWFDBG(LOGDEBUG, "%s, vht_capa_info=0x%x\n", __func__, rwnx_hw->vht_cap_2G.cap);
+#ifdef AIC8800_USE_5G
+	if (rwnx_hw->band_5g_support) {
+		rwnx_hw->vht_cap_5G.vht_supported = true;
+		if (rwnx_hw->mod_params->sgi80)
+			rwnx_hw->vht_cap_5G.cap |= IEEE80211_VHT_CAP_SHORT_GI_80;
+		if (rwnx_hw->mod_params->stbc_on)
+			rwnx_hw->vht_cap_5G.cap |= IEEE80211_VHT_CAP_RXSTBC_1;
+		if (rwnx_hw->mod_params->ldpc_on)
+			rwnx_hw->vht_cap_5G.cap |= IEEE80211_VHT_CAP_RXLDPC;
+		if (rwnx_hw->mod_params->bfmee) {
+			rwnx_hw->vht_cap_5G.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
+			rwnx_hw->vht_cap_5G.cap |=
+				3 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT;
+		}
+		if (nss > 1)
+			rwnx_hw->vht_cap_5G.cap |= IEEE80211_VHT_CAP_TXSTBC;
+
+		// Update the AMSDU max RX size (not shifted as located at offset 0 of
+		// the VHT cap)
+		rwnx_hw->vht_cap_5G.cap |= rwnx_hw->mod_params->amsdu_rx_max;
+
+		if (rwnx_hw->mod_params->bfmer) {
+			rwnx_hw->vht_cap_5G.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE;
+			/* Set number of sounding dimensions */
+			rwnx_hw->vht_cap_5G.cap |=
+				(nss - 1) << IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT;
+		}
+		if (rwnx_hw->mod_params->murx)
+			rwnx_hw->vht_cap_5G.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
+		if (rwnx_hw->mod_params->mutx)
+			rwnx_hw->vht_cap_5G.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);
+		rwnx_hw->vht_cap_5G.vht_mcs.rx_mcs_map = cpu_to_le16(0);
+		for (i = 0; i < nss; i++) {
+			rwnx_hw->vht_cap_5G.vht_mcs.rx_mcs_map |=
+				cpu_to_le16(mcs_map << (i * 2));
+			rwnx_hw->vht_cap_5G.vht_mcs.rx_highest =
+				MAX_VHT_RATE(mcs_map, nss, bw_max);
+			mcs_map = IEEE80211_VHT_MCS_SUPPORT_0_7;
+		}
+		for (; i < 8; i++) {
+			rwnx_hw->vht_cap_5G.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);
+		rwnx_hw->vht_cap_5G.vht_mcs.tx_mcs_map = cpu_to_le16(0);
+		for (i = 0; i < nss; i++) {
+			rwnx_hw->vht_cap_5G.vht_mcs.tx_mcs_map |=
+				cpu_to_le16(mcs_map << (i * 2));
+			rwnx_hw->vht_cap_5G.vht_mcs.tx_highest =
+				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++) {
+			rwnx_hw->vht_cap_5G.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
+			rwnx_hw->vht_cap_5G.cap |= IEEE80211_VHT_CAP_NOT_SUP_WIDTH_80;
+#endif // CONFIG_VENDOR_RWNX_VHT_NO80
+			rwnx_hw->vht_cap_5G.cap &= ~IEEE80211_VHT_CAP_SHORT_GI_80;
+		}
+	}
+#endif // AIC8800_USE_5G
+	return;
+#else
+	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;
+#endif // CONFIG_AIC8800_VHT_FOR_OLD_KERNEL
+
+	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 =
+			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 =
+			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 =
+				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 =
+				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;
+}
+
+#ifdef CONFIG_AIC8800_HE_FOR_OLD_KERNEL
+extern struct ieee80211_sband_iftype_data rwnx_he_capa;
+#endif
+static void rwnx_set_he_capa(struct rwnx_hw *rwnx_hw, struct wiphy *wiphy)
+{
+#ifdef CONFIG_AIC8800_HE_FOR_OLD_KERNEL
+	struct ieee80211_sta_he_cap *he_cap;
+	int i;
+	int nss = rwnx_hw->mod_params->nss;
+	int mcs_map;
+
+	he_cap = (struct ieee80211_sta_he_cap *)&rwnx_he_capa.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_HE_MU_PPDU_FROM_NON_AP_STA;
+	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_BEAMFORMER_FB |
+		IEEE80211_HE_PHY_CAP6_TRIG_MU_BEAMFORMER_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;
+	}
+
+	return;
+#endif
+
+	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 = (struct ieee80211_sta_he_cap *)&band_2ghz->iftype_data->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 = (struct ieee80211_sta_he_cap *)&band_5ghz->iftype_data->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 (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;
+
+#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 (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;
+		}
+	}
+}
+
+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 (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 (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..caa3c30a0a00d
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mod_params.h
@@ -0,0 +1,70 @@
+/* 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 struct ieee80211_regdomain *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,
+				   struct ieee80211_regdomain *regdomain);
+struct ieee80211_regdomain *get_regdomain_from_rwnx_db(struct wiphy *wiphy,
+						       char *alpha2);
+struct ieee80211_regdomain *get_regdomain_from_rwnx_db_index(struct wiphy *wiphy,
+							     int index);
+
+#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..47b6b86e5e7bb
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_mu_group.c
@@ -0,0 +1,646 @@
+// 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
+ */
+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 ||
+	    !(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 the list of group a STA belongs to as a bitfield
+ */
+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 the position of @sta in group @group_id or -1 if the sta
+ * doesn't belongs to the group (or group id 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;
+	u64 group_avail;
+
+	group_match = users[0]->group_info.map;
+	group_avail = users[0]->group_info.map;
+	for (i = 1; i < nb_user; i++) {
+		group_match &= users[i]->group_info.map;
+		group_avail |= users[i]->group_info.map;
+	}
+
+	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..06e0d367eaf15
--- /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 for @mu->active_sta list
+ * @update: node for @mu->update_sta list
+ * @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 mu_group_info - 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 group
+ *
+ * @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 at fw level
+ * @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 taken during group update. If tx happens lock is taken, then tx
+ *        will not used MU.
+ * @next_group_assign: 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..095d85a27a139
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_radar.c
@@ -0,0 +1,1965 @@
+// 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
+ * @domain: DFS regulatory domain
+ * @num_radar_types: number of radar types to follow
+ * @radar_types: radar types array
+ */
+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 the length of the longest sequence in which the pulse has been 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, and NULL if no sequence 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
+ */
+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.
+ */
+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
+ */
+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 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 pointer to pri detector on success, NULL otherwise
+ *
+ * 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.
+ */
+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 is the pulse complete a radar pattern, false otherwise
+ */
+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 maximum radar types for all supported domain
+ * @return the maximum number of radar pattern supported by on 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;
+					}
+				} else {
+					/*
+					 * secondary radar detection chain, simply report info in
+					 * debugfs for now
+					 */
+				}
+
+				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..0f1706d571a0b
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_radar.h
@@ -0,0 +1,278 @@
+/* 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
+ * @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:
+ * @radar_detector_specs: array of radar detection specs
+ * @channel_detectors: list connecting channel_detector elements
+ */
+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
+ */
+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
+ * @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:
+ * @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
+ * @freq:
+ */
+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..e32ccdead401c
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_rx.c
@@ -0,0 +1,2443 @@
+// 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.
+ */
+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_l 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 - Process one data frame
+ *
+ * @rwnx_hw: main driver data
+ * @rwnx_vif: vif that received the buffer
+ * @skb: skb received
+ * @rxhdr: HW rx descriptor
+ * @return: true if buffer has been forwarded to upper layer
+ *
+ * If buffer is amsdu , it is first split into a list of skb.
+ * Then each skb may be:
+ * - forwarded to upper layer
+ * - resent on wireless interface
+ *
+ * When vif is a STA interface, every skb is only forwarded to upper layer.
+ * When vif is an AP interface, multicast skb are forwarded and resent, whereas
+ * skb for other BSS's STA are only resent.
+ */
+
+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);
+
+#ifdef CONFIG_AIC8800_BR_SUPPORT
+	void *br_port = NULL;
+
+	rcu_read_lock();
+	br_port = rcu_dereference(rwnx_vif->ndev->rx_handler_data);
+	rcu_read_unlock();
+
+	if (br_port) {
+		if (nat25_handle_frame(rwnx_vif, rx_skb) == -1) {
+			/* priv->ext_stats.rx_data_drops++; */
+			/* DEBUG_ERR("RX DROP: nat25_handle_frame fail!\n"); */
+			/* return FAIL; */
+		}
+	}
+
+#endif /* CONFIG_AIC8800_BR_SUPPORT */
+
+	/* 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, cpu_to_le16(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) {
+#ifdef CONFIG_AIC8800_BR_SUPPORT
+			void *br_port = NULL;
+
+			/* Insert NAT2.5 RX here! */
+			rcu_read_lock();
+			br_port = rcu_dereference(rwnx_vif->ndev->rx_handler_data);
+			rcu_read_unlock();
+
+			if (br_port) {
+				if (nat25_handle_frame(rwnx_vif, rx_skb) == -1) {
+					/* priv->ext_stats.rx_data_drops++; */
+					/* DEBUG_ERR("RX DROP: nat25_handle_frame fail!\n"); */
+					/* return FAIL; */
+				}
+			}
+#endif /* CONFIG_AIC8800_BR_SUPPORT */
+
+			/* 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, cpu_to_le16(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;
+}
+
+#ifdef CONFIG_AIC8800_HE_FOR_OLD_KERNEL
+
+#endif
+
+/**
+ * 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 (defined CONFIG_AIC8800_HE_FOR_OLD_KERNEL) || (defined CONFIG_AIC8800_VHT_FOR_OLD_KERNEL)
+	struct aic_sta *sta = &rwnx_hw->aic_table[rwnx_vif->ap.aic_index];
+	const u8 *ie;
+	u32 len;
+
+	if (skb->data[0] != 0x80)
+		AICWFDBG(LOGDEBUG, "rxmgmt:%x,%x\n", skb->data[0], skb->data[1]);
+
+	if (ieee80211_is_assoc_req(mgmt->frame_control) &&
+	    rwnx_vif->wdev.iftype == NL80211_IFTYPE_AP) {
+		AICWFDBG(LOGDEBUG, "ASSOC_REQ: sta_idx %d MAC %pM\n", rwnx_vif->ap.aic_index,
+			 mgmt->sa);
+		sta->sta_idx = rwnx_vif->ap.aic_index;
+		len = skb->len - (mgmt->u.assoc_req.variable - skb->data);
+
+#ifdef CONFIG_AIC8800_HE_FOR_OLD_KERNEL
+		struct ieee80211_he_cap_elem *he;
+
+		ie = cfg80211_find_ext_ie(WLAN_EID_EXT_HE_CAPABILITY,
+					  mgmt->u.assoc_req.variable, len);
+		if (ie && ie[1] >= sizeof(*he) + 1) {
+			AICWFDBG(LOGDEBUG, "assoc_req: find he\n");
+			sta->he = true;
+		} else {
+			AICWFDBG(LOGDEBUG, "assoc_req: no find he\n");
+			sta->he = false;
+		}
+#endif
+
+#ifdef CONFIG_AIC8800_VHT_FOR_OLD_KERNEL
+		struct ieee80211_vht_cap *vht;
+
+		ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY,
+				      mgmt->u.assoc_req.variable, len);
+		if (ie && ie[1] >= sizeof(*vht)) {
+			AICWFDBG(LOGDEBUG, "assoc_req: find vht\n");
+			sta->vht = true;
+		} else {
+			AICWFDBG(LOGDEBUG, "assoc_req: no find vht\n");
+			sta->vht = false;
+		}
+#endif
+	}
+#endif
+
+	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) {
+#ifdef CONFIG_AIC8800_GKI
+			/* TODO: the value of parameter sig_dbm need to be confirmed */
+			rwnx_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
+			/* 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);
+#endif
+		} else {
+#ifdef CONFIG_AIC8800_GKI
+			rwnx_cfg80211_report_obss_beacon
+			(rwnx_hw->wiphy, skb->data, skb->len,
+			 hw_rxhdr->phy_info.phy_prim20_freq, rxvect->rssi1);
+#else
+			cfg80211_report_obss_beacon(rwnx_hw->wiphy, skb->data, skb->len,
+						    hw_rxhdr->phy_info.phy_prim20_freq,
+						    rxvect->rssi1);
+#endif
+		}
+	} else if ((ieee80211_is_deauth(mgmt->frame_control) ||
+				ieee80211_is_disassoc(mgmt->frame_control)) &&
+			   (mgmt->u.deauth.reason_code ==
+					WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA ||
+				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).
+ */
+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, 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);
+		WARN_ON_ONCE((rate_idx = legrates_lut[rxvect->leg_rate].idx) == -1);
+		if (phy_info->phy_band == NL80211_BAND_5GHZ)
+			rate_idx -= 4; /* rwnx_ratetable_5ghz[0].hw_value == 4 */
+		*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 - Build radiotap header for skb an send it to netdev
+ *
+ * @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 radiotap header to the receved skb and send it to netdev
+ */
+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, cpu_to_le16(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;
+
+#ifdef CONFIG_AIC8800_BR_SUPPORT
+	void *br_port = NULL;
+
+	/* Insert NAT2.5 RX here! */
+	rcu_read_lock();
+	br_port = rcu_dereference(rwnx_vif->ndev->rx_handler_data);
+	rcu_read_unlock();
+
+	if (br_port) {
+		if (nat25_handle_frame(rwnx_vif, skb) == -1) {
+			/* priv->ext_stats.rx_data_drops++; */
+			/* DEBUG_ERR("RX DROP: nat25_handle_frame fail!\n"); */
+			/* return FAIL; */
+		}
+	}
+#endif /* CONFIG_AIC8800_BR_SUPPORT */
+
+	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,
+				  cpu_to_le16(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_l 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_l 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_l 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 = le32_to_cpu(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) {
+#ifdef CONFIG_AIC8800_GKI
+			rwnx_cfg80211_rx_spurious_frame(rwnx_vif->ndev, hdr->addr2,
+							GFP_ATOMIC);
+#else
+			cfg80211_rx_spurious_frame(rwnx_vif->ndev, hdr->addr2, 0,
+						   GFP_ATOMIC);
+#endif
+		}
+		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) {
+#ifdef CONFIG_AIC8800_GKI
+					rwnx_cfg80211_rx_unexpected_4addr_frame
+						(rwnx_vif->ndev, sta->mac_addr, GFP_ATOMIC);
+#else
+					cfg80211_rx_unexpected_4addr_frame
+						(rwnx_vif->ndev, sta->mac_addr, 0,
+						 GFP_ATOMIC);
+#endif
+				}
+			}
+
+			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..af8e68b9d394b
--- /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];
+	u32 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..61cc1a7696700
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tdls.c
@@ -0,0 +1,776 @@
+// 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 = (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;
+#ifdef CONFIG_AIC8800_GKI
+	if (!rwnx_ieee80211_chandef_to_operating_class(&chan_def, &op_class))
+#else
+	if (!ieee80211_chandef_to_operating_class(&chan_def, &op_class))
+#endif
+		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, &params, 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..3964e7cc59bb1
--- /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..3d51d55106753
--- /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..aa125ead51ea5
--- /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..1660d1cb24bbf
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tx.c
@@ -0,0 +1,1805 @@
+// 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_l 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) {
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_P2P_CLIENT: {
+		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;
+	}
+	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(&eth->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 *)&eth->h_source,
+							      true)) {
+					rwnx_send_mesh_proxy_add_req(rwnx_hw, rwnx_vif,
+								     (u8 *)&eth->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 *)&eth->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 *)&eth->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;
+	}
+	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.
+ */
+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 == 0x8e88 ||
+	    sw_txhdr->desc.host.ethertype == 0xb488) {
+		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_BR_SUPPORT
+int aic_br_client_tx(struct rwnx_vif *vif, struct sk_buff **pskb)
+{
+	struct sk_buff *skb = *pskb;
+
+	int res, is_vlan_tag = 0, i, do_nat25 = 1;
+	unsigned short vlan_hdr = 0;
+	void *br_port = NULL;
+
+#ifdef BR_SUPPORT_DEBUG
+	AICWFDBG(LOGDEBUG, "SA=%pM, br_mac=%pM, type=0x%x, da[0]=%x, scdb=%pM, vif_type=%d\n",
+		 skb->data + MACADDRLEN, vif->br_mac,
+		 *((unsigned short *)(skb->data + MACADDRLEN * 2)), skb->data[0],
+		 vif->scdb_mac, RWNX_VIF_TYPE(vif));
+#endif
+	spin_lock_bh(&vif->br_ext_lock);
+	if (!(skb->data[0] & 1) && br_port &&
+	    memcmp(skb->data + MACADDRLEN, vif->br_mac, MACADDRLEN) &&
+	    *((unsigned short *)(skb->data + MACADDRLEN * 2)) != htons(ETH_P_8021Q) &&
+	    *((unsigned short *)(skb->data + MACADDRLEN * 2)) == htons(ETH_P_IP) &&
+	    !memcmp(vif->scdb_mac, skb->data + MACADDRLEN, MACADDRLEN) &&
+	    vif->scdb_entry) {
+		memcpy(skb->data + MACADDRLEN, vif->ndev->dev_addr, MACADDRLEN);
+		vif->scdb_entry->ageing_timer = jiffies;
+		spin_unlock_bh(&vif->br_ext_lock);
+	} else {
+		if (*((unsigned short *)(skb->data + MACADDRLEN * 2)) ==
+			htons(ETH_P_8021Q)) {
+			is_vlan_tag = 1;
+			vlan_hdr = *((unsigned short *)(skb->data + MACADDRLEN * 2 + 2));
+			for (i = 0; i < 6; i++)
+				*((unsigned short *)(skb->data + MACADDRLEN * 2 + 2 - i * 2)) =
+					*((unsigned short *)(skb->data + MACADDRLEN * 2 - 2 -
+										 i * 2));
+			skb_pull(skb, 4);
+		}
+		/* if SA == br_mac && skb== IP  => copy SIP to br_ip ?? why */
+		if (!memcmp(skb->data + MACADDRLEN, vif->br_mac, MACADDRLEN) &&
+		    (*((unsigned short *)(skb->data + MACADDRLEN * 2)) == htons(ETH_P_IP)))
+			memcpy(vif->br_ip, skb->data + WLAN_ETHHDR_LEN + 12, 4);
+
+		if (*((unsigned short *)(skb->data + MACADDRLEN * 2)) ==
+			htons(ETH_P_IP)) {
+			if (memcmp(vif->scdb_mac, skb->data + MACADDRLEN, MACADDRLEN)) {
+				vif->scdb_entry =
+				(struct nat25_network_db_entry *)
+					scdb_find_entry(vif, skb->data + MACADDRLEN,
+							skb->data + WLAN_ETHHDR_LEN + 12);
+				if (vif->scdb_entry) {
+					memcpy(vif->scdb_mac, skb->data + MACADDRLEN, MACADDRLEN);
+					memcpy(vif->scdb_ip, skb->data + WLAN_ETHHDR_LEN + 12, 4);
+					vif->scdb_entry->ageing_timer = jiffies;
+					do_nat25 = 0;
+				}
+			} else {
+				if (vif->scdb_entry) {
+					vif->scdb_entry->ageing_timer = jiffies;
+					do_nat25 = 0;
+				} else {
+					memset(vif->scdb_mac, 0, MACADDRLEN);
+					memset(vif->scdb_ip, 0, 4);
+				}
+			}
+		}
+		spin_unlock_bh(&vif->br_ext_lock);
+
+		if (do_nat25) {
+			if (nat25_db_handle(vif, skb, NAT25_CHECK) == 0) {
+				struct sk_buff *newskb;
+
+				if (is_vlan_tag) {
+					skb_push(skb, 4);
+					for (i = 0; i < 6; i++)
+						*((unsigned short *)(skb->data + i * 2)) =
+							*((unsigned short *)
+							(skb->data + 4 + i * 2));
+					*((unsigned short *)(skb->data + MACADDRLEN * 2)) =
+						htons(ETH_P_8021Q);
+					*((unsigned short *)(skb->data + MACADDRLEN * 2 + 2)) =
+						vlan_hdr;
+				}
+
+				newskb =
+					skb_copy(skb, in_interrupt() ? GFP_ATOMIC : GFP_KERNEL);
+				if (!newskb) {
+					AICWFDBG(LOGDEBUG, "TX DROP: skb_copy fail!\n");
+					/* goto stop_proc; */
+					return -1;
+				}
+				dev_kfree_skb_any(skb);
+
+				*pskb = skb = newskb;
+				if (is_vlan_tag) {
+					vlan_hdr = *((unsigned short *)
+						(skb->data + MACADDRLEN * 2 + 2));
+					for (i = 0; i < 6; i++)
+						*((unsigned short *)
+						(skb->data + MACADDRLEN * 2 + 2 - i * 2)) =
+						*((unsigned short *)(skb->data +
+							MACADDRLEN * 2 - 2 - i * 2));
+					skb_pull(skb, 4);
+				}
+			}
+
+			if (skb_is_nonlinear(skb))
+				AICWFDBG(LOGDEBUG, "%s(): skb_is_nonlinear!!\n", __func__);
+
+			res = skb_linearize(skb);
+
+			if (res < 0) {
+				AICWFDBG(LOGDEBUG, "TX DROP: skb_linearize fail!\n");
+				/* goto free_and_stop; */
+				return -1;
+			}
+
+			res = nat25_db_handle(vif, skb, NAT25_INSERT);
+			if (res < 0) {
+				if (res == -2) {
+					AICWFDBG(LOGDEBUG, "TX DROP: nat25_db_handle fail!\n");
+					/* goto free_and_stop; */
+					return -1;
+				}
+				return 0;
+			}
+		}
+
+		memcpy(skb->data + MACADDRLEN, vif->ndev->dev_addr, MACADDRLEN);
+
+		dhcp_flag_bcast(vif, skb);
+
+		if (is_vlan_tag) {
+			skb_push(skb, 4);
+			for (i = 0; i < 6; i++)
+				*((unsigned short *)(skb->data + i * 2)) =
+					*((unsigned short *)(skb->data + 4 + i * 2));
+			*((unsigned short *)(skb->data + MACADDRLEN * 2)) =
+				htons(ETH_P_8021Q);
+			*((unsigned short *)(skb->data + MACADDRLEN * 2 + 2)) = vlan_hdr;
+		}
+	}
+
+	/* check if SA is equal to our MAC */
+	if (memcmp(skb->data + MACADDRLEN, vif->ndev->dev_addr, MACADDRLEN)) {
+		AICWFDBG(LOGDEBUG, "TX DROP: untransformed frame SA:%02X%02X%02X%02X%02X%02X!\n",
+			 skb->data[6], skb->data[7], skb->data[8], skb->data[9],
+			 skb->data[10], skb->data[11]);
+		/* goto free_and_stop; */
+		return -1;
+	}
+
+	AICWFDBG(LOGDEBUG, "%s function exit\n", __func__);
+	return 0;
+}
+#endif /* CONFIG_AIC8800_BR_SUPPORT */
+
+#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(&eth_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
+
+#ifdef CONFIG_AIC8800_BR_SUPPORT
+	if (1) { //(check_fwstate(&padapter->mlmepriv, WIFI_STATION_STATE |
+			 // WIFI_ADHOC_STATE) == _TRUE) {
+		void *br_port = NULL;
+
+		rcu_read_lock();
+		br_port = rcu_dereference(rwnx_vif->ndev->rx_handler_data);
+		rcu_read_unlock();
+
+		if (br_port) {
+			s32 res = aic_br_client_tx(rwnx_vif, &skb);
+
+			if (res == -1)
+				goto free;
+		}
+	}
+#endif /* CONFIG_AIC8800_BR_SUPPORT */
+
+	/* 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;
+	if (unlikely(rwnx_vif->wdev.iftype == NL80211_IFTYPE_AP_VLAN))
+		desc->host.vif_idx = rwnx_vif->ap_vlan.master->vif_index;
+	else
+		desc->host.vif_idx = rwnx_vif->vif_index;
+
+	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 (rwnx_vif->wdev.iftype == NL80211_IFTYPE_MESH_POINT) {
+		if (rwnx_vif->is_resending)
+			desc->host.flags |= TXU_CNTRL_MESH_FWD;
+	}
+
+#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 (cpu_to_le16(skb->len) <= MAX_TCP_ACK) {
+		msgbuf = intf_tcp_alloc_msg(msgbuf);
+		msgbuf->rwnx_vif = rwnx_vif;
+		msgbuf->skb = skb;
+		if (filter_send_tcp_ack(rwnx_hw, msgbuf, skb->data,
+					cpu_to_le16(skb->len))) {
+			return NETDEV_TX_OK;
+		}
+		move_tcpack_msg(rwnx_hw, msgbuf);
+		kfree(msgbuf);
+	}
+#endif
+
+	memcpy(&eth_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
+
+#ifdef CONFIG_AIC8800_BR_SUPPORT
+	void *br_port = NULL;
+
+	rcu_read_lock();
+	br_port = rcu_dereference(rwnx_vif->ndev->rx_handler_data);
+	rcu_read_unlock();
+
+	if (br_port) {
+		s32 res = aic_br_client_tx(rwnx_vif, &skb);
+
+		if (res == -1)
+			goto free;
+	}
+#endif /* CONFIG_AIC8800_BR_SUPPORT */
+
+	/* 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;
+	if (unlikely(rwnx_vif->wdev.iftype == NL80211_IFTYPE_AP_VLAN))
+		desc->host.vif_idx = rwnx_vif->ap_vlan.master->vif_index;
+	else
+		desc->host.vif_idx = rwnx_vif->vif_index;
+
+	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 (rwnx_vif->wdev.iftype == NL80211_IFTYPE_MESH_POINT) {
+		if (rwnx_vif->is_resending)
+			desc->host.flags |= TXU_CNTRL_MESH_FWD;
+	}
+
+#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
+ *
+ */
+
+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 (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;
+	}
+
+	/*
+	 * 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
+ *
+ * called with tx_lock hold
+ */
+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 &&
+	    txhdr->hw_hdr.cfm.ampdu_size < IEEE80211_MAX_AMPDU_BUF)
+		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..636e99a8b85e1
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_tx.h
@@ -0,0 +1,193 @@
+/* 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 - Structure added in skb headroom (instead of
+ * rwnx_txhdr) for amsdu subframe buffer (except for the first subframe
+ * that has a normal rwnx_txhdr)
+ *
+ * @list     List of other amsdu subframe (rwnx_sw_txhdr.amsdu.hdrs)
+ * @map_len  Length to be downloaded for this subframe
+ * @dma_addr Buffer address form embedded point of view
+ * @skb      skb
+ * @pad      padding added before this subframe
+ *           (only use when amsdu must be dismantled)
+ * @msdu_len Size, in bytes, of the MSDU (without padding nor amsdu 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 - Structure to manage creation of an A-MSDU, updated
+ * only In the first subframe of an A-MSDU
+ *
+ * @hdrs List of subframe of rwnx_amsdu_txhdr
+ * @len  Current size for this A-MDSU (doesn't take padding into account)
+ *       0 means that no amsdu is in progress
+ * @nb   Number of subframe in the amsdu
+ * @pad  Padding to add before adding a new 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 sta to which this buffer is addressed
+ * @rwnx_vif vif that send the buffer
+ * @txq pointer to TXQ used to send the buffer
+ * @hw_queue Index of the HWQ used to push the buffer.
+ *           May be different than txq->hwq->id on confirmation.
+ * @frame_len Size of the frame (doesn't not include mac header)
+ *            (Only used to update stat, can't we use skb->len instead ?)
+ * @headroom Headroom added in skb to add rwnx_txhdr
+ *           (Only used to remove it before freeing skb, is it needed ?)
+ * @amsdu Description of amsdu whose first subframe is this buffer
+ *        (amsdu.nb = 0 means this buffer is not part of amsdu)
+ * @skb skb received from transmission
+ * @map_len  Length mapped for DMA (only rwnx_hw_txhdr and data are mapped)
+ * @dma_addr DMA address after mapping
+ * @desc Buffer description that will be copied in shared mem for FW
+ */
+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);
+
+int rwnx_dbgfs_print_sta(char *buf, size_t size, struct rwnx_sta *sta,
+			 struct rwnx_hw *rwnx_hw);
+
+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);
+
+void dhcp_flag_bcast(struct rwnx_vif *vif, struct sk_buff *skb);
+
+void *scdb_find_entry(struct rwnx_vif *vif, unsigned char *mac_addr, unsigned char *ip_addr);
+
+int nat25_db_handle(struct rwnx_vif *vif, struct sk_buff *skb, int method);
+
+#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..4ab662a698f63
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_txq.c
@@ -0,0 +1,1379 @@
+// 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_tdls_vif_deinit - Deinitialize TXQ for unknown STA linked to a vif
+ *
+ * @rwnx_vif: Pointer on VIF
+ */
+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_init_unk_txq - Initialize TX queue for the transmission on a offchannel
+ *
+ * @vif: Interface for which the queue has to be initialized
+ *
+ * NOTE: Offchannel txq is only active for the duration of the ROC
+ */
+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_deinit_offchan_txq - Deinitialize TX queue for offchannel
+ *
+ * @vif: Interface that manages the STA
+ *
+ * This function deinitialize txq for one STA.
+ * Any buffer stuck in a queue will be 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 there are buffer ready to be pushed on this txq,
+ * false otherwise
+ */
+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 the TX queue linked to a STA
+ *
+ * @sta: STA whose TX queues must be re-started
+ * @reason: Reason why the TX queue are restarted (among RWNX_TXQ_STOP_xxx)
+ * @rwnx_hw: Driver main data
+ *
+ * This function will re-start all the TX queues of the STA for the reason
+ * specified. It can be :
+ * - 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
+ *
+ * Any TX queue with buffer ready and not Stopped for other reasons, will be
+ * added to the HW queue list
+ * To be called with tx_lock hold
+ */
+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_stop_sta_txq - Stop all the TX queue linked to a STA
+ *
+ * @sta: STA whose TX queues must be stopped
+ * @reason: Reason why the TX queue are stopped (among RWNX_TX_STOP_xxx)
+ * @rwnx_hw: Driver main data
+ *
+ * This function will stop all the TX queues of the STA for the reason
+ * specified. It can be :
+ * - 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
+ *
+ * Any TX queue present in a HW queue list will be removed from this list.
+ * To be called with tx_lock hold
+ */
+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)) {
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_P2P_CLIENT: {
+		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;
+	}
+	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;
+	}
+	default:
+		//WARN_ON_ONCE();
+		break;
+	}
+}
+#endif
+
+/**
+ * rwnx_txq_vif_start - START TX queues of all STA associated to the vif
+ *                      and vif's TXQ
+ *
+ * @vif: Interface to start
+ * @reason: Start reason (RWNX_TXQ_STOP_CHAN or RWNX_TXQ_STOP_VIF_PS)
+ * @rwnx_hw: Driver main data
+ *
+ * Iterate over all the STA associated to the vif and re-start them for the
+ * reason @reason
+ * Take 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 TX queues of all STA associated to the vif
+ *
+ * @vif: Interface to stop
+ * @arg: Stop reason (RWNX_TXQ_STOP_CHAN or RWNX_TXQ_STOP_VIF_PS)
+ * @rwnx_hw: Driver main data
+ *
+ * Iterate over all the STA associated to the vif and stop them for the
+ * reason RWNX_TXQ_STOP_CHAN or RWNX_TXQ_STOP_VIF_PS
+ * Take 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_start_offchan_txq - START TX queue for offchannel frame
+ *
+ * @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_switch_vif_sta_txq - Associate TXQ linked to a STA to a new vif
+ *
+ * @sta: STA whose txq must be switched
+ * @old_vif: Vif currently associated to the STA (may no longer be active)
+ * @new_vif: vif which should be associated to the STA for now on
+ *
+ * This function will switch the vif (i.e. the netdev) associated to all STA's
+ * TXQ. This is used when AP_VLAN interface are created.
+ * If one STA is associated to an AP_vlan vif, it will be moved from the master
+ * AP vif to the AP_vlan vif.
+ * If an AP_vlan vif is removed, then STA will be moved back to mastert AP vif.
+ *
+ */
+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)
+#ifdef CONFIG_AIC8800_GKI
+			rwnx_skb_append(txq->last_retry_skb, skb, &txq->sk_list);
+#else
+			skb_append(txq->last_retry_skb, skb, &txq->sk_list);
+#endif
+		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
+	int group_id;
+
+	user = RWNX_MUMIMO_INFO_POS_ID(sw_txhdr->desc.host.mumimo_info);
+	group_id = RWNX_MUMIMO_INFO_GROUP_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 more buffer are queued in mac80211 txq and false
+ * otherwise.
+ */
+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 txq no longer have buffer ready after the ones returned.
+ *         false otherwise
+ */
+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 false if it is no possible to process this txq.
+ *         true otherwise
+ *
+ * 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;
+}
+
+/**
+ * rwnx_hwq_process - Process one HW queue list
+ *
+ * @rwnx_hw: Driver main data
+ * @hw_queue: HW queue index to process
+ *
+ * The function will iterate over all the TX queues linked in this HW queue
+ * list. For each TX queue, push as many buffers as possible in the HW queue.
+ * (NB: TX queue have at least 1 buffer, otherwise it wouldn't be in the list)
+ * - If TX queue no longer have buffer, remove it from the list and check next
+ *   TX queue
+ * - If TX queue no longer have credits or has a push_limit (PS mode) and it
+ *   is reached , remove it from the list and check next TX queue
+ * - If HW queue is full, update list head to start with the next TX queue on
+ *   next call if current TX queue already pushed "too many" pkt in a row, and
+ *   return
+ *
+ * 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
+ *
+ * To be called with tx_lock hold
+ */
+#define ALL_HWQ_MASK ((1 << CONFIG_USER_MAX) - 1)
+
+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..a5eb90559135a
--- /dev/null
+++ b/drivers/net/wireless/aic/aic8800_fdrv/rwnx_txq.h
@@ -0,0 +1,372 @@
+/* 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: List of TXQ, that have buffers ready for this HWQ
+ * @credits: available credit for the queue (i.e. nb of buffers that
+ *           can be pushed to FW )
+ * @id Id of the HWQ among RWNX_HWQ_....
+ * @size size of the queue
+ * @need_processing Indicate if hwq should be processed
+ * @len number of packet ready to be pushed to fw for this HW queue
+ * @len_stop threshold to stop mac80211(i.e. netdev) queues. Stop queue when
+ *           driver has more than @len_stop packets ready.
+ * @len_start threshold to wake mac8011 queues. Wake queue when driver has
+ *            less than @len_start packets ready.
+ */
+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 Pushing a buffer for retry
+ * @RWNX_PUSH_IMMEDIATE Pushing 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 @rwnx_txq_flags.
+ * @credits: available credit for the queue (i.e. nb of buffers that
+ *           can be pushed to FW).
+ * @pkt_sent: number of consecutive pkt sent without leaving HW queue list
+ * @pkt_pushed: number of pkt currently pending for transmission confirmation
+ * @sched_list: list node for HW queue schedule list (rwnx_hwq.list)
+ * @sk_list: list of buffers to push to fw
+ * @last_retry_skb: pointer on the last skb in @sk_list that is a retry.
+ *                  (retry skb are stored at the beginning of the list)
+ *                  NULL if no retry skb is queued in @sk_list
+ * @nb_retry: Number of retry packet queued.
+ * @hwq: Pointer on the associated HW queue.
+ * @push_limit: number of packet to push before removing the txq from hwq list.
+ *              (we always have push_limit < skb_queue_len(sk_list))
+ * @tid: TID
+ *
+ * SOFTMAC specific:
+ * @baw: Block Ack window information
+ * @amsdu_anchor: pointer to rwnx_sw_txhdr of the first subframe of the A-MSDU.
+ *                NULL if no A-MSDU frame is in construction
+ * @amsdu_ht_len_cap:
+ * @amsdu_vht_len_cap:
+ * @nb_ready_mac80211: Number of buffer ready in mac80211 txq
+ *
+ * FULLMAC specific
+ * @ps_id: Index to use for Power save mode (LEGACY or UAPSD)
+ * @ndev_idx: txq idx from netdev point of view (0xFF for non netdev queue)
+ * @ndev: pointer to ndev of the corresponding vif
+ * @amsdu: pointer to rwnx_sw_txhdr of the first subframe of the A-MSDU.
+ *         NULL if no A-MSDU frame is in construction
+ * @amsdu_len: Maximum size allowed for an A-MSDU. 0 means A-MSDU not allowed
+ */
+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
+ */
+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..27c310aa6b24a
--- /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_l 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..9179dd4a4a916
--- /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_l txdesc_free_idx[SDIO_TXQUEUE_CNT];
+	// Index used that points to the first used TX desc
+	u32_l txdesc_used_idx[SDIO_TXQUEUE_CNT];
+	// Array storing the currently pushed host ids, per IPC queue
+	u64_l 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

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [RFC PATCH wireless-next 4/4] wifi: aic: integrate AIC8800 driver
  2026-07-21 10:20 [RFC PATCH wireless-next 0/4] wifi: aic: add AIC8800 SDIO FullMAC driver Yanli Yang
                   ` (2 preceding siblings ...)
  2026-07-21 10:21 ` [RFC PATCH wireless-next 3/4] wifi: aic: add FullMAC WLAN driver Yanli Yang
@ 2026-07-21 10:21 ` Yanli Yang
  3 siblings, 0 replies; 5+ messages in thread
From: Yanli Yang @ 2026-07-21 10:21 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg, Zhirun Liu, dijiaxu, chunqiuliu, liheng.wei

From: Zhirun Liu <zhirunliu@aicsemi.com>

Connect the completed AIC vendor directory to the wireless build
and add its MAINTAINERS entry. Keeping integration last prevents an earlier
partial source state from being selected by Kconfig.

Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
 MAINTAINERS                   | 10 ++++++++++
 drivers/net/wireless/Kconfig  |  1 +
 drivers/net/wireless/Makefile |  1 +
 3 files changed, 12 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 52f1a55eca99b..bead7f483fba8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -736,6 +736,16 @@ L:	linux-scsi@vger.kernel.org
 S:	Maintained
 F:	drivers/scsi/aic7xxx/
 
+AIC8800 WIRELESS DRIVER
+M:	zhirun.liu <zhirunliu@aicsemi.com>
+M:	dijia.xu <dijiaxu@aicsemi.com>
+M:	chunqiu.liu <chunqiuliu@aicsemi.com>
+M:	liheng.wei <liheng.wei@bedmex.com>
+M:	Yanli Yang <yanli.yang@bedmex.com>
+L:	linux-wireless@vger.kernel.org
+S:	Maintained
+F:	drivers/net/wireless/aic/
+
 AIMSLAB FM RADIO RECEIVER DRIVER
 M:	Hans Verkuil <hverkuil@kernel.org>
 L:	linux-media@vger.kernel.org
diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig
index c6599594dc995..65ab62d073693 100644
--- a/drivers/net/wireless/Kconfig
+++ b/drivers/net/wireless/Kconfig
@@ -19,6 +19,7 @@ menuconfig WLAN
 if WLAN
 
 source "drivers/net/wireless/admtek/Kconfig"
+source "drivers/net/wireless/aic/Kconfig"
 source "drivers/net/wireless/ath/Kconfig"
 source "drivers/net/wireless/atmel/Kconfig"
 source "drivers/net/wireless/broadcom/Kconfig"
diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile
index e1c4141c60044..bb0c29cb3e82f 100644
--- a/drivers/net/wireless/Makefile
+++ b/drivers/net/wireless/Makefile
@@ -4,6 +4,7 @@
 #
 
 obj-$(CONFIG_WLAN_VENDOR_ADMTEK) += admtek/
+obj-$(CONFIG_WLAN_VENDOR_AIC) += aic/
 obj-$(CONFIG_WLAN_VENDOR_ATH) += ath/
 obj-$(CONFIG_WLAN_VENDOR_ATMEL) += atmel/
 obj-$(CONFIG_WLAN_VENDOR_BROADCOM) += broadcom/

^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-07-21 10:22 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-21 10:20 [RFC PATCH wireless-next 0/4] wifi: aic: add AIC8800 SDIO FullMAC driver Yanli Yang
2026-07-21 10:21 ` [RFC PATCH wireless-next 1/4] wifi: aic: add SDIO BSP and build configuration Yanli Yang
2026-07-21 10:21 ` [RFC PATCH wireless-next 2/4] wifi: aic: add FullMAC firmware and platform interface Yanli Yang
2026-07-21 10:21 ` [RFC PATCH wireless-next 3/4] wifi: aic: add FullMAC WLAN driver Yanli Yang
2026-07-21 10:21 ` [RFC PATCH wireless-next 4/4] wifi: aic: integrate AIC8800 driver Yanli Yang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox