From 065ea569db06206874bbfa18eb25ff6121aec09b Mon Sep 17 00:00:00 2001
From: lin <lin@kickpi.com>
Date: Mon, 25 Aug 2025 12:27:08 +0000
Subject: [PATCH] add vs6621s support in kernel

---
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tdls.c                | 1088 +
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mlme.c                | 1208 +
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/Kconfig                   |  145 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/trace.h                   |  684 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_debugfs.c     |  123 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iface.h               |  678 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mlme.h                |   95 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_log_to_file.h |   51 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tdls.h                |   48 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_config.h              |  100 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_user_com.c    |  534 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_rx.c                  | 2102 ++
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iface.c               | 1395 +
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/trace.c                   |   28 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_rx.h                  |  314 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_msg.c                 | 2145 ++
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_timer.h               |   36 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_core.h                |  899 
 longan/kernel/linux-4.9/drivers/misc/Kconfig                                         |    1 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_core.c                | 3530 +++
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_msg.h                 |  721 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_io.c          | 2825 ++
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_regd.c                |  370 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_regd.h                |   60 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_timer.c               |  240 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_compat.h              |  659 
 longan/kernel/linux-4.9/arch/arm64/configs/sun50iw10p1smp_a133_android_defconfig     |    4 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/sv6621s_mem_map.h |  114 
 longan/kernel/linux-4.9/drivers/net/wireless/Makefile                                |    1 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_log.c                 |  379 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/Kconfig              |    9 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_cfg80211.c            | 6186 ++++++
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_log.h                 |   99 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iw.h                  |  345 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_cfg80211.h            |  902 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iw.c                  | 4109 ++++
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/Kconfig                   |   25 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/usb_boot.h            |   94 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_boot.c        | 1226 +
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_main.c      | 2744 ++
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb.h             |   55 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_config.c              |  481 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/Kconfig           |   20 
 longan/kernel/linux-4.9/drivers/net/wireless/Kconfig                                 |    1 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_log_to_file.c |  767 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dentry.h              |   41 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_boot.h        |  297 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/Kconfig               |   10 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_debugfs.h     |   24 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dentry.c              |  188 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_host.h      |   22 
 longan/kernel/linux-4.9/drivers/misc/Makefile                                        |    1 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_host.c      |  120 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_debugfs.h   |   24 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/README.md            |    2 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/README.md         |    1 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_util.h                |  338 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tx.h                  |  150 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_debugfs.c   |  124 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_dump_mem.c    |  284 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/genver.pl                 |   25 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/usb_boot.c            |  469 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio.h           |  325 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/.gitignore                |    1 
 longan/kernel/linux-4.9/include/linux/platform_data/skw_platform_data.h              |  227 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/Makefile                  |   67 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/boot_config.h     |  134 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/Makefile                  |   61 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_recovery.h            |   34 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_log.c       |  591 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_log.h       |   80 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_work.c                |  420 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mbssid.c              |  396 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_edma.h                |  227 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_vendor.c              |  495 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mbssid.h              |   27 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_vendor.h              |  162 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_calib.h               |   93 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_mem_map.h     |  116 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_log.c         |  452 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_edma.c                | 1380 +
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tx.c                  | 2020 ++
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/README.md             |    2 
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_log.h         |   86 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_calib.c               |  206 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dfs.c                 |  729 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_recovery.c            |  561 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_util.c                |  657 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_db.c                  | 3417 +++
 longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_rx.c        | 3419 +++
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dfs.h                 |  179 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_work.h                |  105 
 longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_db.h                  |   37 
 93 files changed, 56,466 insertions(+), 0 deletions(-)

diff --git a/longan/kernel/linux-4.9/arch/arm64/configs/sun50iw10p1smp_a133_android_defconfig b/longan/kernel/linux-4.9/arch/arm64/configs/sun50iw10p1smp_a133_android_defconfig
index b93af7e..390d13a 100644
--- a/longan/kernel/linux-4.9/arch/arm64/configs/sun50iw10p1smp_a133_android_defconfig
+++ b/longan/kernel/linux-4.9/arch/arm64/configs/sun50iw10p1smp_a133_android_defconfig
@@ -251,6 +251,9 @@
 CONFIG_SUNXI_SST_STORAGE=y
 CONFIG_SUNXI_RFKILL=y
 CONFIG_SUNXI_BOOTEVENT=y
+CONFIG_SEEKWAVE_BSP_DRIVERS=y
+CONFIG_SKW_NO_CONFIG=y
+CONFIG_SKW_SDIOHAL=m
 CONFIG_SCSI=y
 CONFIG_BLK_DEV_SD=y
 CONFIG_CHR_DEV_SG=y
@@ -293,6 +296,7 @@
 CONFIG_AIC_WLAN_SUPPORT=y
 CONFIG_AIC8800_WLAN_SUPPORT=m
 CONFIG_AIC8800_BTLPM_SUPPORT=m
+CONFIG_WLAN_VENDOR_SWT6621S=m
 # CONFIG_INPUT_MOUSEDEV is not set
 CONFIG_INPUT_EVDEV=y
 CONFIG_INPUT_KEYRESET=y
diff --git a/longan/kernel/linux-4.9/drivers/misc/Kconfig b/longan/kernel/linux-4.9/drivers/misc/Kconfig
index 6752129..cc11752 100644
--- a/longan/kernel/linux-4.9/drivers/misc/Kconfig
+++ b/longan/kernel/linux-4.9/drivers/misc/Kconfig
@@ -813,4 +813,5 @@
 source "drivers/misc/sunxi-bootevent/Kconfig"
 source "drivers/misc/it6612/Kconfig"
 source "drivers/misc/ncs8801s/Kconfig"
+source "drivers/misc/seekwaveplatform_lite/Kconfig"
 endmenu
diff --git a/longan/kernel/linux-4.9/drivers/misc/Makefile b/longan/kernel/linux-4.9/drivers/misc/Makefile
index ea76a1f..3b15b36 100644
--- a/longan/kernel/linux-4.9/drivers/misc/Makefile
+++ b/longan/kernel/linux-4.9/drivers/misc/Makefile
@@ -81,3 +81,4 @@
 obj-$(CONFIG_SUNXI_BOOTEVENT)   += sunxi-bootevent/
 obj-$(CONFIG_IT66121_RGB_TO_HDMI)   += it6612/
 obj-$(CONFIG_NCS8801S_LVDS_EDP)   += ncs8801s/
+obj-$(CONFIG_SEEKWAVE_BSP_DRIVERS)   += seekwaveplatform_lite/
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/Kconfig b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/Kconfig
new file mode 100755
index 0000000..7d74bc1
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/Kconfig
@@ -0,0 +1,25 @@
+menuconfig SEEKWAVE_BSP_DRIVERS
+        bool "SeekWave Platform Drivers For SeekWave Chip"
+        default n
+        help
+        This is support seekwave chip for incard board
+        if you want to buildin bsp driver
+        please say "y" 
+        Thanks.
+
+config SEEKWAVE_PLD_RELEASE
+    bool "seekwave Platfrom support chip recoverymode"
+    depends on SEEKWAVE_BSP_DRIVERS
+    default n
+
+config SKW_NO_CONFIG
+    bool "skw no config dts"
+    depends on SEEKWAVE_BSP_DRIVERS
+    default n
+
+#seekwave`s wifi bluetooth device driver etc
+if  SEEKWAVE_BSP_DRIVERS
+source "drivers/misc/seekwaveplatform_lite/usb/Kconfig"
+source "drivers/misc/seekwaveplatform_lite/sdio/Kconfig"
+source "drivers/misc/seekwaveplatform_lite/skwutil/Kconfig"
+endif
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/Makefile b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/Makefile
new file mode 100755
index 0000000..a524699
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/Makefile
@@ -0,0 +1,61 @@
+#
+#SeekWave Platform Drivers Makefile
+#
+#src := $(src)
+PWD :=$(shell pwd)
+CURFOLDER ?=$(pwd)
+CURRENT_DIR := $(src)
+@echo "current dir:$src"
+$(info Source directory: $(src))
+#ccflags-y :=-Idrivers/misc/seekwaveplatform_lite/skwutil
+ccflags-y :=-I$(CURRENT_DIR)/skwutil
+ccflags-y +=-I$(CURRENT_DIR)/usb
+ccflags-y +=-I$(CURRENT_DIR)/sdio
+
+
+#ifdef CONFIG_SEEKWAVE_BSP_DRIVERS
+#obj-$(CONFIG_SKW_USB)               += usb/
+#obj-$(CONFIG_SKW_PCIE)               += pcie/
+#obj-$(CONFIG_SKW_SDIOHAL)           += sdio/
+#obj-$(CONFIG_SEEKWAVE_BSP_DRIVERS)  += skwutil/
+#endif
+
+ifeq ($(CONFIG_SKW_BT),m)
+        ccflags-y += -DCONFIG_BT_SEEKWAVE
+endif
+
+ifeq ($(CONFIG_64BIT),y)
+        ccflags-y += -DCONFIG_64BIT
+endif
+
+ifneq ($(skw_extra_flags),)
+        ccflags-y += $(skw_extra_flags) -DSKW_EXT_INC
+endif
+
+ifeq ($(CONFIG_SKW_NO_CONFIG),y)
+        ccflags-y += -DCONFIG_SKW_NO_CONFIG
+endif
+#ccflags-y += -DCONFIG_SV6160_LITE_FPGA
+
+obj-$(CONFIG_SKW_SDIOHAL) += skw_sdio_lite.o
+skw_sdio_lite-y := ./sdio/skw_sdio_main.o
+skw_sdio_lite-y += ./sdio/skw_sdio_rx.o
+skw_sdio_lite-y += ./sdio/skw_sdio_debugfs.o
+skw_sdio_lite-y += ./sdio/skw_sdio_log.o
+skw_sdio_lite-y += ./sdio/skw_sdio_host.o
+skw_sdio_lite-y += ./skwutil/skw_user_com.o
+skw_sdio_lite-y += ./skwutil/skw_log_to_file.o
+skw_sdio_lite-y += ./skwutil/skw_boot.o
+
+obj-$(CONFIG_SKW_USB) += skw_usb_lite.o
+skw_usb_lite-y := ./usb/skw_usb_io.o
+skw_usb_lite-y += ./usb/skw_usb_debugfs.o
+skw_usb_lite-y += ./usb/skw_usb_log.o
+#skw_usb_lite-y += ./usb/skw_test.o
+skw_usb_lite-y += ./skwutil/skw_user_com.o
+skw_usb_lite-y += ./skwutil/skw_log_to_file.o
+skw_usb_lite-y += ./skwutil/skw_boot.o
+
+clean:
+	@rm -rf *.o *.ko *.mod.c *.order *.a *.builtin .*.cmd .*.d
+
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/Kconfig b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/Kconfig
new file mode 100755
index 0000000..13424a6
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/Kconfig
@@ -0,0 +1,9 @@
+config SKW_SDIOHAL
+	tristate "Seekwave Platform  SDIO Driver Support"
+	depends on SEEKWAVE_BSP_DRIVERS 
+	default n
+	help
+	  Enable this module for seekwave
+	  chip sdio interface bus Support.
+	  Please insmod this module before any other
+	  seekwave subsystem. Thanks.
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/README.md b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/README.md
new file mode 100755
index 0000000..3d41bf5
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/README.md
@@ -0,0 +1,2 @@
+#seekwave tech sdio readme
+#seekwave platform driver code
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio.h b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio.h
new file mode 100755
index 0000000..e382d01
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio.h
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2022 Seekwave Tech Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef __SKW_SDIO_H__
+#define __SKW_SDIO_H__
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/version.h>
+
+#include "../skwutil/skw_boot.h"
+#include "skw_sdio_log.h"
+#define skwsdio_log(fmt, args...) \
+	pr_info("[SKWSDIO]:" fmt, ## args)
+
+#define skwsdio_err(fmt, args...) \
+	pr_err("[SKWSDIO_ERR]:" fmt, ## args)
+
+#define skwsdio_data_pr(level, prefix_str, prefix_type, rowsize,\
+		groupsize, buf, len, asscii)\
+		do{if(loglevel) \
+			print_hex_dump(level, prefix_str, prefix_type, rowsize,\
+					groupsize, buf, len, asscii);\
+		}while(0)
+
+
+#define SKW_AP2CP_IRQ_REG 0x1B0
+
+#define	SKW_BUF_SIZE 	2048
+
+#define SKW_SDIO_SDMA	0
+#define SKW_SDIO_ADMA	1
+
+#define SKW_SDIO_INBAND_IRQ	0
+#define SKW_SDIO_EXTERNAL_IRQ	1
+
+#define SDIO_RX_TASK_PRIO 	90
+#define SDIO_UPDATE_TASK_PRIO 	91
+
+#define SKW_SDIO_BLK_SIZE 	skw_sdio_blk_size
+#define MAX_PAC_SIZE 		0x700
+#define MAX2_PAC_SIZE 		0x600
+#define MAX_PAC_COUNT		72
+
+#define SKW_SDIO_NSIZE_BUF_SIZE SKW_SDIO_BLK_SIZE
+
+#define SKW_SDIO_READ 		0
+#define SKW_SDIO_WRITE 		1
+
+#define SKW_SDIO_DATA_FIX 	0
+#define SKW_SDIO_DATA_INC 	1
+
+#define MAX_IO_RW_BLK 		511
+
+#define FUNC_0  		0
+#define FUNC_1  		1
+#define MAX_FUNC_NUM 		2
+
+#define SKW_SDIO_DT_MODE_ADDR	0x0f
+#define SKW_SDIO_PK_MODE_ADDR	0x20
+
+#define SKW_SDIO_RESET_MODE_ADDR	0x1C
+#define SKW_SDIO_CCCR_ABORT		0x06
+#define SDIO_INT_EXT			0x16
+#define SDIO_ABORT_TRANS		0x01
+
+#define SKW_SDIO_FBR_REG		0x15C
+
+#define SKW_CHIP_ID0		0x40000000  	//SV6160 chip id0
+#define SKW_CHIP_ID1		0x40000004  	//SV6160 chip id1
+#define SKW_CHIP_ID2		0x40000008  	//SV6160 chip id2
+#define SKW_CHIP_ID3		0x4000000C  	//SV6160 chip id3
+#define SKW_SMEM_POWERON1 0x40104000  //SMEM power on [2:3] WIFI [6:7] BT
+#define SKW_SMEM_POWERON2 0x40108000  //SMEM power on [2:3] WIFI [6:7] BT
+#define SKW_SERVICE_EN 	0x40100000  //service enable [8:9]
+#define SKW_REG_RW_LENGTH 4
+#define SKW_CHIP_ID_LENGTH	16  		//SV6160 chip id lenght
+
+#define SKW_SDIO_ALIGN_4BYTE(a)  (((a)+3)&(~3))
+#define SKW_SDIO_ALIGN_BLK(a) (((a)%SKW_SDIO_BLK_SIZE) ? \
+	(((a)/SKW_SDIO_BLK_SIZE + 1)*SKW_SDIO_BLK_SIZE) : (a))
+
+#define SDIO_VER_CCCR	(0)
+
+
+#define SKW_SDIO_CARD_OFFLINE 0x8000
+#define SKW_CARD_ONLINE(skw_sdio) \
+	(atomic_read(&skw_sdio->online) < SKW_SDIO_CARD_OFFLINE)
+
+#define SKW_SDIO_RESET_CARD_VAL 0x08
+#define SKW_SDIO_RESET_CP 	0x20
+
+#define	WIFI_SERVICE	0
+#define	BT_SERVICE	1
+
+#define	SERVICE_START	0
+#define	SERVICE_STOP	1
+
+#define	SKW_SDIO_V10 0
+#define	SKW_SDIO_V20 1
+
+
+#define	BSP_ATC_PORT	0
+#define	BSP_LOG_PORT	1
+#define	BT_DATA_PORT	2
+#define	BT_CMD_PORT	3
+#define	BT_AUDIO_PORT	4
+#define	WIFI_CMD_PORT	5
+#define	WIFI_DATA_PORT	6
+#define	LOOPCHECK_PORT	7
+#define	MAX_CH_NUM	8
+
+#define	SDIO2_BSP_ATC_PORT	0
+#define	SDIO2_LOOPCHECK_PORT	1
+#define	SDIO2_BT_CMD_PORT	2
+#define	SDIO2_BT_AUDIO_PORT	3
+#define	SDIO2_BT_ISOC_PORT	4
+#define	SDIO2_BT_DATA_PORT      5
+#define	SDIO2_WIFI_CMD_PORT	6
+#define	SDIO2_WIFI_DATA_PORT	7
+#define	SDIO2_WIFI_DATA1_PORT	8
+#define	SDIO2_BSP_LOG_PORT	9
+#define	SDIO2_BT_LOG_PORT	10
+#define	SDIO2_BSP_UPDATE_PORT	11
+#define	SDIO2_MAX_CH_NUM	12
+
+struct skw_sdio_data_t {
+	struct task_struct *rx_thread;
+	struct completion rx_completed;
+	struct task_struct *update_thread;
+	struct completion update_completed;
+#ifdef  CONFIG_WAKELOCK
+	struct wake_lock rx_wl;
+#else
+	struct wakeup_source *rx_ws;
+#endif
+	atomic_t rx_wakelocked;
+	struct mutex transfer_mutex;
+	struct mutex except_mutex;
+	struct mutex service_mutex;
+	atomic_t resume_flag;
+	atomic_t online;
+	bool threads_exit;
+	bool adma_rx_enable;
+	bool pwrseq;
+	bool blk_size;
+	/* EXTERNAL_IRQ 0, INBAND_IRQ 1. */
+	unsigned char irq_type;
+	atomic_t suspending;
+	int gpio_out;
+	int gpio_in;
+	unsigned int irq_num;
+	unsigned int irq_trigger_type;
+	struct sdio_func *sdio_func[MAX_FUNC_NUM];
+	struct mmc_host *sdio_dev_host;
+	unsigned char *eof_buf;
+
+	unsigned int next_size;
+	unsigned int remain_packet;
+	unsigned long long rx_packer_cnt;
+	char *next_size_buf;
+
+	struct completion scan_done;
+	struct completion remove_done;
+	struct completion download_done;
+	int host_active;
+	int device_active;
+	struct completion device_wakeup;
+	char tx_req_map;
+	int resume_com;
+	int cp_state;
+	int chip_en;
+	unsigned int chip_id[SKW_CHIP_ID_LENGTH];
+	struct seekwave_device *boot_data;
+	struct skw_log_data_t *log_data;
+	unsigned int service_state_map;
+	struct delayed_work skw_except_work;
+	int power_off;
+	unsigned int sdio_exti_gpio_state;
+	int suspend_wake_unlock_enable;
+	int service_index_map;
+	wait_queue_head_t wq;
+	u8  cp_fifo_status;
+	int multi_sdio_drivers;
+	u8 cp_downloaded_flag;
+};
+
+struct debug_vars {
+	u16 cmd_timeout_cnt;
+	u32 rx_inband_irq_cnt;
+	u32 rx_gpio_irq_cnt;
+	u32 rx_irq_statistics_cnt;
+	u32 rx_read_cnt;
+	u32 last_sent_wifi_cmd[3];
+	u64 last_sent_time;
+	u64 last_rx_submit_time;
+	u64 host_assert_cp_time;
+	u64 cp_assert_time;
+	u64 last_irq_time;
+	u64 rx_irq_statistics_time;
+	u32 chn_irq_cnt[SDIO2_MAX_CH_NUM];
+#define CHN_IRQ_RECORD_NUM 3
+	u64 chn_last_irq_time[SDIO2_MAX_CH_NUM][CHN_IRQ_RECORD_NUM];
+	u64 last_irq_times[CHN_IRQ_RECORD_NUM];
+	u64 last_clear_irq_times[CHN_IRQ_RECORD_NUM];
+	u64 last_rx_read_times[CHN_IRQ_RECORD_NUM];
+};
+struct skw_log_data_t {
+	u16 cp_log_en;
+	u16 log_level;
+	u16 log_port;
+	uint32_t reg_val;
+	u32 smem_poweron;
+	u32 service_en;
+	u32 service_eb_val;
+};
+
+
+struct sdio_port {
+	struct platform_device *pdev;
+	struct scatterlist *sg_rx;
+	int	 sg_index;
+	int	 total;
+	int	sent_packet;
+	unsigned int type;
+	unsigned int channel;
+	rx_submit_fn rx_submit;
+	void *rx_data;
+	int	state;
+	char *read_buffer;
+	int rx_rp;
+	int rx_wp;
+	char *write_buffer;
+	int  length;
+	struct completion rx_done;
+	struct completion tx_done;
+	struct mutex rx_mutex;
+	int	rx_packet;
+	int	rx_count;
+	int 	tx_flow_ctrl;
+	int	 rx_flow_ctrl;
+	u16	next_seqno;
+	int     timeout;
+};
+
+
+//=======================================================
+//debug sdio macro and Variable
+//int glb_wifiready_done;
+#define SKW_WIFIONLY_DEBUG 1
+//=======================================================
+void skw_resume_check(void);
+struct skw_sdio_data_t *skw_sdio_get_data(void);
+
+void skw_sdio_rx_up(struct skw_sdio_data_t *skw_sdio);
+int skw_sdio_rx_thread(void *p);
+
+void skw_sdio_unlock_rx_ws(struct skw_sdio_data_t *skw_sdio);
+int skw_recovery_mode(void);
+int skw_sdio_sdma_write(unsigned char *src, unsigned int len);
+int skw_sdio_sdma_read(unsigned char *src, unsigned int len);
+int skw_sdio_adma_write(int portno, struct scatterlist *sgs, int sg_count, int total);
+int skw_sdio_adma_read(struct skw_sdio_data_t *skw_sdio, struct scatterlist *sgs, int sg_count, int total);
+int skw_sdio_dt_read(unsigned int address, void *buf, unsigned int len);
+int skw_sdio_dt_write(unsigned int address, void *buf, unsigned int len);
+int skw_sdio_readb(unsigned int address, unsigned char *data);
+int skw_sdio_writeb(unsigned int address, unsigned char data);
+int skw_sdio_writel(unsigned int address, void *data);
+int skw_sdio_readl(unsigned int address, void *data);
+int send_modem_service_command(u16 service, u16 command);
+int send_modem_assert_command(void);
+int skw_sdio_bind_platform_driver(struct sdio_func * func);
+int skw_sdio_bind_WIFI_driver(struct sdio_func * func);
+#ifndef CONFIG_BT_SEEKWAVE
+int skw_sdio_bind_BT_driver(struct sdio_func * func);
+#endif
+int skw_sdio_bind_btseekwave_driver(struct sdio_func * func);
+int skw_sdio_unbind_platform_driver(struct sdio_func *func);
+int skw_sdio_unbind_WIFI_driver(struct sdio_func * func);
+int skw_sdio_unbind_BT_driver(struct sdio_func * func);
+int skw_boot_loader(struct seekwave_device *boot_data);
+void send_host_suspend_indication(struct skw_sdio_data_t *skw_sdio);
+void send_host_resume_indication(struct skw_sdio_data_t *skw_sdio);
+int try_to_wakeup_modem(int portno);
+int wakeup_modem(int portno);
+void host_gpio_in_routine(int value);
+void skw_sdio_inband_irq_handler(struct sdio_func *func);
+void modem_notify_event(int event);
+int loopcheck_send_data(char *buffer, int size);
+void skw_get_port_statistic(char *buffer, int size);
+void skw_get_sdio_config(char *buffer, int size);
+int skw_sdio_cp_log_disable(int disable);
+int skw_sdio_recovery_debug(int disable);
+int skw_sdio_recovery_debug_status(void);
+int skw_sdio_wifi_serv_debug(int enable);
+int skw_sdio_wifi_serv_debug_status(void);
+int skw_sdio_bt_serv_debug(int enable);
+int skw_sdio_bt_serv_debug_status(void);
+void reboot_to_change_bt_antenna_mode(char *mode);
+void reboot_to_change_bt_uart1(char *mode);
+void get_bt_antenna_mode(char *mode);
+int skw_sdio_wifi_power_on(int power_on);
+int skw_sdio_wifi_status(void);
+int skw_sdio_dloader(int index);
+int skw_sdio_poweron_mem(int index);
+void skw_get_sdio_debug_info(char *buffer, int size);
+void skw_get_assert_print_info(char *buffer, int size);
+int skw_sdio_debug_log_open(void);
+int skw_sdio_debug_log_close(void);
+int skw_sdio_gpio_irq_pre_ops(void);
+int skw_sdio_chk_cp_gpio_cfg(void);
+void send_cp_wakeup_signal(struct skw_sdio_data_t *skw_sdio);
+int skw_sdio_smem_poweron(void); // for smem
+#endif /* SKW_SDIO_H */
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_debugfs.c b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_debugfs.c
new file mode 100755
index 0000000..72424d7
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_debugfs.c
@@ -0,0 +1,124 @@
+/*****************************************************************************
+ * Copyright(c) 2020-2030  Seekwave Corporation.
+ * SEEKWAVE TECH LTD..CO
+ *Seekwave Platform the sdio log debug fs
+ *FILENAME:skw_sdio_debugfs.c
+ *DATE:2022-04-11
+ *MODIFY:
+ *
+ **************************************************************************/
+
+#include "skw_sdio_debugfs.h"
+#include "skw_sdio_log.h"
+#include "skw_sdio.h"
+
+static struct proc_dir_entry *skw_sdio_proc_root = NULL;
+
+static int skw_sdio_proc_show(struct seq_file *seq, void *v)
+{
+#define SKW_BSP_CONFIG_INT(conf)                                          \
+	do {                                                          \
+		seq_printf(seq, "%s=%d\n", #conf, conf);              \
+	} while (0)
+
+#define SKW_BSP_CONFIG_BOOL(conf)                                         \
+	do {                                                          \
+		if (IS_ENABLED(conf))                                 \
+			seq_printf(seq, "%s=y\n", #conf);             \
+		else                                                  \
+			seq_printf(seq, "# %s is not set\n", #conf);  \
+	} while (0)
+
+#define SKW_BSP_CONFIG_STRING(conf)                                       \
+	do {                                                          \
+		seq_printf(seq, "%s=\"%s\"\n", #conf, conf);          \
+	} while (0)
+
+	seq_puts(seq, "\n");
+	seq_printf(seq, "Kernel Version:  \t%s\n",
+			UTS_RELEASE);
+	seq_puts(seq, "\n");
+
+	SKW_BSP_CONFIG_BOOL(CONFIG_SEEKWAVE_BSP_DRIVERS);
+	SKW_BSP_CONFIG_BOOL(CONFIG_SKW_SDIOHAL);
+	SKW_BSP_CONFIG_BOOL(CONFIG_SEEKWAVE_PLD_RELEASE);
+
+	seq_puts(seq, "\n");
+
+	return 0;
+}
+
+static int skw_sdio_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, skw_sdio_proc_show, NULL);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_sdio_profile_proc_fops = {
+	.proc_open = skw_sdio_proc_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+};
+#else
+static const struct file_operations skw_sdio_profile_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_sdio_proc_open,
+	.read = seq_read,
+	.release = single_release,
+};
+#endif
+
+struct proc_dir_entry *skw_sdio_procfs_file(struct proc_dir_entry *parent,
+				       const char *name, umode_t mode,
+				       const void *fops, void *data)
+{
+	struct proc_dir_entry *dentry = parent ? parent : skw_sdio_proc_root;
+
+	if (!dentry)
+		return NULL;
+
+	return proc_create_data(name, mode, dentry, fops, data);
+}
+
+int skw_sdio_proc_init_ex(const char *name, umode_t mode,
+				       const void *fops, void *data)
+{
+	if (!skw_sdio_proc_root)
+		return -1;
+	skw_sdio_procfs_file(skw_sdio_proc_root, name, mode, fops, NULL);
+	return 0;
+}
+
+int skw_sdio_proc_init(void)
+{
+	skw_sdio_proc_root = proc_mkdir("skwsdio", NULL);
+	if (!skw_sdio_proc_root){
+		pr_err("creat proc skwsdio failed\n");
+		return -ENOMEM;
+	}
+
+	skw_sdio_procfs_file(skw_sdio_proc_root,"profile", 0666, &skw_sdio_profile_proc_fops, NULL);
+
+	return 0;
+}
+
+void skw_sdio_proc_deinit(void)
+{
+	if (!skw_sdio_proc_root)
+		return;
+	proc_remove(skw_sdio_proc_root);
+}
+
+int skw_sdio_debugfs_init(void)
+{
+	skw_sdio_dbg("%s :traced\n", __func__);
+	skw_sdio_proc_init();
+	skw_sdio_create_debug_files();
+	return 0;
+}
+
+void skw_sdio_debugfs_deinit(void)
+{
+	skw_sdio_dbg("%s :traced\n", __func__);
+	skw_sdio_proc_deinit();
+}
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_debugfs.h b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_debugfs.h
new file mode 100755
index 0000000..1d050b5
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_debugfs.h
@@ -0,0 +1,24 @@
+/******************************************************************************
+ *
+ * Copyright(c) 2020-2030  Seekwave Corporation.
+ *
+ *****************************************************************************/
+#ifndef __SKW_SDIO_DEBUGFS_H__
+#define __SKW_SDIO_DEBUGFS_H__
+
+#include <linux/fs.h>
+#include <linux/debugfs.h>
+#include <linux/proc_fs.h>
+#include <linux/scatterlist.h>
+#include <generated/utsrelease.h>
+#include "../skwutil/boot_config.h"
+#include "../skwutil/skw_boot.h"
+
+int skw_sdio_debugfs_init(void);
+void skw_sdio_debugfs_deinit(void);
+struct proc_dir_entry *skw_sdio_procfs_file(struct proc_dir_entry *parent,
+				       const char *name, umode_t mode,
+				       const void *proc_fops, void *data);
+int skw_sdio_proc_init_ex(const char *name, umode_t mode,
+				       const void *fops, void *data);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_host.c b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_host.c
new file mode 100755
index 0000000..18c3c23
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_host.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2021 Seekwave Tech Inc.
+ *
+ * Filename : skw_sdio_host.c
+ * Abstract : This file is a implementation for Seekwave sdio  function
+ *
+ * Authors	:skw BSP team
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/version.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/core.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/printk.h>
+#include "skw_sdio.h"
+#include "skw_sdio_log.h"
+#include "skw_sdio_host.h"
+#include "../skwutil/boot_config.h"
+//#define CONFIG_SKW_HOST_PLATFORM_FULLHAN
+
+extern int sdio_reset_comm(struct mmc_card *card);
+int skw_sdio_mmc_scan(int sd_id)
+{
+	int ret = 0;
+	pr_info("%s:[%d]\n", __func__, sd_id);
+#if defined(CONFIG_SKW_HOST_PLATFORM_AMLOGIC)
+	//如果定义了CONFIG_SKW_HOST_PLATFORM_AMLOGIC,则调用extern_wifi_set_enable(1)函数
+	extern_wifi_set_enable(1);
+#elif defined(CONFIG_SKW_HOST_PLATFORM_FULLHAN)
+	pr_info("%s:[mmc%d:card init]\n", __func__, sd_id);
+	fh_sdio_card_scan(sd_id); //fullhan sdio card scan
+#elif defined(CONFIG_SKW_HOST_PLATFORM_ALLWINER)
+	sunxi_wlan_set_power(1);
+	msleep(100);
+	sunxi_mmc_rescan_card(1); //allwiner sdio card rescan
+#elif defined(CONFIG_SKW_HOST_PLATFORM_ROCKCHIP)
+	rockchip_wifi_power(1);
+	msleep(150);
+	rockchip_wifi_set_carddetect(1);
+#else
+	pr_info("%s: no need skw self scan!!\n", __func__);
+#endif
+	pr_info("%s:[-]\n", __func__);
+	return ret;
+}
+
+int skw_sdio_mmc_rescan(int sd_id)
+{
+	int ret = 0;
+#if defined(CONFIG_SKW_HOST_PLATFORM_AMLOGIC)
+	//如果定义了CONFIG_SKW_HOST_PLATFORM_AMLOGIC,则调用extern_wifi_set_enable(1)函数
+	//extern_wifi_set_enable(1);
+#elif defined(CONFIG_SKW_HOST_PLATFORM_FULLHAN)
+	skw_chip_power_reset();
+	fh_sdio_card_scan(sd_id); //fullhan sdio card scan
+#elif defined(CONFIG_SKW_HOST_PLATFORM_ALLWINER)
+	skw_chip_power_reset();
+	msleep(100);
+	sunxi_mmc_rescan_card(1); //allwiner sdio card rescan
+#elif defined(CONFIG_SKW_HOST_PLATFORM_ROCKCHIP)
+	rockchip_wifi_set_carddetect(0);
+	msleep(150);
+	skw_chip_power_reset();
+	msleep(150);
+	rockchip_wifi_set_carddetect(1);
+#else
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	if (skw_sdio) {
+#if (KERNEL_VERSION(4, 18, 0) <= LINUX_VERSION_CODE &&                         \
+     LINUX_VERSION_CODE <= KERNEL_VERSION(5, 18, 19))
+		if (skw_sdio->sdio_dev_host) {
+			sdio_claim_host(skw_sdio->sdio_func[FUNC_1]);
+			skw_chip_power_reset();
+			msleep(100);
+			ret = mmc_sw_reset(skw_sdio->sdio_dev_host);
+			sdio_release_host(skw_sdio->sdio_func[FUNC_1]);
+		} else {
+			return -EINVAL;
+		}
+#elif (KERNEL_VERSION(5, 19, 0) <= LINUX_VERSION_CODE)
+		if (skw_sdio->sdio_dev_host && skw_sdio->sdio_dev_host->card) {
+			sdio_claim_host(skw_sdio->sdio_func[FUNC_1]);
+			skw_chip_power_reset();
+			msleep(100);
+			ret = mmc_sw_reset(skw_sdio->sdio_dev_host->card);
+			sdio_release_host(skw_sdio->sdio_func[FUNC_1]);
+		} else {
+			return -EINVAL;
+		}
+#else
+		if (skw_sdio->sdio_dev_host && skw_sdio->sdio_dev_host->card) {
+			sdio_claim_host(skw_sdio->sdio_func[FUNC_1]);
+			skw_chip_power_reset();
+			msleep(100);
+			ret = mmc_hw_reset((skw_sdio->sdio_dev_host));
+			//ret = sdio_reset_comm((skw_sdio->sdio_dev_host->card));
+			sdio_release_host(skw_sdio->sdio_func[FUNC_1]);
+		} else {
+			return -EINVAL;
+		}
+#endif
+	} else {
+		skw_sdio_warn("sdio_dev_host is null\n");
+	}
+#endif
+	skw_sdio_info("[-]");
+	return ret;
+}
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_host.h b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_host.h
new file mode 100755
index 0000000..c6d78a1
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_host.h
@@ -0,0 +1,22 @@
+/*****************************************************************
+ *Copyright (C) 2021 Seekwave Tech Inc.
+ *Filename : skw_sdio_host .h
+ *Authors:seekwave platform
+ *
+ * This software is licensed under the terms of the the GNU
+ * General Public License version 2, as published by the Free
+ * Software Foundation, and may be copied, distributed, and
+ * modified under those terms.
+ *
+ * This program is distributed in the hope that it will be usefull,
+ * but without any warranty;without even the implied warranty of
+ * merchantability or fitness for a partcular purpose. See the
+ * GUN General Public License for more details.
+ * **************************************************************/
+#ifndef __SKW_SDIO_HOST_H__
+#define __SKW_SDIO_HOST_H__
+#include "skw_sdio.h"
+//int skw_sdio_card_detect_change(int power_on);
+int skw_sdio_mmc_rescan(int sd_id);
+int skw_sdio_mmc_scan(int sd_id);
+#endif /* __SKW_SDIO_HOST_H__ */
\ No newline at end of file
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_log.c b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_log.c
new file mode 100755
index 0000000..0845d67
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_log.c
@@ -0,0 +1,591 @@
+/**************************************************************************
+ * Copyright(c) 2020-2030  Seekwave Corporation.
+ * SEEKWAVE TECH LTD..CO
+ *
+ *Seekwave Platform the sdio log debug fs
+ *FILENAME:skw_sdio_log.c
+ *DATE:2022-04-11
+ *MODIFY:
+ *Author:Jones.Jiang
+ **************************************************************************/
+#include <linux/uaccess.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include "skw_sdio.h"
+#include "skw_sdio_log.h"
+#include "skw_sdio_debugfs.h"
+
+extern char firmware_version[];
+static unsigned long skw_sdio_dbg_level;
+
+unsigned long skw_sdio_log_level(void)
+{
+	return skw_sdio_dbg_level;
+}
+
+static void skw_sdio_set_log_level(int level)
+{
+	unsigned long dbg_level;
+
+	dbg_level = skw_sdio_log_level() & 0xffff0000;
+	dbg_level |= ((level << 1) - 1);
+
+	xchg(&skw_sdio_dbg_level, dbg_level);
+}
+
+static void skw_sdio_enable_func_log(int func, bool enable)
+{
+	unsigned long dbg_level = skw_sdio_log_level();
+
+	if (enable)
+		dbg_level |= func;
+	else
+		dbg_level &= (~func);
+
+	xchg(&skw_sdio_dbg_level, dbg_level);
+}
+
+static int skw_sdio_log_show(struct seq_file *seq, void *data)
+{
+#define SKW_SDIO_LOG_STATUS(s) (level & (s) ? "enable" : "disable")
+
+	int i;
+	u32 level = skw_sdio_log_level();
+	u8 *log_name[] = {"NONE", "ERROR", "WARNNING", "INFO", "DEBUG"};
+
+	for (i = 0; i < 5; i++) {
+		if (!(level & BIT(i)))
+			break;
+	}
+	// if (i >= 5
+	if (i < 5) {
+		seq_printf(seq, "\nlog   level: %s\n", log_name[i]);
+	} else {
+	    seq_printf(seq, "\nlog   level: NONE\n");
+	}
+	seq_puts(seq, "\n");
+	seq_printf(seq, "port0 log: %s\n", SKW_SDIO_LOG_STATUS(SKW_SDIO_PORT0));
+	seq_printf(seq, "port1 log: %s\n", SKW_SDIO_LOG_STATUS(SKW_SDIO_PORT1));
+	seq_printf(seq, "port2 log: %s\n", SKW_SDIO_LOG_STATUS(SKW_SDIO_PORT2));
+	seq_printf(seq, "port3 log: %s\n", SKW_SDIO_LOG_STATUS(SKW_SDIO_PORT3));
+	seq_printf(seq, "port4 log: %s\n", SKW_SDIO_LOG_STATUS(SKW_SDIO_PORT4));
+	seq_printf(seq, "port5 log: %s\n", SKW_SDIO_LOG_STATUS(SKW_SDIO_PORT5));
+	seq_printf(seq, "port6 log: %s\n", SKW_SDIO_LOG_STATUS(SKW_SDIO_PORT6));
+	seq_printf(seq, "port7 log: %s\n", SKW_SDIO_LOG_STATUS(SKW_SDIO_PORT7));
+	seq_printf(seq, "savelog  : %s\n", SKW_SDIO_LOG_STATUS(SKW_SDIO_SAVELOG));
+	seq_printf(seq, "dump  log: %s\n", SKW_SDIO_LOG_STATUS(SKW_SDIO_DUMP));
+
+	return 0;
+}
+
+static int skw_sdio_log_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, &skw_sdio_log_show, inode->i_private);
+}
+
+static int skw_sdio_log_control(const char *cmd, bool enable)
+{
+	if (!strcmp("dump", cmd))
+		skw_sdio_enable_func_log(SKW_SDIO_DUMP, enable);
+	else if (!strcmp("port0", cmd))
+		skw_sdio_enable_func_log(SKW_SDIO_PORT0, enable);
+	else if (!strcmp("port1", cmd))
+		skw_sdio_enable_func_log(SKW_SDIO_PORT1, enable);
+	else if (!strcmp("port2", cmd))
+		skw_sdio_enable_func_log(SKW_SDIO_PORT2, enable);
+	else if (!strcmp("port3", cmd))
+		skw_sdio_enable_func_log(SKW_SDIO_PORT3, enable);
+	else if (!strcmp("port4", cmd))
+		skw_sdio_enable_func_log(SKW_SDIO_PORT4, enable);
+	else if (!strcmp("port5", cmd))
+		skw_sdio_enable_func_log(SKW_SDIO_PORT5, enable);
+    else if (!strcmp("port6", cmd))
+		skw_sdio_enable_func_log(SKW_SDIO_PORT6, enable);
+	else if (!strcmp("port7", cmd))
+		skw_sdio_enable_func_log(SKW_SDIO_PORT7, enable);
+    else if (!strcmp("savelog", cmd))
+		skw_sdio_enable_func_log(SKW_SDIO_SAVELOG, enable);
+	else if (!strcmp("debug", cmd))
+		skw_sdio_set_log_level(SKW_SDIO_DEBUG);
+	else if (!strcmp("info", cmd))
+		skw_sdio_set_log_level(SKW_SDIO_INFO);
+	else if (!strcmp("warn", cmd))
+		skw_sdio_set_log_level(SKW_SDIO_WARNING);
+	else if (!strcmp("error", cmd))
+		skw_sdio_set_log_level(SKW_SDIO_ERROR);
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static ssize_t skw_sdio_log_write(struct file *fp, const char __user *buffer,
+				size_t len, loff_t *offset)
+{
+	int i, idx;
+	char cmd[32];
+	bool enable = false;
+
+	for (idx = 0, i = 0; i < len; i++) {
+		char c;
+
+		if (get_user(c, buffer))
+			return -EFAULT;
+
+		switch (c) {
+		case ' ':
+			break;
+
+		case ':':
+			cmd[idx] = 0;
+			if (!strcmp("enable", cmd))
+				enable = true;
+			else
+				enable = false;
+
+			idx = 0;
+			break;
+
+		case '|':
+		case '\0':
+		case '\n':
+			cmd[idx] = 0;
+			skw_sdio_log_control(cmd, enable);
+			idx = 0;
+			break;
+
+		default:
+			cmd[idx++] = c;
+			idx %= 32;
+
+			break;
+		}
+
+		buffer++;
+	}
+
+	return len;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_sdio_log_proc_fops = {
+	.proc_open = skw_sdio_log_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+	.proc_write = skw_sdio_log_write,
+};
+#else
+static const struct file_operations skw_sdio_log_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_sdio_log_open,
+	.read = seq_read,
+	.release = single_release,
+	.write = skw_sdio_log_write,
+};
+#endif
+
+static int skw_version_show(struct seq_file *seq, void *data)
+{
+	seq_printf(seq, "firmware info: %s\n", firmware_version );
+	return 0;
+}
+static int skw_version_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, &skw_version_show, inode->i_private);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_version_proc_fops = {
+	.proc_open = skw_version_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+};
+#else
+static const struct file_operations skw_version_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_version_open,
+	.read = seq_read,
+	.release = single_release,
+};
+#endif
+
+static int skw_port_statistic_show(struct seq_file *seq, void *data)
+{
+    char *statistic = kzalloc(2048, GFP_KERNEL);
+    if (statistic == NULL) {
+        printk(KERN_ERR "kzalloc statistic failed\n");
+        return -ENOMEM;
+    }
+    skw_get_port_statistic(statistic, 2048);
+	seq_printf(seq, "Statistic:\n %s\n", statistic);
+	skw_get_assert_print_info(statistic, 2048);
+	seq_printf(seq, "sdio last irqs information:\n%s", statistic);
+	skw_get_sdio_debug_info(statistic, 2048);
+	seq_printf(seq, "\nsdio debug information:\n%s", statistic);
+	kfree(statistic);
+        return 0;
+}
+static int skw_port_statistic_open(struct inode *inode, struct file *file)
+{
+        return single_open(file, &skw_port_statistic_show, inode->i_private);
+}
+
+
+static int skw_config_show(struct seq_file *seq, void *data)
+{
+    char *config = kzalloc(2048, GFP_KERNEL);
+    if (config == NULL) {
+        printk(KERN_ERR "kzalloc statistic failed\n");
+        return -ENOMEM;
+    }
+
+	skw_get_sdio_config(config, 2048);
+	seq_printf(seq, "config:\n %s\n", config);
+	kfree(config);
+        return 0;
+}
+
+static int skw_config_open(struct inode *inode, struct file *file)
+{
+        return single_open(file, &skw_config_show, inode->i_private);
+}
+
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_port_statistic_proc_fops = {
+    .proc_open = skw_port_statistic_open,
+    .proc_read = seq_read,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations skw_port_statistic_proc_fops = {
+    .owner = THIS_MODULE,
+    .open = skw_port_statistic_open,
+    .read = seq_read,
+    .release = single_release,
+};
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_config_proc_fops = {
+    .proc_open = skw_config_open,
+    .proc_read = seq_read,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations skw_config_proc_fops = {
+    .owner = THIS_MODULE,
+    .open = skw_config_open,
+    .read = seq_read,
+    .release = single_release,
+};
+#endif
+
+static int skw_bluetooth_UART1_open(struct inode *inode, struct file *file)
+{
+        return single_open(file, NULL, inode->i_private);
+}
+
+
+static ssize_t skw_bluetooth_UART1_write(struct file *fp, const char __user *buffer,
+				size_t len, loff_t *offset)
+{
+	char cmd[32]={0};
+
+	if (len >= sizeof(cmd))
+		return -EINVAL;
+	if (copy_from_user(cmd, buffer, len))
+		return -EFAULT;
+	if (!strncmp("enable", cmd, 6)) {
+		memset(cmd, 0, sizeof(cmd));
+		reboot_to_change_bt_uart1(cmd);
+		printk("%s UART-HCI\n", cmd);
+	}
+	return len;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_bluetooth_UART1_proc_fops = {
+	.proc_open = skw_bluetooth_UART1_open,
+	.proc_release = single_release,
+	.proc_write = skw_bluetooth_UART1_write,
+};
+#else
+static const struct file_operations skw_bluetooth_UART1_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_bluetooth_UART1_open,
+	.release = single_release,
+	.write = skw_bluetooth_UART1_write,
+};
+#endif
+
+static int skw_bluetooth_antenna_show(struct seq_file *seq, void *data)
+{
+	char result[32];
+
+	memset(result, 0, sizeof(result));
+	get_bt_antenna_mode(result);
+	if(strlen(result))
+		seq_printf(seq, result);
+        return 0;
+}
+static int skw_bluetooth_antenna_open(struct inode *inode, struct file *file)
+{
+        return single_open(file, &skw_bluetooth_antenna_show, inode->i_private);
+}
+
+
+static ssize_t skw_bluetooth_antenna_write(struct file *fp, const char __user *buffer,
+				size_t len, loff_t *offset)
+{
+	char cmd[32]={0};
+
+	if (len >= sizeof(cmd))
+		return -EINVAL;
+	if (copy_from_user(cmd, buffer, len))
+		return -EFAULT;
+	if (!strncmp("switch", cmd, 6)) {
+		memset(cmd, 0, sizeof(cmd));
+		reboot_to_change_bt_antenna_mode(cmd);
+		printk("%s\n", cmd);
+	}
+	return len;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_bluetooth_antenna_proc_fops = {
+	.proc_open = skw_bluetooth_antenna_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+	.proc_write = skw_bluetooth_antenna_write,
+};
+#else
+static const struct file_operations skw_bluetooth_antenna_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_bluetooth_antenna_open,
+	.read = seq_read,
+	.release = single_release,
+	.write = skw_bluetooth_antenna_write,
+};
+#endif
+
+static int skw_recovery_debug_show(struct seq_file *seq, void *data)
+{
+	if (skw_sdio_recovery_debug_status())
+		seq_printf(seq, "Disabled");
+	else
+		seq_printf(seq, "Enabled");
+        return 0;
+}
+static int skw_recovery_debug_open(struct inode *inode, struct file *file)
+{
+        return single_open(file, &skw_recovery_debug_show, inode->i_private);
+}
+
+
+static ssize_t skw_recovery_debug_write(struct file *fp, const char __user *buffer,
+				size_t len, loff_t *offset)
+{
+	char cmd[16]={0};
+
+	if (len >= sizeof(cmd))
+		return -EINVAL;
+	if (copy_from_user(cmd, buffer, len))
+		return -EFAULT;
+	if (!strncmp("disable", cmd, 7))
+		skw_sdio_recovery_debug(1);
+	else if (!strncmp("enable", cmd, 6))
+		skw_sdio_recovery_debug(0);
+
+	return len;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_recovery_debug_proc_fops = {
+	.proc_open = skw_recovery_debug_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+	.proc_write = skw_recovery_debug_write,
+};
+#else
+static const struct file_operations skw_recovery_debug_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_recovery_debug_open,
+	.read = seq_read,
+	.release = single_release,
+	.write = skw_recovery_debug_write,
+};
+#endif
+
+static int skw_wifi_serv_debug_show(struct seq_file *seq, void *data)
+{
+	if (skw_sdio_wifi_serv_debug_status())
+		seq_printf(seq, "START");
+	else
+		seq_printf(seq, "STOP");
+        return 0;
+}
+static int skw_wifi_serv_debug_open(struct inode *inode, struct file *file)
+{
+        return single_open(file, &skw_wifi_serv_debug_show, inode->i_private);
+}
+
+
+static ssize_t skw_wifi_serv_debug_write(struct file *fp, const char __user *buffer,
+				size_t len, loff_t *offset)
+{
+	char cmd[16]={0};
+
+	if (len >= sizeof(cmd))
+		return -EINVAL;
+	if (copy_from_user(cmd, buffer, len))
+		return -EFAULT;
+	if (!strncmp("start", cmd, 5))
+		skw_sdio_wifi_serv_debug(1);
+	else if (!strncmp("stop", cmd, 4))
+		skw_sdio_wifi_serv_debug(0);
+
+	return len;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_sdio_wifi_serv_proc_fops = {
+	.proc_open = skw_wifi_serv_debug_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+	.proc_write = skw_wifi_serv_debug_write,
+};
+#else
+static const struct file_operations skw_sdio_wifi_serv_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_wifi_serv_debug_open,
+	.read = seq_read,
+	.release = single_release,
+	.write = skw_wifi_serv_debug_write,
+};
+#endif
+
+static int skw_bt_serv_debug_show(struct seq_file *seq, void *data)
+{
+	if (skw_sdio_bt_serv_debug_status())
+		seq_printf(seq, "START");
+	else
+		seq_printf(seq, "STOP");
+        return 0;
+}
+static int skw_bt_serv_debug_open(struct inode *inode, struct file *file)
+{
+        return single_open(file, &skw_bt_serv_debug_show, inode->i_private);
+}
+
+
+static ssize_t skw_bt_serv_debug_write(struct file *fp, const char __user *buffer,
+				size_t len, loff_t *offset)
+{
+	char cmd[16]={0};
+
+	if (len >= sizeof(cmd))
+		return -EINVAL;
+	if (copy_from_user(cmd, buffer, len))
+		return -EFAULT;
+	if (!strncmp("start", cmd, 5))
+		skw_sdio_bt_serv_debug(1);
+	else if (!strncmp("stop", cmd, 4))
+		skw_sdio_bt_serv_debug(0);
+
+	return len;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_sdio_bt_serv_proc_fops = {
+	.proc_open = skw_bt_serv_debug_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+	.proc_write = skw_bt_serv_debug_write,
+};
+#else
+static const struct file_operations skw_sdio_bt_serv_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_bt_serv_debug_open,
+	.read = seq_read,
+	.release = single_release,
+	.write = skw_bt_serv_debug_write,
+};
+#endif
+
+static int skw_sdio_wifi_show(struct seq_file *seq, void *data)
+{
+	if (skw_sdio_wifi_status())
+		seq_printf(seq, "PowerOn");
+	else
+		seq_printf(seq, "PowerOff");
+	return 0;
+}
+static int skw_sdio_wifi_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, &skw_sdio_wifi_show, inode->i_private);
+}
+
+
+static ssize_t skw_sdio_wifi_poweron(struct file *fp, const char __user *buffer,
+                                  size_t len, loff_t *offset)
+{
+          char cmd[16]={0};
+
+          if (len >= sizeof(cmd))
+                  return -EINVAL;
+          if (copy_from_user(cmd, buffer, len))
+                  return -EFAULT;
+          if (!strncmp("on", cmd, 2))
+                  skw_sdio_wifi_power_on(1);
+          else if (!strncmp("off", cmd, 3))
+                skw_sdio_wifi_power_on(0);
+
+          return len;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_sdio_wifi_proc_fops = {
+	.proc_open = skw_sdio_wifi_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+	.proc_write = skw_sdio_wifi_poweron,
+};
+#else
+static const struct file_operations skw_sdio_wifi_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_sdio_wifi_open,
+	.read = seq_read,
+	.release = single_release,
+	.write = skw_sdio_wifi_poweron,
+};
+#endif
+
+void skw_sdio_log_level_init(void)
+{
+	skw_sdio_set_log_level(SKW_SDIO_INFO);
+
+	skw_sdio_enable_func_log(SKW_SDIO_DUMP, false);
+	skw_sdio_enable_func_log(SKW_SDIO_PORT0, false);
+	skw_sdio_enable_func_log(SKW_SDIO_PORT1, false);
+	skw_sdio_enable_func_log(SKW_SDIO_PORT2, false);
+	skw_sdio_enable_func_log(SKW_SDIO_PORT3, false);
+	skw_sdio_enable_func_log(SKW_SDIO_PORT4, false);
+	skw_sdio_enable_func_log(SKW_SDIO_PORT5, false);
+	skw_sdio_enable_func_log(SKW_SDIO_PORT6, false);
+	skw_sdio_enable_func_log(SKW_SDIO_SAVELOG, false);
+	skw_sdio_enable_func_log(SKW_SDIO_PORT7, false);
+}
+void skw_sdio_create_debug_files(void) 
+{
+	skw_sdio_proc_init_ex("log_level", 0666, &skw_sdio_log_proc_fops, NULL);
+	skw_sdio_proc_init_ex("Version", 0666, &skw_version_proc_fops, NULL);
+	skw_sdio_proc_init_ex("Statistic", 0666, &skw_port_statistic_proc_fops, NULL);
+	skw_sdio_proc_init_ex("config", 0666, &skw_config_proc_fops, NULL);
+	skw_sdio_proc_init_ex("recovery", 0666, &skw_recovery_debug_proc_fops, NULL);
+	skw_sdio_proc_init_ex("BT_ANT", 0666, &skw_bluetooth_antenna_proc_fops, NULL);
+	skw_sdio_proc_init_ex("BT_UART1", 0666, &skw_bluetooth_UART1_proc_fops, NULL);
+	skw_sdio_proc_init_ex("WiFi", 0666, &skw_sdio_wifi_proc_fops, NULL);
+	skw_sdio_proc_init_ex("wifi_service", 0666, &skw_sdio_wifi_serv_proc_fops, NULL);
+	skw_sdio_proc_init_ex("bt_service", 0666, &skw_sdio_bt_serv_proc_fops, NULL);
+}
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_log.h b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_log.h
new file mode 100755
index 0000000..c37e444
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_log.h
@@ -0,0 +1,80 @@
+/******************************************************************************
+ *
+ * Copyright(c) 2020-2030  Seekwave Corporation.
+ *
+ *****************************************************************************/
+#ifndef __SKW_SDIO_LOG_H__
+#define __SKW_SDIO_LOG_H__
+
+#define SKW_SDIO_ERROR    BIT(0)
+#define SKW_SDIO_WARNING  BIT(1)
+#define SKW_SDIO_INFO     BIT(2)
+#define SKW_SDIO_DEBUG    BIT(3)
+
+#define SKW_SDIO_CMD      BIT(16)
+#define SKW_SDIO_EVENT    BIT(17)
+#define SKW_SDIO_SCAN     BIT(18)
+#define SKW_SDIO_TIMER    BIT(19)
+#define SKW_SDIO_STATE    BIT(20)
+
+#define SKW_SDIO_PORT0     BIT(21)
+#define SKW_SDIO_PORT1     BIT(22)
+#define SKW_SDIO_PORT2     BIT(23)
+#define SKW_SDIO_PORT3     BIT(24)
+#define SKW_SDIO_PORT4     BIT(25)
+#define SKW_SDIO_PORT5     BIT(26)
+#define SKW_SDIO_PORT6     BIT(27)
+#define SKW_SDIO_PORT7     BIT(28)
+#define SKW_SDIO_SAVELOG     BIT(29)
+#define SKW_SDIO_DUMP     BIT(31)
+
+unsigned long skw_sdio_log_level(void);
+
+#define skw_sdio_log(level, fmt, ...) \
+	do { \
+		if (skw_sdio_log_level() & level) \
+			pr_err(fmt,  ##__VA_ARGS__); \
+	} while (0)
+
+#define skw_sdio_port_log(port_num, fmt, ...) \
+	do { \
+		if (skw_sdio_log_level() &(SKW_SDIO_PORT0<<port_num)) \
+			pr_err(fmt,  ##__VA_ARGS__); \
+	} while (0)
+
+#define skw_port_log(port_num,fmt, ...) \
+	skw_sdio_log((SKW_SDIO_PORT0<<port_num), "[PORT_LOG] %s: "fmt, __func__, ##__VA_ARGS__)
+
+#define skw_sdio_err(fmt, ...) \
+	skw_sdio_log(SKW_SDIO_ERROR, "[SKWSDIO ERROR] %s: "fmt, __func__, ##__VA_ARGS__)
+
+#define skw_sdio_warn(fmt, ...) \
+	skw_sdio_log(SKW_SDIO_WARNING, "[SKWSDIO WARN] %s: "fmt, __func__, ##__VA_ARGS__)
+
+#define skw_sdio_info(fmt, ...) \
+	skw_sdio_log(SKW_SDIO_INFO, "[SKWSDIO INFO] %s: "fmt, __func__, ##__VA_ARGS__)
+
+#define skw_sdio_dbg(fmt, ...) \
+	skw_sdio_log(SKW_SDIO_DEBUG, "[SKWSDIO DBG] %s: "fmt, __func__, ##__VA_ARGS__)
+
+#define skw_sdio_hex_dump(prefix, buf, len) \
+	do { \
+		if (skw_sdio_log_level() & SKW_SDIO_DUMP) { \
+			u8 str[32] = {0};  \
+			snprintf(str, sizeof(str), "[SKWSDIO DUMP] %s", prefix); \
+			print_hex_dump(KERN_ERR, str, \
+				DUMP_PREFIX_OFFSET, 16, 1, buf, len, true); \
+		} \
+	} while (0)
+#if 0
+#define skw_sdio_port_log(port_num, fmt, ...) \
+	do { \
+		if (skw_sdio_log_level() &(SKW_SDIO_PORT0<<port_num)) \
+			pr_err("[PORT_LOG] %s:"fmt,__func__,  ##__VA_ARGS__); \
+	} while (0)
+
+#endif
+void skw_sdio_log_level_init(void);
+void skw_sdio_create_debug_files(void);
+#endif
+
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_main.c b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_main.c
new file mode 100755
index 0000000..31e34fb
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_main.c
@@ -0,0 +1,2744 @@
+/*
+ * Copyright (C) 2021 Seekwave Tech Inc.
+ *
+ * Filename : skw_sdio.c
+ * Abstract : This file is a implementation for Seekwave sdio  function
+ *
+ * Authors	:
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/kthread.h>
+#include <linux/version.h>
+#include <linux/ktime.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/pm_runtime.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/core.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_func.h>
+#include "skw_sdio_log.h"
+#include "skw_sdio_host.h"
+#include "skw_sdio_debugfs.h"
+#include "skw_sdio.h"
+int bind_device=0;
+
+extern unsigned int test_debug;
+extern int skw_use_sdma;
+extern int g_chipen_pin;
+extern struct sv6160_platform_data ucom_pdata;
+extern struct sv6160_platform_data wifi_pdata;
+extern struct sdio_port sdio_ports[];
+
+static int card_id = SKW_MMC_HOST_SD_INDEX;
+module_param(card_id, int, S_IRUGO);
+module_param(bind_device, int, S_IRUGO);
+module_param(test_debug, uint, S_IRUGO);
+module_param(skw_use_sdma, int, S_IRUGO);
+
+#ifndef MMC_CAP2_SDIO_IRQ_NOTHREAD
+#define MMC_CAP2_SDIO_IRQ_NOTHREAD (1 << 17)
+#endif
+
+#define skw_sdio_transfer_enter() mutex_lock(&skw_sdio->transfer_mutex)
+#define skw_sdio_transfer_exit() mutex_unlock(&skw_sdio->transfer_mutex)
+
+irqreturn_t skw_gpio_irq_handler(int irq, void *dev_id); //interrupt
+//int (*skw_dloader)(unsigned int subsys);
+//static int skw_get_chipid(char *chip_id);
+static int check_chipid(void);
+static int skw_sdio_cp_reset(void);
+static int skw_sdio_cp_service_ops(int service_ops);
+static int skw_sdio_cpdebug_boot(void);
+struct skw_sdio_data_t *g_skw_sdio_data;
+static struct sdio_driver skw_sdio_driver;
+static struct mutex dloader_mutex;
+static int skw_sdio_set_dma_type(unsigned int address, unsigned int dma_type);
+static int skw_sdio_slp_feature_en(unsigned int address, unsigned int slp_en);
+static int skw_sdio_host_irq_init(unsigned int irq_gpio_num);
+static int skw_WIFI_service_start(void);
+static int skw_WIFI_service_stop(void);
+static int skw_BT_service_start(void);
+static int skw_BT_service_stop(void);
+static int skw_sdio_host_check(struct skw_sdio_data_t *skw_sdio);
+extern int sdio_reset_comm(struct mmc_card *card);
+extern void kernel_restart(char *cmd);
+extern void skw_sdio_exception_work(struct work_struct *work);
+static int skw_sdio_reg_reset_cp(void);
+extern int  cp_detect_sleep_mode;
+extern char skw_cp_ver;
+extern int max_ch_num;
+extern int max_pac_size;
+extern int skw_sdio_blk_size;
+extern char assert_context[];
+extern int  assert_context_size;
+extern struct debug_vars debug_infos;
+extern void sunxi_wlan_set_power(int on);
+extern void sunxi_mmc_rescan_card(unsigned ids);
+
+//=======================================================
+//debug sdio macro and Variable
+//=======================================================
+
+struct skw_sdio_data_t *skw_sdio_get_data(void)
+{
+	return g_skw_sdio_data;
+}
+
+void skw_sdio_unlock_rx_ws(struct skw_sdio_data_t *skw_sdio)
+{
+
+	if (!atomic_read(&skw_sdio->rx_wakelocked))
+		return;
+	atomic_set(&skw_sdio->rx_wakelocked, 0);
+#ifdef CONFIG_WAKELOCK
+	__pm_relax(&skw_sdio->rx_wl.ws);
+#else
+	__pm_relax(skw_sdio->rx_ws);
+#endif
+}
+static void skw_sdio_lock_rx_ws(struct skw_sdio_data_t *skw_sdio)
+{
+//	if (atomic_read(&skw_sdio->rx_wakelocked))
+		return;
+	atomic_set(&skw_sdio->rx_wakelocked, 1);
+#ifdef CONFIG_WAKELOCK
+	__pm_stay_awake(&skw_sdio->rx_wl.ws);
+#else
+	__pm_stay_awake(skw_sdio->rx_ws);
+#endif
+}
+static void skw_sdio_wakeup_source_init(struct skw_sdio_data_t *skw_sdio)
+{
+	if(skw_sdio) {
+#ifdef CONFIG_WAKELOCK
+	wake_lock_init(&skw_sdio->rx_wl, WAKE_LOCK_SUSPEND,"skw_sdio_r_wakelock");
+#else
+	skw_sdio->rx_ws = skw_wakeup_source_register(NULL, "skw_sdio_r_wakelock");
+#endif
+	}
+}
+static void skw_sdio_wakeup_source_destroy(struct skw_sdio_data_t *skw_sdio)
+{
+	if(skw_sdio) {
+#ifdef CONFIG_WAKELOCK
+	wake_lock_destroy(&skw_sdio->rx_wl);
+#else
+	wakeup_source_unregister(skw_sdio->rx_ws);
+#endif
+	}
+}
+
+void skw_resume_check(void)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	unsigned int timeout;
+
+	timeout = 0;
+	while((!atomic_read(&skw_sdio->resume_flag)) && (timeout++ < 20000))
+		usleep_range(1500, 2000);
+}
+
+static void skw_sdio_abort(int err_code)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_func *func0 = skw_sdio->sdio_func[FUNC_0];
+	unsigned char value;
+	int ret;
+
+	if (err_code == -ETIMEDOUT)
+		return;
+	if(err_code != 0) {
+		if(skw_sdio->cp_state){
+			skw_sdio_warn("cp_state:%d on recoving!!\n", skw_sdio->cp_state);
+			return;
+		}
+		//send_modem_assert_command();
+		skw_sdio->cp_state = 1;
+		schedule_delayed_work(&skw_sdio->skw_except_work , msecs_to_jiffies(2000));
+	}
+	sdio_claim_host(func0);
+
+	value = sdio_readb(func0, SDIO_VER_CCCR, &ret);
+
+	sdio_writeb(func0, SDIO_ABORT_TRANS, SKW_SDIO_CCCR_ABORT, &ret);
+
+	value = sdio_readb(func0, SDIO_VER_CCCR, &ret);
+	skw_sdio_err("SDIO Abort, SDIO_VER_CCCR:0x%x\n", value);
+
+	sdio_release_host(func0);
+}
+
+int skw_sdio_sdma_write(unsigned char *src, unsigned int len)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_func *func = skw_sdio->sdio_func[FUNC_1];
+	int blksize = func->cur_blksize;
+	int ret = 0;
+
+	if (!src || len%4) {
+		skw_sdio_err("%s invalid para %p, %d\n", __func__, src, len);
+		return -1;
+	}
+
+	len = (len + blksize -1)/blksize*blksize;
+
+	skw_resume_check();
+	skw_sdio_transfer_enter();
+	sdio_claim_host(func);
+	ret = sdio_writesb(func, SKW_SDIO_PK_MODE_ADDR, src, len);
+	if (ret < 0)
+		skw_sdio_err("%s  ret = %d\n", __func__, ret);
+	sdio_release_host(func);
+	if (ret) 
+		skw_sdio_abort(ret);
+	skw_sdio_transfer_exit();
+
+	return ret;
+}
+
+int skw_sdio_sdma_read(unsigned char *src, unsigned int len)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_func *func = skw_sdio->sdio_func[FUNC_1];
+	int ret = 0;
+	skw_resume_check();
+	skw_sdio_transfer_enter();
+	sdio_claim_host(func);
+	ret = sdio_readsb(func, src, SKW_SDIO_PK_MODE_ADDR, len);
+	sdio_release_host(func);
+	if (ret != 0)
+		skw_sdio_abort(ret);
+	skw_sdio_transfer_exit();
+	return ret;
+}
+
+void *skw_get_bus_dev(void)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	int time_count = 0;
+	if ((!skw_sdio) || (!skw_sdio->sdio_dev_host)) {
+		skw_sdio_err("%d try again get sdio bus dev  \n", __LINE__);
+		do {
+			skw_sdio = skw_sdio_get_data();
+			if (skw_sdio && skw_sdio->sdio_dev_host) {
+				break;
+			}
+			msleep(10);
+			time_count++;
+		} while (time_count < 50);
+	}
+	if ((!skw_sdio) || (!skw_sdio->sdio_dev_host)) {
+		skw_sdio_err("skw_sdio or dev_host is NULL!\n");
+		return NULL;
+	}
+	return &skw_sdio->sdio_func[FUNC_1]->dev;
+}
+irqreturn_t skw_host_wake_irq_handler(int irq, void *dev_id) //interrupt
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	int	value = gpio_get_value(skw_sdio->gpio_in);
+
+	skw_sdio_dbg("gpio request_irq=%d  GPIO value %d!\n", irq, value);
+	return IRQ_HANDLED;
+}
+
+static int skw_sdio_host_wake_irq_init(unsigned int gpio_num)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	int ret = 0;
+
+	skw_sdio->device_active = gpio_get_value(skw_sdio->gpio_in);
+	skw_sdio->irq_num = gpio_to_irq(skw_sdio->gpio_in);
+	skw_sdio->irq_trigger_type = IRQF_TRIGGER_RISING;
+	skw_sdio_info("gpio_irq %d\n", skw_sdio->irq_num);
+	if (skw_sdio->irq_num) {
+		ret = request_irq(skw_sdio->irq_num, skw_host_wake_irq_handler,
+				skw_sdio->irq_trigger_type | IRQF_ONESHOT, "skw-gpio-irq", NULL);
+		if (ret != 0) {
+			free_irq(skw_sdio->irq_num, NULL);
+			skw_sdio_err("%s request gpio irq fail ret=%d\n", __func__, ret);
+			return -1;
+		} else {
+			skw_sdio_dbg("gpio request_irq=%d  GPIO value %d!\n",
+					skw_sdio->irq_num, skw_sdio->device_active);
+		}
+	}
+	enable_irq_wake(skw_sdio->irq_num);
+	return ret;
+}
+
+int skw_sdio_gpio_irq_pre_ops(void)
+{
+	int ret = 0;
+	struct sdio_func *func;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	if (!skw_sdio->boot_data)
+		return -1;
+        
+	if (skw_sdio->boot_data->gpio_in < 0 || skw_sdio->boot_data->gpio_out < 0) {
+		skw_sdio_err("gpio_in is %d or gpio_out is %d invalid\n",
+			skw_sdio->boot_data->gpio_in, skw_sdio->boot_data->gpio_out);
+			skw_sdio->gpio_out = skw_sdio->boot_data->gpio_out;
+			skw_sdio->gpio_in = skw_sdio->boot_data->gpio_in;
+		return -1;
+	}
+
+	skw_sdio->gpio_in = skw_sdio->boot_data->gpio_in;
+	skw_sdio->gpio_out = skw_sdio->boot_data->gpio_out;
+	if (skw_sdio->boot_data->gpio_in < 0 ||
+	    skw_sdio->boot_data->gpio_out < 0) {
+		if (skw_sdio->boot_data->gpio_in >= 0)
+			ret = skw_sdio_host_wake_irq_init(
+				skw_sdio->boot_data->gpio_in);
+		skw_sdio_warn(" no support gpio irq!!!\n");
+		return ret;
+	}
+	switch (cp_detect_sleep_mode) {
+	case 0:
+		break;
+	case 1:
+	case 2:
+		func = skw_sdio->sdio_func[FUNC_1];
+		sdio_claim_host(func);
+		sdio_release_irq(func);
+		sdio_release_host(func);
+		ret = skw_sdio_host_irq_init(skw_sdio->gpio_in);
+		break;
+	case 3:
+		gpio_set_value(skw_sdio->boot_data->gpio_out, 0);
+		msleep(100);
+		loopcheck_send_data("APGPIORDY", 9);
+		msleep(5);
+		gpio_set_value(skw_sdio->boot_data->gpio_out, 1);
+		func = skw_sdio->sdio_func[FUNC_1];
+		sdio_claim_host(func);
+		sdio_release_irq(func);
+		sdio_release_host(func);
+		ret = skw_sdio_host_irq_init(skw_sdio->gpio_in);
+		break;
+	default:
+		break;
+	}
+	if (ret)
+		skw_sdio_err("gpio irq init fail\n");
+	return ret;
+}
+static int skw_sdio_start_transfer(struct scatterlist *sgs, int sg_count,
+	int total, struct sdio_func *sdio_func, uint fix_inc, bool dir, uint addr)
+{
+	struct mmc_request mmc_req;
+	struct mmc_command mmc_cmd;
+	struct mmc_data mmc_dat;
+	struct mmc_host *host = sdio_func->card->host;
+	bool fifo = (fix_inc == SKW_SDIO_DATA_FIX);
+	uint fn_num = sdio_func->num;
+	uint blk_num, blk_size, max_blk_count, max_req_size;
+	int err_ret = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+
+	blk_size = SKW_SDIO_BLK_SIZE;
+	max_blk_count = min_t(unsigned int, host->max_blk_count, (uint)MAX_IO_RW_BLK);
+	max_req_size = min_t(unsigned int,	max_blk_count*blk_size, host->max_req_size);
+
+	memset(&mmc_req, 0, sizeof(struct mmc_request));
+	memset(&mmc_cmd, 0, sizeof(struct mmc_command));
+	memset(&mmc_dat, 0, sizeof(struct mmc_data));
+
+	if (total % blk_size != 0) {
+		skw_sdio_err("total %d not aligned to blk size\n", total);
+		return -1;
+	}
+
+	blk_num = total / blk_size;
+	mmc_dat.sg = sgs;
+	mmc_dat.sg_len = sg_count;
+	mmc_dat.blksz = blk_size;
+	mmc_dat.blocks = blk_num;
+	mmc_dat.flags = dir ? MMC_DATA_WRITE : MMC_DATA_READ;
+	mmc_cmd.opcode = 53; /* SD_IO_RW_EXTENDED */
+	mmc_cmd.arg = dir ? 1<<31 : 0;
+	mmc_cmd.arg |= (fn_num & 0x7) << 28;
+	mmc_cmd.arg |= 1<<27;
+	mmc_cmd.arg |= fifo ? 0 : 1<<26;
+	mmc_cmd.arg |= (addr & 0x1FFFF) << 9;
+	mmc_cmd.arg |= blk_num & 0x1FF;
+	mmc_cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
+	mmc_req.cmd = &mmc_cmd;
+	mmc_req.data = &mmc_dat;
+	if (!fifo)
+		addr += total;
+	skw_sdio_dbg("total:%d sg_count:%d cmd_arg 0x%x\n", total, sg_count, mmc_cmd.arg);
+	sdio_claim_host(sdio_func);
+	if(skw_sdio->power_off == 1){
+		sdio_release_host(sdio_func);
+		return 0;
+	}
+	mmc_set_data_timeout(&mmc_dat, sdio_func->card);
+	mmc_wait_for_req(host, &mmc_req);
+	sdio_release_host(sdio_func);
+
+	err_ret = mmc_cmd.error ? mmc_cmd.error : mmc_dat.error;
+	if (err_ret != 0) {
+		skw_sdio_err("%s:CMD53 %s failed error=%d\n",__func__,
+				  dir ? "write" : "read", err_ret);
+	}
+	return err_ret;
+}
+
+int skw_sdio_adma_write(int portno, struct scatterlist *sgs, int sg_count, int total)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	int ret = 0;
+
+	skw_resume_check();
+	skw_sdio_transfer_enter();
+	if(skw_sdio->resume_com==0)
+		skw_sdio->resume_com = 1; 
+	ret = skw_sdio_start_transfer(sgs, sg_count, SKW_SDIO_ALIGN_BLK(total),
+				  skw_sdio->sdio_func[FUNC_1], SKW_SDIO_DATA_FIX,
+				  SKW_SDIO_WRITE, SKW_SDIO_PK_MODE_ADDR);
+	if (ret) {
+		skw_sdio_abort(ret);
+	} else {
+		if (skw_sdio->device_active==0 && skw_sdio->irq_type && skw_sdio->gpio_in >= 0)
+			skw_sdio->device_active = gpio_get_value(skw_sdio->gpio_in);
+	}
+	skw_sdio_transfer_exit();
+
+	return ret;
+}
+
+int skw_sdio_adma_read(struct skw_sdio_data_t *skw_sdio, struct scatterlist *sgs, int sg_count, int total)
+{
+	int ret = 0;
+
+	skw_resume_check();
+	skw_sdio_transfer_enter();
+	ret = skw_sdio_start_transfer(sgs, sg_count, total,
+				  skw_sdio->sdio_func[FUNC_1], SKW_SDIO_DATA_FIX,
+				  SKW_SDIO_READ, SKW_SDIO_PK_MODE_ADDR);
+	if (ret)
+		skw_sdio_abort(ret);
+	skw_sdio_transfer_exit();
+	return ret;
+}
+
+static int skw_sdio_dt_set_address(unsigned int address)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_func *func = skw_sdio->sdio_func[FUNC_0];
+	unsigned char value ;
+	int err = 0;
+	int i;
+
+	sdio_claim_host(func);
+	for (i = 0; i < 4; i++) {
+		value = (address >> (8 * i)) & 0xFF;
+		sdio_writeb(func, value, SKW_SDIO_FBR_REG+i, &err);
+		if (err != 0)
+			break;
+	}
+	sdio_release_host(func);
+
+	return err;
+}
+
+
+int skw_sdio_writel(unsigned int address, void *data)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_func *func = skw_sdio->sdio_func[FUNC_1];
+	int ret = 0;
+
+	skw_resume_check();
+	skw_sdio_transfer_enter();
+
+	ret = skw_sdio_dt_set_address(address);
+	if (ret != 0) {
+		skw_sdio_transfer_exit();
+		return ret;
+	}
+
+	sdio_claim_host(func);
+	sdio_writel(func, *(unsigned int *)data, SKW_SDIO_DT_MODE_ADDR, &ret);
+	sdio_release_host(func);
+	skw_sdio_transfer_exit();
+
+	if (ret) {
+		skw_sdio_err("%s fail ret:%d, addr=0x%x\n", __func__,
+				ret, address);
+		skw_sdio_abort(ret);
+	}
+	skw_sdio_info("debug----the address=0x%x \n",address);
+	return ret;
+}
+
+int skw_sdio_readl(unsigned int address, void *data)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_func *func = skw_sdio->sdio_func[FUNC_1];
+	int ret = 0;
+
+
+	skw_resume_check();
+	skw_sdio_transfer_enter();
+	ret = skw_sdio_dt_set_address(address);
+	if (ret != 0) {
+		skw_sdio_transfer_exit();
+		return ret;
+	}
+
+	sdio_claim_host(func);
+
+	*(unsigned int *)data = sdio_readl(func, SKW_SDIO_DT_MODE_ADDR, &ret);
+
+	sdio_release_host(func);
+	skw_sdio_transfer_exit();
+	if (ret) {
+		skw_sdio_err("%s fail ret:%d, addr=0x%x\n", __func__, ret, address);
+		skw_sdio_abort(ret);
+	}
+
+	return ret;
+}
+/*
+ *command = 0: service_start else service stop
+ *service = 0: WIFI_service else BT service.
+ */
+int send_modem_service_command(u16 service, u16 command)
+{
+	u16 cmd;
+	int ret = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	if(command)
+		skw_sdio->service_state_map&= ~(1<<service);
+		//command = 1;
+	cmd = (service<<1)|command;
+	cmd = 1 << cmd;
+	if (cmd>>8) {
+		skw_sdio_err("service command error 0x%x!", cmd);
+			return -EINVAL;
+	}
+	skw_sdio_info("service = %d cmd %x\n", service, cmd);
+	if(skw_sdio->cp_state)
+		return -EINVAL;
+
+	ret = skw_sdio_writeb(SKW_AP2CP_IRQ_REG, cmd & 0xff);
+	skw_sdio_info("ret = %d command %x\n", ret, command);
+	return ret;
+}
+
+static unsigned int max_bytes(struct sdio_func *func)
+{
+	unsigned int mval = func->card->host->max_blk_size;
+
+	if (func->card->quirks & MMC_QUIRK_BLKSZ_FOR_BYTE_MODE)
+		mval = min(mval, func->cur_blksize);
+	else
+		mval = min(mval, func->max_blksize);
+
+	if (func->card->quirks & MMC_QUIRK_BROKEN_BYTE_MODE_512)
+		return min(mval, 511u);
+
+	/* maximum size for byte mode */
+	return min(mval, 512u);
+}
+
+int skw_sdio_dt_write(unsigned int address,	void *buf, unsigned int len)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_func *func = skw_sdio->sdio_func[FUNC_1];
+	unsigned int remainder = len;
+	unsigned int trans_len;
+	int ret = 0;
+	char *data= skw_sdio->next_size_buf;
+
+	skw_resume_check();
+	skw_sdio_transfer_enter();
+
+	ret = skw_sdio_dt_set_address(address);
+	if (ret != 0) {
+		skw_sdio_err("%s set address error!!!", __func__);
+		skw_sdio_transfer_exit();
+		return ret;
+	}
+
+	if(skw_sdio->resume_com==0)
+		skw_sdio->resume_com = 1;
+	sdio_claim_host(func);
+	while (remainder > 0) {
+		if (remainder >= func->cur_blksize)
+			trans_len = func->cur_blksize;
+		else
+			trans_len = min(remainder, max_bytes(func));
+
+		memcpy(data, buf,trans_len);
+		ret = sdio_memcpy_toio(func, SKW_SDIO_DT_MODE_ADDR, data, trans_len);
+		if (ret) {
+			skw_sdio_err("%s sdio_memcpy_toio failed!!!", __func__);
+			break;
+		}
+		remainder -= trans_len;
+		buf += trans_len;
+	}
+	sdio_release_host(func);
+	skw_sdio_transfer_exit();
+	if (ret) {
+		skw_sdio_err("dt write fail ret:%d, address=0x%x\n", ret, address);
+		skw_sdio_abort(ret);
+	}
+	return ret;
+}
+
+int skw_sdio_dt_read(unsigned int address, void *buf, unsigned int len)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_func *func = skw_sdio->sdio_func[FUNC_1];
+	unsigned int remainder = len;
+	unsigned int trans_len;
+	int ret = 0;
+
+	ret = skw_sdio_dt_set_address(address);
+	if (ret != 0) {
+		skw_sdio_err("set address error ret=%d !!!", ret);
+		return ret;
+	}
+	if(skw_sdio->resume_com==0)
+		skw_sdio->resume_com = 1; 
+	skw_sdio_transfer_enter();
+	sdio_claim_host(func);
+	while (remainder > 0) {
+		if (remainder >= func->cur_blksize)
+			trans_len = func->cur_blksize;
+		else
+			trans_len = min(remainder, max_bytes(func));
+		ret = sdio_memcpy_fromio(func, buf, SKW_SDIO_DT_MODE_ADDR, trans_len);
+		if (ret) {
+			skw_sdio_err("sdio_memcpy_fromio: %p 0x%x ret=%d\n", buf, *(uint32_t *)buf, ret);
+			break;
+		}
+		remainder -= trans_len;
+		buf += trans_len;
+	}
+	sdio_release_host(func);
+	skw_sdio_transfer_exit();
+	if (ret) {
+		skw_sdio_err("dt read fail ret:%d, address=0x%x\n", ret, address);
+		skw_sdio_abort(ret);
+	}
+
+	return ret;
+}
+
+int skw_sdio_readb(unsigned int address, unsigned char *value)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_func *func = skw_sdio->sdio_func[FUNC_0];
+	unsigned char reg = 0;
+	int err = 0, i;
+
+	for (i=0; i<2; i++) {
+		try_to_wakeup_modem(max_ch_num);
+		sdio_claim_host(func);
+		reg = sdio_readb(func, address, &err);
+		if (value)
+			*value = reg;
+		sdio_release_host(func);
+		if (err ==0)
+			break;
+	}
+	return err;
+}
+
+int skw_sdio_writeb(unsigned int address, unsigned char value)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_func *func = skw_sdio->sdio_func[FUNC_0];
+	int err = 0, i;
+
+	for (i=0; i<2; i++) {
+		try_to_wakeup_modem(max_ch_num);
+		sdio_claim_host(func);
+		sdio_writeb(func, value, address, &err);
+		sdio_release_host(func);
+		if (err == 0)
+			break;
+	}
+	return err;
+}
+
+static int skw_sdio_host_irq_init(unsigned int irq_gpio_num)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	int ret = 0;
+
+	skw_sdio->irq_type = SKW_SDIO_EXTERNAL_IRQ;
+	skw_sdio->device_active = gpio_get_value(skw_sdio->gpio_in);
+	skw_sdio->irq_num = gpio_to_irq(skw_sdio->gpio_in);
+	skw_sdio->irq_trigger_type = IRQF_TRIGGER_RISING;
+	skw_sdio_info("gpio_In:%d,gpio_out:%d irq %d\n",skw_sdio->gpio_in,
+		skw_sdio->gpio_out, skw_sdio->irq_num);
+	if (skw_sdio->irq_num) {
+		ret = request_irq(skw_sdio->irq_num, skw_gpio_irq_handler,
+				skw_sdio->irq_trigger_type | IRQF_ONESHOT, "skw-gpio-irq", NULL);
+		if (ret != 0) {
+			free_irq(skw_sdio->irq_num, NULL);
+			skw_sdio_err("%s request gpio irq fail ret=%d\n", __func__, ret);
+			return -1;
+		} else {
+			skw_sdio->device_active = gpio_get_value(skw_sdio->gpio_in);
+			skw_sdio_info("gpio request_irq=%d  GPIO value %d!\n",
+					skw_sdio->irq_num, skw_sdio->device_active);
+		}
+	}
+	enable_irq_wake(skw_sdio->irq_num);
+	skw_sdio_rx_up(skw_sdio);
+	return ret;
+}
+
+static int skw_sdio_get_dev_func(struct sdio_func *func)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	if (func->num >= MAX_FUNC_NUM) {
+		skw_sdio_err("func num err!!! func num is %d!!!",
+			func->num);
+		return -1;
+	}
+	skw_sdio_dbg("func num is %d.", func->num);
+
+	if (func->num == 1) {
+		skw_sdio->sdio_func[FUNC_0] = kmemdup(func, sizeof(*func),
+							 GFP_KERNEL);
+		if (!skw_sdio->sdio_func[FUNC_0]) {
+		    return -ENOMEM;
+		}
+		skw_sdio->sdio_func[FUNC_0]->num = 0;
+		skw_sdio->sdio_func[FUNC_0]->max_blksize = SKW_SDIO_BLK_SIZE;
+	}
+	skw_sdio->sdio_func[FUNC_1] = func;
+
+	return 0;
+}
+
+extern u64 skw_local_clock(void);
+void skw_sdio_inband_irq_handler(struct sdio_func *func)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_func *func0 = skw_sdio->sdio_func[FUNC_0];
+	int ret;
+
+	if(!skw_sdio->cp_downloaded_flag){//fw not download done
+		return;
+	}
+
+	if (!debug_infos.cp_assert_time) {
+		debug_infos.last_irq_time = skw_local_clock();
+		debug_infos.last_irq_times[debug_infos.rx_inband_irq_cnt % CHN_IRQ_RECORD_NUM] = debug_infos.last_irq_time;
+		skw_sdio_dbg("irq coming %d\n", debug_infos.rx_inband_irq_cnt);
+	}
+	if (!SKW_CARD_ONLINE(skw_sdio)) {
+		skw_sdio_err("%s  card offline\n", __func__);
+		return;
+	}
+
+	skw_resume_check();
+
+	/* send cmd to clear cp int status */
+	sdio_claim_host(func0);
+	try_to_wakeup_modem(max_ch_num);
+	sdio_f0_readb(func0, SDIO_CCCR_INTx, &ret);
+	if (!debug_infos.cp_assert_time) {
+		debug_infos.last_clear_irq_times[debug_infos.rx_inband_irq_cnt % CHN_IRQ_RECORD_NUM] = debug_infos.last_irq_time;
+		debug_infos.rx_inband_irq_cnt++;
+	}
+	sdio_release_host(func0);
+	if (ret < 0)
+		skw_sdio_err("%s error %d\n", __func__, ret);
+	skw_sdio_lock_rx_ws(skw_sdio);
+	skw_sdio_rx_up(skw_sdio);
+}
+
+int skw_sdio_enable_async_irq(void)
+{
+	int ret = 0;
+	u8 reg;
+	skw_sdio_info("[+]");
+	ret = skw_sdio_readb(SDIO_INT_EXT, &reg);
+	if (ret)
+		return ret;
+
+	reg |= 1 << 1; /* Enable Asynchronous Interrupt */
+
+	ret = skw_sdio_writeb(SDIO_INT_EXT, reg & 0xff);
+	if (ret)
+		return ret;
+	ret = skw_sdio_readb(SDIO_INT_EXT, &reg);
+	if (ret)
+		return ret;
+
+	if (!(reg & (1 << 1)))
+		skw_sdio_err("enable sdio async irq fail reg = 0x%x\n", reg);
+
+	return ret;
+}
+
+#ifdef CONFIG_PM_SLEEP
+int skw_sdio_set_suspend_indication(void)
+{
+	int ret = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	skw_sdio_info("Enter slp_disable=%d\n", skw_sdio->boot_data->slp_disable);
+	ret = skw_sdio_writeb(SDIOHAL_CPLOG_TO_AP_SWITCH, 0x02);
+	if (ret < 0) {
+		skw_sdio_err("cls the log signal fail ret=%d\n", ret);
+		return ret;
+	}
+	ret = skw_sdio_writeb(SKW_AP2CP_IRQ_REG, BIT(5));
+	if (ret < 0) {
+		skw_sdio_err("cls log irq fail ret=%d\n", ret);
+		return ret;
+	}
+	return 0;
+}
+int skw_sdio_set_resume_indication(void)
+{
+	int ret = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	skw_sdio_info("Enter slp_disable=%d\n", skw_sdio->boot_data->slp_disable);
+	ret = skw_sdio_writeb(SDIOHAL_CPLOG_TO_AP_SWITCH, 0x04);
+	if (ret < 0) {
+		skw_sdio_err("cls the log signal fail ret=%d\n", ret);
+		return ret;
+	}
+	ret = skw_sdio_writeb(SKW_AP2CP_IRQ_REG, BIT(5));
+	if (ret < 0) {
+		skw_sdio_err("cls log irq fail ret=%d\n", ret);
+		return ret;
+	}
+	return 0;
+}
+
+static int skw_sdio_suspend(struct device *dev)
+{
+	struct sdio_func *func = container_of(dev, struct sdio_func, dev);
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	int  ret = 0;
+
+	skw_sdio_dbg("[%s]enter\n", __func__);
+	skw_sdio_set_suspend_indication();
+
+	atomic_set(&skw_sdio->resume_flag, 0);
+
+	if (SKW_CARD_ONLINE(skw_sdio))
+		func->card->host->pm_flags |= MMC_PM_KEEP_POWER;
+
+	func = skw_sdio->sdio_func[FUNC_1];
+	send_host_suspend_indication(skw_sdio);
+	if ((skw_sdio->irq_type == SKW_SDIO_INBAND_IRQ) && skw_sdio->resume_com) {
+		sdio_claim_host(func);
+		try_to_wakeup_modem(max_ch_num);
+		msleep(1);
+		ret = sdio_release_irq(func);
+		sdio_release_host(func);
+		skw_sdio_dbg("%s sdio_release_irq ret = %d\n", __func__, ret);
+	} 
+	atomic_set(&skw_sdio->suspending, 1);
+#ifdef CONFIG_NO_SERVICE_PD
+	if (!skw_sdio->cp_state && skw_sdio->service_state_map==0)
+		skw_sdio->power_off = 1;
+#endif
+	skw_sdio->resume_com = 0;
+	return ret;
+}
+
+static int skw_sdio_resume(struct device *dev)
+{
+	struct sdio_func *func = container_of(dev, struct sdio_func, dev);
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	int ret = 0;
+
+	skw_sdio_dbg("[%s]enter\n", __func__);
+#if defined(SKW_BOOT_DEBUG)
+	skw_dloader(2);
+#endif
+	skw_sdio_set_resume_indication();
+	skw_sdio->suspend_wake_unlock_enable =0;
+	if (SKW_CARD_ONLINE(skw_sdio))
+		func->card->host->pm_flags &= ~MMC_PM_KEEP_POWER;
+
+	func = skw_sdio->sdio_func[FUNC_1];
+	send_host_resume_indication(skw_sdio);
+	atomic_set(&skw_sdio->resume_flag, 1);
+	if (!func->irq_handler && (skw_sdio->irq_type == SKW_SDIO_INBAND_IRQ)) {
+		sdio_claim_host(func);
+		try_to_wakeup_modem(max_ch_num);
+		ret = sdio_claim_irq(func, skw_sdio_inband_irq_handler);
+		sdio_release_host(func);
+		if(ret < 0) {
+			skw_sdio_err("%s sdio_claim_irq ret = %d\n", __func__, ret);
+		} else {
+			ret = skw_sdio_enable_async_irq();
+			if (ret < 0)
+				skw_sdio_err("enable sdio async irq fail ret = %d\n", ret);
+		}
+	}
+	return ret;
+}
+#endif
+irqreturn_t skw_gpio_irq_handler(int irq, void *dev_id) //interrupt
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	int value = gpio_get_value(skw_sdio->gpio_in);
+	//skw_sdio_info("debug ----line[%d]- cp active[%d]--enter\n", __LINE__,value);
+	if (!debug_infos.cp_assert_time) {
+		debug_infos.last_irq_time = skw_local_clock();
+		debug_infos.last_irq_times[debug_infos.rx_gpio_irq_cnt % CHN_IRQ_RECORD_NUM] = debug_infos.last_irq_time;
+		debug_infos.rx_gpio_irq_cnt++;
+		skw_sdio_dbg("irq coming %d\n", debug_infos.rx_gpio_irq_cnt);
+	}
+	if (!SKW_CARD_ONLINE(skw_sdio)) {
+		skw_sdio_err("%s card offline\n", __func__);
+		return IRQ_HANDLED;
+	}
+	if (!skw_sdio->suspend_wake_unlock_enable) {
+		skw_sdio_dbg("suspend wake lock enable!!!!\n");
+		skw_sdio_lock_rx_ws(skw_sdio);
+	}
+
+	if (value && (skw_sdio->irq_type == SKW_SDIO_EXTERNAL_IRQ)){
+		skw_sdio_rx_up(skw_sdio);
+	 }
+	host_gpio_in_routine(value);
+	return IRQ_HANDLED;
+}
+
+static int skw_check_cp_ready(void)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	if (wait_for_completion_timeout(&skw_sdio->download_done,
+		msecs_to_jiffies(2000)) == 0) {
+		skw_sdio_err("CP-ready timeout chip_en:%d, kick RXTHREAD\n",
+				 gpio_get_value(skw_sdio->chip_en));
+		skw_sdio_rx_up(skw_sdio);
+		return -ETIME;
+	}
+	return 0;
+}
+static void skw_sdio_launch_thread(void)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	init_completion(&skw_sdio->rx_completed);
+	skw_sdio_wakeup_source_init(skw_sdio);
+	skw_sdio->rx_thread =
+		kthread_create(skw_sdio_rx_thread, NULL, "skw_sdio_rx_thread");
+	if (IS_ERR(skw_sdio->rx_thread)) {
+		skw_sdio_err("creat skw_sdio_rx_thread fail\n");
+		return;
+	}
+	if (skw_sdio->rx_thread) {
+#if KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE
+		sched_set_fifo_low(skw_sdio->rx_thread);
+#else
+		struct sched_param param;
+		param.sched_priority = 1;
+		sched_setscheduler(skw_sdio->rx_thread, SCHED_FIFO, &param);
+#endif
+		kthread_bind(skw_sdio->rx_thread, cpumask_first(cpu_online_mask));
+		set_user_nice(skw_sdio->rx_thread, SKW_MIN_NICE);
+		wake_up_process(skw_sdio->rx_thread);
+	} else
+		skw_sdio_err("creat skw_sdio_rx_thread fail\n");
+}
+
+static int skw_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct mmc_host *host = func->card->host;
+	int ret;
+
+	skw_sdio_info(": func->class=%x, vendor=0x%04x, device=0x%04x, "
+		      "func_num=0x%04x, clock=%d blksize=0x%x max_blkcnt %d\n",
+		      func->class, func->vendor, func->device, func->num,
+		      host->ios.clock, func->cur_blksize,
+		      func->card->host->max_blk_count);
+
+	ret = skw_sdio_get_dev_func(func);
+	if (ret < 0) {
+		skw_sdio_err("get func err\n");
+		return ret;
+	}
+
+	skw_sdio->sdio_dev_host = skw_sdio->sdio_func[FUNC_1]->card->host;
+	if (skw_sdio->sdio_dev_host == NULL) {
+		kfree(skw_sdio->sdio_func[FUNC_1]);
+		skw_sdio->sdio_func[FUNC_1] = NULL;
+		skw_sdio_err("get host failed!!!");
+		return -1;
+	}
+
+	skw_sdio_debugfs_init();
+	skw_sdio_launch_thread();
+	if (!skw_sdio->pwrseq) {
+		struct sdio_func *func1 = skw_sdio->sdio_func[FUNC_1];
+		/* Enable Function 1 */
+		sdio_claim_host(func1);
+		ret = sdio_enable_func(func1);
+
+		skw_sdio_info("sdio_enable_func ret=%d type %d\n", ret, skw_sdio->irq_type);
+		if(!ret) {
+			sdio_set_block_size(func1, SKW_SDIO_BLK_SIZE);
+			func1->max_blksize = SKW_SDIO_BLK_SIZE;
+			if (skw_sdio->irq_type == SKW_SDIO_INBAND_IRQ) {
+				if(sdio_claim_irq(func1,skw_sdio_inband_irq_handler)) {
+					skw_sdio_err("sdio_claim_irq failed\n");
+				} else {
+					ret = skw_sdio_enable_async_irq();
+					if (ret < 0)
+						skw_sdio_err("enable sdio async irq fail ret = %d\n", ret);
+				}
+			}
+			sdio_release_host(func1);
+		} else {
+			sdio_disable_func(func1); //disable func for remove
+			sdio_release_host(func1);
+			kfree(skw_sdio->sdio_func[FUNC_1]);
+			skw_sdio->sdio_func[FUNC_1] = NULL;
+			skw_sdio_err("enable func1 err!!! ret is %d\n", ret);
+			return ret;
+		}
+		skw_sdio->resume_com = 1;
+		skw_sdio_info("enable func1 done\n");
+	} else
+		pm_runtime_put_noidle(&func->dev);
+	if (!SKW_CARD_ONLINE(skw_sdio))
+		atomic_sub(SKW_SDIO_CARD_OFFLINE, &skw_sdio->online);
+
+	check_chipid();
+	if(!strncmp((char *)skw_sdio->chip_id,"SV6160LITE",10))
+	{
+		struct sdio_func *func1 = skw_sdio->sdio_func[FUNC_1];
+		skw_sdio_info("SV6160LITE chip\n");
+		sdio_claim_host(func1);
+		skw_sdio->sdio_func[FUNC_0]->max_blksize = SKW_SDIO_BLK_SIZE;
+		sdio_set_block_size(func1, SKW_SDIO_BLK_SIZE);
+		func1->max_blksize = SKW_SDIO_BLK_SIZE;
+		sdio_release_host(func1);
+	}
+	/* the card is nonremovable */
+	if (skw_sdio->sdio_dev_host->caps & MMC_CAP_NONREMOVABLE) {
+		//skw_sdio->sdio_dev_host->caps |= MMC_CAP_NONREMOVABLE; //| MMC_CAP_SDIO_IRQ;
+		skw_sdio_info("nonremovable card detected\n");
+	} else {
+		skw_sdio_info("removable card detected default\n");
+#ifdef SKW_SUPPORT_MMC_NONREMOVABLE
+		skw_sdio->sdio_dev_host->caps |= MMC_CAP_NONREMOVABLE;
+#endif
+	}
+	skw_sdio_bind_platform_driver(skw_sdio->sdio_func[FUNC_1]);
+#ifdef CONFIG_BT_SEEKWAVE
+	skw_sdio_bind_btseekwave_driver(skw_sdio->sdio_func[FUNC_1]);
+#endif
+	skw_sdio->service_state_map = 0;
+	skw_sdio->service_index_map = 0;
+	skw_sdio->host_active = 1;
+	skw_sdio->power_off = 0;
+	complete(&skw_sdio->scan_done);
+	return 0;
+}
+
+static void skw_sdio_remove(struct sdio_func *func)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	skw_sdio_info("Enter\n");
+
+	complete(&skw_sdio->remove_done);
+
+	if (skw_sdio->irq_type == SKW_SDIO_INBAND_IRQ) {
+		sdio_claim_host(skw_sdio->sdio_func[FUNC_1]);
+		sdio_release_irq(skw_sdio->sdio_func[FUNC_1]);
+		sdio_release_host(skw_sdio->sdio_func[FUNC_1]);
+	}
+	if (skw_sdio->irq_num >= 0)
+		free_irq(skw_sdio->irq_num, NULL);
+
+	skw_sdio->host_active = 0;
+	skw_sdio_unbind_platform_driver(skw_sdio->sdio_func[FUNC_1]);
+	skw_sdio_unbind_WIFI_driver(skw_sdio->sdio_func[FUNC_1]);
+	skw_sdio_unbind_BT_driver(skw_sdio->sdio_func[FUNC_1]);
+	kfree(skw_sdio->sdio_func[FUNC_0]);
+}
+
+void skw_sdio_stop_thread(void)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	if (skw_sdio->rx_thread) {
+		skw_sdio->threads_exit = 1;
+		skw_sdio_rx_up(skw_sdio);
+		kthread_stop(skw_sdio->rx_thread);
+		skw_sdio->rx_thread = NULL;
+		skw_sdio_wakeup_source_destroy(skw_sdio);
+	}
+	skw_sdio_info("done\n");
+}
+
+static const struct dev_pm_ops skw_sdio_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(skw_sdio_suspend, skw_sdio_resume)
+};
+
+static const struct sdio_device_id skw_sdio_ids[] = {
+	//{ .compatible = "seekwave-sdio", },
+	//{SDIO_DEVICE(0, 0)},
+	{SDIO_DEVICE(0x3607, 0x6160)},
+	{SDIO_DEVICE(0x1FFE, 0x6621)},
+	{},
+};
+
+static struct sdio_driver skw_sdio_driver = {
+	.probe = skw_sdio_probe,
+	.remove = skw_sdio_remove,
+	.name = "skw_sdio",
+	.id_table = skw_sdio_ids,
+	.drv = {
+		.pm = &skw_sdio_pm_ops,
+	},
+};
+
+void skw_sdio_remove_card(void)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	sdio_unregister_driver(&skw_sdio_driver);
+	skw_sdio_info(" sdio_unregister_driver: %s\n", skw_sdio_driver.name);
+	wait_for_completion_timeout(&skw_sdio->remove_done, msecs_to_jiffies(100));
+	skw_sdio_info("remove card end\n");
+
+}
+
+int skw_sdio_scan_card(void)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	int ret = 0;
+
+	skw_sdio_info("sdio_scan_card\n");
+	init_completion(&skw_sdio->scan_done);
+	init_completion(&skw_sdio->remove_done);
+	init_completion(&skw_sdio->download_done);
+	init_completion(&skw_sdio->device_wakeup);
+	init_waitqueue_head(&skw_sdio->wq);
+	//skw_sdio->irq_type = SKW_SDIO_EXTERNAL_IRQ;
+	skw_sdio->irq_type = SKW_SDIO_INBAND_IRQ;
+	ret = sdio_register_driver(&skw_sdio_driver);
+	if (ret != 0) {
+		skw_sdio_driver.name = "skw_sdio_lite";
+		skw_sdio->multi_sdio_drivers = 1;
+		ret = sdio_register_driver(&skw_sdio_driver);
+		skw_sdio_info("sdio_register_driver rename :%s\n", skw_sdio_driver.name);
+		if (ret)
+			skw_sdio_err("sdio_register_driver error :%d\n", ret);
+	}
+	if (wait_for_completion_timeout(&skw_sdio->scan_done, msecs_to_jiffies(1000)) == 0) {
+		skw_sdio_err("wait scan card time out\n");
+		return -ENODEV;
+	}
+	return ret;
+}
+
+/****************************************************************
+ *Description:sleep feature support en api
+ *Author:junwei.jiang
+ *Date:2023-06-14
+ * ************************************************************/
+static int skw_sdio_slp_feature_en(unsigned int address, unsigned int slp_en)
+{
+	int ret = 0;
+	ret = skw_sdio_writeb(address,slp_en);
+	if(ret !=0){
+		skw_sdio_err("no-sleep support en write fail, ret=%d\n",ret);
+		return -1;
+	}
+	skw_sdio_info("no-sleep_support_enable:%d\n ",slp_en);
+	return 0;
+}
+
+/****************************************************************
+ *Description:set the dma type SDMA, AMDA
+ *Author:junwei.jiang
+ *Date:2021-11-23
+ * ************************************************************/
+static int skw_sdio_set_dma_type(unsigned int address, unsigned int dma_type)
+{
+	int ret = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	if(dma_type == SDMA){
+		/*support the sdma so adma_rx_enable set 0*/
+		skw_sdio->adma_rx_enable = 0;
+	}
+	if(!bind_device){
+		ret = skw_sdio_writeb(address, ADMA);
+		if(ret !=0){
+			skw_sdio_err("dma type write fail, ret=%d\n",ret);
+			return -1;
+		}
+	}
+	skw_sdio_info("dma_type=%d,adma_rx_enable:%d\n ",dma_type,skw_sdio->adma_rx_enable);
+	return 0;
+}
+
+/****************************************************************
+*Description:
+*Func:used the ap boot cp interface;
+*Output:the dloader the bin to cp
+*Return:0:pass; other : fail
+*Author:JUNWEI.JIANG
+*Date:2021-09-07
+****************************************************************/
+static int skw_sdio_boot_cp(int boot_mode)
+{
+	int ret =0;
+	//struct sdio_func *func;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	skw_sdio_set_dma_type(skw_sdio->boot_data->dma_type_addr,
+			skw_sdio->boot_data->dma_type);
+	skw_sdio_slp_feature_en(skw_sdio->boot_data->slp_disable_addr,
+			skw_sdio->boot_data->slp_disable);
+
+	//2:download the boot bin 1CPALL 2, wifi 3,bt
+	skw_sdio_info("DOWNLOAD BIN TO CP\n");
+	skw_sdio_info("line:%d dram_dl_size = %d iram_dl_size = %d iram_dl_addr = 0x%x, dram_dl_addr = 0x%x\n", __LINE__,
+		skw_sdio->boot_data->dram_dl_size, skw_sdio->boot_data->iram_dl_size,
+		skw_sdio->boot_data->iram_dl_addr, skw_sdio->boot_data->dram_dl_addr);
+	if(skw_sdio->boot_data->dram_dl_size)
+		ret = skw_sdio_dt_write(skw_sdio->boot_data->dram_dl_addr,
+				skw_sdio->boot_data->dram_img_data,skw_sdio->boot_data->dram_dl_size);
+	if(skw_sdio->boot_data->iram_dl_size)
+		ret = skw_sdio_dt_write(skw_sdio->boot_data->iram_dl_addr,
+				skw_sdio->boot_data->iram_img_data,skw_sdio->boot_data->iram_dl_size);
+	if(ret !=0)
+		goto FAIL;
+	if (skw_sdio->cp_state && skw_sdio->sdio_exti_gpio_state) {
+		ret = skw_sdio_writeb(SKW_SDIO_AP2CP_EXTI_SETVAL,
+				      (skw_sdio->sdio_exti_gpio_state) & 0xff);
+		skw_sdio_info("the -exti gpio state is %d\n",
+			      (skw_sdio->sdio_exti_gpio_state) & 0xff);
+	}
+	//first boot need the setup cp first_dl_flag=0 is first
+	skw_sdio_info("line:%d write the download done flag\n", __LINE__);
+	skw_sdio->cp_downloaded_flag = 1;
+	ret |= skw_sdio_writeb(skw_sdio->boot_data->save_setup_addr, BIT(0));
+	if (!skw_sdio->cp_state)
+		ret |= skw_check_cp_ready();
+
+	if (ret != 0)
+		goto FAIL;
+
+#if !defined(SKW_BOOT_MEMPOWERON)
+	if(!ret&&boot_mode==SKW_FIRST_BOOT) {
+		if(skw_sdio->chip_en < 0){
+			skw_sdio_err("chip_en:%d Invalid Pls check HW !!\n", skw_sdio->chip_en);
+			ret = -1;
+			goto FAIL;
+		}
+		ret = skw_sdio_host_check(skw_sdio);
+		ret |= skw_sdio_chk_cp_gpio_cfg();//check the cp gpio cfg
+		if (ret < 0)
+			return ret;
+
+		skw_sdio_bind_WIFI_driver(skw_sdio->sdio_func[FUNC_1]);
+#ifndef CONFIG_BT_SEEKWAVE
+		skw_sdio_bind_BT_driver(skw_sdio->sdio_func[FUNC_1]);
+#endif
+	}
+#endif
+	skw_sdio_info("boot cp pass!!\n");
+	return ret;
+FAIL:
+	skw_sdio_err("fail ret=%d\n", ret);
+	//modem_notify_event(DEVICE_BLOCKED_EVENT);
+	return ret;
+}
+int skw_sdio_service_enable(void)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	uint32_t value = 0;
+	int ret = 0;
+	skw_sdio->log_data->service_en = SKW_SERVICE_EN; //service enable [8:9]
+	//get the service enable reg value
+	ret = skw_sdio_dt_read(skw_sdio->log_data->service_en,
+			       (void *)&skw_sdio->log_data->service_eb_val,
+			       SKW_REG_RW_LENGTH);
+	if (ret < 0) {
+		skw_sdio_err("read poweron cp reg  fail ret=%d\n", ret);
+		return ret;
+	}
+	//set the service enable bit [8:9] = 2'b11
+	value = (unsigned int)skw_sdio->log_data->service_eb_val;
+	value |= 0x300;
+	ret = skw_sdio_dt_write(skw_sdio->log_data->service_en, (void *)&value,
+				SKW_REG_RW_LENGTH);
+	if (ret < 0) {
+		skw_sdio_err("write poweron cp reg  fail ret=%d\n", ret);
+		return ret;
+	}
+	skw_sdio_info(" reg write success\n");
+	return ret;
+}
+
+/****************************************************************/
+// add the sdio memdump poweron cp mem
+//6160:
+//wifi power on
+//40104000[3:2]=2'b0
+//6160lite:
+//40108000[3:2]=2'b0
+//6316:
+//40108000[3:2]=2'b0
+/**********************************************************/
+int skw_sdio_smem_poweron(void)
+{
+	int ret = 0;
+	uint32_t value = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+#if 1
+	if(skw_sdio->log_data->smem_poweron) {
+		return ret;
+	} else {
+		skw_sdio_info("memdump poweron OPS !!\n");
+		skw_sdio_service_enable();
+		msleep(10);
+		skw_sdio->log_data->smem_poweron = 1;
+	}
+#endif
+	//get chip id
+	skw_sdio_info(" the chip id:%s\n", (char *)skw_sdio->chip_id);
+	if (!strncmp((char *)skw_sdio->chip_id,"SV6160LITE",10)) {
+		skw_sdio->log_data->smem_poweron = SKW_SMEM_POWERON2;
+	} else if (!strncmp((char *)skw_sdio->chip_id,"SV6316",6)) {
+		skw_sdio->log_data->smem_poweron = SKW_SMEM_POWERON2;
+	} else if(!strncmp((char *)skw_sdio->chip_id,"SV6160",6)) {
+		skw_sdio->log_data->smem_poweron = SKW_SMEM_POWERON1;//SKW_SMEM_POWERON1
+	} else {
+		skw_sdio_err("chip_id ls unkown not support memdump!\n");
+		return -1;
+	}
+	//get the memdump addr value
+	ret = skw_sdio_dt_read(skw_sdio->log_data->smem_poweron,
+		(void *)&skw_sdio->log_data->reg_val, SKW_REG_RW_LENGTH);
+	if(ret <0) {
+		skw_sdio_err("read poweron cp reg  fail ret=%d\n", ret);
+		return ret;
+	}
+	//if vaule [3:2] =2'b0 means power on done,else set the value [3:2]=2'b0
+	if (!((unsigned int)skw_sdio->log_data->reg_val & (0x3<<2))) {
+		skw_sdio_info("cp has poweron done reg  addr:%x value:%x\n",
+		skw_sdio->log_data->smem_poweron,(unsigned int)skw_sdio->log_data->reg_val);
+		return 0;
+	}
+	//set the memdump addr value 40108000[3:2]=2'b0;
+	value = (unsigned int)skw_sdio->log_data->reg_val;
+	value &= ~(0x3<<2);
+	skw_sdio_info("poweron cp reg  addr:%x value:%x\n",
+		skw_sdio->log_data->smem_poweron,value);
+	ret = skw_sdio_dt_write(skw_sdio->log_data->smem_poweron,
+		(void *)&value, SKW_REG_RW_LENGTH);
+	if (ret <0) {
+		skw_sdio_err("write	poweron cp reg  fail ret=%d\n", ret);
+		return ret;
+	}
+	return 0;
+}
+
+/************************************************************************
+ *Decription:release CP close the CP log
+ *Author:junwei.jiang
+ *Date:2023-02-16
+ *Modfiy:
+ *
+ ********************************************************************* */
+int skw_sdio_cp_log_disable(int disable)
+{
+	int ret = 0;
+	skw_sdio_info("Enter\n");
+	ret = skw_sdio_writeb(SDIOHAL_CPLOG_TO_AP_SWITCH, disable);
+	if (ret < 0) {
+		skw_sdio_err("cls the log signal fail ret=%d\n", ret);
+		return ret;
+	}
+	ret = skw_sdio_writeb(SKW_AP2CP_IRQ_REG, BIT(5));
+	if (ret < 0) {
+		skw_sdio_err("cls log irq fail ret=%d\n", ret);
+		return ret;
+	}
+	if (!disable)
+		skw_sdio_info(" enable the CP log \n");
+	else
+		skw_sdio_info(" disable the CP log !!\n");
+
+	return 0;
+}
+
+
+/************************************************************************
+ *Decription:send WIFI start command to modem.
+ *Author:junwei.jiang
+ *Date:2022-10-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int skw_WIFI_service_start(void)
+{
+	int ret;
+
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	skw_sdio_info("Enter STARTWIFI cp_state:%d\n",skw_sdio->cp_state);
+	if (skw_sdio->service_state_map & (1<<WIFI_SERVICE))
+		return 0;
+
+	mutex_lock(&skw_sdio->except_mutex);
+	if (skw_sdio->service_state_map==0 && skw_sdio->power_off){
+		skw_reinit_completion(skw_sdio->download_done);
+		skw_recovery_mode();
+    }
+	skw_reinit_completion(skw_sdio->download_done);
+	ret = send_modem_service_command(WIFI_SERVICE, SERVICE_START);
+	if (ret==0)
+		ret = skw_check_cp_ready();
+	mutex_unlock(&skw_sdio->except_mutex);
+	return ret;
+}
+/************************************************************************
+ *Decription: send WIFI stop command to modem.
+  *Author:junwei.jiang
+ *Date:2022-10-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int skw_WIFI_service_stop(void)
+{
+	int ret = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+#if CONFIG_NO_SERVICE_PD
+	struct sdio_func *sdio_func = skw_sdio->sdio_func[FUNC_1];
+#endif
+	skw_sdio_info("Enter,STOPWIFI  cp_state:%d",skw_sdio->cp_state);
+	mutex_lock(&skw_sdio->except_mutex);
+	if (skw_sdio->service_state_map & (1<<WIFI_SERVICE))
+		ret = send_modem_service_command(WIFI_SERVICE, SERVICE_STOP);
+	if (!skw_sdio->cp_state && ret==0 && skw_sdio->service_state_map==0) {
+#if CONFIG_NO_SERVICE_PD
+		skw_sdio->service_index_map = 0;
+		skw_sdio->power_off = 1;
+		skw_sdio->cp_downloaded_flag = 0;
+
+		sdio_claim_host(sdio_func);
+		skw_chip_set_power(0);
+		sdio_release_host(sdio_func);
+		skw_sdio_info("chip power off %d\n", ret);
+
+#endif
+	}
+	mutex_unlock(&skw_sdio->except_mutex);
+	return ret;
+}
+/************************************************************************
+ *Decription:send BT start command to modem.
+ *Author:junwei.jiang
+ *Date:2022-10-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int skw_BT_service_start(void)
+{
+	int ret;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	skw_sdio_info("Enter cpstate=%d\n",skw_sdio->cp_state);
+	if(assert_context_size)
+		skw_sdio_info("%s\n", assert_context);
+	if (skw_sdio->service_state_map & (1<<BT_SERVICE))
+		return 0;
+
+	mutex_lock(&skw_sdio->except_mutex);
+	if (skw_sdio->service_state_map==0 && skw_sdio->power_off){
+		skw_reinit_completion(skw_sdio->download_done);
+		skw_recovery_mode();
+    }
+	skw_reinit_completion(skw_sdio->download_done);
+	ret =send_modem_service_command(BT_SERVICE, SERVICE_START);
+	if (!ret)
+		ret = skw_check_cp_ready();
+	mutex_unlock(&skw_sdio->except_mutex);
+	return ret;
+}
+
+/************************************************************************
+ *Decription:send BT stop command to modem.
+ *Author:junwei.jiang
+ *Date:2022-10-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int skw_BT_service_stop(void)
+{
+	int ret = 0;
+
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+#if CONFIG_NO_SERVICE_PD
+	struct sdio_func *sdio_func = skw_sdio->sdio_func[FUNC_1];
+#endif
+	skw_sdio_info("Enter cpstate=%d\n",skw_sdio->cp_state);
+
+	mutex_lock(&skw_sdio->except_mutex);
+	if (skw_sdio->service_state_map & (1<<BT_SERVICE) && !skw_sdio->cp_state) {
+		skw_reinit_completion(skw_sdio->download_done);
+		ret = send_modem_service_command(BT_SERVICE, SERVICE_STOP);
+	}
+	if (!skw_sdio->cp_state && ret==0 && skw_sdio->service_state_map==0) {
+#if CONFIG_NO_SERVICE_PD
+		skw_sdio->service_index_map = 0;
+		skw_sdio->power_off = 1;
+		skw_sdio->cp_downloaded_flag = 0;
+		sdio_claim_host(sdio_func);
+		skw_chip_set_power(0);
+		sdio_release_host(sdio_func);
+
+		skw_sdio_info("chip power off %d\n", ret);
+
+#endif
+	}
+	mutex_unlock(&skw_sdio->except_mutex);
+	return ret;
+}
+
+/****************************************************************
+*Description:
+*Func:used the ap boot cp interface;
+*Output:the dloader the bin to cp
+*Return:0:pass; other : fail
+*Author:JUNWEI.JIANG
+*Date:2021-09-07
+****************************************************************/
+static int skw_sdio_cp_service_ops(int service_ops)
+{
+	int ret =0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	switch(service_ops)
+	{
+		case SKW_WIFI_START:
+			ret = wait_event_interruptible_timeout(skw_sdio->wq,
+				!skw_sdio->cp_state, msecs_to_jiffies(2000));
+			if (ret <= 0) {
+				skw_sdio_err("cp_state:%d cp not ready!!\n",
+					     skw_sdio->cp_state);
+			}
+			ret = skw_WIFI_service_start();
+			skw_sdio_info("-----WIFI SERIVCE START\n");
+		break;
+		case SKW_WIFI_STOP:
+			ret =skw_WIFI_service_stop();
+			skw_sdio_dbg("----WIFI SERVICE---STOP\n");
+		break;
+		case SKW_BT_START:
+		{
+			ret = wait_event_interruptible_timeout(skw_sdio->wq,
+				!skw_sdio->cp_state, msecs_to_jiffies(2000));
+			if (ret <= 0) {
+				skw_sdio_err("cp_state:%d cp not ready!!\n",
+							skw_sdio->cp_state);
+			}
+			ret=skw_BT_service_start();
+			skw_sdio_dbg("-----BT SERIVCE --START\n");
+		}
+		break;
+		case SKW_BT_STOP:
+			ret =skw_BT_service_stop();
+			skw_sdio_dbg("-----BT SERVICE --STOP\n");
+		break;
+		default:
+		break;
+	}
+	return ret;
+}
+int skw_sdio_chk_cp_gpio_cfg(void)
+{
+	int ret = 0;
+	unsigned char exti_val = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	if (skw_sdio->gpio_in < 0 || skw_sdio->gpio_out < 0) {
+		skw_sdio_info("gpio_in and gpio_out no config\n");
+		return ret;
+	}
+	if (!cp_detect_sleep_mode) {
+		skw_sdio_err(
+			"GPIOOUT:%d cannot be operated, pls check gpio num or hw connect!!.\n",
+			skw_sdio->gpio_out);
+		return -1;
+	}
+	if (skw_sdio->gpio_in == skw_sdio->gpio_out) {
+		skw_sdio_err(
+			"gpio_in :%d and gpio_out:%d can't be the same!!\n",
+			skw_sdio->gpio_in, skw_sdio->gpio_out);
+		return -1;
+	}
+	ret = skw_sdio_readb(SKW_SDIO_CP2AP_EXTI_GETVAL, &exti_val);
+	if (ret) {
+		skw_sdio_err("read exti val fail exti:%d, ret=%d \n", exti_val,
+			     ret);
+		return -1;
+	}
+	if (cp_detect_sleep_mode == 1 || cp_detect_sleep_mode == 2) {
+		if (exti_val != cp_detect_sleep_mode) {
+			skw_sdio_err("exti_val is %d error \n", exti_val);
+			return -1;
+		}
+		skw_sdio->sdio_exti_gpio_state = exti_val;
+	} else if (cp_detect_sleep_mode == 3) {
+		if (exti_val != 1 && exti_val != 2) {
+			skw_sdio_err(
+				"exti val:%d error, pls check your gpio num config or hw connect !!\n",
+				exti_val);
+			return -1;
+		}
+		skw_sdio->sdio_exti_gpio_state = exti_val;
+	}
+	skw_sdio_info("chk cp gpio cfg is %d\n", exti_val);
+	return ret;
+}
+
+/****************************************************************
+*Description:skw_boot_loader
+*Func:used the ap boot cp interface;
+*Output:the dloader the bin to cp
+*Return:0:pass; other : fail
+*Author:JUNWEI.JIANG
+*Date:2021-09-07
+****************************************************************/
+int skw_boot_loader(struct seekwave_device *boot_data)
+{
+	int ret =0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+#if defined(SKW_BOOT_MEMPOWERON)
+	struct sdio_func *func;
+	skw_sdio->boot_data= boot_data;
+	skw_sdio->gpio_in = skw_sdio->boot_data->gpio_in;
+	skw_sdio->gpio_out = skw_sdio->boot_data->gpio_out;
+	skw_sdio_info("debug ------ line :%d \n", __LINE__);
+	if(!skw_sdio->boot_data->first_boot_flag ){
+		skw_sdio_info("debug ------ line :%d \n", __LINE__);
+		if(skw_sdio->boot_data->iram_img_data) {
+			if(skw_sdio->boot_data->gpio_in >= 0) {
+				skw_sdio_set_dma_type(skw_sdio->boot_data->dma_type_addr,
+						skw_sdio->boot_data->dma_type);
+				skw_sdio_slp_feature_en(skw_sdio->boot_data->slp_disable_addr,
+						skw_sdio->boot_data->slp_disable);
+				func = skw_sdio->sdio_func[FUNC_1];
+				sdio_claim_host(func);
+				try_to_wakeup_modem(max_ch_num);
+				ret = sdio_release_irq(func);
+				sdio_release_host(func);
+				skw_sdio->irq_type = SKW_SDIO_EXTERNAL_IRQ;
+				ret = skw_sdio_host_irq_init(skw_sdio->gpio_in);
+				if (ret < 0) {
+					skw_sdio_err("gpio irq init  fail\n");
+					goto FAIL;
+				}
+			} else {
+				ret = skw_sdio_cpdebug_boot();
+				if (ret < 0) {
+					skw_sdio_err("cpdebug_boot fail\n");
+					goto FAIL;
+				}
+		}
+		if(skw_sdio->boot_data->chip_en < 0){
+			skw_sdio_err("chip_en:%d Invalid Pls check HW !!\n", skw_sdio->boot_data->chip_en);
+			ret = -1;
+			return ret;
+		}
+
+		ret = skw_sdio_host_check(skw_sdio);
+		if (ret < 0)
+			return ret;
+		skw_sdio_bind_WIFI_driver(skw_sdio->sdio_func[FUNC_1]);
+#ifndef CONFIG_BT_SEEKWAVE
+		skw_sdio_bind_BT_driver(skw_sdio->sdio_func[FUNC_1]);
+#endif
+		if(!skw_sdio->service_index_map&&skw_sdio->boot_data->iram_img_data){
+			skw_chip_set_power(0);
+			skw_sdio->power_off = 1;
+			skw_sdio_info(" No service Power Down CP--!!!\n");
+		}else{
+			skw_sdio_err("sevice_index_map:%d or iram_img_data is NULL Pls CONFIG!!\n", skw_sdio->service_index_map);
+			ret = -1;
+			return ret;
+		}
+	}else{
+		if(!skw_sdio->service_index_map&&skw_sdio->boot_data->iram_img_data){
+#ifdef CONFIG_SV6160_LITE_FPGA
+			skw_sdio_info(" FPGA DEUBG NO Down CP--!!!\n");
+			skw_sdio_boot_cp(RECOVERY_BOOT);
+#else
+			skw_sdio_info("first boot recovery dl firmware -!!!\n");
+			skw_recovery_mode();
+#endif
+		}else{
+			if(skw_sdio->boot_data->dl_base_img){
+				skw_sdio_info("the dloader CP IMG dl_done_signal =%d the dl_base_addr =0x%x,offset=0x%x dl_size=0x%x---!\n",
+				skw_sdio->boot_data->dl_done_signal,skw_sdio->boot_data->dl_base_addr,skw_sdio->boot_data->dl_offset_addr,
+				skw_sdio->boot_data->dl_size);
+#if 0
+				print_hex_dump(KERN_ERR, "dump_data:", 0, 16, 1,
+				skw_sdio->boot_data->dl_base_img+skw_sdio->boot_data->dl_offset_addr, 32, 1);
+#endif
+				if(skw_sdio->boot_data->dl_size){
+					ret = skw_sdio_dt_write(skw_sdio->boot_data->dl_base_addr,
+							skw_sdio->boot_data->dl_base_img+skw_sdio->boot_data->dl_offset_addr,
+							skw_sdio->boot_data->dl_size);
+					if(ret)
+						goto FAIL;
+				}
+				ret = skw_sdio_writeb(skw_sdio->boot_data->save_setup_addr,skw_sdio->boot_data->dl_done_signal);
+				if(ret)
+					goto FAIL;
+			}
+		}
+		ret=skw_sdio_cp_service_ops(skw_sdio->boot_data->service_ops);
+	}
+#else
+	skw_sdio->boot_data= boot_data;
+	if (skw_sdio->power_off)
+		boot_data->dl_module = RECOVERY_BOOT;
+	/*--------CP RESET RESCAN------------*/
+	if (boot_data->dl_module== RECOVERY_BOOT) {
+		skw_sdio_info("rescan and reset CHIP\n");
+		skw_recovery_mode();
+	} else {
+	/*-------FIRST AP BOOT--------------*/
+		if (!skw_sdio->boot_data->first_dl_flag) {
+			if (!strncmp((char *)skw_sdio->chip_id,"SV6160",10)){
+				boot_data->chip_id = 0x6160;
+				skw_sdio_info("boot chip id 0x%x\n", boot_data->chip_id);
+			}
+			skw_sdio->chip_en = boot_data->chip_en;
+			if (skw_sdio->boot_data->iram_dl_size&&
+					skw_sdio->boot_data->dram_dl_size){
+				ret=skw_sdio_boot_cp(SKW_FIRST_BOOT);
+			} else
+				ret=skw_sdio_cpdebug_boot();
+
+			if(ret !=0)
+				goto FAIL;
+		}
+	}
+	ret=skw_sdio_cp_service_ops(skw_sdio->boot_data->service_ops);
+#endif
+	if(ret !=0)
+		goto FAIL;
+
+	skw_sdio_info("boot loader ops end!!!\n");
+	return 0;
+FAIL:
+	skw_sdio_err("line:%d  fail ret=%d\n", __LINE__, ret);
+	return ret;
+}
+
+void get_bt_antenna_mode(char *mode)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct seekwave_device *boot_data = skw_sdio->boot_data;
+	u32 bt_antenna = boot_data->bt_antenna;
+
+	if(bt_antenna==0)
+		return;
+	bt_antenna--;
+	if(!mode)
+		return;
+	if (bt_antenna)
+		sprintf(mode,"bt_antenna : alone\n");
+	else
+		sprintf(mode,"bt_antenna : share\n");
+}
+
+void reboot_to_change_bt_antenna_mode(char *mode)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct seekwave_device *boot_data = skw_sdio->boot_data;
+	u32 *data = (u32 *) &boot_data->iram_img_data[boot_data->head_addr-4];
+	u32 bt_antenna;
+
+	if(boot_data->bt_antenna == 0)
+		return;
+
+	bt_antenna = boot_data->bt_antenna - 1;
+	bt_antenna = 1 - bt_antenna;
+	data[0] = (bt_antenna) | 0x80000000;
+	if(!mode)
+		return;
+	if (bt_antenna==1) {
+		boot_data->bt_antenna = 2;
+		sprintf(mode,"bt_antenna : alone\n");
+	} else {
+		boot_data->bt_antenna = 1;
+		sprintf(mode,"bt_antenna : share\n");
+	}
+	//skw_recovery_mode();
+	send_modem_assert_command();
+}
+
+void reboot_to_change_bt_uart1(char *mode)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct seekwave_device *boot_data = skw_sdio->boot_data;
+	u32 *data = (u32 *) &boot_data->iram_img_data[boot_data->head_addr-4];
+
+	if(data[0] & 0x80000000)
+		data[0] |=  0x0000008;
+	else
+		data[0] = 0x80000008;
+	//skw_recovery_mode();
+	send_modem_assert_command();
+}
+
+/****************************************************************
+*Description:check dev ready
+*Func:used the ap boot cp interface;
+*Calls:sdio or usb
+*Call By:host dev ready
+*Input:NULL
+*Output:pass :0 or fail ENODEV
+*Others:
+*Author:JUNWEI.JIANG
+*Date:2022-06-09
+****************************************************************/
+int skw_reset_bus_dev(void)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	if (skw_sdio->chip_en) {
+		skw_chip_power_reset();
+	} else {
+		skw_sdio_err("the chip_en is NULL\n");
+		skw_sdio_reg_reset_cp();
+	}
+	return 0;
+}
+static int skw_sdio_reg_reset_cp(void)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	int ret;
+	/*reset CP register set value:0x4010001C 0x20:CP sys hif reset*/
+	skw_sdio_info(" [+]\n");
+	//cia force chip reset
+	if(!strncmp((char *)skw_sdio->chip_id,"SV6160",12)){
+		skw_sdio_info("cia SV6160 force chip reset\n");
+		ret =skw_sdio_writeb(0x125,BIT(0));
+	}else{
+		skw_sdio_info("cia SV6160 Lite cia reset\n");
+		ret =skw_sdio_writeb(0x125,BIT(7));
+	}
+	if(ret < 0){
+		skw_sdio_err("cia the chip %s reset fail\n",(char *)skw_sdio->chip_id);
+		return ret;
+	}
+	skw_sdio_info("cia rest the chip %s\n",(char *)skw_sdio->chip_id);
+	return ret;
+}
+
+/****************************************************************
+*Description:skw_sdio_reset_card
+*Func:used the ap boot cp interface;
+*Calls:boot data
+*Call By:the ap host
+*Input:the boot data informations
+*Output:the dloader the bin to cp
+*Return:0:pass; other : fail
+*Others:
+*Author:JUNWEI.JIANG
+*Date:2021-10-11
+****************************************************************/
+
+static int skw_sdio_reset_card(void)
+{
+	int ret;
+	int skw_sd_id = SKW_MMC_HOST_SD_INDEX;
+	skw_sdio_info(" [+]\n");
+	ret = skw_sdio_mmc_rescan(skw_sd_id);
+	if (ret < 0) {
+		skw_sdio_err("the reset card fail try again\n");
+		ret = skw_sdio_mmc_rescan(skw_sd_id);
+	}
+	skw_sdio_info(" [-]\n");
+	return ret;
+}
+
+static int skw_sdio_cp_reset(void)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	int ret;
+	struct sdio_func *func = skw_sdio->sdio_func[FUNC_1];
+	skw_sdio_info(" [+]\n");
+	/* Disable Function 1 */
+	if (skw_sdio->irq_type == SKW_SDIO_INBAND_IRQ){
+		sdio_claim_host(skw_sdio->sdio_func[FUNC_1]);
+		ret=sdio_release_irq(skw_sdio->sdio_func[FUNC_1]);
+		sdio_release_host(skw_sdio->sdio_func[FUNC_1]);
+		if(ret < 0)
+			skw_sdio_err(" sdio_release_irq ret = %d\n", ret);
+	}
+
+	ret = skw_sdio_reset_card();
+	if (ret < 0) {
+		skw_sdio_err("the reset card fail \n");
+		return ret;
+	}
+	sdio_claim_host(func);
+	ret = sdio_enable_func(func);
+	sdio_set_block_size(func, SKW_SDIO_BLK_SIZE);
+	func->max_blksize = SKW_SDIO_BLK_SIZE;
+	/* Enable Function 1 */
+	if (skw_sdio->irq_type == SKW_SDIO_INBAND_IRQ){
+		ret=sdio_claim_irq(skw_sdio->sdio_func[FUNC_1], skw_sdio_inband_irq_handler);
+		if(ret < 0) {
+			skw_sdio_err(" sdio_claim_irq ret = %d\n", ret);
+		} else {
+			ret = skw_sdio_enable_async_irq();
+			if (ret < 0)
+				skw_sdio_err("enable sdio async irq fail ret = %d\n", ret);
+		}
+	} else {
+		skw_sdio_info("the CP is external irq\n");
+	}
+	sdio_release_host(skw_sdio->sdio_func[FUNC_1]);
+	if ( ret < 0) {
+		skw_sdio_err("sdio_claim_irq fail\n");
+		return ret;
+	}
+	skw_sdio_info("CP RESET OK!\n");
+	return 0;
+}
+/****************************************************************
+*Description:skw_sdio_cpdebug_boot
+*Func:used the ap boot cp interface;
+*Others:
+*Author:JUNWEI.JIANG
+*Date:2022-07-15
+****************************************************************/
+static int skw_sdio_cpdebug_boot(void)
+{
+	int ret =0;
+	struct sdio_func *func;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	skw_sdio_info("not download CP from AP!!!!\n");
+	skw_sdio_set_dma_type(skw_sdio->boot_data->dma_type_addr,
+			skw_sdio->boot_data->dma_type);
+	skw_sdio_slp_feature_en(skw_sdio->boot_data->slp_disable_addr,
+			skw_sdio->boot_data->slp_disable);
+	if(skw_sdio->gpio_in >=0) {
+		func = skw_sdio->sdio_func[FUNC_1];
+		sdio_claim_host(func);
+		try_to_wakeup_modem(max_ch_num);
+		ret = sdio_release_irq(func);
+		sdio_release_host(func);
+		skw_sdio->irq_type = SKW_SDIO_EXTERNAL_IRQ;
+		ret = skw_sdio_host_irq_init(skw_sdio->gpio_in);
+	}
+	skw_sdio_info(" CP DUEBGBOOT Done!!!\n");
+	return ret;
+}
+
+/****************************************************************
+*Description:skw_recovery_mode
+*Func:used the ap boot cp interface;
+*Calls:boot data
+*Call By:the ap host
+*Input:the boot data informations
+*Output:reset cp
+*Return:0:pass; other : fail
+*Others:
+*Author:JUNWEI.JIANG
+*Date:2022-07-15
+****************************************************************/
+int skw_recovery_mode(void)
+{
+	int ret=0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	skw_sdio_info("the CHIPID:%s \n", (char *)&skw_sdio->chip_id);
+	ret = skw_sdio_recovery_debug_status();
+	if(!skw_sdio->boot_data->dram_dl_size ||
+		!skw_sdio->boot_data->iram_dl_size || ret) {
+		skw_sdio_err("CP DEBUG BOOT,AND NO NEED RECOVERY!!! \n");
+		return -1;
+	}
+	//skw_reinit_completion(skw_sdio->download_done);
+	ret=skw_sdio_cp_reset();
+	if(ret!=0){
+		skw_sdio_err("CP RESET fail \n");
+		return -1;
+	}
+	skw_sdio->power_off = 0;
+	skw_sdio->service_index_map = 0;
+	skw_sdio->cp_fifo_status = 0;
+
+	ret = skw_sdio_boot_cp(RECOVERY_BOOT);
+	if(ret!=0){
+		skw_sdio_err("CP RESET fail \n");
+		return -1;
+	}
+	skw_sdio_info("Recovery ok\n");
+	return ret;
+}
+
+/****************************************************************
+*Description:poweron_bt_mem
+*Func:used the ap boot cp interface;
+*Calls:boot data
+*Call By:the ap host
+*Input:the boot data informations
+*Output:the dloader the bin to cp
+*Return:0:pass; other : fail
+*Others:
+*Author:JUNWEI.JIANG
+*Date:2021-09-07
+****************************************************************/
+static int poweron_bt_mem(struct seekwave_device *boot_data)
+{
+	int ret=0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	ktime_t cur, stop;
+
+#if !defined(SKW_BOOT_DEBUG)
+	//ops the power on wifi reg;
+	unsigned char bt_poweron_reg = 0;
+	unsigned char tmp_val=0;
+#endif
+	if (skw_sdio->service_state_map & (1<<BT_SERVICE)){
+		skw_sdio_info("No need power on BT mem!!!\n");
+		return 0;
+	}
+	skw_sdio_info("---Enter---\n");
+	//set the poweron wifi flag 0x164
+	skw_sdio_readb(SKW_SDIO_DL_CP2AP_BSP, &tmp_val);
+	ret = skw_sdio_writeb(SKW_SDIO_DL_POWERON_MODULE, SKW_BT);
+	ret = skw_sdio_writeb(SKW_AP2CP_IRQ_REG, BIT(6));
+	if(ret !=0){
+		skw_sdio_err("%s poweron fail \n", __func__);
+		return -1;
+	}
+	//the flag 164 tell the CP poweron the
+	stop = ktime_add_ns(ktime_get(), 2000);
+#if !defined(SKW_BOOT_DEBUG)
+	do{
+		//SDIO AON 180
+		ret = skw_sdio_readb(SKW_SDIO_DL_CP2AP_BSP, &bt_poweron_reg);
+		cur = ktime_get();
+		// get the cp poweron the wifi signal /184
+	}while(tmp_val==bt_poweron_reg && !ret && ktime_before(cur, stop));
+#endif
+	if(ret !=0 ){
+		skw_sdio_info("%s power on BT fail \n", __func__);
+		return -1;
+	}
+	//Poweron_Module = boot_data->dl_module;
+	skw_sdio->boot_data->dl_module = SKW_BT;
+	//ops the poweron the bt reg
+	skw_sdio_info("%s power on pass---time=%lld \n", __func__, ktime_sub(stop, cur));
+	return 0;
+}
+/****************************************************************
+*Description:poweron_wifi_mem
+*Func:used the ap boot cp interface;
+*Calls:boot data
+*Call By:the ap host
+*Input:the boot data informations
+*Output:the dloader the bin to cp
+*Return:0:pass; other : fail
+*Others: 
+*Author:JUNWEI.JIANG
+*Date:2021-09-07
+****************************************************************/
+static int poweron_wifi_mem(struct seekwave_device *boot_data)
+{
+	int ret=0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	ktime_t cur, stop;
+
+#if !defined(SKW_BOOT_DEBUG)
+	//ops the power on wifi reg;
+	unsigned char wifi_poweron_reg = 0;
+	unsigned char tmp_val=0;
+#endif
+	if (skw_sdio->service_state_map & (1<<WIFI_SERVICE)){
+		skw_sdio_info(" No need power on WIFI mem!!! \n");
+		return ret;
+	}
+
+	skw_sdio_info("---Enter---\n");
+	//set the poweron wifi flag 0x164
+	msleep(5);//waiting cp ready...
+	skw_sdio_readb(SKW_SDIO_DL_CP2AP_BSP, &tmp_val);
+	ret = skw_sdio_writeb(SKW_SDIO_DL_POWERON_MODULE, SKW_WIFI);
+	ret = skw_sdio_writeb(SKW_AP2CP_IRQ_REG, BIT(6));
+	if(ret !=0){
+		skw_sdio_err("%s poweron fail \n", __func__);
+		return -1;
+	}
+	//the flag 164 tell the CP poweron the
+	stop = ktime_add_ns(ktime_get(), 2000);
+#if !defined(SKW_BOOT_DEBUG)
+	do{
+		//SDIO AON 180
+		ret = skw_sdio_readb(SKW_SDIO_DL_CP2AP_BSP, &wifi_poweron_reg);
+		cur = ktime_get();
+		// get the cp poweron the wifi signal /184
+	}while(tmp_val==wifi_poweron_reg && ktime_before(cur, stop));
+#endif
+	//ret = ktime_before(cur, stop);
+	if(ret !=0 ){
+
+		skw_sdio_info(" power on WIFI fail \n");
+		return -1;
+	}
+	skw_sdio->boot_data->dl_module = SKW_WIFI;
+
+	//Poweron_Module = boot_data->dl_module;
+	skw_sdio_info("%s power on wifi pass \n", __func__);
+	return 0;
+}
+
+/****************************************************************
+*Description:skw_sdio_poweron_mem
+*Func:used the ap boot cp interface;
+*Others:
+*Author:JUNWEI.JIANG
+*Date:2022-07-15
+****************************************************************/
+int skw_sdio_poweron_mem(int index)
+{
+	int ret=0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	skw_sdio_info(" Enter\n");
+	//skw_sdio_poweron_mem(index);
+	switch(index)
+	{
+		case SKW_WIFI:
+			poweron_wifi_mem(skw_sdio->boot_data);
+		break;
+		case SKW_BT:
+			poweron_bt_mem(skw_sdio->boot_data);
+		break;
+		default:
+			skw_sdio_info("no need poweron service mem\n");
+		break;
+	}
+	return ret;
+
+}
+/****************************************************************
+*Description:skw_sdio_dloader
+*Func:used the ap boot cp interface;
+*Others:
+*Author:JUNWEI.JIANG
+*Date:2022-07-15
+****************************************************************/
+int skw_sdio_dloader(int service_index)
+{
+	int ret=0;
+#if defined(SKW_BOOT_MEMPOWERON)
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	skw_sdio_info(" Enter\n");
+	mutex_lock(&dloader_mutex);
+	ret=skw_sdio_poweron_mem(service_index);
+	if(ret){
+		skw_sdio_err("power the module=%d fail\n", service_index);
+		mutex_unlock(&dloader_mutex);
+		return ret;
+	}
+	switch(service_index)
+	{
+		case SKW_BOOT:
+			//skw_sdio->boot_data->skw_dloader_module(SKW_BOOT);
+            skw_recovery_mode();
+		break;
+		case SKW_WIFI:
+			if(skw_sdio->gpio_in  >= 0&&skw_sdio->gpio_out >= 0){
+				skw_sdio_slp_feature_en(skw_sdio->boot_data->slp_disable_addr,1);
+				send_cp_wakeup_signal(skw_sdio);
+				skw_sdio->boot_data->skw_dloader_module(SKW_WIFI);
+				skw_sdio_slp_feature_en(skw_sdio->boot_data->slp_disable_addr,0);
+				send_cp_wakeup_signal(skw_sdio);
+			}else{
+				skw_sdio_warn("the gpio_in=%d gpio_out=%d is not set\n",skw_sdio->gpio_in,skw_sdio->gpio_out);
+			}
+		break;
+		case SKW_BT:
+			if(skw_sdio->gpio_in  >= 0&&skw_sdio->gpio_out >= 0){
+				skw_sdio_slp_feature_en(skw_sdio->boot_data->slp_disable_addr,1);
+				send_cp_wakeup_signal(skw_sdio);
+				skw_sdio->boot_data->skw_dloader_module(SKW_BT);
+				skw_sdio_slp_feature_en(skw_sdio->boot_data->slp_disable_addr,0);
+				send_cp_wakeup_signal(skw_sdio);
+			}else{
+				skw_sdio_warn("the gpio_in=%d gpio_out=%d is not set\n",skw_sdio->gpio_in,skw_sdio->gpio_out);
+			}
+		break;
+		case SKW_ALL:
+			if(skw_sdio->gpio_in  >= 0&&skw_sdio->gpio_out >= 0){
+				skw_sdio_slp_feature_en(skw_sdio->boot_data->slp_disable_addr,1);
+				send_cp_wakeup_signal(skw_sdio);
+				skw_sdio->boot_data->skw_dloader_module(SKW_ALL);
+				skw_sdio_slp_feature_en(skw_sdio->boot_data->slp_disable_addr,0);
+				send_cp_wakeup_signal(skw_sdio);
+			}else{
+				skw_sdio_warn("the gpio_in=%d gpio_out=%d is not set\n",skw_sdio->gpio_in,skw_sdio->gpio_out);
+			}
+		break;
+		default:
+			skw_sdio_info("no need the dloader servce mem\n");
+		break;
+	}
+	mutex_unlock(&dloader_mutex);
+#else
+	skw_sdio_info("no need the dloader servce mem\n");
+#endif
+	return ret;
+
+}
+
+static int check_chipid(void)
+{
+	int ret;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	ret = skw_sdio_dt_read(SKW_CHIP_ID0, skw_sdio->chip_id, SKW_CHIP_ID_LENGTH);
+	if(!strncmp((char *)skw_sdio->chip_id,"SV6160LITE",10)){
+		skw_cp_ver = SKW_SDIO_V20;
+		max_ch_num = SDIO2_MAX_CH_NUM;
+		max_pac_size = MAX2_PAC_SIZE;
+		skw_sdio_blk_size = 512;
+		skw_sdio_info("Chip id:%s used SDIO20 ", (char *)skw_sdio->chip_id);
+	}else if(!strncmp((char *)skw_sdio->chip_id,"SV6160",6)){
+		skw_cp_ver = SKW_SDIO_V10;
+		max_ch_num = MAX_CH_NUM;
+		max_pac_size = MAX_PAC_SIZE;
+		skw_sdio_blk_size = 256;
+		skw_sdio_info("Chip id:%s used SDIO10",(char *)skw_sdio->chip_id);
+	}else if(!strncmp((char *)skw_sdio->chip_id,"SV6316",6)){
+		skw_cp_ver = SKW_SDIO_V20;
+		max_ch_num = SDIO2_MAX_CH_NUM;
+		max_pac_size = MAX2_PAC_SIZE;
+		skw_sdio_blk_size = 512;
+		skw_sdio_info("Chip id:%s used SDIO20 ", (char *)skw_sdio->chip_id);
+	}else{
+		skw_cp_ver = SKW_SDIO_V20;
+		max_ch_num = SDIO2_MAX_CH_NUM;
+		max_pac_size = MAX2_PAC_SIZE;
+		skw_sdio_blk_size = 512;
+		skw_sdio_info("Chip id:%s used SDIO20 ", (char *)skw_sdio->chip_id);
+	}
+	if(ret<0){
+		skw_sdio_err("Get the chip id fail!!\n");
+		return ret;
+	}
+	return 0;
+}
+#if 0
+static int skw_get_chipid(char *chip_id)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	chip_id = (char *)&skw_sdio->chip_id;
+	skw_sdio_info("---the chip id---%s\n", (char *)skw_sdio->chip_id);
+	return 0;
+}
+#endif
+
+
+void skw_get_sdio_config(char *buffer, int size)
+{
+	int ret = 0;
+
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_func *func1 = skw_sdio->sdio_func[FUNC_1];
+	struct mmc_host *host = func1->card->host;
+	struct mmc_ios	*ios = &host->ios;
+	const char *str;
+
+	static const char *vdd_str[] = {
+		[8]	= "2.0",
+		[9]	= "2.1",
+		[10]	= "2.2",
+		[11]	= "2.3",
+		[12]	= "2.4",
+		[13]	= "2.5",
+		[14]	= "2.6",
+		[15]	= "2.7",
+		[16]	= "2.8",
+		[17]	= "2.9",
+		[18]	= "3.0",
+		[19]	= "3.1",
+		[20]	= "3.2",
+		[21]	= "3.3",
+		[22]	= "3.4",
+		[23]	= "3.5",
+		[24]	= "3.6",
+	};
+
+	if(!buffer) {
+		skw_sdio_info("buffer is null!\n");
+		return;
+	}
+	ret += sprintf(&buffer[ret], "sdio infomation:\n");
+	ret += sprintf(&buffer[ret], "clock:\t\t%u Hz\n", ios->clock);
+	if (host->actual_clock)
+		ret += sprintf(&buffer[ret], "actual clock:\t%u Hz\n", host->actual_clock);
+	ret += sprintf(&buffer[ret], "vdd:\t\t%u ", ios->vdd);
+	if ((1 << ios->vdd) & MMC_VDD_165_195)
+		ret += sprintf(&buffer[ret], "(1.65 - 1.95 V)\n");
+	else if (ios->vdd < (ARRAY_SIZE(vdd_str) - 1)
+			&& vdd_str[ios->vdd] && vdd_str[ios->vdd + 1])
+		ret += sprintf(&buffer[ret], "(%s ~ %s V)\n", vdd_str[ios->vdd],
+				vdd_str[ios->vdd + 1]);
+	else
+		ret += sprintf(&buffer[ret], "(invalid)\n");
+
+	switch (ios->bus_mode) {
+	case MMC_BUSMODE_OPENDRAIN:
+		str = "open drain";
+		break;
+	case MMC_BUSMODE_PUSHPULL:
+		str = "push-pull";
+		break;
+	default:
+		str = "invalid";
+		break;
+	}
+	ret += sprintf(&buffer[ret], "bus mode:\t%u (%s)\n", ios->bus_mode, str);
+
+	switch (ios->chip_select) {
+	case MMC_CS_DONTCARE:
+		str = "don't care";
+		break;
+	case MMC_CS_HIGH:
+		str = "active high";
+		break;
+	case MMC_CS_LOW:
+		str = "active low";
+		break;
+	default:
+		str = "invalid";
+		break;
+	}
+	ret += sprintf(&buffer[ret], "chip select:\t%u (%s)\n", ios->chip_select, str);
+
+	switch (ios->power_mode) {
+	case MMC_POWER_OFF:
+		str = "off";
+		break;
+	case MMC_POWER_UP:
+		str = "up";
+		break;
+	case MMC_POWER_ON:
+		str = "on";
+		break;
+	default:
+		str = "invalid";
+		break;
+	}
+	ret += sprintf(&buffer[ret], "power mode:\t%u (%s)\n", ios->power_mode, str);
+	ret += sprintf(&buffer[ret], "bus width:\t%u (%u bits)\n",
+			ios->bus_width, 1 << ios->bus_width);
+
+	switch (ios->timing) {
+	case MMC_TIMING_LEGACY:
+		str = "legacy";
+		break;
+	case MMC_TIMING_MMC_HS:
+		str = "mmc high-speed";
+		break;
+	case MMC_TIMING_SD_HS:
+		str = "sd high-speed";
+		break;
+	case MMC_TIMING_UHS_SDR12:
+		str = "sd uhs SDR12";
+		break;
+	case MMC_TIMING_UHS_SDR25:
+		str = "sd uhs SDR25";
+		break;
+	case MMC_TIMING_UHS_SDR50:
+		str = "sd uhs SDR50";
+		break;
+	case MMC_TIMING_UHS_SDR104:
+		str = "sd uhs SDR104";
+		break;
+	case MMC_TIMING_UHS_DDR50:
+		str = "sd uhs DDR50";
+		break;
+	case MMC_TIMING_MMC_DDR52:
+		str = "mmc DDR52";
+		break;
+	case MMC_TIMING_MMC_HS200:
+		str = "mmc HS200";
+		break;
+	case MMC_TIMING_MMC_HS400:
+		str = host->card->host->ios.enhanced_strobe ?
+			"mmc HS400 enhanced strobe" : "mmc HS400";
+		break;
+	default:
+		str = "invalid";
+		break;
+	}
+	ret += sprintf(&buffer[ret], "timing spec:\t%u (%s)\n", ios->timing, str);
+
+	switch (ios->signal_voltage) {
+	case MMC_SIGNAL_VOLTAGE_330:
+		str = "3.30 V";
+		break;
+	case MMC_SIGNAL_VOLTAGE_180:
+		str = "1.80 V";
+		break;
+	case MMC_SIGNAL_VOLTAGE_120:
+		str = "1.20 V";
+		break;
+	default:
+		str = "invalid";
+		break;
+	}
+	ret += sprintf(&buffer[ret], "signal voltage:\t%u (%s)\n", ios->signal_voltage, str);
+
+	switch (ios->drv_type) {
+	case MMC_SET_DRIVER_TYPE_A:
+		str = "driver type A";
+		break;
+	case MMC_SET_DRIVER_TYPE_B:
+		str = "driver type B";
+		break;
+	case MMC_SET_DRIVER_TYPE_C:
+		str = "driver type C";
+		break;
+	case MMC_SET_DRIVER_TYPE_D:
+		str = "driver type D";
+		break;
+	default:
+		str = "invalid";
+		break;
+	}
+	ret += sprintf(&buffer[ret], "driver type:\t%u (%s)\n", ios->drv_type, str);
+
+
+
+	switch (skw_sdio->irq_type) {
+	case SKW_SDIO_INBAND_IRQ:
+		str = "INBAND_IRQ";
+		break;
+	case SKW_SDIO_EXTERNAL_IRQ:
+		str = "EXTERNAL_IRQ";
+		break;
+	default:
+		str = "invalid";
+		break;
+	}
+	ret += sprintf(&buffer[ret], "irq type:\t%u (%s)\n", skw_sdio->irq_type, str);
+
+	if(!strncmp((char *)skw_sdio->chip_id,"SV6160LITE",10)){
+		str = "SV6160LITE";
+	}else if(!strncmp((char *)skw_sdio->chip_id,"SV6160",6)){
+		str = "SV6160";
+	}else if(!strncmp((char *)skw_sdio->chip_id,"SV6316",6)){
+		str = "SV6316";
+	}else{
+		str = "invalid";
+	}
+
+	ret += sprintf(&buffer[ret], "chipid:\t%s (%s)\n", (char *)skw_sdio->chip_id, str);
+
+	switch (skw_cp_ver) {
+	case SKW_SDIO_V10:
+		str = "sdio v1.0";
+		break;
+	case SKW_SDIO_V20:
+		str = "sdio v2.0";
+		break;
+	default:
+		str = "invalid";
+		break;
+	}
+	ret += sprintf(&buffer[ret], "skw_cp_ver:\t%u (%s)\n", skw_cp_ver,str);
+
+	str = "byte";
+	ret += sprintf(&buffer[ret], "max_pkg_size:\t%u (%s)\n", max_pac_size, str);
+	ret += sprintf(&buffer[ret], "skw_sdio_blk_size:\t%u (%s)\n", skw_sdio_blk_size, str);
+	ret += sprintf(&buffer[ret], "max_tx_pkg_cnt:\t%u\n", MAX_PAC_COUNT);
+	ret += sprintf(&buffer[ret], "max_rx_pkg_cnt:\t%u\n", wifi_pdata.max_buffer_size/max_pac_size);
+
+
+	ret += sprintf(&buffer[ret], "wifi data_port:\t%u\n", wifi_pdata.data_port);
+	ret += sprintf(&buffer[ret], "wifi cmd_port:\t%u\n", wifi_pdata.cmd_port);
+
+
+	ret += sprintf(&buffer[ret], "ucom data_port:\t%u\n", ucom_pdata.data_port);
+	ret += sprintf(&buffer[ret], "ucom cmd_port:\t%u\n", ucom_pdata.cmd_port);
+	ret += sprintf(&buffer[ret], "ucom audio_port:\t%u\n", ucom_pdata.audio_port);
+
+	ret += sprintf(&buffer[ret], "max_ch_num:\t%u\n", max_ch_num);
+
+	if(g_chipen_pin>=0){
+		switch (gpio_get_value(g_chipen_pin)) {
+		case 0:
+			str = "low";
+			break;
+		case 1:
+			str = "high";
+			break;
+		default:
+			str = "invalid";
+			break;
+		}
+	}
+	ret += sprintf(&buffer[ret], "chipen gpio_out:\t%u (%s)\n", g_chipen_pin, str);
+
+	if(skw_sdio->gpio_out>=0){
+		switch (gpio_get_value(skw_sdio->gpio_out)) {
+		case 0:
+			str = "low";
+			break;
+		case 1:
+			str = "high";
+			break;
+		default:
+			str = "invalid";
+			break;
+		}
+	}
+	ret += sprintf(&buffer[ret], "ap2cp gpio_out:\t%u (%s)\n", skw_sdio->gpio_out, str);
+
+	if(skw_sdio->gpio_in>=0){
+		switch (gpio_get_value(skw_sdio->gpio_in)) {
+		case 0:
+			str = "low";
+			break;
+		case 1:
+			str = "high";
+			break;
+		default:
+			str = "invalid";
+			break;
+		}
+	}
+	ret += sprintf(&buffer[ret], "cp2ap gpio_in:\t%u (%s)\n", skw_sdio->gpio_in, str);
+
+#if defined(CONFIG_NO_SERVICE_PD) && (CONFIG_NO_SERVICE_PD == 1)
+    str = "Enable";
+#else
+    str = "Disable";
+#endif
+
+	ret += sprintf(&buffer[ret], "sleep with cp powerdown:\t(%s)\n",  str);
+
+
+	switch (skw_use_sdma) {
+	case 1:
+		str = "sdma";
+		break;
+	case 0:
+		str = "adma";
+		break;
+	default:
+		str = "adma";
+		break;
+	}
+	ret += sprintf(&buffer[ret], "dma type:\t%u (%s)\n", skw_use_sdma, str);
+
+
+	switch (skw_sdio->boot_data->slp_disable) {
+	case 1:
+		str = "Disable";
+		break;
+	case 0:
+		str = "Enable";
+		break;
+	default:
+		str = "Enable";
+		break;
+	}
+	ret += sprintf(&buffer[ret], "fw deepsleep:\t%u (%s)\n", skw_sdio->boot_data->slp_disable, str);
+
+	ret += sprintf(&buffer[ret], "host_active:\t%u\n", skw_sdio->host_active);
+	ret += sprintf(&buffer[ret], "device_active:\t%u\n", skw_sdio->device_active);
+	ret += sprintf(&buffer[ret], "resume_com:\t%u\n", skw_sdio->resume_com);
+	ret += sprintf(&buffer[ret], "cp_state:\t%u\n", skw_sdio->cp_state);
+
+
+	ret += sprintf(&buffer[ret], "VID:\t0x%x\n", skw_sdio->sdio_func[FUNC_0]->vendor);
+	ret += sprintf(&buffer[ret], "PID:\t0x%x\n", skw_sdio->sdio_func[FUNC_0]->device);
+
+
+	ret += sprintf(&buffer[ret], "wifi_device name:\t%s\n", sdio_ports[WIFI_DATA_PORT].pdev->name);
+
+
+	if(ret >= size)
+		skw_sdio_info("ret bigger than size %d %d\n", ret, size);
+
+}
+
+
+static int skw_sdio_host_check(struct skw_sdio_data_t *skw_sdio)
+{
+	int ret = 0;
+
+	struct sdio_func *func1 = skw_sdio->sdio_func[FUNC_1];
+	struct mmc_host *host = func1->card->host;
+
+	if ((SKW_SDIO_INBAND_IRQ == skw_sdio->irq_type) && (0 == (host->caps & MMC_CAP_SDIO_IRQ))) {
+		skw_sdio_err("Please add cap-sdio-irq to dts! irq_type=%d caps=0x%x\n", skw_sdio->irq_type, host->caps);
+		ret = -EPERM;
+	} else if ((host->ios.clock > 50000000UL) && (0 == (host->caps & MMC_CAP_UHS_SDR104))) {
+		skw_sdio_err("please add sd-uhs-sdr104 to dts! clock=%d cap=0x%x\n", host->ios.clock, host->caps);
+		ret = -EPERM;
+	} else if ((host->ios.clock <= 50000000UL) && (0 != (host->caps & MMC_CAP_UHS_SDR104))) {
+		skw_sdio_err("please remove sd-uhs-sdr104 from dts! clock=%d cap=0x%x\n", host->ios.clock, host->caps);
+		ret = -EPERM;
+	} else if (host->ios.clock != host->f_max) {
+		skw_sdio_err("actual clock is not equal to max clock! clock=%d f_max=%d\n", host->ios.clock, host->f_max);
+		ret = -EPERM;
+	} else if (host->ios.timing != MMC_TIMING_UHS_SDR104) {
+		skw_sdio_err("actual timing is not equal to max timing! timing=%d t_max=%d\n", host->ios.timing, MMC_TIMING_UHS_SDR104);
+		ret = -EPERM;
+	}
+
+	return ret;
+}
+
+static int __init skw_sdio_io_init(void)
+{
+	struct skw_sdio_data_t *skw_sdio;
+	int ret = 0;
+	int skw_sd_id = SKW_MMC_HOST_SD_INDEX;
+	if (card_id != SKW_MMC_HOST_SD_INDEX)
+		skw_sd_id = card_id;
+	skw_sdio_mmc_scan(skw_sd_id);
+	memset(&debug_infos, 0, sizeof(struct debug_vars));
+	sunxi_wlan_set_power(0);
+	msleep(200);
+	sunxi_wlan_set_power(1);
+	msleep(80);
+	sunxi_mmc_rescan_card(1);
+	msleep(20);
+	skw_sdio_log_level_init();
+
+	skw_sdio = kzalloc(sizeof(struct skw_sdio_data_t), GFP_KERNEL);
+	if (!skw_sdio) {
+		WARN_ON(1);
+		return -ENOMEM;
+	}
+
+	/* card not ready */
+	g_skw_sdio_data = skw_sdio;
+	mutex_init(&skw_sdio->transfer_mutex);
+	mutex_init(&skw_sdio->except_mutex);
+	mutex_init(&dloader_mutex);
+	mutex_init(&skw_sdio->service_mutex);
+	atomic_set(&skw_sdio->resume_flag, 1);
+	skw_sdio->next_size_buf = kzalloc(SKW_BUF_SIZE, GFP_KERNEL);
+	if(skw_sdio->next_size_buf == NULL){
+		kfree(skw_sdio);
+		skw_sdio = NULL;
+		return -ENOMEM;
+	}
+	skw_sdio->eof_buf = kzalloc(SKW_BUF_SIZE, GFP_KERNEL);
+	if(skw_sdio->eof_buf == NULL){
+		kfree(skw_sdio->next_size_buf);
+		skw_sdio->next_size_buf = NULL;
+		kfree(skw_sdio);
+		skw_sdio = NULL;
+		return -ENOMEM;
+	}
+	atomic_set(&skw_sdio->online, SKW_SDIO_CARD_OFFLINE);
+	if(!bind_device){
+		skw_sdio->adma_rx_enable = 1;
+	}
+	//kzmalloc the log_data
+	skw_sdio->log_data = kzalloc(sizeof(struct skw_log_data_t), GFP_KERNEL);
+	if (skw_sdio->log_data == NULL) {
+		kfree(skw_sdio->next_size_buf);
+		skw_sdio->next_size_buf = NULL;
+		kfree(skw_sdio->eof_buf);
+		skw_sdio->eof_buf = NULL;
+		kfree(skw_sdio);
+		skw_sdio = NULL;
+		skw_sdio_err("kzalloc the log_data fail\n");
+		return -ENOMEM;
+	}
+	skw_sdio->irq_num = -1;
+	INIT_DELAYED_WORK(&skw_sdio->skw_except_work, skw_sdio_exception_work);
+	ret = skw_sdio_scan_card();
+	if (ret < 0) {
+		skw_sdio_remove_card();
+		skw_sdio_err("scan card fail\n");
+		kfree(skw_sdio->log_data);
+		skw_sdio->log_data = NULL;
+		kfree(skw_sdio->next_size_buf);
+		skw_sdio->next_size_buf = NULL;
+		kfree(skw_sdio->eof_buf);
+		skw_sdio->eof_buf = NULL;
+		kfree(skw_sdio);
+		skw_sdio = NULL;
+		return ret;
+	}
+	if (skw_sdio->sdio_dev_host) {
+		seekwave_boot_init((char *)skw_sdio->chip_id);
+	}
+	return ret;
+}
+
+static void __exit  skw_sdio_io_exit(void)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	if (skw_sdio->sdio_dev_host)
+		seekwave_boot_exit();
+	skw_sdio_debugfs_deinit();
+	if (skw_sdio) {
+		skw_sdio_stop_thread();
+		if (!skw_sdio->suspend_wake_unlock_enable) {
+			skw_sdio_unlock_rx_ws(skw_sdio);
+		}
+		skw_sdio_remove_card();
+		if (skw_sdio->sdio_dev_host)
+			skw_sdio_reset_card();
+		cancel_delayed_work_sync(&skw_sdio->skw_except_work);
+		mutex_destroy(&skw_sdio->transfer_mutex);
+		mutex_destroy(&skw_sdio->except_mutex);
+		mutex_destroy(&dloader_mutex);
+		mutex_destroy(&skw_sdio->service_mutex);
+
+		kfree(skw_sdio->next_size_buf);
+		kfree(skw_sdio->eof_buf);
+		skw_sdio->boot_data = NULL;
+		skw_sdio->sdio_dev_host = NULL;
+		kfree(skw_sdio->log_data);
+		skw_sdio->log_data = NULL;
+		kfree(skw_sdio);
+		skw_sdio = NULL;
+	}
+	skw_sdio_info(" OK\n");
+}
+module_init(skw_sdio_io_init)
+module_exit(skw_sdio_io_exit)
+MODULE_LICENSE("GPL v2");
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_rx.c b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_rx.c
new file mode 100755
index 0000000..2bb6f46
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/sdio/skw_sdio_rx.c
@@ -0,0 +1,3419 @@
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/bitops.h>
+#include <linux/kthread.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/dma-mapping.h>
+#include <linux/semaphore.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/timer.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/skbuff.h>
+#include "skw_sdio.h"
+#include "skw_sdio_log.h"
+#include <linux/workqueue.h>
+#if  LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)
+#include <linux/bits.h>
+#else
+#include <linux/bitops.h>
+#endif
+#ifndef GENMASK
+#ifdef CONFIG_64BIT
+#define BITS_PER_LONG 64
+#else
+#define BITS_PER_LONG 32
+#endif /* CONFIG_64BIT */
+#define GENMASK(h, l) \
+	(((~0UL) - (1UL << (l)) + 1) & (~0UL >> (BITS_PER_LONG - 1 - (h))))
+#endif
+#define MODEM_ASSERT_TIMEOUT_VALUE  2*HZ
+#define MAX_SG_COUNT	100
+#define SDIO_BUFFER_SIZE	(16*1024)
+#define FRAGSZ_SIZE (3*1024)
+#define MAX_FIRMWARE_SIZE 256
+#define PORT_STATE_IDLE	0
+#define PORT_STATE_OPEN	1
+#define PORT_STATE_CLSE	2
+#define PORT_STATE_ASST	3
+
+#define CRC_16_L_SEED	0x80
+#define CRC_16_L_POLYNOMIAL  0x8000
+#define CRC_16_POLYNOMIAL  0x1021
+
+int recovery_debug_status=0;
+int wifi_serv_debug_status=0;
+int bt_serv_debug_status=0;
+
+/***********************************************************/
+char firmware_version[128];
+char assert_context[1024];
+int  assert_context_size=0;
+static int assert_info_print;
+static u64 port_dmamask = DMA_BIT_MASK(32);
+struct sdio_port sdio_ports[SDIO2_MAX_CH_NUM];
+static u8 fifo_ind;
+struct debug_vars debug_infos;
+static BLOCKING_NOTIFIER_HEAD(modem_notifier_list);
+unsigned int crc_16_l_calc(char *buf_ptr,unsigned int len);
+static int skw_sdio_rx_port_follow_ctl(int portno, int rx_fctl);
+//add the crc api the same as cp crc_16 api
+extern void kernel_restart(char *cmd);
+static int skw_sdio_irq_ops(int irq_enable);
+static int bt_service_start(void);
+static int bt_service_stop(void);
+static int wifi_service_start(void);
+static int wifi_service_stop(void);
+void send_cp_wakeup_signal(struct skw_sdio_data_t *skw_sdio);
+static int skw_sdio_dump(unsigned int address, void *buf, unsigned int len);
+char skw_cp_ver = SKW_SDIO_V10;
+int max_ch_num = MAX_CH_NUM;
+int max_pac_size = MAX_PAC_SIZE;
+int skw_sdio_blk_size = 256;
+int cp_detect_sleep_mode;
+static u8 is_timeout_kick;
+#ifdef CONFIG_PRINTK_TIME_FROM_ARM_ARCH_TIMER
+#include <clocksource/arm_arch_timer.h>
+u64 skw_local_clock(void)
+{
+        u64 ns;
+
+        ns = arch_timer_read_counter() * 1000;
+        do_div(ns, 24);
+
+        return ns;
+}
+#else
+#if KERNEL_VERSION(4,11,0) <= LINUX_VERSION_CODE
+#include <linux/sched/clock.h>
+#else
+#include <linux/sched.h>
+#endif
+u64 skw_local_clock(void)
+{
+        return local_clock();
+}
+#endif
+#define IS_LOG_PORT(portno)  ((skw_cp_ver == SKW_SDIO_V10)?(portno==1):(portno==SDIO2_BSP_LOG_PORT))
+
+
+void skw_get_assert_print_info(char *buffer, int size)
+{
+	int ret = 0;
+	int j = 0;
+	u64 ts;
+	u64 rem_nsec;
+	u64 rem_usec;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	if(!buffer) {
+		skw_sdio_info("buffer is null!\n");
+		return;
+	}
+	ret += sprintf(&buffer[ret], "last irq times: [%6d] [%6d] ", debug_infos.rx_inband_irq_cnt, debug_infos.rx_gpio_irq_cnt);
+	for (j = 0; j < CHN_IRQ_RECORD_NUM; j++) {
+		ts = debug_infos.last_irq_times[j];
+		rem_nsec = do_div(ts, 1000000000);
+		rem_usec = do_div(rem_nsec, 1000);
+		ret += sprintf(&buffer[ret], "[%5lu.%06llu] ", (unsigned long)ts, rem_usec);
+	}
+	if (SKW_SDIO_INBAND_IRQ == skw_sdio->irq_type) {
+		ret += sprintf(&buffer[ret], "\nlast clear irq times:    [%6d] ", debug_infos.rx_inband_irq_cnt);
+		for (j = 0; j < CHN_IRQ_RECORD_NUM; j++) {
+			ts = debug_infos.last_clear_irq_times[j];
+			rem_nsec = do_div(ts, 1000000000);
+			rem_usec = do_div(rem_nsec, 1000);
+			ret += sprintf(&buffer[ret], "[%5lu.%06llu] ", (unsigned long)ts, rem_usec);
+		}
+	}
+	ret += sprintf(&buffer[ret], "\nlast rx read times:      [%6d] ", debug_infos.rx_read_cnt);
+	for (j = 0; j < CHN_IRQ_RECORD_NUM; j++) {
+		ts = debug_infos.last_rx_read_times[j];
+		rem_nsec = do_div(ts, 1000000000);
+		rem_usec = do_div(rem_nsec, 1000);
+		ret += sprintf(&buffer[ret], "[%5lu.%06llu] ", (unsigned long)ts, rem_usec);
+	}
+
+	if(ret >= size)
+		skw_sdio_info("ret bigger than size %d %d\n", ret, size);
+}
+
+void skw_get_sdio_debug_info(char *buffer, int size)
+{
+	int ret = 0;
+	int i = 0;
+	int j = 0;
+	u64 ts;
+	u64 rem_nsec;
+	u64 rem_usec;
+	u32 irq_cnt = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	if(!buffer) {
+		skw_sdio_info("buffer is null!\n");
+		return;
+	}
+
+	if (0 == debug_infos.rx_irq_statistics_cnt) {
+		if (SKW_SDIO_EXTERNAL_IRQ == skw_sdio->irq_type)
+			debug_infos.rx_irq_statistics_cnt = debug_infos.rx_gpio_irq_cnt;
+		else
+			debug_infos.rx_irq_statistics_cnt = debug_infos.rx_inband_irq_cnt;
+		debug_infos.rx_irq_statistics_time = skw_local_clock();
+		skw_sdio_info("===============rx irq statistics start:%d!===============\n", debug_infos.rx_irq_statistics_cnt);
+	} else {
+		ret += sprintf(&buffer[ret], "rx irq statistics:\n");
+		ts = skw_local_clock() - debug_infos.rx_irq_statistics_time;
+		rem_nsec = do_div(ts, 1000000000);
+		rem_usec = do_div(rem_nsec, 1000);
+		if (SKW_SDIO_EXTERNAL_IRQ == skw_sdio->irq_type)
+			irq_cnt = debug_infos.rx_gpio_irq_cnt - debug_infos.rx_irq_statistics_cnt;
+		else
+			irq_cnt = debug_infos.rx_inband_irq_cnt - debug_infos.rx_irq_statistics_cnt;	
+		skw_sdio_info("===============rx irq statistics end:%d!===============\n", debug_infos.rx_irq_statistics_cnt + irq_cnt);
+		ret += sprintf(&buffer[ret], "rx irq time: [%5lu.%06llu] count:%d count per second:%lu\n", (unsigned long)ts, rem_usec, irq_cnt, irq_cnt /(unsigned long)ts);
+
+		debug_infos.rx_irq_statistics_cnt = 0;
+		debug_infos.rx_irq_statistics_time = 0;
+	}
+
+	ret += sprintf(&buffer[ret], "channel irq times:\n");
+	for (i = 0; i < max_ch_num; i++) {
+		ret += sprintf(&buffer[ret], "channel[%2d]: [%6d] ", i, debug_infos.chn_irq_cnt[i]);
+		for (j = 0; j < CHN_IRQ_RECORD_NUM; j++) {
+			ts = debug_infos.chn_last_irq_time[i][j];
+			rem_nsec = do_div(ts, 1000000000);
+			rem_usec = do_div(rem_nsec, 1000);
+			ret += sprintf(&buffer[ret], "[%5lu.%06llu] ", (unsigned long)ts, rem_usec);
+		}
+		ret += sprintf(&buffer[ret], "\n");
+	}
+	ret += sprintf(&buffer[ret], "cmd_timeout_cnt: %d\n", debug_infos.cmd_timeout_cnt);
+	ret += sprintf(&buffer[ret], "last_sent_wifi_cmd[0]: 0x%x\n", debug_infos.last_sent_wifi_cmd[0]);
+	ret += sprintf(&buffer[ret], "last_sent_wifi_cmd[1]: 0x%x\n", debug_infos.last_sent_wifi_cmd[1]);
+	ret += sprintf(&buffer[ret], "last_sent_wifi_cmd[2]: 0x%x\n", debug_infos.last_sent_wifi_cmd[2]);
+	ts = debug_infos.last_sent_time;
+	rem_nsec = do_div(ts, 1000000000);
+	rem_usec = do_div(rem_nsec, 1000);
+	ret += sprintf(&buffer[ret], "last_sent_time: [%5lu.%06llu]\n", (unsigned long)ts, rem_usec);
+	ts = debug_infos.last_rx_submit_time;
+	rem_nsec = do_div(ts, 1000000000);
+	rem_usec = do_div(rem_nsec, 1000);
+	ret += sprintf(&buffer[ret], "last_rx_submit_time: [%5lu.%06llu]\n", (unsigned long)ts, rem_usec);
+	if (debug_infos.host_assert_cp_time) {
+		ts = debug_infos.host_assert_cp_time;
+		rem_nsec = do_div(ts, 1000000000);
+		rem_usec = do_div(rem_nsec, 1000);
+		ret += sprintf(&buffer[ret], "host_assert_cp_time: [%5lu.%06llu]\n", (unsigned long)ts, rem_usec);
+	}
+	if (debug_infos.cp_assert_time) {
+		ts = debug_infos.cp_assert_time;
+		rem_nsec = do_div(ts, 1000000000);
+		rem_usec = do_div(rem_nsec, 1000);
+		ret += sprintf(&buffer[ret], "cp_assert_time: [%5lu.%06llu]\n", (unsigned long)ts, rem_usec);
+	}
+	if (debug_infos.host_assert_cp_time > debug_infos.last_sent_time) {
+		ts = debug_infos.host_assert_cp_time - debug_infos.last_sent_time;
+		rem_nsec = do_div(ts, 1000000000);
+		rem_usec = do_div(rem_nsec, 1000);
+		ret += sprintf(&buffer[ret], "timeout: [%5lu.%06llu]\n", (unsigned long)ts, rem_usec);
+	}
+
+	if(ret >= size)
+		skw_sdio_info("ret bigger than size %d %d\n", ret, size);
+}
+//=======================================================
+//debug sdio macro and Variable
+int glb_wifiready_done;
+//#define SKW_WIFIONLY_DEBUG 1
+//=======================================================
+
+/********************************************************
+ * skw_sdio_update img crc checksum
+ * For update the CP IMG
+ *Author: JUNWEI JIANG
+ *Date:2022-08-11
+ * *****************************************************/
+
+int skw_log_port(void)
+{
+	return (skw_cp_ver == SKW_SDIO_V10)?(BSP_LOG_PORT):(SDIO2_BSP_LOG_PORT);
+}
+
+void skw_get_port_statistic(char *buffer, int size)
+{
+	int ret = 0;
+	int i;
+
+	if(!buffer)
+		return;
+	ret += sprintf(&buffer[ret], "%s", firmware_version);
+	for(i=0; i<max_ch_num; i++) {
+		if(ret >= size)
+			break;
+		if(sdio_ports[i].state)
+			ret += sprintf(&buffer[ret], "port%d: rx %d %d, tx %d %d\n",
+				i, sdio_ports[i].rx_count, sdio_ports[i].rx_packet,
+				sdio_ports[i].total, sdio_ports[i].sent_packet);
+	}
+}
+
+unsigned int crc_16_l_calc(char *buf_ptr,unsigned int len)
+{
+	unsigned int i;
+	unsigned short crc=0;
+
+	while(len--!=0)
+	{
+		for(i= CRC_16_L_SEED;i!=0;i=i>>1)
+		{
+			if((crc &CRC_16_L_POLYNOMIAL)!=0)
+			{
+				crc= crc<<1;
+				crc= crc ^ CRC_16_POLYNOMIAL;
+			}else{
+				crc = crc <<1;
+			}
+
+			if((*buf_ptr &i)!=0)
+			{
+				crc = crc ^ CRC_16_POLYNOMIAL;
+			}
+		}
+		buf_ptr++;
+	}
+	return (crc);
+}
+
+static int skw_sdio_rx_port_follow_ctl(int portno, int rx_fctl)
+{
+	char ftl_val = 0;
+	int ret = 0;
+
+	skw_sdio_info(" portno:%d, rx_fctl:%d \n", portno, rx_fctl);
+
+	if((portno < 0) || (portno > max_ch_num))
+		return -1;
+
+	if(portno < 8){
+		ret = skw_sdio_readb(SKW_SDIO_RX_CHANNEL_FTL0, &ftl_val);
+		if(ret)
+			return -1;
+
+		if(rx_fctl)
+			ftl_val = ftl_val | (1 << portno);
+		else
+			ftl_val = ftl_val & (~(1 << portno));
+		ret = skw_sdio_writeb(SKW_SDIO_RX_CHANNEL_FTL0, ftl_val);
+	}
+	else{
+		portno = portno - 8;
+		ret = skw_sdio_readb(SKW_SDIO_RX_CHANNEL_FTL1, &ftl_val);
+		if(ret)
+			return -1;
+
+		if(rx_fctl)
+			ftl_val = ftl_val | (1 << portno);
+		else
+			ftl_val = ftl_val & (~(1 << portno));
+		ret = skw_sdio_writeb(SKW_SDIO_RX_CHANNEL_FTL1, ftl_val);
+	}
+	return ret;
+}
+
+void modem_register_notify(struct notifier_block *nb)
+{
+	blocking_notifier_chain_register(&modem_notifier_list, nb);
+}
+void modem_unregister_notify(struct notifier_block *nb)
+{
+	blocking_notifier_chain_unregister(&modem_notifier_list, nb);
+}
+void modem_notify_event(int event)
+{
+	blocking_notifier_call_chain(&modem_notifier_list, event, NULL);
+}
+
+void skw_sdio_exception_work(struct work_struct *work)
+{
+	int i=0;
+	int port_num=5;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	skw_sdio_info(" entern..cp_state = %d.\n", skw_sdio->cp_state);
+	mutex_lock(&skw_sdio->except_mutex);
+	if(skw_sdio->cp_state!=1)
+	{
+		skw_sdio_warn("cp assert already\n");
+		mutex_unlock(&skw_sdio->except_mutex);
+		return;
+	}
+	skw_sdio->cp_state = DEVICE_BLOCKED_EVENT;
+	mutex_unlock(&skw_sdio->except_mutex);
+	modem_notify_event(DEVICE_BLOCKED_EVENT);
+	for (i=0; i<port_num; i++)
+	{
+		if(!sdio_ports[i].state || sdio_ports[i].state==PORT_STATE_CLSE)
+			continue;
+
+		sdio_ports[i].state = PORT_STATE_ASST;
+		complete(&(sdio_ports[i].rx_done));
+
+		if(i!=1)
+			complete(&(sdio_ports[i].tx_done));
+		if(i==0 || i==skw_log_port())
+			sdio_ports[i].next_seqno= 1;
+
+	}
+	skw_sdio->service_state_map=0;
+	skw_recovery_mode();
+}
+
+static inline void *skw_sdio_alloc_frag(size_t fragsz, gfp_t gfp_mask)
+{
+	void *addr;
+	struct page *page;
+	addr = netdev_alloc_frag(fragsz);
+	if (!addr)
+		return NULL;
+
+	page = virt_to_head_page(addr);
+
+	skw_sdio_dbg(
+		"dbg: alloc addr: 0x%lx, size: %ld, page addr: 0x%lx, ref: %d\n",
+		(long)addr, (long int)fragsz, (long)page, page_count(page));
+
+	return addr;
+}
+static inline void skw_page_frag_free(void *addr)
+{
+	struct page *page = NULL;
+	if (!addr) {
+		skw_sdio_warn("dbg: free addr is NULL\n");
+		return;
+	}
+
+	page = virt_to_head_page(addr);
+	skw_sdio_dbg("dbg: free addr: 0x%lx, page addr: 0x%lx, ref: %d\n",
+		     (long)addr, (long)page, page_count(page));
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
+	skb_free_frag(addr);
+#else
+	put_page(virt_to_head_page(addr));
+#endif
+}
+
+static void skw_sdio_rx_down(struct skw_sdio_data_t * skw_sdio)
+{
+	wait_for_completion_interruptible(&skw_sdio->rx_completed);
+}
+void skw_sdio_rx_up(struct skw_sdio_data_t * skw_sdio)
+{
+	skw_reinit_completion(skw_sdio->rx_completed);
+	complete(&skw_sdio->rx_completed);
+}
+void skw_sdio_dispatch_packets(struct skw_sdio_data_t * skw_sdio)
+{
+	int i;
+	struct sdio_port *port;
+
+	for(i=0; i<max_ch_num; i++) {
+		port = &sdio_ports[i];
+		if(!port->state)
+			continue;
+		if(port->rx_rp!=port->rx_wp)
+			skw_sdio_dbg("port[%d] sg_index=%d (%d,%d)\n", i,
+				port->sg_index, port->rx_rp, port->rx_wp);
+		if(port->rx_submit && port->sg_index) {
+			debug_infos.last_rx_submit_time = skw_local_clock();
+			port->rx_submit(port->channel, port->sg_rx, port->sg_index, port->rx_data);
+			skw_sdio_dbg("port[%d] sg_index=%d (%d,%d)\n", i,
+				port->sg_index, port->rx_rp, port->rx_wp);
+			port->sg_index = 0;
+		}
+	}
+}
+static void skw_sdio_sdma_set_nsize(unsigned int size)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	int count;
+	if (size == 0) {
+		skw_sdio->next_size = max_pac_size;
+		return;
+	}
+
+	count = (size >> 10) + 9;
+	size = SKW_SDIO_ALIGN_BLK(size + (count<<3));
+	skw_sdio->next_size = (size>SDIO_BUFFER_SIZE) ? SDIO_BUFFER_SIZE:size;
+}
+
+static void skw_sdio_adma_set_packet_num(unsigned int num)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	if (num == 0)
+		num = 1;
+
+	if (num >= MAX_SG_COUNT)
+		skw_sdio->remain_packet = MAX_SG_COUNT;
+	else 
+		skw_sdio->remain_packet = num;
+}
+
+/************************************************************************
+ *Decription:release debug recovery auto test
+ *Author:junwei.jiang
+ *Date:2023-05-30
+ *Modfiy:
+ *
+ ********************************************************************* */
+int skw_sdio_recovery_debug(int disable)
+{
+	recovery_debug_status = disable;
+	skw_sdio_info("the recovery status =%d\n", recovery_debug_status);
+	return 0;
+}
+
+int skw_sdio_recovery_debug_status(void)
+{
+	skw_sdio_info("the recovery val =%d\n", recovery_debug_status);
+	return recovery_debug_status;
+}
+
+int skw_sdio_bt_serv_debug(int enable)
+{
+	bt_serv_debug_status = enable;
+
+	skw_sdio_info("the bt_service status =%d\n", bt_serv_debug_status);
+	if(enable){
+		bt_service_start();
+	}else{
+		bt_service_stop();
+	}
+	return 0;
+}
+
+int skw_sdio_bt_serv_debug_status(void)
+{
+	skw_sdio_info("the bt_service val =%d\n", bt_serv_debug_status);
+
+	return bt_serv_debug_status;
+}
+int skw_sdio_wifi_serv_debug(int enable)
+{
+	wifi_serv_debug_status = enable;
+
+	skw_sdio_info("the wifi_service status =%d\n", wifi_serv_debug_status);
+	if(enable){
+		wifi_service_start();
+	}else{
+		wifi_service_stop();
+	}
+	return 0;
+}
+
+int skw_sdio_wifi_serv_debug_status(void)
+{
+	skw_sdio_info("the wifi_service val =%d\n", wifi_serv_debug_status);
+	return wifi_serv_debug_status;
+}
+
+static int force_cp_wakeup(void)
+{
+	int ret = 0;
+	u32 val = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	send_cp_wakeup_signal(skw_sdio);
+	ret = wait_for_completion_interruptible_timeout(&skw_sdio->device_wakeup, HZ/100);
+	if (ret <= 0) {
+		skw_sdio_err("wait for device wakeup timeout\n");
+		return -ETIMEDOUT;
+	}
+	val = gpio_get_value(skw_sdio->gpio_in);
+	if(val)
+		return 0;
+	else {
+		send_cp_wakeup_signal(skw_sdio);
+		ret = wait_for_completion_interruptible_timeout(&skw_sdio->device_wakeup, HZ/100);
+		if (ret == 0)
+			return -ETIMEDOUT;
+		val = gpio_get_value(skw_sdio->gpio_in);
+		if(val)
+			return 0;
+		else
+			return -ETIMEDOUT;
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int skw_sdio_dump(unsigned int address, void *buf, unsigned int len)
+{
+	int ret = 0;
+	ret = skw_sdio_smem_poweron();
+	if (ret != 0) {
+		skw_sdio_err(" pweron fail ret:%d, addr=0x%x\n", ret, address);
+		return ret;
+	}
+	ret = skw_sdio_dt_read(address, buf, len);
+	if (ret != 0) {
+		skw_sdio_err(" dt read fail ret:%d, addr=0x%x\n", ret, address);
+		return ret;
+	}
+	return ret;
+}
+int update_download_flag(bool enable)
+{
+	int ret;
+	u32 buffer;
+
+	/* 1. read DL_FLAG reg */
+	ret = force_cp_wakeup();
+	if (ret) {
+		skw_sdio_err("force cp wakeup failed %d\n", __LINE__);
+		return ret;
+	}
+	ret = skw_sdio_dt_read(SKW_DL_FLAG_BASE, &buffer, 4);
+	if (ret) {
+		ret = force_cp_wakeup();
+		if (ret) {
+			skw_sdio_err("force cp wakeup failed %d\n", __LINE__);
+			return ret;
+		}
+		ret = skw_sdio_dt_read(SKW_DL_FLAG_BASE, &buffer, 4);
+		if (ret) {
+			skw_sdio_err("read dl flag failed\n");
+			return ret;
+		}
+	}
+
+	if (enable == 1)
+		buffer |= SKW_DL_FLAG_BIT_MASK;
+	else
+		buffer &= ~SKW_DL_FLAG_BIT_MASK;
+
+	/* 2. update DL_FLAG bit */
+	ret = force_cp_wakeup();
+	if (ret) {
+		skw_sdio_err("force cp wakeup failed %d\n", __LINE__);
+		return ret;
+	}
+	ret = skw_sdio_dt_write(SKW_DL_FLAG_BASE, &buffer, 4);
+	if (ret) {
+		ret = force_cp_wakeup();
+		if (ret) {
+			skw_sdio_err("force cp wakeup failed %d\n", __LINE__);
+			return ret;
+		}
+		ret = skw_sdio_dt_write(SKW_DL_FLAG_BASE, &buffer, 4);
+		if (ret) {
+			skw_sdio_err("write dl flag failed\n");
+			return ret;
+		}
+	}
+	/* 3. Make sure CP update flag successfully */
+	ret = force_cp_wakeup();
+	if (ret) {
+		skw_sdio_err("force cp wakeup failed %d\n", __LINE__);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int skw_pin_config(void)
+{
+	int i, ret;
+	u32 val;
+	int func_index = 0;
+	int g_offset = 0;
+	u32 pin_group_index = 0;
+	u32 func_group_index = 0;
+	u32 func_sel_val[2] = {0};
+	u32 func_sel_off[] = {PN_FUNC_SEL0_OFFSET, PN_FUNC_SEL1_OFFSET};
+	u32 *pin_val;
+	u8 pin_off[PN_CNT] = {0};
+	u32 buffer;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+
+	ret = update_download_flag(1);
+	if (ret) {
+		skw_sdio_err("set download flag failed\n");
+		return ret;
+	}
+
+	pin_val = (u32 *)kzalloc(PN_CNT * sizeof(u32), GFP_KERNEL);
+	if (!pin_val) {
+		skw_sdio_err("pin_val alloc failed\n");
+		return -ENOMEM;
+	}
+
+	while (g_offset < skw_sdio->boot_data->nv_mem_pnfg_size) {
+		//1. pin offset
+		pin_off[pin_group_index] = skw_sdio->boot_data->nv_mem_pnfg_data[g_offset];
+
+		//2. func_sel value
+		val = skw_sdio->boot_data->nv_mem_pnfg_data[g_offset + 1];
+		func_sel_val[func_group_index] |= (val << (3 * func_index)) & (7 << (3 * func_index));
+
+		//3. pin config value
+		pin_val[pin_group_index] |=
+				((u32)skw_sdio->boot_data->nv_mem_pnfg_data[g_offset + 2] << BIT_PN_DSLP_EN_START) & \
+				GENMASK(BIT_PN_DSLP_EN_END, BIT_PN_DSLP_EN_START);//dslp_en
+		pin_val[pin_group_index] |=
+				((u32)skw_sdio->boot_data->nv_mem_pnfg_data[g_offset + 3] << BIT_DRV_STREN_START) & \
+				GENMASK(BIT_DRV_STREN_END, BIT_DRV_STREN_START);//drv_strength
+		pin_val[pin_group_index] |=
+				((u32)skw_sdio->boot_data->nv_mem_pnfg_data[g_offset + 4] << BIT_NORMAL_WP_START) & \
+				GENMASK(BIT_NORMAL_WP_END, BIT_NORMAL_WP_START);//normal:wpu/wpd
+		pin_val[pin_group_index] |=
+				((u32)skw_sdio->boot_data->nv_mem_pnfg_data[g_offset + 5] << BIT_SCHMITT_START) & \
+				GENMASK(BIT_SCHMITT_END, BIT_SCHMITT_START);//schmitt trigger enable
+		pin_val[pin_group_index] |=
+				((u32)skw_sdio->boot_data->nv_mem_pnfg_data[g_offset + 6] << BIT_SLP_WP_START) & \
+				GENMASK(BIT_SLP_WP_END, BIT_SLP_WP_START);//sleep:wpu/wpd
+		pin_val[pin_group_index] |=
+				((u32)skw_sdio->boot_data->nv_mem_pnfg_data[g_offset + 7]) & \
+				GENMASK(BIT_SLEEP_IE_OE_END, BIT_SLEEP_IE_OE_START);//sleep:ie/oe
+
+		g_offset += 8; // 1(offset) + 1(pin sel) + 6(pin config)
+		func_index++;
+		pin_group_index++;
+		if (func_index == PN_FUNCSEL_ONEGRP_CNT){
+			func_group_index++;
+			func_index = 0;
+		}
+	}
+
+	//function select
+	for (i = 0;i < sizeof(func_sel_off) / sizeof(u32);i++) {
+		buffer = func_sel_val[i];
+		skw_sdio_dbg("sel_off,sel_val::0x%08x,0x%08x\n", func_sel_off[i], buffer);
+		ret = skw_sdio_dt_write(SKW_PINREG_BASE + func_sel_off[i], &buffer, 4);
+		if (ret) {
+			kfree(pin_val);
+			pin_val = NULL;
+			skw_sdio_err("write pinreg[0x%x] failed\n", func_sel_off[i]);
+			return ret;
+		}
+	}
+
+	//pin config
+	for (i = 0;i < PN_CNT;i++) {
+		buffer = pin_val[i];
+		skw_sdio_dbg("pin_off,pin_val:0x%08x,0x%08x\n", pin_off[i], buffer);
+		ret = skw_sdio_dt_write(SKW_PINREG_BASE + pin_off[i], &buffer, 4);
+		if (ret) {
+			kfree(pin_val);
+			pin_val = NULL;
+			skw_sdio_err("write pinreg[0x%x] failed\n", pin_off[i]);
+			return ret;
+		}
+	}
+	kfree(pin_val);
+	pin_val = NULL;
+	ret = update_download_flag(0);
+	if (ret) {
+		skw_sdio_err("clear download flag failed\n");
+		return ret;
+	}
+	return 0;
+}
+
+static int skw_sdio_handle_packet(struct skw_sdio_data_t *skw_sdio,
+		struct scatterlist *sg, struct skw_packet_header *header, int portno)
+{
+	struct sdio_port  *port;
+	int buf_size, i, ret;
+	int log_port_num = 0;
+	char *addr;
+	u32 *data;
+	if (portno >= max_ch_num)
+		return -EINVAL;
+	log_port_num = skw_log_port();
+	port = &sdio_ports[portno];
+	port->rx_packet++;
+	port->rx_count += header->len;
+	if(portno == LOOPCHECK_PORT) {
+		char *cmd = (char *)(header+4);
+		cmd[header->len - 12] = 0;
+		skw_sdio_info("LOOPCHECK channel received: %s\n", (char *)cmd);
+		if (header->len==19 && !strncmp(cmd, "BTREADY", 7)) {
+			skw_sdio->service_state_map |= 2;
+			//kernel_restart(0);
+			skw_sdio->device_active = 1;
+			complete(&skw_sdio->download_done);
+		}else if(header->len==21 && !strncmp(cmd, "WIFIREADY", 9)){
+			skw_sdio->service_state_map |= 1;
+			//kernel_restart(0);
+			skw_sdio->device_active = 1;
+			complete(&skw_sdio->download_done);
+		} else if (!strncmp((char *)cmd, "BSPASSERT", 9)){
+			sprintf(firmware_version, "%s:%s\n", firmware_version, cmd);
+			debug_infos.cp_assert_time = skw_local_clock();
+#ifdef CONFIG_SEEKWAVE_PLD_RELEASE
+			if(!skw_sdio->cp_state && sdio_ports[log_port_num].state != PORT_STATE_OPEN)
+				schedule_delayed_work(&skw_sdio->skw_except_work , msecs_to_jiffies(800));
+			else if(!skw_sdio->cp_state && sdio_ports[log_port_num].state == PORT_STATE_OPEN)
+				schedule_delayed_work(&skw_sdio->skw_except_work , msecs_to_jiffies(8000));
+#else
+			if(!skw_sdio->cp_state)
+				schedule_delayed_work(&skw_sdio->skw_except_work , msecs_to_jiffies(12000));
+#endif
+			mutex_lock(&skw_sdio->except_mutex);
+			if(skw_sdio->cp_state==DEVICE_BLOCKED_EVENT){
+				if(skw_sdio->adma_rx_enable)
+					skw_page_frag_free(header);
+
+				mutex_unlock(&skw_sdio->except_mutex);
+				return 0;
+			}
+			skw_sdio->cp_state=1;/*cp except set value*/
+			mutex_unlock(&skw_sdio->except_mutex);
+			skw_sdio->service_state_map = 0;
+			memset(assert_context, 0, 1024);
+			assert_context_size = 0;
+			modem_notify_event(DEVICE_ASSERT_EVENT);
+			skw_sdio_err(" bsp assert !!!\n");
+		}else if (header->len==20 && !strncmp(cmd, "DUMPDONE",8)){
+			mutex_lock(&skw_sdio->except_mutex);
+			if(skw_sdio->cp_state==DEVICE_BLOCKED_EVENT){
+				if(skw_sdio->adma_rx_enable)
+					skw_page_frag_free(header);
+
+				mutex_unlock(&skw_sdio->except_mutex);
+				return 0;
+			}
+			skw_sdio->cp_state=DEVICE_DUMPDONE_EVENT;/*cp except set value 2*/
+			mutex_unlock(&skw_sdio->except_mutex);
+			cancel_delayed_work_sync(&skw_sdio->skw_except_work);
+			if(sdio_ports[log_port_num].state == PORT_STATE_OPEN)
+				modem_notify_event(DEVICE_DUMPDONE_EVENT);
+			skw_sdio_err("The CP DUMPDONE OK : \n %d::%s\n",assert_context_size, assert_context);
+			for (i=0; i<5; i++) {
+				if(!sdio_ports[i].state || sdio_ports[i].state==PORT_STATE_CLSE)
+					continue;
+
+				sdio_ports[i].state = PORT_STATE_ASST;
+				complete(&(sdio_ports[i].rx_done));
+				if(i!=1)
+					complete(&(sdio_ports[i].tx_done));
+				if(i==0 || i==skw_log_port())
+					sdio_ports[i].next_seqno= 1;
+			}
+#ifdef CONFIG_SEEKWAVE_PLD_RELEASE
+			skw_recovery_mode();//recoverymode open api
+#else
+			if(!strncmp((char *)skw_sdio->chip_id,"SV6160",6) && !recovery_debug_status
+					&&skw_sdio->cp_state !=DEVICE_BLOCKED_EVENT){
+				skw_recovery_mode();//recoverymode open api
+			}
+#endif
+		}else if (!strncmp("trunk_W", cmd, 7)) {
+			memset(firmware_version, 0 , sizeof(firmware_version));
+			if (strlen(cmd) > strlen(firmware_version)) {
+				strncpy(firmware_version, cmd, sizeof(firmware_version)-1);
+				firmware_version[sizeof(firmware_version)-1] = '\0';
+			} else {
+				strncpy(firmware_version, cmd, strlen(cmd));
+				//strcpy(firmware_version, cmd);
+			}
+			cmd = strstr(firmware_version, "slp=");
+			if (cmd)
+				cp_detect_sleep_mode = cmd[4] - 0x30;
+			else
+				cp_detect_sleep_mode = 4;
+
+			if(!skw_sdio->boot_data->first_dl_flag){
+				skw_sdio_gpio_irq_pre_ops();
+			}
+			if(!skw_sdio->cp_state)
+				complete(&skw_sdio->download_done);
+			if(skw_sdio->cp_state){
+				assert_info_print = 0;
+				if(sdio_ports[0].state == PORT_STATE_ASST)
+					sdio_ports[0].state = PORT_STATE_OPEN;
+				modem_notify_event(DEVICE_BSPREADY_EVENT);
+				skw_sdio_info("send the bsp state to log service or others\n");
+			}
+			skw_sdio->cp_state = 0;
+			skw_sdio->log_data->smem_poweron = 0;
+			wake_up(&skw_sdio->wq);
+			skw_sdio_info("cp_state = %d \n", skw_sdio->cp_state);
+			//skw_sdio_info("firmware version: %s:%s \n",cmd, firmware_version);
+			if (skw_sdio->boot_data->nv_mem_pnfg_data != NULL && skw_sdio->boot_data->nv_mem_pnfg_size != 0) {
+				skw_sdio_info("UPDATE '%s' PINCFG from %s\n", (char *)skw_sdio->chip_id, skw_sdio->boot_data->skw_nv_name);
+				ret = skw_pin_config();
+				if (ret)
+					skw_sdio_err("Update pin config failed!!!\n");
+			}
+		} else if (!strncmp(cmd, "BSPREADY",8)) {
+			loopcheck_send_data("RDVERSION", 9);
+		}
+		skw_sdio_dbg("Line:%d the port=%d \n", __LINE__, port->channel);
+		if(skw_sdio->adma_rx_enable)
+			skw_page_frag_free(header);
+		return 0;
+	}
+	if(!port->state) {
+		if(skw_sdio->adma_rx_enable){
+			if (!IS_LOG_PORT(portno))
+				skw_sdio_err("port%d discard data for wrong state\n", portno);
+			skw_page_frag_free(header);
+			return 0;
+		}
+	}
+	if (port->sg_rx && port->rx_data){
+		if (port->sg_index >= MAX_SG_COUNT){
+			skw_sdio_err(" rx sg_buffer is overflow!\n");
+		}else{
+			sg_set_buf(&port->sg_rx[port->sg_index++], header, header->len+4);
+		}
+	}else {
+		int packet=0, total=0;
+		mutex_lock(&port->rx_mutex);
+		buf_size = (port->length + port->rx_wp - port->rx_rp)%port->length;
+		buf_size = port->length - 1 - buf_size;
+		addr = (char *)(header+1);
+		data = (u32 *) addr;
+		if(((data[2] & 0xffff) != port->next_seqno) &&
+			(header->len > 12) && !IS_LOG_PORT(portno)) {
+			skw_sdio_err("portno:%d, packet lost recv seqno=%d expected %d\n", port->channel,
+					data[2] & 0xffff, port->next_seqno);
+			if(skw_sdio->adma_rx_enable)
+				skw_page_frag_free(header);
+			mutex_unlock(&port->rx_mutex);
+			return 0;
+		}
+		if(header->len > 12) {
+			port->next_seqno++;
+			addr += 12;
+			header->len -= 12;
+			total = data[1] >> 8;
+			packet = data[2] & 0xFFFF;
+		} else if (header->len == 12) {
+			header->len = 0;
+			port->tx_flow_ctrl--;
+			complete(&port->tx_done);
+			skw_port_log(portno,"%s link msg: 0x%x 0x%x port%d: %d \n", __func__,
+					data[0], data[1], portno, port->tx_flow_ctrl);
+		}
+		if(skw_sdio->cp_state){
+			if(header->len!=245 || buf_size < 2048) {
+				if(assert_info_print++ < 28 && strncmp((const char *)addr, "+LOG", 4)) {
+					if (assert_context_size + header->len < sizeof(assert_context)) {
+						memcpy(assert_context + assert_context_size, addr, header->len);
+						assert_context_size += header->len;
+					}
+				}
+			}
+			if(buf_size <2048)
+				msleep(10);
+		}
+		if (port->rx_submit && !port->sg_rx) {
+			if (header->len && port->pdev)
+				port->rx_submit(portno, port->rx_data, header->len, addr);
+		} else if (buf_size < header->len) {
+			skw_port_log(portno,"%s port%d overflow:buf_size %d-%d, packet size %d (w,r)=(%d, %d)\n",
+					__func__, portno, buf_size, port->length, header->len,
+					port->rx_wp,  port->rx_rp);
+		} else if(port->state && header->len) {
+			if(port->length - port->rx_wp > header->len){
+				memcpy(&port->read_buffer[port->rx_wp], addr, header->len);
+				port->rx_wp += header->len;
+			} else {
+				memcpy(&port->read_buffer[port->rx_wp], addr, port->length - port->rx_wp);
+				memcpy(&port->read_buffer[0], &addr[port->length - port->rx_wp],
+						header->len - port->length + port->rx_wp);
+				port->rx_wp = header->len - port->length + port->rx_wp;
+			}
+
+			if(!port->rx_flow_ctrl && buf_size-header->len < (port->length/3)) {
+				port->rx_flow_ctrl = 1;
+				skw_sdio_rx_port_follow_ctl(portno, port->rx_flow_ctrl);
+			}
+			mutex_unlock(&port->rx_mutex);
+			complete(&port->rx_done);
+			if(skw_sdio->adma_rx_enable)
+				skw_page_frag_free(header);
+			return 0;
+		}
+		mutex_unlock(&port->rx_mutex);
+		if(skw_sdio->adma_rx_enable)
+			skw_page_frag_free(header);
+	}
+	return 0;
+}
+
+static int skw_sdio2_handle_packet(struct skw_sdio_data_t *skw_sdio,
+		struct scatterlist *sg, struct skw_packet2_header *header, int portno)
+{
+	struct sdio_port  *port;
+	int buf_size, i, ret;
+	int log_port_num = 0;
+	char *addr;
+	u32 *data;
+
+	if (portno >= max_ch_num)
+		return -EINVAL;
+	port = &sdio_ports[portno];
+	log_port_num = skw_log_port();
+	port->rx_packet++;
+	port->rx_count += header->len;
+	if(portno == SDIO2_LOOPCHECK_PORT) {
+		char *cmd = (char *)(header+4);
+		cmd[header->len - 12] = 0;
+		skw_sdio_info("LOOPCHECK channel received: %s\n", (char *)cmd);
+		if (header->len==19 && !strncmp(cmd, "BTREADY", 7)) {
+			skw_sdio->service_state_map |= 2;
+			//kernel_restart(0);
+			skw_sdio->device_active = 1;
+			complete(&skw_sdio->download_done);
+		}else if(header->len==21 && !strncmp(cmd, "WIFIREADY", 9)){
+			skw_sdio->service_state_map |= 1;
+			//kernel_restart(0);
+			skw_sdio->device_active = 1;
+			complete(&skw_sdio->download_done);
+		} else if (!strncmp((char *)cmd, "BSPASSERT", 9)) {
+			sprintf(firmware_version, "%s:%s\n", firmware_version, cmd);
+			debug_infos.cp_assert_time = skw_local_clock();
+#ifdef CONFIG_SEEKWAVE_PLD_RELEASE
+			if(!skw_sdio->cp_state && sdio_ports[log_port_num].state != PORT_STATE_OPEN)
+				schedule_delayed_work(&skw_sdio->skw_except_work , msecs_to_jiffies(800));
+			else if(!skw_sdio->cp_state && sdio_ports[log_port_num].state == PORT_STATE_OPEN)
+				schedule_delayed_work(&skw_sdio->skw_except_work , msecs_to_jiffies(8000));
+#else
+			if(!skw_sdio->cp_state)
+				schedule_delayed_work(&skw_sdio->skw_except_work , msecs_to_jiffies(12000));
+#endif
+			mutex_lock(&skw_sdio->except_mutex);
+			if(skw_sdio->cp_state==DEVICE_BLOCKED_EVENT){
+				if (skw_sdio->adma_rx_enable) {
+					skw_page_frag_free(header);
+				}
+
+				mutex_unlock(&skw_sdio->except_mutex);
+				return 0;
+			}
+			skw_sdio->cp_state=1;/*cp except set value*/
+			mutex_unlock(&skw_sdio->except_mutex);
+			skw_sdio->service_state_map = 0;
+			memset(assert_context, 0, 1024);
+			assert_context_size = 0;
+			modem_notify_event(DEVICE_ASSERT_EVENT);
+			skw_sdio_err(" bsp assert !!!\n");
+		}else if (header->len==20 && !strncmp(cmd, "DUMPDONE",8)){
+			mutex_lock(&skw_sdio->except_mutex);
+			if(skw_sdio->cp_state==DEVICE_BLOCKED_EVENT){
+				if (skw_sdio->adma_rx_enable) {
+					skw_page_frag_free(header);
+				}
+
+				mutex_unlock(&skw_sdio->except_mutex);
+				return 0;
+			}
+			skw_sdio->cp_state=DEVICE_DUMPDONE_EVENT;/*cp except set value 2*/
+			mutex_unlock(&skw_sdio->except_mutex);
+			cancel_delayed_work_sync(&skw_sdio->skw_except_work);
+			if(sdio_ports[log_port_num].state == PORT_STATE_OPEN)
+				modem_notify_event(DEVICE_DUMPDONE_EVENT);
+			skw_sdio_err("The CP DUMPDONE OK : \n %d::%s\n",assert_context_size, assert_context);
+			for (i=0; i<5; i++) {
+				if(!sdio_ports[i].state || sdio_ports[i].state==PORT_STATE_CLSE)
+					continue;
+
+				sdio_ports[i].state = PORT_STATE_ASST;
+				complete(&(sdio_ports[i].rx_done));
+				if(i!=1)
+					complete(&(sdio_ports[i].tx_done));
+				if(i==1)
+					sdio_ports[i].next_seqno= 1;
+			}
+#ifdef CONFIG_SEEKWAVE_PLD_RELEASE
+			skw_recovery_mode();//recoverymode open api
+#else
+			if(!strncmp((char *)skw_sdio->chip_id,"SV6160LITE",12) && !recovery_debug_status
+					&&skw_sdio->cp_state !=DEVICE_BLOCKED_EVENT){
+				skw_recovery_mode();//recoverymode open api
+			}
+#endif
+
+		}else if (!strncmp("trunk_W", cmd, 7)) {
+			memset(firmware_version, 0 , sizeof(firmware_version));
+			if (strlen(cmd) > strlen(firmware_version)) {
+				strncpy(firmware_version, cmd, sizeof(firmware_version)-1);
+				firmware_version[sizeof(firmware_version)-1] = '\0';
+			} else {
+				strncpy(firmware_version, cmd, strlen(cmd));
+				//strcpy(firmware_version, cmd);
+			}
+			cmd = strstr(firmware_version, "slp=");
+			if (cmd)
+				cp_detect_sleep_mode = cmd[4] - 0x30;
+			else
+				cp_detect_sleep_mode = 4;
+
+			if(!skw_sdio->boot_data->first_dl_flag){
+				skw_sdio_gpio_irq_pre_ops();
+			}
+			if(!skw_sdio->cp_state)
+				complete(&skw_sdio->download_done);
+
+			if(skw_sdio->cp_state){
+				assert_info_print = 0;
+				if(sdio_ports[0].state == PORT_STATE_ASST)
+					sdio_ports[0].state = PORT_STATE_OPEN;
+				modem_notify_event(DEVICE_BSPREADY_EVENT);
+			}
+			skw_sdio->cp_state = 0;
+			skw_sdio->log_data->smem_poweron = 0;
+			if (skw_sdio->boot_data->nv_mem_pnfg_data != NULL && skw_sdio->boot_data->nv_mem_pnfg_size != 0) {
+				skw_sdio_info("UPDATE '%s' PINCFG from %s\n", (char *)skw_sdio->chip_id, skw_sdio->boot_data->skw_nv_name);
+				ret = skw_pin_config();
+				if (ret)
+					skw_sdio_err("Update pin config failed!!!\n");
+			}
+		} else if (!strncmp(cmd, "BSPREADY",8)) {
+			loopcheck_send_data("RDVERSION", 9);
+		}
+		skw_sdio_dbg("Line:%d the port=%d \n", __LINE__, port->channel);
+		if (skw_sdio->adma_rx_enable) {
+			skw_page_frag_free(header);
+		}
+		return 0;
+	}
+	//skw_sdio_info("Line:%d the port=%d \n", __LINE__, port->channel);
+	if(!port->state) {
+		if(skw_sdio->adma_rx_enable){
+			if (!IS_LOG_PORT(portno))
+				skw_sdio_err(
+					"port%d discard data for wrong state\n",
+					portno);
+			skw_page_frag_free(header);
+			return 0;
+		}
+	}
+	if (port->sg_rx){
+		if (port->sg_index >= MAX_SG_COUNT){
+			skw_sdio_err(" rx sg_buffer is overflow!\n");
+		}else{
+			sg_set_buf(&port->sg_rx[port->sg_index++], header, header->len+4);
+		}
+	}else {
+		int packet=0, total=0;
+		mutex_lock(&port->rx_mutex);
+		buf_size = (port->length + port->rx_wp - port->rx_rp)%port->length;
+		buf_size = port->length - 1 - buf_size;
+		addr = (char *)(header+1);
+		data = (u32 *) addr;
+		if(((data[2] & 0xffff) != port->next_seqno) && header->len > 12) {
+			skw_sdio_err("portno:%d, packet lost recv seqno=%d expected %d\n", port->channel,
+					data[2] & 0xffff, port->next_seqno);
+			port->next_seqno = (data[2] & 0xffff);
+		}
+		if(header->len > 12) {
+			port->next_seqno++;
+			addr += 12;
+			header->len -= 12;
+			total = data[1] >> 8;
+			packet = data[2] & 0xFFFF;
+		} else if (header->len == 12) {
+			header->len = 0;
+			port->tx_flow_ctrl--;
+			skw_port_log(portno,"%s link msg: 0x%x 0x%x 0x%x: %d\n", __func__,
+					data[0], data[1], data[2], port->tx_flow_ctrl);
+			complete(&port->tx_done);
+		}
+		if (skw_sdio->cp_state && !IS_LOG_PORT(portno)) {
+			if (header->len != 245 || buf_size < 2048)
+				skw_sdio_info("(%d.%d) (%d,%d) len=%d : 0x%x\n",
+					      portno, port->next_seqno,
+					      port->rx_wp, port->rx_rp,
+					      header->len, data[3]);
+			if (buf_size < 2048)
+				msleep(10);
+		}
+		if (port->rx_submit && !port->sg_rx) {
+#ifdef CONFIG_BT_SEEKWAVE
+			if (header->len)
+#else
+			if (header->len && port->pdev)
+#endif
+				port->rx_submit(portno, port->rx_data, header->len, addr);
+		} else 	if (buf_size < header->len) {
+			skw_port_log(portno,"%s port%d overflow:buf_size %d-%d, packet size %d (w,r)=(%d, %d)\n",
+					__func__, portno, buf_size, port->length, header->len,
+					port->rx_wp,  port->rx_rp);
+		} else if(port->state && header->len) {
+			if (port->read_buffer != NULL) {
+				if(port->length - port->rx_wp > header->len){
+					memcpy(&port->read_buffer[port->rx_wp], addr, header->len);
+					port->rx_wp += header->len;
+				} else {
+					memcpy(&port->read_buffer[port->rx_wp], addr, port->length - port->rx_wp);
+					memcpy(&port->read_buffer[0], &addr[port->length - port->rx_wp],
+							header->len - port->length + port->rx_wp);
+					port->rx_wp = header->len - port->length + port->rx_wp;
+				}
+			}
+
+			if(!port->rx_flow_ctrl && buf_size-header->len < (port->length/3)) {
+				port->rx_flow_ctrl = 1;
+				skw_sdio_rx_port_follow_ctl(portno, port->rx_flow_ctrl);
+			}
+			mutex_unlock(&port->rx_mutex);
+			complete(&port->rx_done);
+			if(skw_sdio->adma_rx_enable){
+				skw_page_frag_free(header);
+			}
+			return 0;
+		}
+		mutex_unlock(&port->rx_mutex);
+		if (skw_sdio->adma_rx_enable) {
+			skw_page_frag_free(header);
+		}
+	}
+	return 0;
+}
+int send_modem_assert_command(void)
+{
+	int ret =0;
+	u32 *cmd = debug_infos.last_sent_wifi_cmd;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	char *statistic = kzalloc(2048, GFP_KERNEL);
+	if(!statistic){
+		skw_sdio_err("the statistic malloc fail !!\n");
+		return -1;
+	}
+	if(skw_sdio->cp_state) {
+		skw_sdio_err("the cp state is %d !!\n", skw_sdio->cp_state);
+		kfree(statistic);
+		statistic = NULL;
+		return ret;
+	}
+	skw_sdio->cp_state=1;/*cp except set value*/
+	ret =skw_sdio_writeb(SKW_AP2CP_IRQ_REG, BIT(4));
+	if (ret != 0) {
+		skw_sdio_err("the sdio writeb fail ret= %d !!\n",ret);
+	}
+	debug_infos.host_assert_cp_time = skw_local_clock();
+	skw_sdio_err("%s ret=%d cmd: 0x%x 0x%x 0x%x :%d-%d %ums-%ums\n", __func__,
+			 ret, cmd[0], cmd[1], cmd[2], skw_sdio->cp_fifo_status, fifo_ind, jiffies_to_msecs(debug_infos.last_sent_time), jiffies_to_msecs(debug_infos.host_assert_cp_time));
+	skw_get_assert_print_info(statistic, 2048);
+	skw_sdio_info("sdio last irqs information:\n%s\n", statistic);
+	kfree(statistic);
+	statistic = NULL;
+#ifdef CONFIG_SEEKWAVE_PLD_RELEASE
+	schedule_delayed_work(&skw_sdio->skw_except_work , msecs_to_jiffies(1000));
+#else
+	schedule_delayed_work(&skw_sdio->skw_except_work , msecs_to_jiffies(8000));
+#endif
+	return ret;
+}
+
+/* for adma */
+static int skw_sdio_adma_parser(struct skw_sdio_data_t *skw_sdio, struct scatterlist *sgs,
+				int packet_count)
+{
+	struct skw_packet_header *header = NULL;
+	unsigned int i;
+	int channel = 0;
+	unsigned int parse_len = 0;
+	uint32_t *data;
+	struct sdio_port *port;
+
+	port = &sdio_ports[0];
+	for (i = 0; i < packet_count; i++) {
+		header = (struct skw_packet_header *)sg_virt(sgs + i);
+		data = (uint32_t *)header;
+		if (atomic_read(&skw_sdio->suspending))
+			skw_sdio_info("ch:%d len:%d 0x%x 0x%x\n", header->channel, header->len, data[2], data[3]);
+		skw_port_log(header->channel, "%s[%d]:ch:%d len:0x%0x 0x%08X 0x%08X : 0x%08X 0x%08x 0x%08X\n", __func__,
+			i,	header->channel, header->len, data[2], data[3], data[5], data[6], data[7]);
+		channel = header->channel;
+
+		if (!header->eof && (channel < max_ch_num) && header->len) {
+			parse_len += header->len;
+			data = (uint32_t *)(header+1);
+			if ((channel >= max_ch_num) || (header->len >
+				(max_pac_size - sizeof(struct skw_packet_header))) ||
+				(header->len == 0)) {
+				if (channel!=0xff)
+					skw_sdio_err("%s invalid header[%d]len[%d]: 0x%x 0x%x\n",
+						__func__,  header->channel, header->len, data[0], data[1]);
+				skw_page_frag_free(header);
+				continue;
+			}
+			skw_sdio->rx_packer_cnt++;
+			skw_sdio_handle_packet(skw_sdio, sgs+i, header, channel);
+		} else {
+			skw_sdio_err("%s[%d]:ch:%d len:0x%0x 0x%08X 0x%08X : 0x%08X 0x%08x 0x%08X\n", __func__,
+			i,	header->channel, header->len, data[2], data[3], data[5], data[6], data[7]);
+#if 0
+			print_hex_dump(KERN_ERR, "PACKET ERR:", 0, 16, 1,
+					header, 1792, 1);
+			skw_sdio_err("%s PUB HAEAD ERROR: packet[%d/%d] channel=%d,size=%d eof=%d!!!",
+					__func__, i, packet_count, channel, header->len, header->eof);
+#endif
+			skw_page_frag_free(header);
+			continue;
+		}
+	}
+	if (channel >= max_ch_num)
+		skw_sdio_err("line: %d channel number error %d %d\n", __LINE__, channel, max_ch_num);
+	if (debug_infos.last_irq_time && (channel > 0 && channel < max_ch_num)) {
+		debug_infos.chn_last_irq_time[channel][debug_infos.chn_irq_cnt[channel] % CHN_IRQ_RECORD_NUM] = debug_infos.last_irq_time;
+		debug_infos.chn_irq_cnt[channel]++;
+	}
+	atomic_set(&skw_sdio->suspending, 0);
+	return 0;
+}
+
+static int skw_sdio2_adma_parser(struct skw_sdio_data_t *skw_sdio, struct scatterlist *sgs,
+				int packet_count)
+{
+	struct skw_packet2_header *header = NULL;
+	unsigned int i;
+	int channel = 0;
+	unsigned int parse_len = 0;
+	uint32_t *data;
+	struct sdio_port *port;
+
+	port = &sdio_ports[0];
+	for (i = 0; i < packet_count; i++) {
+		header = (struct skw_packet2_header *)sg_virt(sgs + i);
+		data = (uint32_t *)header;
+		if (atomic_read(&skw_sdio->suspending))
+			skw_sdio_info("ch:%d len:%d 0x%x 0x%x\n", header->channel, header->len, data[2], data[3]);
+
+		skw_sdio_dbg("[%d]:protno:%d len:0x%0x 0x%08X 0x%08X : 0x%08X 0x%08x 0x%08X\n",
+			i,      header->channel, header->len, data[2], data[3], data[5], data[6], data[7]);
+		skw_port_log(header->channel, "%s[%d]:ch:%d len:0x%0x 0x%08X 0x%08X : 0x%08X 0x%08x 0x%08X\n", __func__,
+			i,	header->channel, header->len, data[2], data[3], data[5], data[6], data[7]);
+
+		channel = header->channel;
+
+		if (!header->eof && (channel < max_ch_num) && header->len) {
+			parse_len += header->len;
+			data = (uint32_t *)(header+1);
+			if ((channel >= max_ch_num) || (header->len >
+				(max_pac_size - sizeof(struct skw_packet2_header))) ||
+				(header->len == 0)) {
+				if (channel!=0xff)
+					skw_sdio_err("%s invalid header[%d]len[%d]: 0x%x 0x%x\n",
+						__func__,  header->channel, header->len, data[0], data[1]);
+				skw_page_frag_free(header);
+				continue;
+			}
+			skw_sdio->rx_packer_cnt++;
+			skw_sdio2_handle_packet(skw_sdio, sgs+i, header, channel);
+		} else {
+			if (channel!=0xff)
+				skw_sdio_err("%s[%d]:ch:%d len:0x%0x 0x%08X 0x%08X : 0x%08X 0x%08x 0x%08X\n", __func__,
+				i,	header->channel, header->len, data[2], data[3], data[5], data[6], data[7]);
+			skw_page_frag_free(header);
+			continue;
+		}
+	}
+	if (channel >= max_ch_num && channel!=0xff)
+		skw_sdio_err("line: %d channel number error %d %d\n", __LINE__, channel, max_ch_num);
+	if (debug_infos.last_irq_time && (channel > 0 && channel < max_ch_num)) {
+		debug_infos.chn_last_irq_time[channel][debug_infos.chn_irq_cnt[channel] % CHN_IRQ_RECORD_NUM] = debug_infos.last_irq_time;
+		debug_infos.chn_irq_cnt[channel]++;
+	}
+	atomic_set(&skw_sdio->suspending, 0);
+	return 0;
+}
+/* for normal dma */
+static int skw_sdio_sdma_parser(char *data_buf, int total)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct skw_packet_header *header = NULL;
+	int channel = 0;
+	uint32_t *data;
+	unsigned char *p = NULL;
+	unsigned int parse_len = 0;
+	int current_len=0;
+#if 0
+	print_hex_dump(KERN_ERR, "skw_rx_buf:", 0, 16, 1,
+			data_buf, total, 1);
+#endif
+	header = (struct skw_packet_header *)data_buf;
+	for (parse_len = 0; parse_len < total;) {
+		if (header->eof != 0)
+			break;
+		p = (unsigned char *)header;
+		data = (uint32_t *)header;
+		if (atomic_read(&skw_sdio->suspending))
+			skw_sdio_info("ch:%d len:%d 0x%x 0x%x\n", header->channel, header->len, data[2], data[3]);
+		skw_port_log(header->channel, "%s:ch:%d len:0x%0x 0x%08X 0x%08X : 0x%08X 0x%08x 0x%08X\n", __func__,
+				header->channel, header->len, data[1], data[2], data[3], data[4], data[5]);
+		channel = header->channel;
+		current_len = header->len;
+		parse_len += MAX_PAC_SIZE;
+		if ((channel >= max_ch_num) || (current_len == 0) ||
+			(current_len > (max_pac_size - sizeof(struct skw_packet_header)))) {
+			skw_sdio_err("%s skip [%d]len[%d]\n",__func__, header->channel, current_len);
+			break;
+		}
+		skw_sdio->rx_packer_cnt++;
+		skw_sdio_handle_packet(skw_sdio, NULL, header, channel);
+		skw_port_log(header->channel, "the -header->len----%d\n", current_len);
+		/* pointer to next packet header*/
+		p += MAX_PAC_SIZE;
+		header = (struct skw_packet_header *)p;
+	}
+	if (channel >= max_ch_num)
+		skw_sdio_err("line: %d channel number error %d %d\n", __LINE__, channel, max_ch_num);
+	if (debug_infos.last_irq_time && (channel > 0 && channel < max_ch_num)) {
+		debug_infos.chn_last_irq_time[channel][debug_infos.chn_irq_cnt[channel] % CHN_IRQ_RECORD_NUM] = debug_infos.last_irq_time;
+		debug_infos.chn_irq_cnt[channel]++;
+	}
+	atomic_set(&skw_sdio->suspending, 0);
+	return 0;
+}
+static int skw_sdio2_sdma_parser(char *data_buf, int total)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct skw_packet2_header *header = NULL;
+	int channel = 0;
+	uint32_t *data;
+	unsigned char *p = NULL;
+	unsigned int parse_len = 0;
+	int current_len=0;
+#if 0
+	print_hex_dump(KERN_ERR, "skw_rx_buf:", 0, 16, 1,
+			data_buf, total, 1);
+#endif
+	header = (struct skw_packet2_header *)data_buf;
+	for (parse_len = 0; parse_len < total;) {
+		if (header->eof != 0)
+			break;
+		p = (unsigned char *)header;
+		data = (uint32_t *)header;
+		if (atomic_read(&skw_sdio->suspending))
+			skw_sdio_info("ch:%d len:%d 0x%x 0x%x\n", header->channel, header->len, data[2], data[3]);
+
+		skw_sdio_dbg("ch:%d len:0x%0x 0x%08X 0x%08X : 0x%08X 0x%08x 0x%08X\n",
+			header->channel, header->len, data[1], data[2], data[3],data[4], data[5]);
+		skw_port_log(header->channel, "ch:%d len:0x%0x 0x%08X 0x%08X : 0x%08X 0x%08x 0x%08X\n",
+				header->channel, header->len, data[1], data[2], data[3], data[4], data[5]);
+		channel = header->channel;
+		current_len = header->len;
+		parse_len += MAX2_PAC_SIZE;
+		if ((channel >= max_ch_num) || (current_len == 0) ||
+			(current_len > (max_pac_size - sizeof(struct skw_packet2_header)))) {
+			skw_sdio_err("%s skip [%d]len[%d]\n",__func__, header->channel, current_len);
+			break;
+		}
+		skw_sdio->rx_packer_cnt++;
+		skw_sdio2_handle_packet(skw_sdio, NULL, header, channel);
+		skw_port_log(header->channel, "the -header->len----%d\n", current_len);
+		/* pointer to next packet header*/
+		p +=  MAX2_PAC_SIZE;
+		header = (struct skw_packet2_header *)p;
+	}
+	if (channel >= max_ch_num)
+		skw_sdio_err("line: %d channel number error %d %d\n", __LINE__, channel, max_ch_num);
+	if (debug_infos.last_irq_time && (channel > 0 && channel < max_ch_num)) {
+		debug_infos.chn_last_irq_time[channel][debug_infos.chn_irq_cnt[channel] % CHN_IRQ_RECORD_NUM] = debug_infos.last_irq_time;
+		debug_infos.chn_irq_cnt[channel]++;
+	}
+	atomic_set(&skw_sdio->suspending, 0);
+	return 0;
+}
+struct scatterlist *skw_sdio_prepare_adma_buffer(struct skw_sdio_data_t *skw_sdio, int *sg_count, int *nsize_offset)
+{
+	struct scatterlist *sgs;
+	void *buffer;
+	int	i, j, data_size;
+	int	alloc_size = FRAGSZ_SIZE;
+
+	sgs = kzalloc((*sg_count) * sizeof(struct scatterlist), GFP_KERNEL);
+	if(sgs == NULL)
+		return NULL;
+
+	for(i = 0; i < (*sg_count) - 1; i++) {
+		skw_sdio_dbg("skw_sdio_alloc_frag ++ %d\n", i);
+		buffer = skw_sdio_alloc_frag(alloc_size,GFP_ATOMIC);
+		if(buffer)
+			sg_set_buf(&sgs[i], buffer, max_pac_size);
+		else{
+			*sg_count = i+1;
+			break;
+		}
+	}
+
+	if(i <= 0)
+		goto err;
+	sg_mark_end(&sgs[*sg_count - 1]);
+	data_size = max_pac_size*((*sg_count)-1);
+	data_size = data_size%SKW_SDIO_NSIZE_BUF_SIZE;
+	*nsize_offset = SKW_SDIO_NSIZE_BUF_SIZE - data_size;
+	if(*nsize_offset < 8)
+		*nsize_offset = SKW_SDIO_NSIZE_BUF_SIZE + *nsize_offset;
+	*nsize_offset = *nsize_offset + SKW_SDIO_NSIZE_BUF_SIZE;
+	sg_set_buf(sgs + i, skw_sdio->next_size_buf, *nsize_offset);
+	return sgs;
+err:
+	skw_sdio_err("%s failed\n", __func__);
+	if (i > 0) {
+		for (j = 0; j < i; j++) {
+			skw_page_frag_free(sg_virt(sgs + j));
+		}
+	}
+	kfree(sgs);
+	sgs = NULL;
+	return NULL;
+
+}
+
+int skw_sdio_rx_thread(void *p)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	int read_len, buf_num;
+	int ret = 0;
+	unsigned int rx_nsize = 0;
+	unsigned int valid_len = 0;
+	char *rx_buf = NULL;
+	struct scatterlist *sgs = NULL;
+	char fifo_ind = 0;
+	unsigned char reg = 0;
+
+	skw_sdio_sdma_set_nsize(0);
+	skw_sdio_adma_set_packet_num(1);
+	skw_sdio->cp_fifo_status = 0;
+	while (1) {
+		/* Wait the semaphore */
+		skw_sdio_rx_down(skw_sdio);
+		if (!debug_infos.cp_assert_time) {
+			debug_infos.last_rx_read_times[debug_infos.rx_read_cnt % CHN_IRQ_RECORD_NUM] = skw_local_clock();
+			debug_infos.rx_read_cnt++;
+		}
+		if (skw_sdio->threads_exit) {
+			skw_sdio_err("line %d threads exit\n",__LINE__);
+			break;
+		}
+		if (!SKW_CARD_ONLINE(skw_sdio)) {
+			skw_sdio_unlock_rx_ws(skw_sdio);
+			skw_sdio_err("line %d not have card\n",__LINE__);
+			continue;
+		}
+		skw_resume_check();
+		if (skw_sdio->irq_type == SKW_SDIO_EXTERNAL_IRQ) {
+			int value = gpio_get_value(skw_sdio->gpio_in);
+			if(value == 0) {
+				skw_sdio_err("line %d gpio_in:%d\n",__LINE__, value);
+				skw_sdio_unlock_rx_ws(skw_sdio);
+				continue;
+			}
+			ret = skw_sdio_readb(SKW_SDIO_CP2AP_FIFO_IND, &fifo_ind);
+			if(ret) {
+				skw_sdio_err("line %d sdio cmd52 read fail ret:%d\n",__LINE__, ret);
+				skw_sdio_unlock_rx_ws(skw_sdio);
+				continue;
+			}
+			skw_sdio_dbg("line:%d cp fifo status(%d,%d) ret=%d\n",
+					__LINE__,fifo_ind, skw_sdio->cp_fifo_status, ret);
+			if (!ret && !fifo_ind)
+				skw_sdio_dbg("cp fifo ret -- %d \n", ret);
+			if(fifo_ind == skw_sdio->cp_fifo_status && !is_timeout_kick) {
+				skw_sdio_info("line:%d cp fifo status(%d,%d) ret=%d\n",
+						__LINE__,fifo_ind, skw_sdio->cp_fifo_status, ret);
+				skw_sdio_unlock_rx_ws(skw_sdio);
+				continue;
+			}
+#if defined(SKW_BOOT_MEMPOWERON)
+			if(fifo_ind == 0xFF){
+				skw_sdio_err("line %d cp assert !! the cp2ap signal=%d\n",__LINE__,fifo_ind);
+				skw_sdio->service_index_map = SKW_WIFI;
+				//modem_notify_event(DEVICE_ASSERT_EVENT);
+				skw_sdio->boot_data->skw_dloader_module(SKW_WIFI);
+
+			}
+#endif
+		}
+		is_timeout_kick = 0;
+		skw_sdio->cp_fifo_status = fifo_ind;
+receive_again:
+		if (skw_sdio->adma_rx_enable) {
+			int	nsize_offset;
+			buf_num = skw_sdio->remain_packet;
+			if (buf_num > MAX_PAC_COUNT)
+				buf_num = MAX_PAC_COUNT;
+
+			buf_num = buf_num + 1;
+			sgs = skw_sdio_prepare_adma_buffer(skw_sdio, &buf_num, &nsize_offset);
+			buf_num = buf_num -1;
+			if (!sgs) {
+				skw_sdio_err("prepare adma buffer fail\n");
+				goto submit_packets;
+			}
+			ret = skw_sdio_adma_read(skw_sdio, sgs, buf_num + 1,
+					buf_num * max_pac_size+nsize_offset);
+			if (ret != 0) {
+				kfree(sgs);
+				sgs = NULL;
+				skw_sdio_err("%s adma read fail ret:%d\n", __func__, ret);
+				goto submit_packets;
+			}
+			rx_nsize =  *((uint32_t *)(skw_sdio->next_size_buf + (nsize_offset - 4)));
+			if (SKW_SDIO_INBAND_IRQ == skw_sdio->irq_type && rx_nsize == 0) {
+				ret = skw_sdio_readb(SDIO_INT_EXT, &reg);
+				if (ret < 0) {
+					skw_sdio_err("line %d sdio readb error ret=%d\n", __LINE__, ret);
+				} else {
+					skw_sdio_dbg("line %d SDIO_INT_EXT=0x%x\n", __LINE__, reg);
+				}
+			}
+
+			valid_len = *((uint32_t *)(skw_sdio->next_size_buf + (nsize_offset - 8)));
+			skw_sdio_dbg("line:%d total:%lld next_pac:%d:, valid len:%d cnt %d\n",
+					  __LINE__,skw_sdio->rx_packer_cnt, rx_nsize, valid_len, buf_num);
+
+			if(skw_cp_ver == SKW_SDIO_V10){
+				skw_sdio_adma_parser(skw_sdio, sgs, buf_num);
+			}
+			else{
+				skw_sdio2_adma_parser(skw_sdio, sgs, buf_num);
+			}
+			kfree(sgs);
+			sgs = NULL;
+		} else {
+			unsigned int alloc_size;
+
+			buf_num = skw_sdio->remain_packet;
+			if (buf_num > MAX_PAC_COUNT)
+				buf_num = MAX_PAC_COUNT;
+			if(skw_cp_ver == SKW_SDIO_V10)
+				read_len = MAX_PAC_SIZE * buf_num + SKW_SDIO_BLK_SIZE;
+			else
+				read_len = MAX2_PAC_SIZE * buf_num + SKW_SDIO_BLK_SIZE;
+			alloc_size = SKW_SDIO_ALIGN_BLK(read_len);
+			rx_buf = kzalloc(alloc_size, GFP_KERNEL);
+			if (!rx_buf) {
+				skw_sdio_err("line %d kzalloc fail\n",__LINE__);
+				goto submit_packets;
+			}
+
+			ret = skw_sdio_sdma_read(rx_buf, alloc_size);
+#if 0
+			print_hex_dump(KERN_ERR, "src_sdma_data:", 0, 16, 1,
+					rx_buf, alloc_size, 1);
+#endif
+			if (ret != 0) {
+				if(rx_buf){
+					kfree(rx_buf);
+					rx_buf = NULL;
+				}
+				skw_sdio_err("line %d sdma read fail ret:%d\n",__LINE__, ret);
+				rx_nsize = 0;
+				goto submit_packets;
+			}
+			rx_nsize = *((uint32_t *)(rx_buf + (alloc_size- 4)));
+			if (SKW_SDIO_INBAND_IRQ == skw_sdio->irq_type && rx_nsize == 0) {
+				ret = skw_sdio_readb(SDIO_INT_EXT, &reg);
+				if (ret < 0) {
+					skw_sdio_err("line %d sdio readb error ret=%d\n", __LINE__, ret);
+				} else {
+					skw_sdio_dbg("line %d SDIO_INT_EXT=0x%x\n", __LINE__, reg);
+				}
+			}
+			valid_len = *((uint32_t *)(rx_buf + (alloc_size - 8)));
+
+			skw_sdio_dbg("%s the sdma rx thread alloc_size:%d,read_len:%d,rx_nsize:%d,valid_len:%d\n",
+					__func__,alloc_size, read_len, rx_nsize, valid_len);
+			if(skw_cp_ver == SKW_SDIO_V10){
+				skw_sdio_sdma_parser(rx_buf, buf_num*MAX_PAC_SIZE);
+			} else {
+				skw_sdio2_sdma_parser(rx_buf, buf_num*MAX2_PAC_SIZE);
+			}
+		}
+submit_packets:
+		skw_sdio_dispatch_packets(skw_sdio);
+		if(rx_buf)
+			kfree(rx_buf);
+		skw_sdio_adma_set_packet_num(rx_nsize);
+		if (skw_sdio->power_off)
+			rx_nsize = 0;
+		if (rx_nsize > 0)
+			goto receive_again;
+
+		debug_infos.last_irq_time = 0;
+		skw_sdio_unlock_rx_ws(skw_sdio);
+	}
+	skw_sdio_info("%s exit\n", __func__);
+	return 0;
+}
+
+static int open_sdio_port(int id, void *callback, void *data)
+{
+	struct sdio_port *port;
+	if(id >= max_ch_num)
+		return -EINVAL;
+
+	port = &sdio_ports[id];
+	if((port->state==PORT_STATE_OPEN) || port->rx_submit)
+		return -EBUSY;
+	port->rx_submit = callback;
+	port->rx_data = data;
+	init_completion(&port->rx_done);
+	init_completion(&port->tx_done);
+	mutex_init(&port->rx_mutex);
+	port->state = PORT_STATE_OPEN;
+	port->tx_flow_ctrl = 0;
+	port->rx_flow_ctrl = 0;
+	if(id && id!=skw_log_port()) {
+		port->next_seqno = 1; //cp start seqno default no 1
+		port->rx_wp = port->rx_rp = 0;
+	}
+	if(id == skw_log_port()) {
+		skw_sdio_cp_log_disable(0);
+	}
+	skw_sdio_info("%s(%d) %s portno = %d\n", current->comm, current->pid, __func__, id);
+	return 0;
+}
+static int close_sdio_port(int id)
+{
+	struct sdio_port *port;
+	if(id >= max_ch_num)
+		return -EINVAL;
+	port = &sdio_ports[id];
+	skw_sdio_info("%s(state=%d) portno = %d\n", current->comm, port->state, id);
+	if(!port->state)
+		return -ENODEV;
+	port->state = PORT_STATE_CLSE;
+	port->rx_submit = NULL;
+	if(id == skw_log_port()) {
+		skw_sdio_cp_log_disable(1);
+	}
+	complete(&port->rx_done);
+	return 0;
+}
+
+void send_host_suspend_indication(struct skw_sdio_data_t *skw_sdio)
+{
+	uint32_t value = 0;
+	uint32_t timeout = 2000, timeout1 = 20;
+	if(skw_sdio->gpio_out>=0 && skw_sdio->gpio_in>=0 && skw_sdio->resume_com) {
+		skw_sdio_dbg("%s enter gpio=0\n", __func__);
+		skw_sdio->host_active = 0;
+		if (gpio_get_value(skw_sdio->gpio_in) == 0) {
+			udelay(10);
+			if (gpio_get_value(skw_sdio->gpio_in) == 0) {
+				disable_irq(skw_sdio->irq_num);
+				gpio_set_value(skw_sdio->gpio_out, 0);
+				do {
+					value = gpio_get_value(skw_sdio->gpio_in);
+					if (value || timeout1 == 0) {
+						skw_sdio_info("%s cp sts:%d in %d ms\n", __func__, value, 20 - timeout1);
+						enable_irq(skw_sdio->irq_num);
+						goto next;
+					}
+					mdelay(1);
+				} while(timeout1--);
+			}
+		}
+		gpio_set_value(skw_sdio->gpio_out, 0);
+next:
+		skw_sdio->device_active = 0;
+		do {
+			value = gpio_get_value(skw_sdio->gpio_in);
+			if(value == 0)
+				break;
+			udelay(10);
+		}while(timeout--);
+	} else
+		skw_sdio_dbg("%s enter\n", __func__);
+}
+
+void send_host_resume_indication(struct skw_sdio_data_t *skw_sdio)
+{
+	if(skw_sdio->gpio_out >= 0) {
+		skw_sdio_dbg("%s enter\n", __func__);
+		skw_sdio->host_active = 1;
+		gpio_set_value(skw_sdio->gpio_out, 1);
+		skw_sdio->resume_com = 1;
+	}
+}
+
+void send_cp_wakeup_signal(struct skw_sdio_data_t *skw_sdio)
+{
+	if(skw_sdio->gpio_out < 0)
+		return;
+
+	gpio_set_value(skw_sdio->gpio_out, 0);
+	udelay(5);
+	gpio_set_value(skw_sdio->gpio_out, 1);
+}
+
+extern int skw_sdio_enable_async_irq(void);
+int try_to_wakeup_modem(int portno)
+{
+	int ret = 0;
+	int val;
+	unsigned long flags;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	if(skw_sdio->gpio_out < 0 || skw_sdio->gpio_in < 0
+		|| skw_sdio->gpio_out == skw_sdio->gpio_in)
+		return 0;
+	skw_sdio->device_active = gpio_get_value(skw_sdio->gpio_in);
+
+	if(skw_sdio->device_active)
+		return 0;
+	skw_reinit_completion(skw_sdio->device_wakeup);
+	skw_sdio->tx_req_map |= 1<<portno;
+	skw_sdio_dbg("%s enter gpio_val=%d : %d\n", __func__, skw_sdio->device_active, skw_sdio->resume_com);
+	skw_port_log(portno,"%s enter device_active=%d : %d\n", __func__, skw_sdio->device_active, skw_sdio->resume_com);
+	if(skw_sdio->device_active == 0) {
+		local_irq_save(flags);
+		if(skw_sdio->resume_com==0)
+			gpio_set_value(skw_sdio->gpio_out, 1);
+		else
+			send_cp_wakeup_signal(skw_sdio);
+		local_irq_restore(flags);
+		ret = wait_for_completion_interruptible_timeout(&skw_sdio->device_wakeup, HZ/100);
+		if (ret < 0) {
+			skw_sdio->tx_req_map &= ~(1<<portno);
+			return -ETIMEDOUT;
+		}
+	}
+	val = gpio_get_value(skw_sdio->gpio_in);
+	if(!val) {
+		local_irq_save(flags);
+		send_cp_wakeup_signal(skw_sdio);
+		local_irq_restore(flags);
+		ret = wait_for_completion_interruptible_timeout(&skw_sdio->device_wakeup, HZ/100);
+		if (ret < 0) {
+			skw_sdio->tx_req_map &= ~(1<<portno);
+			return -ETIMEDOUT;
+		}
+		val = gpio_get_value(skw_sdio->gpio_in);
+	}
+	if ( val && !skw_sdio->sdio_func[FUNC_1]->irq_handler &&
+		!skw_sdio->resume_com && skw_sdio->irq_type == SKW_SDIO_INBAND_IRQ) {
+		sdio_claim_host(skw_sdio->sdio_func[FUNC_1]);
+		ret=sdio_claim_irq(skw_sdio->sdio_func[FUNC_1],skw_sdio_inband_irq_handler);
+		ret = skw_sdio_enable_async_irq();
+		if (ret < 0)
+			skw_sdio_err("enable sdio async irq fail ret = %d\n", ret);
+		sdio_release_host(skw_sdio->sdio_func[FUNC_1]);
+		skw_port_log(portno,"%s enable SDIO inband IRQ ret=%d\n", __func__, ret);
+	}
+	return ret;
+}
+
+int wakeup_modem(int portno)
+{
+	int ret = 0;
+	int val;
+	unsigned long flags;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	if(skw_sdio->gpio_out < 0 || skw_sdio->gpio_in < 0
+		|| skw_sdio->gpio_out == skw_sdio->gpio_in)
+		return 0;
+	skw_sdio->device_active = gpio_get_value(skw_sdio->gpio_in);
+
+	skw_reinit_completion(skw_sdio->device_wakeup);
+	skw_sdio->tx_req_map |= 1<<portno;
+	skw_sdio_dbg("%s enter gpio_val=%d : %d\n", __func__, skw_sdio->device_active, skw_sdio->resume_com);
+	skw_port_log(portno,"%s enter device_active=%d : %d\n", __func__, skw_sdio->device_active, skw_sdio->resume_com);
+
+	local_irq_save(flags);
+	if(skw_sdio->resume_com==0)
+		gpio_set_value(skw_sdio->gpio_out, 1);
+	else
+		send_cp_wakeup_signal(skw_sdio);
+	local_irq_restore(flags);
+	ret = wait_for_completion_interruptible_timeout(&skw_sdio->device_wakeup, HZ/100);
+	if (ret < 0) {
+		skw_sdio->tx_req_map &= ~(1<<portno);
+		return -ETIMEDOUT;
+	}
+
+	val = gpio_get_value(skw_sdio->gpio_in);
+	if(!val) {
+		local_irq_save(flags);
+		send_cp_wakeup_signal(skw_sdio);
+		local_irq_restore(flags);
+		ret = wait_for_completion_interruptible_timeout(&skw_sdio->device_wakeup, HZ/100);
+		if (ret < 0) {
+			skw_sdio->tx_req_map &= ~(1<<portno);
+			return -ETIMEDOUT;
+		}
+		val = gpio_get_value(skw_sdio->gpio_in);
+	}
+	if ( val && !skw_sdio->sdio_func[FUNC_1]->irq_handler &&
+		!skw_sdio->resume_com && skw_sdio->irq_type == SKW_SDIO_INBAND_IRQ) {
+		sdio_claim_host(skw_sdio->sdio_func[FUNC_1]);
+		ret=sdio_claim_irq(skw_sdio->sdio_func[FUNC_1],skw_sdio_inband_irq_handler);
+		ret = skw_sdio_enable_async_irq();
+		if (ret < 0)
+			skw_sdio_err("enable sdio async irq fail ret = %d\n", ret);
+		sdio_release_host(skw_sdio->sdio_func[FUNC_1]);
+		skw_port_log(portno,"%s enable SDIO inband IRQ ret=%d\n", __func__, ret);
+	}
+	return ret;
+}
+
+void host_gpio_in_routine(int value)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	int  device_active = skw_sdio->device_active;
+	if(skw_sdio->gpio_out < 0)
+		return;
+
+	skw_sdio->device_active = value;
+	skw_sdio_dbg("%s enter %d-%d, host tx=0x%x:%d\n", __func__, device_active,
+			skw_sdio->device_active, skw_sdio->tx_req_map, skw_sdio->host_active);
+	if(device_active  && !skw_sdio->device_active &&
+		skw_sdio->tx_req_map && skw_sdio->host_active) {
+		send_cp_wakeup_signal(skw_sdio);
+	}
+	if(skw_sdio->device_active && atomic_read(&skw_sdio->resume_flag))
+		complete(&skw_sdio->device_wakeup);
+	if(skw_sdio->device_active) {
+		if(skw_sdio->host_active == 0)
+			skw_sdio->host_active = 1;
+		gpio_set_value(skw_sdio->gpio_out, 1);
+		skw_sdio->resume_com = 1;
+	}
+}
+
+static int setup_sdio_packet(void *packet, u8 channel, char *msg, int size)
+{
+	struct skw_packet_header *header = NULL;
+	u32 *data = packet;
+
+	data[0] = 0;
+	header = (struct skw_packet_header *)data;
+	header->channel = channel;
+	header->len = size;
+	memcpy(data+1, msg, size);
+	data++;
+	data[size>>2] = 0;
+	header = (struct skw_packet_header *)&data[size>>2];
+	header->eof = 1;
+	size += 8;
+	return size;
+}
+static int setup_sdio2_packet(void *packet, u8 channel, char *msg, int size)
+{
+	struct skw_packet2_header *header = NULL;
+	u32 *data = packet;
+
+	data[0] = 0;
+	header = (struct skw_packet2_header *)data;
+	header->channel = channel;
+	header->len = size;
+	memcpy(data+1, msg, size);
+	data++;
+	data[size>>2] = 0;
+	header = (struct skw_packet2_header *)&data[size>>2];
+	header->eof = 1;
+	size += 8;
+	return size;
+}
+int loopcheck_send_data(char *buffer, int size)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_port *port;
+	int ret, count;
+	int loop_port = 0;
+	if(skw_cp_ver == SKW_SDIO_V10){
+		loop_port = LOOPCHECK_PORT;
+	} else {
+		loop_port = SDIO2_LOOPCHECK_PORT;
+	}
+	port = &sdio_ports[loop_port];
+	count = (size + 3) & 0xFFFFFFFC;
+	if(count + 8 < port->length) {
+		if(skw_cp_ver == SKW_SDIO_V10){
+			count = setup_sdio_packet(port->write_buffer, port->channel, buffer, count);
+		}
+		else{
+			count = setup_sdio2_packet(port->write_buffer, port->channel, buffer, count);
+		}
+		try_to_wakeup_modem(loop_port);
+		if(!(ret = skw_sdio_sdma_write(port->write_buffer, count))) {
+			port->total += count;
+			port->sent_packet++;
+			ret = size;
+		}
+		skw_sdio->tx_req_map &= ~(1<<loop_port);
+		return ret;
+	}
+	return -ENOMEM;
+}
+
+static int skw_sdio_suspend_send_data(int portno, char *buffer, int size)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_port *port;
+	int ret, count, i;
+	u32 *data = (u32 *)buffer;
+	if(size==0)
+		return 0;
+	if(portno >= max_ch_num)
+		return -EINVAL;
+	port = &sdio_ports[portno];
+	if(!port->state || skw_sdio->cp_state)
+		return -EIO;
+
+	if(port->state == PORT_STATE_CLSE) {
+		port->state = PORT_STATE_IDLE;
+		return -EIO;
+	}
+
+	count = (size + 3) & 0xFFFFFFFC;
+	if(count + 8 < port->length) {
+		if(skw_cp_ver == SKW_SDIO_V10){
+			count = setup_sdio_packet(port->write_buffer, port->channel, buffer, count);
+		}
+		else{
+			count = setup_sdio2_packet(port->write_buffer, port->channel, buffer, count);
+		}
+		skw_reinit_completion(port->tx_done);
+		try_to_wakeup_modem(portno);
+
+		if(skw_sdio->cp_state)
+			return -EIO;
+
+		if(!(ret = skw_sdio_sdma_write(port->write_buffer, count))) {
+			port->tx_flow_ctrl++;
+			if(sdio_ports[portno].state != PORT_STATE_ASST) {
+				ret = wait_for_completion_interruptible_timeout(&port->tx_done,
+						msecs_to_jiffies(100));
+				if(!ret && port->tx_flow_ctrl) {
+					try_to_wakeup_modem(portno);
+					port->tx_flow_ctrl--;
+				}
+			}
+			port->total += count;
+			port->sent_packet++;
+			ret = size;
+		} else {
+			skw_sdio_info("%s ret=%d\n", __func__, ret);
+		}
+		skw_sdio->tx_req_map &= ~(1<<portno);
+		skw_port_log(portno,"%s port%d size=%d 0x%x 0x%x\n",
+			__func__, portno, size, data[0], data[1]);
+		return ret;
+	} else {
+		for(i=0; i<2; i++) {
+			try_to_wakeup_modem(portno);
+			if(!(ret = skw_sdio_sdma_write(buffer, count))) {
+				port->total += count;
+				port->sent_packet++;
+				ret = size;
+				break;
+			} else {
+				skw_sdio_info("%s ret=%d\n", __func__, ret);
+				if(ret == -ETIMEDOUT && !skw_sdio->device_active)
+					continue;
+			}
+		}
+		skw_sdio->tx_req_map &= ~(1<<portno);
+		skw_port_log(portno,"%s port%d size=%d 0x%x 0x%x\n",
+			__func__, portno, size, data[0], data[1]);
+		return ret;
+	}
+	return -ENOMEM;
+}
+static int send_data(int portno, char *buffer, int size)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	struct sdio_port *port;
+	int ret, count, i;
+	u32 *data = (u32 *)buffer;
+	if(size==0)
+		return 0;
+	if(portno >= max_ch_num)
+		return -EINVAL;
+	port = &sdio_ports[portno];
+	if(!port->state || skw_sdio->cp_state)
+		return -EIO;
+
+	if(port->state == PORT_STATE_CLSE) {
+		port->state = PORT_STATE_IDLE;
+		return -EIO;
+	}
+
+	count = (size + 3) & 0xFFFFFFFC;
+	if(count + 8 < port->length) {
+		if(skw_cp_ver == SKW_SDIO_V10){
+			count = setup_sdio_packet(port->write_buffer, port->channel, buffer, count);
+		}
+		else{
+			count = setup_sdio2_packet(port->write_buffer, port->channel, buffer, count);
+		}
+		skw_reinit_completion(port->tx_done);
+		for(i=0; i<2; i++) {
+		try_to_wakeup_modem(portno);
+
+		if(skw_sdio->cp_state)
+			return -EIO;
+
+		if(!(ret = skw_sdio_sdma_write(port->write_buffer, count))) {
+			port->tx_flow_ctrl++;
+			if(sdio_ports[portno].state != PORT_STATE_ASST) {
+				ret = wait_for_completion_interruptible_timeout(&port->tx_done, 
+						msecs_to_jiffies(100));
+				if(!ret && port->tx_flow_ctrl) {
+					skw_sdio_info("%s ret=%d:%d and retry again\n", __func__, ret, port->tx_flow_ctrl);
+					port->tx_flow_ctrl--;
+					continue;
+				}
+			}
+			port->total += count;
+			port->sent_packet++;
+			ret = size;
+			break;
+		} else {
+			skw_sdio_info("%s ret=%d\n", __func__, ret);
+			if(ret == -ETIMEDOUT && !skw_sdio->device_active)
+				continue;
+		}
+		}
+		skw_sdio->tx_req_map &= ~(1<<portno);
+		skw_port_log(portno,"%s port%d size=%d 0x%x 0x%x\n",
+			__func__, portno, size, data[0], data[1]);
+		return ret;
+	} else {
+		for(i=0; i<2; i++) {
+			try_to_wakeup_modem(portno);
+			if(!(ret = skw_sdio_sdma_write(buffer, count))) {
+				port->total += count;
+				port->sent_packet++;
+				ret = size;
+				break;
+			} else {
+				skw_sdio_info("%s ret=%d\n", __func__, ret);
+				if(ret == -ETIMEDOUT && !skw_sdio->device_active)
+					continue;
+			}
+		}
+		skw_sdio->tx_req_map &= ~(1<<portno);
+		skw_port_log(portno,"%s port%d size=%d 0x%x 0x%x\n",
+			__func__, portno, size, data[0], data[1]);
+		return ret;
+	}
+	return -ENOMEM;
+}
+static int sdio_read(struct sdio_port *port, char *buffer, int size)
+{
+	int data_size;
+	int	ret = 0;
+	int buffer_size;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	skw_sdio_dbg("%s buffer size = %d , (wp, rp) = (%d, %d), state %d\n",
+			__func__, size, port->rx_wp, port->rx_rp, port->state);
+	if(port->state == PORT_STATE_ASST) {
+		skw_sdio_err("Line:%d The CP assert  portno =%d error code =%d cp_state=%d !!\n",__LINE__,
+				port->channel, ENOTCONN, skw_sdio->cp_state);
+		if(skw_sdio->cp_state!=0){
+			if(port->channel==skw_log_port())
+				port->state = PORT_STATE_OPEN;
+
+			return -ENOTCONN;
+		}
+	}
+try_again0:
+	skw_reinit_completion(port->rx_done);
+	if(port->rx_wp == port->rx_rp) {
+
+		if ((port->state == PORT_STATE_CLSE) ||((port->channel>0&&port->channel !=skw_log_port())
+					&& !(skw_sdio->service_state_map & (1<<BT_SERVICE)))) {
+			skw_sdio_err("the log port or at port ---%d --%d\n", port->channel, skw_log_port());
+			return -EIO;
+		}
+		if (port->timeout==0) {
+			ret = wait_for_completion_interruptible(&port->rx_done);
+			if(ret)
+				return ret;
+		} else{
+			ret = wait_for_completion_interruptible_timeout(&port->rx_done,msecs_to_jiffies(port->timeout));
+			if(ret==0) {
+                               skw_sdio_info(" read timeout=%d\n", port->timeout);
+                               return ret;
+			}
+		}
+		if(port->state == PORT_STATE_CLSE) {
+			port->state = PORT_STATE_IDLE;
+			return -EAGAIN;
+		}else if(port->state == PORT_STATE_ASST) {
+			skw_sdio_err("The CP assert  portno =%d error code =%d!!!!\n", port->channel, ENOTCONN);
+			if(skw_sdio->cp_state!=0){
+				if(port->channel==skw_log_port())
+					port->state = PORT_STATE_OPEN;
+
+				return -ENOTCONN;
+			}
+		}
+	}
+	mutex_lock(&port->rx_mutex);
+	data_size = (port->length + port->rx_wp - port->rx_rp)%port->length;
+	if(data_size==0) {
+		skw_sdio_info("%s buffer size = %d , (wp, rp) = (%d, %d)\n",
+			__func__, size, port->rx_wp, port->rx_rp);
+		mutex_unlock(&port->rx_mutex);
+		goto try_again0;
+	}
+	if(size > data_size)
+		size = data_size;
+	data_size = port->length - port->rx_rp;
+	if(size > data_size) {
+		memcpy(buffer, &port->read_buffer[port->rx_rp], data_size);
+		memcpy(buffer+data_size, &port->read_buffer[0], size - data_size);
+		port->rx_rp = size - data_size;
+	} else {
+		skw_sdio_dbg("size1 = %d , (wp, rp) = (%d, %d) (packet, total)=(%d, %d)\n",
+				size, port->rx_wp, port->rx_rp, port->rx_packet, port->rx_count);
+		memcpy(buffer, &port->read_buffer[port->rx_rp], size);
+		port->rx_rp += size;
+	}
+
+	if (port->rx_rp == port->length)
+		port->rx_rp = 0;
+
+	if(port->rx_rp == port->rx_wp){
+		port->rx_rp = 0;
+		port->rx_wp = 0;
+	}
+	if(port->rx_flow_ctrl) {
+		buffer_size = (port->length + port->rx_wp - port->rx_rp)%port->length;
+		buffer_size = port->length - 1 - buffer_size;
+
+		if (buffer_size > (port->length*2/3)) {
+			port->rx_flow_ctrl = 0;
+			skw_sdio_rx_port_follow_ctl(port->channel, port->rx_flow_ctrl);
+		}
+	}
+	mutex_unlock(&port->rx_mutex);
+	return size;
+}
+
+int recv_data(int portno, char *buffer, int size)
+{
+	struct sdio_port *port;
+	int ret;
+	if(size==0)
+		return 0;
+	if(portno >= max_ch_num)
+		return -EINVAL;
+	port = &sdio_ports[portno];
+	if(!port->state)
+		return -EIO;
+	if(port->state == PORT_STATE_CLSE) {
+		port->state = PORT_STATE_IDLE;
+		return -EIO;
+	}
+	ret = sdio_read(port, buffer, size);
+	return ret;
+}
+
+int recv_data_timeout(int portno, char *buffer, int size, int *actual, int timeout)
+{
+       struct sdio_port *port;
+       int ret;
+       if(size==0)
+               return 0;
+       if(portno >= max_ch_num)
+               return -EINVAL;
+       port = &sdio_ports[portno];
+       if(!port->state)
+               return -EIO;
+       if(port->state == PORT_STATE_CLSE) {
+               port->state = PORT_STATE_IDLE;
+               return -EIO;
+       }
+       port->timeout = timeout;
+       ret = sdio_read(port, buffer, size);
+       port->timeout = 0;
+       return ret;
+}
+
+int send_data_timeout(int portno, char *buffer, int size, int *actual, int timeout)
+{
+       struct sdio_port *port;
+       int ret;
+       if(size==0)
+               return 0;
+       if(portno >= max_ch_num)
+               return -EINVAL;
+       port = &sdio_ports[portno];
+       if(!port->state)
+               return -EIO;
+       if(port->state == PORT_STATE_CLSE) {
+               port->state = PORT_STATE_IDLE;
+               return -EIO;
+       }
+       ret = send_data(portno, buffer, size);
+       return ret;
+}
+
+int skw_sdio_suspend_adma_cmd(int portno, struct scatterlist *sg, int sg_num, int total)
+{
+	struct sdio_port *port;
+	int ret, i;
+	int irq_state = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	u32 *data;
+
+	if(total==0)
+		return 0;
+	if(portno >= max_ch_num)
+		return -EINVAL;
+	port = &sdio_ports[portno];
+	if(!port->state)
+		return -EIO;
+	data = (u32 *)sg_virt(sg);
+	irq_state = skw_sdio_irq_ops(0);
+	for(i=0; i<2; i++) {
+		try_to_wakeup_modem(portno);
+		ret = skw_sdio_adma_write(portno, sg, sg_num, total);
+		if(skw_sdio->gpio_in >=0)
+			skw_sdio_info("timeout gpioin value=%d \n",gpio_get_value(skw_sdio->gpio_in));
+		if(!ret){
+			break;
+		}
+	}
+	if(!irq_state){
+		skw_sdio_irq_ops(1);
+	}
+	skw_sdio->tx_req_map &= ~(1<<portno);
+	skw_port_log(portno,"%s port%d sg_num=%d total=%d 0x%x 0x%x\n",
+			__func__, portno, sg_num, total, data[0], data[1]);
+	if(portno == WIFI_CMD_PORT) {
+		memcpy(debug_infos.last_sent_wifi_cmd, data, 12);
+		debug_infos.last_sent_time = jiffies;
+		if (skw_sdio->gpio_in >=0 && !gpio_get_value(skw_sdio->gpio_in)) {
+			skw_sdio_info("modem is sleep and wakeup it\n");
+			try_to_wakeup_modem(portno);
+		}
+	}
+	port->total += total;
+	port->sent_packet += sg_num;
+	return ret;
+}
+
+static int skw_sdio_irq_ops(int irq_enable)
+{
+	int ret =-1;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+#if 0//def CONFIG_SKW_DL_TIME_STATS
+	ktime_t cur_time,last_time;
+#endif
+	//struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	if(skw_sdio->gpio_in < 0 ) {
+		skw_sdio_warn("gpio_in < 0 no need the cls the irq ops !!\n");
+		return ret;
+	}
+	skw_sdio_info("gpio_in num %d the value %d !!\n",skw_sdio->gpio_in,gpio_get_value(skw_sdio->gpio_in));
+	if(irq_enable){
+		skw_sdio_info("enable irq\n");
+		skw_sdio->suspend_wake_unlock_enable = 1;
+		enable_irq(skw_sdio->irq_num);
+		ret=0;
+		//enable_irq_wake(skw_sdio->irq_num);
+		//last_time = ktime_get();
+		//skw_sdio_info("line %d start time %llu and the over time %llu ,the usertime=%llu \n",__LINE__,
+		//cur_time, last_time,(last_time-cur_time));
+	}else{
+		if (gpio_get_value(skw_sdio->gpio_in) == 0) {
+			udelay(10);
+			if (gpio_get_value(skw_sdio->gpio_in) == 0) {
+				disable_irq(skw_sdio->irq_num);
+				ret=0;
+#if 0//def CONFIG_SKW_DL_TIME_STATS
+				cur_time = ktime_get();
+#endif
+				skw_sdio_info("disable irq\n");
+			}else{
+				skw_sdio_info("NO disable irq cp wake !the value %d !!\n",gpio_get_value(skw_sdio->gpio_in));
+				ret = -2;
+			}
+		}
+		//disable_irq_wake(skw_sdio->irq_num);
+		//disable_irq(skw_sdio->irq_num);
+	}
+
+	return ret;
+};
+
+int wifi_send_cmd(int portno, struct scatterlist *sg, int sg_num, int total)
+{
+	struct sdio_port *port;
+	int ret, i;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	u32 *data;
+
+	if(total==0)
+		return 0;
+	if(portno >= max_ch_num)
+		return -EINVAL;
+	port = &sdio_ports[portno];
+	if(!port->state)
+		return -EIO;
+	data = (u32 *)sg_virt(sg);
+	for(i=0; i<2; i++) {
+		try_to_wakeup_modem(portno);
+		ret = skw_sdio_adma_write(portno, sg, sg_num, total);
+		if(!ret)
+			break;
+		if (skw_sdio->gpio_in >= 0) {
+			skw_sdio_info("timeout gpioin value=%d \n",gpio_get_value(skw_sdio->gpio_in));
+		}
+	}
+	skw_sdio->tx_req_map &= ~(1<<portno);
+	if (portno == WIFI_CMD_PORT ||
+	   (skw_cp_ver == SKW_SDIO_V20 && portno ==  WIFI_DATA_PORT)) {
+		skw_port_log(portno, "%s port%d sg_num=%d total=%d 0x%x 0x%x 0x%x 0x%x\n",
+			__func__, portno, sg_num, total, data[0], data[1], data[2], data[3]);
+		memcpy(debug_infos.last_sent_wifi_cmd, data, 12);
+		debug_infos.last_sent_time = skw_local_clock();
+	}
+	port->total += total;
+	port->sent_packet += sg_num;
+	return ret;
+}
+static int register_rx_callback(int id, void *func, void *para)
+{
+	struct sdio_port *port;
+
+	if(id >= max_ch_num)
+		return -EINVAL;
+	port = &sdio_ports[id];
+	if(port->state && func)
+		return -EBUSY;
+	port->rx_submit = func;
+	port->rx_data = para;
+	if(func) {
+		port->sg_rx = kzalloc(MAX_SG_COUNT * sizeof(struct scatterlist), GFP_KERNEL);
+		if(port->sg_rx == NULL)
+			return -ENOMEM;
+		port->state = PORT_STATE_OPEN;
+	} else {
+		if (port->sg_rx) {
+			kfree(port->sg_rx);
+			port->sg_rx = NULL;
+		}
+		port->state = PORT_STATE_IDLE;
+	}
+
+	return 0;
+}
+/***************************************************************************
+ *Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ **************************************************************************/
+static int bt_service_start(void)
+{
+	int ret =0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+#if defined(SKW_BOOT_MEMPOWERON)
+	ktime_t cur, start_poll;
+	skw_sdio_info("the ---debug---line:%d \n",__LINE__);
+
+	cur = ktime_get();
+	if(skw_sdio->boot_data==NULL ||(skw_sdio->service_state_map & (1<<BT_SERVICE)))
+		return ret;
+
+	skw_sdio_info("the ---debug---line:%d \n",__LINE__);
+	mutex_lock(&skw_sdio->service_mutex);
+	if(skw_sdio->boot_data->iram_img_data && skw_sdio->service_index_map){
+		skw_sdio_info("just download the BT img!!\n");
+		skw_sdio->service_index_map = SKW_BT;
+		skw_sdio_poweron_mem(SKW_BT);
+		skw_sdio->boot_data->skw_dloader_module(SKW_BT);
+	}
+	ret=skw_sdio->boot_data->bt_start();
+	skw_sdio->service_index_map = SKW_BT;
+	start_poll = ktime_get();
+	skw_sdio_info("the start service time =%lld", ktime_sub(start_poll, cur));
+	mutex_unlock(&skw_sdio->service_mutex);
+#else
+	if(skw_sdio->boot_data==NULL ||(skw_sdio->service_state_map & (1<<BT_SERVICE)))
+		return ret;
+	ret = skw_sdio->boot_data->bt_start();
+#endif
+	return ret;
+}
+
+/***************************************************************************
+ *Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ **************************************************************************/
+static int bt_service_stop(void)
+{
+	int ret =0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	skw_sdio_info("the ---debug---line:%d \n",__LINE__);
+
+	if(skw_sdio->boot_data ==NULL)
+		return ret;
+	mutex_lock(&skw_sdio->service_mutex);
+	ret=skw_sdio->boot_data->bt_stop();
+	mutex_unlock(&skw_sdio->service_mutex);
+	return ret;
+}
+
+
+/***************************************************************************
+ *Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ **************************************************************************/
+static int wifi_service_start(void)
+{
+	int ret =0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	skw_sdio_info("the ---debug---line:%d \n",__LINE__);
+
+	if (skw_sdio->boot_data==NULL ||(skw_sdio->service_state_map & (1<<WIFI_SERVICE)))
+		return 0;
+#if defined(SKW_BOOT_MEMPOWERON)
+	skw_sdio_info("the ---debug---line:%d \n",__LINE__);
+	mutex_lock(&skw_sdio->service_mutex);
+#if SKW_WIFIONLY_DEBUG//for the debug wifi only  setvalue 0
+	if(skw_sdio->boot_data->iram_img_data && glb_wifiready_done){
+#else
+	if(skw_sdio->boot_data->iram_img_data && skw_sdio->service_index_map){
+#endif
+		//skw_sdio->service_index_map &= SKW_WIFI;
+		skw_sdio_info("the ---debug---line:%d \n",__LINE__);
+#if 0
+		if(!skw_sdio->service_index_map){
+			skw_sdio_info("the first downkload firmware!!\n");
+			//skw_recovery_mode();
+			//skw_sdio->service_index_map = SKW_WIFI;
+		}else{
+			skw_sdio_info("just download the WIFI img!!\n");
+			skw_sdio_poweron_mem(SKW_WIFI);
+			skw_sdio->boot_data->skw_dloader_module(SKW_WIFI);
+		}
+#endif
+		skw_sdio_info("just download the WIFI img!!\n");
+		skw_sdio->service_index_map = SKW_WIFI;
+		skw_sdio_poweron_mem(SKW_WIFI);
+		skw_sdio->boot_data->skw_dloader_module(SKW_WIFI);
+	}
+	skw_sdio_info("the ---debug---line:%d \n",__LINE__);
+	if (skw_sdio->boot_data->wifi_start)
+		ret=skw_sdio->boot_data->wifi_start();
+	skw_sdio->service_index_map = SKW_WIFI;
+	//skw_sdio->service_index_map = SKW_WIFI;
+	glb_wifiready_done=1;
+	mutex_unlock(&skw_sdio->service_mutex);
+#else
+	ret=skw_sdio->boot_data->wifi_start();
+#endif
+	return ret;
+}
+
+/***************************************************************************
+ *Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ **************************************************************************/
+static int wifi_service_stop(void)
+{
+	int ret =0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	skw_sdio_info ("the ---debug---line:%d \n",__LINE__);
+	//debug code end
+	if(skw_sdio->boot_data ==NULL){
+		skw_sdio_info("no wifi service start before!!");
+		return ret;
+	}
+	//mutex_lock(&skw_sdio->service_mutex);
+	if(skw_sdio->boot_data->wifi_stop)
+		ret=skw_sdio->boot_data->wifi_stop();
+	//mutex_unlock(&skw_sdio->service_mutex);
+	return ret;
+}
+
+static int wifi_get_credit(void)
+{
+	char val;
+	int err;
+
+	err = skw_sdio_readb(SDIOHAL_PD_DL_CP2AP_SIG4, &val);
+	if(err)
+		return err;
+	return val;
+}
+static int wifi_store_credit_to_cp(unsigned char val)
+{
+	int err;
+
+	err = skw_sdio_writeb(SKW_SDIO_CREDIT_TO_CP, val);
+
+	return err;
+}
+
+void kick_rx_thread(void)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	debug_infos.cmd_timeout_cnt++;
+	is_timeout_kick = 1;
+	if(skw_sdio->gpio_out < 0) {
+		skw_sdio_rx_up(skw_sdio);
+	} else {
+		skw_sdio->device_active = gpio_get_value(skw_sdio->gpio_in);
+		if(skw_sdio->device_active) {
+			skw_sdio_rx_up(skw_sdio);
+		} else {
+			try_to_wakeup_modem(LOOPCHECK_PORT);
+		}
+	}
+}
+/************************************************************************
+ *Decription:bluetooth log enable
+ *Author:junwei.jiang
+ *Date:2023-02-16
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int skw_sdio_bluetooth_log(int disable)
+{
+	int ret = 0;
+	skw_sdio_info("Enter\n");
+	if (disable) {
+		skw_sdio_info("disable the CP log \n");
+		disable = 0x10;
+	} else {
+		skw_sdio_info(" enable the CP log !!\n");
+		disable = 0x18;
+	}
+	ret = skw_sdio_writeb(SDIOHAL_CPLOG_TO_AP_SWITCH, disable);
+	if (ret < 0) {
+		skw_sdio_err("cls the log signal fail ret=%d\n", ret);
+		return ret;
+	}
+	ret = skw_sdio_writeb(SKW_AP2CP_IRQ_REG, BIT(5));
+	if (ret < 0) {
+		skw_sdio_err("cls log irq fail ret=%d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+struct sv6160_platform_data wifi_pdata = {
+	.data_port =  WIFI_DATA_PORT,
+	.cmd_port =  WIFI_CMD_PORT,
+	.bus_type = SDIO_LINK|TX_ADMA|RX_ADMA|CP_DBG,
+	.max_buffer_size = 84*1536,
+	.align_value = 256,
+	.hw_adma_tx = wifi_send_cmd,
+	.hw_sdma_tx = send_data,
+	.callback_register = register_rx_callback,
+	.modem_assert = send_modem_assert_command,
+	.service_start = wifi_service_start,
+	.service_stop = wifi_service_stop,
+	.skw_dloader = skw_sdio_dloader,
+	.modem_register_notify = modem_register_notify,
+	.modem_unregister_notify = modem_unregister_notify,
+	.wifi_power_on = skw_sdio_wifi_power_on,
+	.at_ops = {
+		.port = 0,
+		.open = open_sdio_port,
+		.close = close_sdio_port,
+		.read = recv_data,
+		.write = send_data,
+		.read_tm = recv_data_timeout,
+		.write_tm = send_data_timeout,
+	},
+	.wifi_get_credit=wifi_get_credit,
+	.wifi_store_credit=wifi_store_credit_to_cp,
+	.debug_info = assert_context,
+	.rx_thread_wakeup = kick_rx_thread,
+	.suspend_adma_cmd = skw_sdio_suspend_adma_cmd,
+	.suspend_sdma_cmd  = skw_sdio_suspend_send_data,
+
+};
+struct sv6160_platform_data ucom_pdata = {
+	.data_port = 2,
+	.cmd_port  = 3,
+	.audio_port = 4,
+	.bus_type = SDIO_LINK,
+	.max_buffer_size = 0x1000,
+	.align_value = 4,
+	.hw_sdma_rx = recv_data,
+	.hw_sdma_tx = send_data,
+	.open_port = open_sdio_port,
+	.close_port = close_sdio_port,
+	.modem_assert = send_modem_assert_command,
+	.service_start = bt_service_start,
+	.service_stop = bt_service_stop,
+	.modem_register_notify = modem_register_notify,
+	.modem_unregister_notify = modem_unregister_notify,
+	.skw_dump_mem = skw_sdio_dump,
+	.bluetooth_log_disable = skw_sdio_bluetooth_log,
+};
+
+int skw_sdio_bind_platform_driver(struct sdio_func *func)
+{
+	struct platform_device *pdev;
+	char	pdev_name[32];
+	struct sdio_port *port;
+	int ret = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	memset(sdio_ports, 0, sizeof(struct sdio_port)*max_ch_num);
+	sprintf(pdev_name, "skw_ucom");
+/*
+ *	creaete AT device
+ */
+	pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+	if(!pdev)
+		return -ENOMEM;
+	pdev->dev.parent = &func->dev;
+	pdev->dev.dma_mask = &port_dmamask;
+	pdev->dev.coherent_dma_mask = port_dmamask;
+	ucom_pdata.port_name = "ATC";
+	ucom_pdata.data_port = 0;
+	memcpy(ucom_pdata.chipid, skw_sdio->chip_id, SKW_CHIP_ID_LENGTH);
+	ret = platform_device_add_data(pdev, &ucom_pdata, sizeof(ucom_pdata));
+	if(ret) {
+		dev_err(&func->dev, "failed to add platform data \n");
+		platform_device_put(pdev);;
+		return ret;
+	}
+	port = &sdio_ports[ucom_pdata.data_port];
+	port->state = PORT_STATE_IDLE;
+	port->next_seqno = 1; //cp start seqno default no 1
+	ret = platform_device_add(pdev);
+	if(ret) {
+		dev_err(&func->dev, "failt to register platform device\n");
+		platform_device_put(pdev);
+		return ret;
+	}
+	port->pdev = pdev;
+	port->channel = ucom_pdata.data_port;
+	port->length = SDIO_BUFFER_SIZE >> 2; //4K
+	port->read_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->read_buffer == NULL) {
+		port->pdev = NULL;
+		platform_device_put(pdev);
+		dev_err(&func->dev, "failed to allocate %s RX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	}
+	port->write_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->write_buffer == NULL) {
+		kfree(port->read_buffer);
+		port->read_buffer = NULL;
+		platform_device_put(pdev);
+		dev_err(&func->dev, "failed to allocate %s TX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	}
+/*
+ *	creaete log device
+ */
+	pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+	if(!pdev)
+		return -ENOMEM;
+	pdev->dev.parent = &func->dev;
+	pdev->dev.dma_mask = &port_dmamask;
+	pdev->dev.coherent_dma_mask = port_dmamask;
+	ucom_pdata.port_name = "LOG";
+	if(skw_cp_ver == SKW_SDIO_V10)
+		ucom_pdata.data_port = 1;
+	else
+		ucom_pdata.data_port = SDIO2_BSP_LOG_PORT;
+	ret = platform_device_add_data(pdev, &ucom_pdata, sizeof(ucom_pdata));
+	if(ret) {
+		dev_err(&func->dev, "failed to add %s device \n", ucom_pdata.port_name);
+		platform_device_put(pdev);
+		return ret;
+	}
+	port = &sdio_ports[ucom_pdata.data_port];
+	port->state = PORT_STATE_IDLE;
+	port->next_seqno = 1; //cp start seqno default no 1
+	ret = platform_device_add(pdev);
+	if(ret) {
+		dev_err(&func->dev, "failt to register platform device\n");
+		platform_device_put(pdev);
+		return ret;
+	}
+
+	port->pdev = pdev;
+	port->channel = ucom_pdata.data_port;
+	port->length = SDIO_BUFFER_SIZE >> 2; //4K
+	port->read_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->read_buffer == NULL) {
+		platform_device_put(pdev);
+		dev_err(&func->dev, "failed to allocate %s RX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	}
+	port->write_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->write_buffer == NULL) {
+		kfree(port->read_buffer);
+		port->read_buffer = NULL;
+		platform_device_put(pdev);
+		dev_err(&func->dev, "failed to allocate %s TX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	}
+#ifndef CONFIG_BT_SEEKWAVE
+	/*
+	*	creaete LOOPCHECK device
+	*/
+	pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+	if(!pdev)
+		return -ENOMEM;
+	pdev->dev.parent = &func->dev;
+	pdev->dev.dma_mask = &port_dmamask;
+	pdev->dev.coherent_dma_mask = port_dmamask;
+	ucom_pdata.port_name = "LOOPCHECK";
+	if(skw_cp_ver == SKW_SDIO_V10)
+		ucom_pdata.data_port = 7;
+	else
+		ucom_pdata.data_port = SDIO2_LOOPCHECK_PORT;
+	ret = platform_device_add_data(pdev, &ucom_pdata, sizeof(ucom_pdata));
+	if(ret) {
+		dev_err(&func->dev, "failed to add platform data \n");
+		platform_device_put(pdev);;
+		return ret;
+	}
+	port = &sdio_ports[ucom_pdata.data_port];
+	port->state = PORT_STATE_IDLE;
+	ret = platform_device_add(pdev);
+	if(ret) {
+		dev_err(&func->dev, "failt to register platform device\n");
+		platform_device_put(pdev);
+		return ret;
+	}
+
+	port->pdev = pdev;
+	port->channel = ucom_pdata.data_port;
+	port->length = SDIO_BUFFER_SIZE >> 2;
+	port->read_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->read_buffer == NULL) {
+		dev_err(&func->dev, "failed to allocate %s RX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	}
+	port->write_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->write_buffer == NULL) {
+		dev_err(&func->dev, "failed to allocate %s TX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	}
+#endif
+#if 0
+	if(skw_cp_ver == SKW_SDIO_V20){
+	/*
+	 *	create BSPUPDATE device
+	 */
+		pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+		if(!pdev)
+			return -ENOMEM;
+		pdev->dev.parent = &func->dev;
+		pdev->dev.dma_mask = &port_dmamask;
+		pdev->dev.coherent_dma_mask = port_dmamask;
+		ucom_pdata.port_name = "BSPUPDATE";
+		ucom_pdata.data_port = SDIO2_BSP_UPDATE_PORT;
+		ret = platform_device_add_data(pdev, &ucom_pdata, sizeof(ucom_pdata));
+		if(ret) {
+			dev_err(&func->dev, "failed to add platform data \n");
+			platform_device_put(pdev);;
+			return ret;
+		}
+		port = &sdio_ports[ucom_pdata.data_port];
+		port->state = PORT_STATE_IDLE;
+		ret = platform_device_add(pdev);
+		if(ret) {
+			dev_err(&func->dev, "failt to register platform device\n");
+			platform_device_put(pdev);
+			return ret;
+		}
+
+		port->pdev = pdev;
+		port->channel = ucom_pdata.data_port;
+		port->length = SDIO_BUFFER_SIZE >> 2;
+		port->read_buffer = kzalloc(port->length , GFP_KERNEL);
+		if(port->read_buffer == NULL) {
+			dev_err(&func->dev, "failed to allocate %s RX buffer\n", ucom_pdata.port_name);
+			return -ENOMEM;
+		}
+		port->write_buffer = kzalloc(port->length , GFP_KERNEL);
+		if(port->write_buffer == NULL) {
+			dev_err(&func->dev, "failed to allocate %s TX buffer\n", ucom_pdata.port_name);
+			return -ENOMEM;
+		}
+	}
+#endif
+	return ret;
+}
+static int skw_sdio_gpio_check(struct skw_sdio_data_t *skw_sdio)
+{
+	int ret = -1;
+	if(!skw_sdio)
+		return ret;
+
+	if ((SKW_SDIO_INBAND_IRQ == skw_sdio->irq_type) && (skw_sdio->gpio_in>=0 && skw_sdio->gpio_out>=0)) {
+			skw_sdio_err("Please use SKW_SDIO_EXTERNAL_IRQ! irq_type=%d gpio_in=%d gpio_out=%d\n", skw_sdio->irq_type, skw_sdio->gpio_in,skw_sdio->gpio_out);
+			return ret;
+	}
+	return 0;
+}
+
+int skw_sdio_bind_WIFI_driver(struct sdio_func *func)
+{
+	struct platform_device *pdev;
+	char	pdev_name[32];
+	struct sdio_port *port;
+	int ret = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	if (sdio_ports[WIFI_DATA_PORT].pdev)
+		return 0;
+
+	if(skw_sdio_gpio_check(skw_sdio)<0)
+		return -EPERM;
+
+	if (!strncmp((char *)skw_sdio->chip_id, "SV6160LITE", 10)) {
+#ifdef SV6621S_WIRELESS
+		sprintf(pdev_name, "%s%d", SV6621S_WIRELESS, func->num);
+#else
+		sprintf(pdev_name, "%s%d", SV6160_WIRELESS, func->num);
+#endif
+	} else if (!strncmp((char *)skw_sdio->chip_id, "SV6160", 6)) {
+		sprintf(pdev_name, "%s%d", SV6160_WIRELESS, func->num);
+	} else {
+		skw_sdio_err(
+			"unknow chip id!!! pls check you porting code!! and the connect the seekwave!!\n");
+		sprintf(pdev_name, "%s%d", SV6160_WIRELESS, func->num);
+	}
+	pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+	if(!pdev)
+		return -ENOMEM;
+	pdev->dev.parent = &func->dev;
+	pdev->dev.dma_mask = &port_dmamask;
+	pdev->dev.coherent_dma_mask = port_dmamask;
+#ifdef CONFIG_SEEKWAVE_PLD_RELEASE
+	wifi_pdata.bus_type |= CP_RLS;
+#else
+	if (!strncmp((char *)skw_sdio->chip_id, "SV6160", 6) ||
+	    !strncmp((char *)skw_sdio->chip_id, "SV6160LITE", 10)) {
+		wifi_pdata.bus_type |= CP_RLS;
+	}
+#endif
+	/*support the sdma type bus*/
+	if (!skw_sdio->adma_rx_enable) {
+		if(skw_cp_ver == SKW_SDIO_V10)
+			wifi_pdata.bus_type = SDIO_LINK|TX_SDMA|RX_SDMA;
+		else
+			wifi_pdata.bus_type = SDIO2_LINK|TX_SDMA|RX_SDMA;
+	} else {
+		if(skw_cp_ver == SKW_SDIO_V10)
+			wifi_pdata.bus_type = SDIO_LINK|TX_ADMA|RX_ADMA|CP_DBG;
+		else
+			wifi_pdata.bus_type = SDIO2_LINK|TX_ADMA|RX_ADMA|CP_DBG;
+	}
+	wifi_pdata.align_value = skw_sdio_blk_size;
+	skw_sdio_info(" wifi_pdata bus_type:0x%x \n", wifi_pdata.bus_type);
+	if(skw_cp_ver == SKW_SDIO_V20){
+		if(!strncmp((char *)skw_sdio->chip_id,"SV6316",6)){
+			wifi_pdata.data_port = (SDIO2_WIFI_DATA1_PORT << 4) | SDIO2_WIFI_DATA_PORT;
+			wifi_pdata.cmd_port = SDIO2_WIFI_CMD_PORT;
+		}else if(!strncmp((char *)skw_sdio->chip_id,"SV6160LITE",10)){
+			wifi_pdata.data_port = SDIO2_WIFI_DATA_PORT;
+			wifi_pdata.cmd_port = SDIO2_WIFI_CMD_PORT;
+		} else {
+			wifi_pdata.data_port = (SDIO2_WIFI_DATA1_PORT << 4) | SDIO2_WIFI_DATA_PORT;
+			wifi_pdata.cmd_port = SDIO2_WIFI_CMD_PORT;
+		}
+	}
+	memcpy(wifi_pdata.chipid, skw_sdio->chip_id, SKW_CHIP_ID_LENGTH);
+	ret = platform_device_add_data(pdev, &wifi_pdata, sizeof(wifi_pdata));
+	if(ret) {
+		dev_err(&func->dev, "failed to add platform data \n");
+		//add kfree wifi_pdata
+		platform_device_put(pdev);;
+		return ret;
+	}
+	if(skw_cp_ver == SKW_SDIO_V20){
+		if(!strncmp((char *)skw_sdio->chip_id,"SV6316",12)){
+			port = &sdio_ports[(wifi_pdata.data_port >> 4) & 0x0F];
+			port->pdev = pdev;
+			port->channel = (wifi_pdata.data_port >> 4) & 0x0F;
+			port->rx_wp = 0;
+			port->rx_rp = 0;
+			port->sg_index = 0;
+			port->state = 0;
+		}
+	}
+
+	port = &sdio_ports[wifi_pdata.data_port & 0x0F];
+	port->pdev = pdev;
+	port->channel = wifi_pdata.data_port & 0x0F;
+	port->rx_wp = 0;
+	port->rx_rp = 0;
+	port->sg_index = 0;
+	port->state = 0;
+
+	port = &sdio_ports[wifi_pdata.cmd_port];
+	port->pdev = pdev;
+	port->channel = wifi_pdata.cmd_port;
+	port->rx_wp = 0;
+	port->rx_rp = 0;
+	port->sg_index = 0;
+	port->state = 0;
+
+	ret = platform_device_add(pdev);
+	if(ret) {
+		dev_err(&func->dev, "failt to register platform device\n");
+		platform_device_put(pdev);
+	}
+	return ret;
+}
+int skw_sdio_wifi_status(void)
+{
+	struct sdio_port *port = &sdio_ports[wifi_pdata.cmd_port];
+	if (port->pdev == NULL)
+		return 0;
+	return 1;
+}
+int skw_sdio_wifi_power_on(int power_on)
+{
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+	int ret;
+	if (power_on) {
+		if (skw_sdio->power_off)
+			skw_recovery_mode();
+
+		ret = skw_sdio_bind_WIFI_driver(skw_sdio->sdio_func[FUNC_1]);
+	} else {
+		ret = skw_sdio_unbind_WIFI_driver(skw_sdio->sdio_func[FUNC_1]);
+	}
+	return ret;
+}
+#ifdef CONFIG_BT_SEEKWAVE
+int skw_sdio_bind_btseekwave_driver(struct sdio_func *func)
+{
+	struct platform_device *pdev;
+	char	pdev_name[32];
+	struct sdio_port *port;
+	int ret = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+	sprintf(pdev_name, "btseekwave");
+/*
+ *	creaete BT DATA device
+ */
+	pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+	if(!pdev)
+		return -ENOMEM;
+	pdev->dev.parent = &func->dev;
+	pdev->dev.dma_mask = &port_dmamask;
+	pdev->dev.coherent_dma_mask = port_dmamask;
+
+	if(skw_cp_ver == SKW_SDIO_V20){
+		ucom_pdata.data_port = SDIO2_BT_DATA_PORT;
+		ucom_pdata.cmd_port = SDIO2_BT_CMD_PORT;
+		ucom_pdata.audio_port = SDIO2_BT_AUDIO_PORT;
+	} else {
+		ucom_pdata.data_port = BT_DATA_PORT;
+	}
+
+	memcpy(ucom_pdata.chipid, skw_sdio->chip_id, SKW_CHIP_ID_LENGTH);
+	ret = platform_device_add_data(pdev, &ucom_pdata, sizeof(ucom_pdata));
+	if(ret) {
+		dev_err(&func->dev, "failed to add platform data \n");
+		platform_device_put(pdev);;
+		return ret;
+	}
+	port = &sdio_ports[ucom_pdata.data_port];
+	port->state = PORT_STATE_IDLE;
+	ret = platform_device_add(pdev);
+	if(ret) {
+		dev_err(&func->dev, "failt to register platform device\n");
+		platform_device_put(pdev);
+		return ret;
+	}
+	port->pdev = pdev;
+	port->channel = ucom_pdata.data_port;
+	port->length = SDIO_BUFFER_SIZE;
+	port->write_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->write_buffer == NULL) {
+		platform_device_put(pdev);
+		dev_err(&func->dev, "failed to allocate %s TX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	} else {
+		skw_sdio_info("alloc %s TX buffer success\n", ucom_pdata.port_name);
+	}
+
+/*
+ *	creaete BT COMMAND device
+ */
+	ucom_pdata.data_port = ucom_pdata.cmd_port;
+	port = &sdio_ports[ucom_pdata.data_port];
+	port->state = PORT_STATE_IDLE;
+	port->pdev = NULL;
+	port->channel = ucom_pdata.data_port;
+	port->length = SDIO_BUFFER_SIZE;
+	port->write_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->write_buffer == NULL) {
+		dev_err(&func->dev, "failed to allocate %s TX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	} else {
+		skw_sdio_info("alloc %s TX buffer success\n", ucom_pdata.port_name);
+	}
+
+/*
+ *	creaete BT audio device
+ */
+	ucom_pdata.data_port = ucom_pdata.audio_port;
+	port = &sdio_ports[ucom_pdata.data_port];
+	port->state = PORT_STATE_IDLE;
+	port->pdev = NULL;
+	port->channel = ucom_pdata.data_port;
+	port->length = SDIO_BUFFER_SIZE;
+	port->write_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->write_buffer == NULL) {
+		dev_err(&func->dev, "failed to allocate %s TX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	} else {
+		skw_sdio_info("alloc %s TX buffer success\n", ucom_pdata.port_name);
+	}
+	return ret;
+}
+#else
+int skw_sdio_bind_BT_driver(struct sdio_func *func)
+{
+	struct platform_device *pdev;
+	char	pdev_name[32];
+	struct sdio_port *port;
+	int ret = 0;
+	struct skw_sdio_data_t *skw_sdio = skw_sdio_get_data();
+
+
+	sprintf(pdev_name, "skw_ucom");
+/*
+ *	creaete BT DATA device
+ */
+	pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+	if(!pdev)
+		return -ENOMEM;
+	pdev->dev.parent = &func->dev;
+	pdev->dev.dma_mask = &port_dmamask;
+	pdev->dev.coherent_dma_mask = port_dmamask;
+	ucom_pdata.port_name = "BTDATA";
+	if(skw_cp_ver == SKW_SDIO_V20)
+		ucom_pdata.data_port = SDIO2_BT_DATA_PORT;
+	else
+		ucom_pdata.data_port = BT_DATA_PORT;
+	memcpy(ucom_pdata.chipid, skw_sdio->chip_id, SKW_CHIP_ID_LENGTH);
+	ret = platform_device_add_data(pdev, &ucom_pdata, sizeof(ucom_pdata));
+	if(ret) {
+		dev_err(&func->dev, "failed to add platform data \n");
+		platform_device_put(pdev);;
+		return ret;
+	}
+	port = &sdio_ports[ucom_pdata.data_port];
+	port->state = PORT_STATE_IDLE;
+	ret = platform_device_add(pdev);
+	if(ret) {
+		dev_err(&func->dev, "failt to register platform device\n");
+		platform_device_put(pdev);
+		return ret;
+	}
+	port->pdev = pdev;
+	port->channel = ucom_pdata.data_port;
+	port->length = SDIO_BUFFER_SIZE;
+	port->read_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->read_buffer == NULL) {
+		dev_err(&func->dev, "failed to allocate %s RX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	}
+	port->write_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->write_buffer == NULL) {
+		platform_device_put(pdev);
+		kfree(port->read_buffer);
+		port->read_buffer = NULL;
+		dev_err(&func->dev, "failed to allocate %s TX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	}
+
+/*
+ *	creaete BT COMMAND device
+ */
+	pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+	if(!pdev)
+		return -ENOMEM;
+	pdev->dev.parent = &func->dev;
+	pdev->dev.dma_mask = &port_dmamask;
+	pdev->dev.coherent_dma_mask = port_dmamask;
+	ucom_pdata.port_name = "BTCMD";
+	if(skw_cp_ver == SKW_SDIO_V20)
+		ucom_pdata.data_port = SDIO2_BT_CMD_PORT;
+	else
+		ucom_pdata.data_port = ucom_pdata.cmd_port;
+
+	//memcpy(ucom_pdata.chipid, skw_sdio->chip_id, SKW_CHIP_ID_LENGTH);
+	skw_sdio_info("The check chipid ucompdata = %s \n",ucom_pdata.chipid);
+	ret = platform_device_add_data(pdev, &ucom_pdata, sizeof(ucom_pdata));
+	if(ret) {
+		dev_err(&func->dev, "failed to add %s device \n", ucom_pdata.port_name);
+		platform_device_put(pdev);;
+		return ret;
+	}
+	port = &sdio_ports[ucom_pdata.data_port];
+	port->state = PORT_STATE_IDLE;
+	ret = platform_device_add(pdev);
+	if(ret) {
+		dev_err(&func->dev, "failt to register platform device\n");
+		platform_device_put(pdev);
+		return ret;
+	}
+
+	port->pdev = pdev;
+	port->channel = ucom_pdata.data_port;
+	port->length = SDIO_BUFFER_SIZE >> 2;
+	port->read_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->read_buffer == NULL) {
+		dev_err(&func->dev, "failed to allocate %s RX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	}
+	port->write_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->write_buffer == NULL) {
+		dev_err(&func->dev, "failed to allocate %s TX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	}
+
+/*
+ *	creaete BT audio device
+ */
+	pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+	if(!pdev)
+		return -ENOMEM;
+	pdev->dev.parent = &func->dev;
+	pdev->dev.dma_mask = &port_dmamask;
+	pdev->dev.coherent_dma_mask = port_dmamask;
+	ucom_pdata.port_name = "BTAUDIO";
+	if(skw_cp_ver == SKW_SDIO_V20)
+		ucom_pdata.data_port = SDIO2_BT_AUDIO_PORT;
+	else
+		ucom_pdata.data_port = ucom_pdata.audio_port;
+	ret = platform_device_add_data(pdev, &ucom_pdata, sizeof(ucom_pdata));
+	if(ret) {
+		dev_err(&func->dev, "failed to add platform data \n");
+		platform_device_put(pdev);;
+		return ret;
+	}
+	port = &sdio_ports[ucom_pdata.data_port];
+	port->state = PORT_STATE_IDLE;
+	ret = platform_device_add(pdev);
+	if(ret) {
+		dev_err(&func->dev, "failt to register platform device\n");
+		platform_device_put(pdev);
+		return ret;
+	}
+
+	port->pdev = pdev;
+	port->channel = ucom_pdata.data_port;
+	port->length = SDIO_BUFFER_SIZE >> 2;
+	port->read_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->read_buffer == NULL) {
+		dev_err(&func->dev, "failed to allocate %s RX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	}
+	port->write_buffer = kzalloc(port->length , GFP_KERNEL);
+	if(port->write_buffer == NULL) {
+		kfree(port->read_buffer);
+		port->read_buffer = NULL;
+		dev_err(&func->dev, "failed to allocate %s TX buffer\n", ucom_pdata.port_name);
+		return -ENOMEM;
+	}
+
+	if(skw_cp_ver == SKW_SDIO_V20){
+		/*
+		*	create BTISOC device
+		*/
+		pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+		if(!pdev)
+			return -ENOMEM;
+		pdev->dev.parent = &func->dev;
+		pdev->dev.dma_mask = &port_dmamask;
+		pdev->dev.coherent_dma_mask = port_dmamask;
+		ucom_pdata.port_name = "BTISOC";
+		ucom_pdata.data_port = SDIO2_BT_ISOC_PORT;
+		ret = platform_device_add_data(pdev, &ucom_pdata, sizeof(ucom_pdata));
+		if(ret) {
+			dev_err(&func->dev, "failed to add platform data \n");
+			platform_device_put(pdev);;
+			return ret;
+		}
+		port = &sdio_ports[ucom_pdata.data_port];
+		port->state = PORT_STATE_IDLE;
+		ret = platform_device_add(pdev);
+		if(ret) {
+			dev_err(&func->dev, "failt to register platform device\n");
+			platform_device_put(pdev);
+			return ret;
+		}
+
+		port->pdev = pdev;
+		port->channel = ucom_pdata.data_port;
+		port->length = SDIO_BUFFER_SIZE >> 2;
+		port->read_buffer = kzalloc(port->length , GFP_KERNEL);
+		if(port->read_buffer == NULL) {
+			dev_err(&func->dev, "failed to allocate %s RX buffer\n", ucom_pdata.port_name);
+			return -ENOMEM;
+		}
+		port->write_buffer = kzalloc(port->length , GFP_KERNEL);
+		if(port->write_buffer == NULL) {
+			kfree(port->read_buffer);
+			port->read_buffer = NULL;
+			dev_err(&func->dev, "failed to allocate %s TX buffer\n", ucom_pdata.port_name);
+			return -ENOMEM;
+		}
+
+		/*
+		*	create BTLOG device
+		*/
+		pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+		if(!pdev)
+			return -ENOMEM;
+		pdev->dev.parent = &func->dev;
+		pdev->dev.dma_mask = &port_dmamask;
+		pdev->dev.coherent_dma_mask = port_dmamask;
+		ucom_pdata.port_name = "BTLOG";
+		ucom_pdata.data_port = SDIO2_BT_LOG_PORT;
+		ret = platform_device_add_data(pdev, &ucom_pdata, sizeof(ucom_pdata));
+		if(ret) {
+			dev_err(&func->dev, "failed to add platform data \n");
+			platform_device_put(pdev);;
+			return ret;
+		}
+		port = &sdio_ports[ucom_pdata.data_port];
+		port->state = PORT_STATE_IDLE;
+		ret = platform_device_add(pdev);
+		if(ret) {
+			dev_err(&func->dev, "failt to register platform device\n");
+			platform_device_put(pdev);
+			return ret;
+		}
+
+		port->pdev = pdev;
+		port->channel = ucom_pdata.data_port;
+		port->length = SDIO_BUFFER_SIZE >> 2;
+		port->read_buffer = kzalloc(port->length , GFP_KERNEL);
+		if(port->read_buffer == NULL) {
+			dev_err(&func->dev, "failed to allocate %s RX buffer\n", ucom_pdata.port_name);
+			return -ENOMEM;
+		}
+		port->write_buffer = kzalloc(port->length , GFP_KERNEL);
+		if(port->write_buffer == NULL) {
+			kfree(port->read_buffer);
+			port->read_buffer = NULL;
+			dev_err(&func->dev, "failed to allocate %s TX buffer\n", ucom_pdata.port_name);
+			return -ENOMEM;
+		}
+	}
+	return ret;
+}
+#endif
+static int skw_sdio_unbind_sdio_port_driver(struct sdio_func *func, int portno)
+{
+	void *pdev;
+	struct sdio_port *port;
+	struct sv6160_platform_data *pdata = NULL;
+	skw_sdio_info("port no %d\n", portno);
+	port = &sdio_ports[portno];
+	pdev = port->pdev;
+
+	if (pdev) {
+		pdata = ((struct sv6160_platform_data
+				  *)((struct platform_device *)pdev)
+				 ->dev.platform_data);
+		if (pdata->port_name != NULL)
+			skw_sdio_info("port name %s %d\n", pdata->port_name,
+				      portno);
+	}
+	port->pdev = NULL;
+	if (port->read_buffer)
+		kfree(port->read_buffer);
+	if (port->write_buffer)
+		kfree(port->write_buffer);
+	if (port->sg_rx)
+		kfree(port->sg_rx);
+	if (pdev)
+		platform_device_unregister(pdev);
+	port->sg_rx = NULL;
+	port->read_buffer = NULL;
+	port->write_buffer = NULL;
+	port->rx_wp = 0;
+	port->rx_rp = 0;
+	port->sg_index = 0;
+	port->state = 0;
+	return 0;
+}
+
+int skw_sdio_unbind_platform_driver(struct sdio_func *func)
+{
+	int ret;
+
+	ret = skw_sdio_unbind_sdio_port_driver(func, SDIO2_BSP_ATC_PORT);
+	if(skw_cp_ver == SKW_SDIO_V20) {
+		ret |= skw_sdio_unbind_sdio_port_driver(func, SDIO2_BSP_LOG_PORT);
+#ifndef CONFIG_BT_SEEKWAVE
+		ret |= skw_sdio_unbind_sdio_port_driver(func, SDIO2_LOOPCHECK_PORT);
+#endif
+	} else {
+		ret |= skw_sdio_unbind_sdio_port_driver(func, BSP_LOG_PORT);
+		ret |= skw_sdio_unbind_sdio_port_driver(func, LOOPCHECK_PORT);
+	}
+	return ret;
+}
+
+int skw_sdio_unbind_WIFI_driver(struct sdio_func *func)
+{
+	int ret;
+
+	ret = skw_sdio_unbind_sdio_port_driver(func, SDIO2_WIFI_CMD_PORT);
+	return ret;
+}
+
+int skw_sdio_unbind_BT_driver(struct sdio_func *func)
+{
+	int ret = 0;
+	if (skw_cp_ver == SKW_SDIO_V20) {
+		ret = skw_sdio_unbind_sdio_port_driver(func, SDIO2_BT_DATA_PORT);
+		ret |= skw_sdio_unbind_sdio_port_driver(func, SDIO2_BT_CMD_PORT);
+		ret |= skw_sdio_unbind_sdio_port_driver(func, SDIO2_BT_AUDIO_PORT);
+#ifndef CONFIG_BT_SEEKWAVE
+		ret |= skw_sdio_unbind_sdio_port_driver(func, SDIO2_BT_LOG_PORT);
+		ret |= skw_sdio_unbind_sdio_port_driver(func, SDIO2_BT_ISOC_PORT);
+#endif
+	} else {
+		ret = skw_sdio_unbind_sdio_port_driver(func, BT_DATA_PORT);
+		ret |= skw_sdio_unbind_sdio_port_driver(func, BT_CMD_PORT);
+		ret |= skw_sdio_unbind_sdio_port_driver(func, BT_AUDIO_PORT);
+	}
+	return ret;
+}
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/Kconfig b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/Kconfig
new file mode 100755
index 0000000..bda1658
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/Kconfig
@@ -0,0 +1,20 @@
+#
+# SEEKWAVE Platfrom Device Drivers (NEW )Configuration
+#
+config SKW_BSP_UCOM
+	tristate "SeekWave BSP Drivers util For SeekWave Chip"
+	depends on SEEKWAVE_BSP_DRIVERS
+	default m
+	help
+	  This is support seekwave chip for incard board.
+	  if you want to buildin bsp driver.
+	  please say "y".
+		Thanks.
+config SKW_BSP_BOOT
+	tristate "Seekwave Platform SKW BOOT Driver Support"
+        depends on SEEKWAVE_BSP_DRIVERS
+	default m
+	help
+	  Enable this module for seekwave.
+	  if you want to use this driver,please insmod skw_boot.ko.
+	  Thanks.
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/README.md b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/README.md
new file mode 100755
index 0000000..a0a4610
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/README.md
@@ -0,0 +1 @@
+#seekwave platform the skwutil demo
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/boot_config.h b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/boot_config.h
new file mode 100755
index 0000000..7cd88e6
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/boot_config.h
@@ -0,0 +1,134 @@
+/******************************************************************************
+ *
+ * Copyright(c) 2020-2030  Seekwave Corporation.
+ *
+ *****************************************************************************/
+#ifndef __BOOT_CONFIG_H__
+#define __BOOT_CONFIG_H__
+#include <linux/types.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+
+#ifdef CONFIG_SKW_NO_CONFIG
+#define  MODEM_ENABLE_GPIO   	202
+#define  HOST_WAKEUP_GPIO_IN 	-1
+#define  MODEM_WAKEUP_GPIO_OUT  -1
+#else
+//#define  MODEM_ENABLE_GPIO   	-1
+#error "WIFI CHIP_EN Is Not Configured Pls Check The Config!!!"
+#define  HOST_WAKEUP_GPIO_IN 	-1
+#define  MODEM_WAKEUP_GPIO_OUT  -1
+#endif
+#define  SEEKWAVE_NV_NAME  "SEEKWAVE_NV_SWT6621S.bin"
+//#define CONFIG_SEEKWAVE_FIRMWARE_LOAD
+//#define  SKW_IRAM_FILE_PATH  "/vendor/firmware/SWT6621S_IRAM_USB.bin"
+//#define  SKW_DRAM_FILE_PATH  "/vendor/firmware/SWT6621S_DRAM_USB.bin"
+#define  SKW_POWER_OFF_VALUE   0
+
+//#define SKW_SUPPORT_MMC_NONREMOVABLE //support the host mmc card is not removable but sdcard is removable default
+#define CONFIG_SEEKWAVE_PLD_RELEASE 1
+#define SKW_DUMP_BUFFER_SIZE     (1200*1024)
+#define POWERON_DELAY_TIME  200
+#define  SKW_DMA_TYPE_CFG   ADMA
+#define  SKW_BT_ANTENNA_CFG   0
+
+#define CONFIG_NO_SERVICE_PD 0 // default ps mode cp not poweroff
+
+
+/***********************************************************
+**CONFIG_SKW_HOST_SUPPORT_ADMA 1 : use ADMA	0 : use SDMA
+**
+***********************************************************/
+//#define CONFIG_SKW_HOST_SUPPORT_ADMA
+
+#if defined(CONFIG_SKW_HOST_SUPPORT_ADMA)
+#define TX_DMA_TYPE		TX_ADMA
+#else
+#define TX_DMA_TYPE		TX_SDMA
+#endif
+//#define CONFIG_SKW_HOST_PLATFORM_AMLOGIC 1
+//#define CONFIG_SKW_HOST_PLATFORM_FULLHAN
+
+//#define  USB_POWEROFF_IN_LOWPOWER  1
+#define SKW_CHIP_POWEROFF(gpiono) \
+{ \
+	if(gpiono >= 0) { \
+		gpio_set_value(gpiono>>1, (gpiono&0x01)); \
+	} \
+}
+
+#define SKW_CHIP_POWERON(gpiono) \
+{ \
+	if(gpiono >= 0) { \
+	gpio_set_value(gpiono>>1, 1-(gpiono&0x01)); \
+	} \
+}
+#define SKW_MMC_HOST_SD_INDEX  1 //default sd index is 1 if not 1 pls set to 0 or 2
+
+#if defined(CONFIG_SKW_HOST_PLATFORM_FULLHAN)
+#define SKW_MMC_HOST_SD_INDEX  1 //default sd index is 1 if not 1 pls set to 0 or 2
+extern void fh_sdio_card_scan(int sd_id); //fullhan sdio card scan
+#endif
+#if defined(CONFIG_SKW_HOST_PLATFORM_AMLOGIC)
+extern void extern_wifi_set_enable(int is_on);
+#elif defined(CONFIG_SKW_HOST_PLATFORM_ALLWINER)
+extern void sunxi_wlan_set_power(int on);
+extern void sunxi_mmc_rescan_card(unsigned ids);
+#elif defined(CONFIG_SKW_HOST_PLATFORM_ROCKCHIP)
+extern int rockchip_wifi_power(int on);
+extern int rockchip_wifi_set_carddetect(int val);
+#else
+extern int skw_chipen_gpio_reset(int on);
+static inline int skw_chip_power_ops(int on)
+{
+	return skw_chipen_gpio_reset(on);
+}
+#endif
+
+static inline void skw_chip_set_power(int on)
+{
+#if defined(CONFIG_SKW_HOST_PLATFORM_AMLOGIC)
+	extern_wifi_set_enable(on);
+#elif defined(CONFIG_SKW_HOST_PLATFORM_ALLWINER)
+	sunxi_wlan_set_power(on);
+#elif defined(CONFIG_SKW_HOST_PLATFORM_ROCKCHIP)
+	rockchip_wifi_power(on);
+#elif defined(CONFIG_SKW_HOST_PLATFORM_HISI_BIGFISH)
+	hi_drv_gpio_set_dir_bit(MODEM_ENABLE_GPIO, 0);
+	hi_drv_gpio_write_bit(MODEM_ENABLE_GPIO, on);
+#else
+	skw_chip_power_ops(on);
+#endif
+
+}
+static inline void skw_chip_power_reset(void)
+{
+#if defined(CONFIG_SKW_HOST_PLATFORM_AMLOGIC)
+	printk("amlogic skw chip power reset !!\n");
+	extern_wifi_set_enable(0);
+	msleep(POWERON_DELAY_TIME);
+	extern_wifi_set_enable(1);
+#elif defined(CONFIG_SKW_HOST_PLATFORM_ALLWINER)
+	printk("allwinner skw chip power reset !!\n");
+	sunxi_wlan_set_power(0);
+	msleep(POWERON_DELAY_TIME);
+	sunxi_wlan_set_power(1);
+#elif defined(CONFIG_SKW_HOST_PLATFORM_ROCKCHIP)
+	printk("rockchip skw chip power reset !!\n");
+	rockchip_wifi_power(0);
+	msleep(POWERON_DELAY_TIME);
+	rockchip_wifi_power(1);
+#elif defined(CONFIG_SKW_HOST_PLATFORM_HISI_BIGFISH)
+	printk("hisi skw chip power reset !!\n");
+	hi_drv_gpio_set_dir_bit(MODEM_ENABLE_GPIO,0);
+	hi_drv_gpio_write_bit(MODEM_ENABLE_GPIO,0);
+	msleep(POWERON_DELAY_TIME);
+	hi_drv_gpio_write_bit(MODEM_ENABLE_GPIO,1);
+#else
+	printk("self skw chip power reset !!\n");
+	skw_chip_power_ops(0);
+	msleep(POWERON_DELAY_TIME);
+	skw_chip_power_ops(1);
+#endif
+}
+#endif /* __BOOT_CONFIG_H__ */
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_boot.c b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_boot.c
new file mode 100755
index 0000000..a1d0db1
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_boot.c
@@ -0,0 +1,1226 @@
+/*****************************************************************
+ *Copyright (C) 2021 Seekwave Tech Inc.
+ *Filename : skw_boot.c
+ *Authors:seekwave platform
+ *
+ * This software is licensed under the terms of the the GNU
+ * General Public License version 2, as published by the Free
+ * Software Foundation, and may be copied, distributed, and
+ * modified under those terms.
+ *
+ * This program is distributed in the hope that it will be usefull,
+ * but without any warranty;without even the implied warranty of
+ * merchantability or fitness for a partcular purpose. See the
+ * GUN General Public License for more details.
+ * **************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/of_gpio.h>
+#include <linux/completion.h>
+#include <linux/moduleparam.h>
+#include <linux/workqueue.h>
+#include <linux/of.h>
+#include <linux/device.h>
+#include <linux/version.h>
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/ctype.h>
+#include <linux/errno.h>
+#include <linux/firmware.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/dma-mapping.h>
+#include <linux/cdev.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+#include <linux/scatterlist.h>
+#include <linux/platform_device.h>
+#include <linux/pci.h>
+#include "skw_mem_map.h"
+#include "skw_boot.h"
+#include "boot_config.h"
+/**************************sdio boot start******************************/
+extern int cp_exception_sts;
+unsigned int test_debug = 0;
+unsigned char dl_signal_acount=0;
+struct platform_device *btboot_pdev;
+static u64 port_dmamask = DMA_BIT_MASK(32);
+static struct mutex boot_mutex;
+static char *local_chip_id = "SV6160LITE";
+int g_chipen_pin = -1;//default chipen -1,need update by dts or CHIPEN MACRO
+int skw_use_sdma = 0;//default use adma, 0:adma 1:sdma
+
+
+//static char iram_image_buffer[360448];
+//static char dram_image_buffer[206848];
+#ifdef CONFIG_OF
+#undef CONFIG_OF
+#endif
+//#define SDIO_BUFFER_SIZE	 (16*1024)
+/*
+ *add the little endian
+ * */
+#define _LITTLE_ENDIAN  1
+
+#define CP_IMG_HEAD0	"kees"		 //"6B656573"
+#define CP_IMG_HEAD1	"0616"		//"30363136"
+#define CP_IMG_TAIL0	"evaw"		//"65766177"
+#define CP_IMG_TAIL1	"0616"		//"30363136" //ASCII code 36 31 36 30
+#define CP_NV_HEAD	  "TSVN"		//"5453564E" //ASCII code 36 31 36 30
+#define CP_NV_TAIL		 "DEVN"		//"4445564E" //ASCII code 36 31 36 30
+
+#define CHIP_DEV_NAME "sv6160lite" //same with dts device
+#define CHIP_DEV_NAME_COM "seekwave," CHIP_DEV_NAME
+
+#define IMG_HEAD_OPS_LEN	4
+#define RAM_ADDR_OPS_LEN	8
+#define MODULE_INFO_LEN		12
+
+#define IMG_HEAD_INFOR_RANGE	0x200  //10K Byte
+
+static unsigned int EndianConv_32(unsigned int value);
+/***********sdio drv extern interface **************/
+/* driect mode,reg access.etc */
+//extern int skw_get_chipid(char *chip_id);
+extern int skw_boot_loader(struct seekwave_device *boot_data);
+extern void *skw_get_bus_dev(void);
+extern int skw_reset_bus_dev(void);
+static int skw_first_boot(struct seekwave_device *boot_data);
+static int skw_boot_init(struct seekwave_device *boot_data);
+static int skw_download_signal_ops(void);
+static int get_sleep_status(int portno, char *buffer, int size);
+static int set_sleep_status(int portno, char *buffer, int size);
+//int skw_cp_exception_reboot(void);
+static int skw_start_bt_service(void);
+static int skw_stop_bt_service(void);
+/**************************sdio boot end********************************/
+struct seekwave_device *boot_data;
+
+//=======================================================
+//debug sdio macro and Variable
+//int glb_wifiready_done;
+#define SKW_CPBOOT_DEBUG 0
+//=======================================================
+/***************************************************************************
+ *Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ **************************************************************************/
+static unsigned int crc_16_l_calc(char *buf_ptr,unsigned int len)
+{
+	unsigned int i;
+	unsigned short crc=0;
+
+	while(len--!=0)
+	{
+		for(i= CRC_16_L_SEED;i!=0;i=i>>1)
+		{
+			if((crc &CRC_16_L_POLYNOMIAL)!=0)
+			{
+				crc= crc<<1;
+				crc= crc ^ CRC_16_POLYNOMIAL;
+			}else{
+				crc = crc <<1;
+			}
+
+			if((*buf_ptr &i)!=0)
+			{
+				crc = crc ^ CRC_16_POLYNOMIAL;
+			}
+		}
+		buf_ptr++;
+	}
+	return (crc);
+}
+
+
+static int skw_request_firmwares(struct seekwave_device *boot_data,
+	const char *dram_image_name, const char *iram_image_name, const char *nv_mem_name)
+{
+	int ret;
+	const struct firmware *fw = NULL;
+
+	skwboot_log("request_firmware %s\n", dram_image_name);
+	ret = request_firmware(&fw, dram_image_name, NULL);
+	if (ret < 0) {
+		skwboot_err("request_firmware %s fail\n", dram_image_name);
+		goto ret;
+	}
+	if(!boot_data->first_boot_flag && !boot_data->dram_img_data){
+		boot_data->dram_img_data = (char *)vmalloc( fw->size);
+		if (!boot_data->dram_img_data) {
+			skwboot_err("%s,line:%d the dram img data malloc fail \n",
+					__func__, __LINE__);
+			return -ENOMEM;
+		}
+	}
+	skwboot_log("boot data dram_img_data %p\n",boot_data->dram_img_data);
+	memset(boot_data->dram_img_data, 0, fw->size);
+	memcpy(boot_data->dram_img_data, fw->data, fw->size);
+	boot_data->dram_dl_size = fw->size;
+	release_firmware(fw);
+	//dram crc16
+	boot_data->dram_crc_en = 1;
+	boot_data->dram_crc_offset=0;
+	boot_data->dram_crc_val = crc_16_l_calc(boot_data->dram_img_data + boot_data->dram_crc_offset, boot_data->dram_dl_size);
+
+	skwboot_log("request_firmware %s\n", iram_image_name);
+	ret = request_firmware(&fw, iram_image_name, NULL);
+	if (ret < 0) {
+		skwboot_err("request_firmware %s fail\n", iram_image_name);
+		goto ret;
+	}
+	if(!boot_data->first_boot_flag &&!boot_data->iram_img_data){
+		boot_data->iram_img_data = (char *)vmalloc(fw->size);
+		if (!boot_data->iram_img_data) {
+			vfree(boot_data->dram_img_data);
+			boot_data->dram_img_data = NULL;
+			skwboot_err("%s,line:%d the iram img data malloc fail \n",
+					__func__, __LINE__);
+			return -ENOMEM;
+		}
+	}
+	memset(boot_data->iram_img_data, 0, fw->size);
+	memcpy(boot_data->iram_img_data, fw->data, fw->size);
+	boot_data->iram_dl_size = fw->size;
+	release_firmware(fw);
+	//iram crc16
+	boot_data->iram_crc_en = 1;
+	boot_data->iram_crc_offset=0;
+	boot_data->iram_crc_val = crc_16_l_calc(boot_data->iram_img_data + boot_data->iram_crc_offset, boot_data->iram_dl_size);
+
+	skwboot_log("boot data iram_img_data %p\n",boot_data->iram_img_data);
+	if(nv_mem_name == NULL) {
+		ret = 0;
+		skwboot_warn("nv_mem_name is NULL\n");
+		goto ret;
+	}
+	skwboot_log("request_firmware %s\n", nv_mem_name);
+	ret = request_firmware(&fw, nv_mem_name, NULL);
+	if (ret < 0) {
+		skwboot_err("request_firmware %s fail\n", nv_mem_name);
+		ret = ENOENT;
+		goto ret;
+	}
+
+	boot_data->nv_mem_data = (char *)kzalloc(fw->size, GFP_KERNEL);
+	if (boot_data->nv_mem_data == NULL) {
+		skwboot_err("alloc nv memory failed\n");
+		goto relese_fw;
+	}
+	memcpy(boot_data->nv_mem_data, fw->data, fw->size);
+	boot_data->nv_mem_size = fw->size;
+	if (boot_data->nv_mem_size > 20) {//new nv
+		boot_data->nv_mem_cmfg_data = boot_data->nv_mem_data + *(u32 *)(boot_data->nv_mem_data + NV_CMFG_OFFSET);
+		boot_data->nv_mem_cmfg_size = *(u32 *)(boot_data->nv_mem_data + NV_CMFG_SIZE);
+
+		if (*(u32 *)(boot_data->nv_mem_data + NV_PNFG_OFFSET) != 0)
+			boot_data->nv_mem_pnfg_data = boot_data->nv_mem_data + *(u32 *)(boot_data->nv_mem_data + NV_PNFG_OFFSET);
+		else
+			boot_data->nv_mem_pnfg_data = NULL;
+		boot_data->nv_mem_pnfg_size = *(u32 *)(boot_data->nv_mem_data + NV_PNFG_SIZE);
+	}
+	ret=0;
+	boot_data->nvmem_crc_en = 1;
+	boot_data->nvmem_crc_offset=0;
+	boot_data->nvmem_crc_val = crc_16_l_calc(boot_data->nv_mem_data + boot_data->nvmem_crc_offset, boot_data->nv_mem_size);
+	//print_hex_dump(KERN_ERR, "nvdata:", 0, 16, 1, boot_data->nv_mem_data, boot_data->nv_mem_size, 1);
+
+relese_fw:
+	release_firmware(fw);
+ret:
+	return ret;
+}
+static int skw_of_property_read(const struct device_node *np,
+				const char *propname, u32 *out_value)
+{
+#if KERNEL_VERSION(3, 1, 0) <= LINUX_VERSION_CODE
+	return of_property_read_u32(np,	propname, out_value);
+#else
+#if 0
+	const unsigned int *value=NULL;
+	value = of_get_property(np, propname,out_value);
+	if (value == NULL){
+		return ENXIO;
+	}else{
+	    return 0;
+	}
+#endif
+	return ENXIO;
+#endif
+}
+static int seekwave_boot_parse_dt(struct platform_device *pdev, struct seekwave_device *boot_data)
+{
+	int ret = 0;
+	enum of_gpio_flags flags;
+	int tmp_gpio;
+	struct device_node *np = pdev->dev.of_node;
+	/*add the dma type dts config*/
+	if (skw_of_property_read(np, "bt_antenna", &(boot_data->bt_antenna))){
+		skwboot_warn("no BT_antenna setting\n");
+		boot_data->bt_antenna = SKW_BT_ANTENNA_CFG;
+	} else
+		skwboot_log("BT_antenna setting: %d\n", boot_data->bt_antenna);
+
+	if (skw_of_property_read(np, "dma_type", &(boot_data->dma_type))){
+		boot_data->dma_type = SKW_DMA_TYPE_CFG;
+		g_chipen_pin = boot_data->chip_en = MODEM_ENABLE_GPIO;
+		boot_data->host_gpio =  HOST_WAKEUP_GPIO_IN;
+		boot_data->chip_gpio =  MODEM_WAKEUP_GPIO_OUT;
+		skwboot_warn("no DTS setting\n");
+	} else {
+#if KERNEL_VERSION(3, 1, 0) <= LINUX_VERSION_CODE
+		boot_data->host_gpio = of_get_named_gpio_flags(np, "gpio_host_wake", 0, &flags);
+		boot_data->chip_gpio = of_get_named_gpio_flags(np, "gpio_chip_wake",0, &flags);
+		g_chipen_pin = boot_data->chip_en = of_get_named_gpio_flags(np, "gpio_chip_en",0, &flags);
+#endif
+	}
+	boot_data->dma_type = skw_use_sdma?SDMA:ADMA;
+	skwboot_log("%s,modem boot setting::=>\n", __func__);
+	skwboot_log("chipen=%d\n", boot_data->chip_en);
+	skwboot_log("hst_wak_wf=%d\n", boot_data->chip_gpio);
+	skwboot_log("wf_wak_hst=%d\n", boot_data->host_gpio);
+	skwboot_log("dma_type=%d\n", boot_data->dma_type);
+	skwboot_log("bt_antenna=%d\n", boot_data->bt_antenna);
+
+	if (test_debug) {
+		if ((test_debug & 0xF) == 0x1) {
+			boot_data->chip_gpio = -1;
+			boot_data->host_gpio = -1;
+		} else if (test_debug == 5)
+			boot_data->host_gpio = -1;
+#if KERNEL_VERSION(3, 1, 0) <= LINUX_VERSION_CODE
+		if (boot_data->host_gpio >= 0) {
+			if ((test_debug & 0xF) == 0x2) {
+				skwboot_log("gpio in out swap\n");
+				tmp_gpio = boot_data->chip_gpio;
+				boot_data->chip_gpio = boot_data->host_gpio;
+				boot_data->host_gpio = tmp_gpio;
+				ret = devm_gpio_request_one(
+					&pdev->dev, boot_data->host_gpio,
+					GPIOF_IN, "WL_WAKE_HOST");
+			} else if ((test_debug & 0xF) == 0x3) {
+				skwboot_log("test_debug 3 = gpio in high\n");
+				ret = devm_gpio_request_one(
+					&pdev->dev, boot_data->host_gpio,
+					GPIOF_OUT_INIT_HIGH, "WL_WAKE_HOST");
+			} else if ((test_debug & 0xF) == 0x4) {
+				skwboot_log(
+					"test_debug 4 = gpio in out swap out low\n");
+				tmp_gpio = boot_data->chip_gpio;
+				boot_data->chip_gpio = boot_data->host_gpio;
+				boot_data->host_gpio = tmp_gpio;
+				ret = devm_gpio_request_one(
+					&pdev->dev, boot_data->host_gpio,
+					GPIOF_OUT_INIT_LOW, "WL_WAKE_HOST");
+			} else {
+				ret = devm_gpio_request_one(
+					&pdev->dev, boot_data->host_gpio,
+					GPIOF_IN, "WL_WAKE_HOST");
+			}
+			if (boot_data->chip_gpio >= 0)
+				ret = devm_gpio_request_one(
+					&pdev->dev, boot_data->chip_gpio,
+					GPIOF_OUT_INIT_HIGH, "HOST_WAKE_WL");
+		}
+#endif
+		if (boot_data->chip_gpio >= 0 && boot_data->host_gpio >= 0) {
+			if (test_debug & 0xF0)
+				boot_data->slp_disable = 1;
+			else
+				boot_data->slp_disable = 0;
+		} else {
+			boot_data->slp_disable = 1;
+		}
+	} else {
+#if KERNEL_VERSION(3, 1, 0) <= LINUX_VERSION_CODE
+		if (boot_data->host_gpio >= 0)
+			ret = devm_gpio_request_one(&pdev->dev,
+						    boot_data->host_gpio,
+						    GPIOF_IN, "WL_WAKE_HOST");
+		if (boot_data->chip_gpio >= 0)
+			ret = devm_gpio_request_one(&pdev->dev,
+						    boot_data->chip_gpio,
+						    GPIOF_OUT_INIT_HIGH,
+						    "HOST_WAKE_WL");
+#endif
+		if (boot_data->chip_gpio >= 0 && boot_data->host_gpio >= 0) {
+			boot_data->slp_disable = 0;
+		} else {
+			boot_data->slp_disable = 1;
+		}
+	}
+	skwboot_log("%s,chipen=%d, gpio_out:%d gpio_in:%d ret = %d\n", __func__,
+		    boot_data->chip_en, boot_data->chip_gpio,
+		    boot_data->host_gpio, ret);
+#if KERNEL_VERSION(3, 1, 0) <= LINUX_VERSION_CODE
+	if (boot_data->chip_en >= 0)
+		ret = devm_gpio_request_one(&pdev->dev, boot_data->chip_en, GPIOF_OUT_INIT_HIGH,"CHIP_EN");
+#endif
+	return ret;
+}
+
+/************************************************************************/
+//Description: BT start service
+//Func: BT start service
+//Call:
+//Author:junwei.jiang
+//Date:2021-110
+//Modify:
+/************************************************************************/
+static int bt_start_service(int id, void *callback, void *data)
+{
+	int ret=0;
+	if(cp_exception_sts)
+		return -1;
+
+	skwboot_log("%s pid:%d %s\n", current->comm, current->pid, __func__);
+	ret = skw_start_bt_service();
+	if(ret < 0){
+		skwboot_err("%s boot bt fail \n", __func__);
+		return -1;
+	}
+	return 0;
+}
+
+/************************************************************************/
+//Description: BT stop service
+//Func: BT stop service
+//Call:
+//Author:junwei.jiang
+//Date:2021-11-1
+//Modify:
+/************************************************************************/
+static int bt_stop_service(int id)
+{
+	int ret=0;
+
+	if(cp_exception_sts)
+		return 0;
+
+	ret = skw_stop_bt_service();
+	if(ret < 0){
+		skwboot_err("%s boot bt fail \n", __func__);
+		return -1;
+	}
+	skwboot_log("%s OK\n",__func__);
+	return 0;
+}
+static void seekwave_release(struct device *dev)
+
+{
+}
+static struct platform_device seekwave_device ={
+	.name = CHIP_DEV_NAME,
+	.dev = {
+		.release = seekwave_release,
+	}
+};
+
+/***************************************************************************
+ *Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ **************************************************************************/
+static int seekwave_boot_probe(struct  platform_device *pdev)
+{
+	int ret;
+	int time_count=0;
+	struct device *io_bus;
+
+	if (pdev != &seekwave_device)
+		seekwave_device.name = NULL;
+	boot_data = devm_kzalloc(&pdev->dev, sizeof(struct seekwave_device), GFP_KERNEL);
+	if (!boot_data) {
+		skwboot_err("%s :kzalloc error !\n", __func__);
+		return -ENOMEM;
+	}
+	mutex_init(&boot_mutex);
+	seekwave_boot_parse_dt(pdev, boot_data);
+	io_bus = skw_get_bus_dev();
+	if (!io_bus) {
+		skwboot_log("%s :CHIP_RESET AGAIN!\n", __func__);
+		skw_chip_power_reset();
+		do {
+			msleep(10);
+			io_bus = skw_get_bus_dev();
+		} while(!io_bus && time_count++ < 50);
+	}
+	if (!io_bus) {
+		devm_kfree(&pdev->dev, boot_data);
+		skwboot_err("%s get bus dev fail !\n",__func__);
+		return -ENODEV;
+	}
+	//get chip id
+	if (!strncmp(local_chip_id,"SV6160LITE",10)) {
+		if (!strncmp(io_bus->bus->name, "usb", 3)) {
+			boot_data->iram_file_path = "SWT6621S_IRAM_USB.bin";
+			boot_data->dram_file_path = "SWT6621S_DRAM_USB.bin";
+			boot_data->skw_nv_name = "SWT6621S_NV_USB.bin";
+		} else {
+			boot_data->iram_file_path = "SWT6621S_IRAM_SDIO.bin";
+			boot_data->dram_file_path = "SWT6621S_DRAM_SDIO.bin";
+			boot_data->skw_nv_name = "SWT6621S_NV_SDIO.bin";
+		}
+	} else if (!strncmp(local_chip_id,"SV6160",6)) {
+		if (!strncmp(io_bus->bus->name, "usb", 3)) {
+			boot_data->iram_file_path = "SWT6621_IRAM_USB.bin";
+			boot_data->dram_file_path = "SWT6621_DRAM_USB.bin";
+		} else {
+			boot_data->iram_file_path = "SWT6621_IRAM_SDIO.bin";
+			boot_data->dram_file_path = "SWT6621_DRAM_SDIO.bin";
+		}
+		boot_data->skw_nv_name = NULL;
+	} else if (!strncmp(local_chip_id,"SV6316",6)) {
+		if (!strncmp(io_bus->bus->name, "usb", 3)) {
+			boot_data->iram_file_path = "SWT6652_IRAM_USB.bin";
+			boot_data->dram_file_path = "SWT6652_DRAM_USB.bin";
+		} else if (!strncmp(io_bus->bus->name, "pci", 3)) {
+			boot_data->pdev = pdev;
+			if (container_of(io_bus, struct pci_dev, dev)->device == 0x6316) {
+				boot_data->iram_file_path = "SWT6652_IRAM_PCIE.bin";
+				boot_data->dram_file_path = "SWT6652_DRAM_PCIE.bin";
+			} else if (container_of(io_bus, struct pci_dev, dev)->device == 0x6315) {
+				boot_data->iram_file_path = "SWT6652S_IRAM_PCIE.bin";
+				boot_data->dram_file_path = "SWT6652S_DRAM_PCIE.bin";
+			}
+		} else {
+			boot_data->iram_file_path = "SWT6652_IRAM_SDIO.bin";
+			boot_data->dram_file_path = "SWT6652_DRAM_SDIO.bin";
+		}
+		boot_data->skw_nv_name = "SEEKWAVE_NV_SWT6652.bin";
+	} else {
+		skwboot_warn("%s:get chip id is NULL!!!\n", __func__);
+	}
+	ret = skw_boot_init(boot_data);
+	if (ret < 0) {
+		skwboot_err("%s:boot init fail\n", __func__);
+		return -1;
+	}
+	boot_data->pdev = pdev;
+	ret = skw_first_boot(boot_data);
+	if (strncmp(io_bus->bus->name, "usb", 3))
+		skw_bind_boot_driver(io_bus);
+	return ret;
+}
+/***************************************************************************
+ *Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ **************************************************************************/
+static int seekwave_boot_remove(struct  platform_device *pdev)
+{
+	skwboot_log("%s the Enter \n", __func__);
+
+	if (btboot_pdev) {
+		platform_device_unregister(btboot_pdev);
+		btboot_pdev = NULL;
+	}
+	if (boot_data) {
+		if (!boot_data->iram_img_data) {
+			skwboot_log(":iram_img_data is NULL\n");
+		} else {
+			vfree(boot_data->iram_img_data);
+			boot_data->iram_img_data = NULL;
+		}
+		if (!boot_data->dram_img_data) {
+		    skwboot_err(":dram_img_data is NULL\n");
+		} else {
+			vfree(boot_data->dram_img_data);
+			boot_data->dram_img_data = NULL;
+		}
+		if (boot_data->nv_mem_size > 20) { //new nv
+			skwboot_log(":free nv_mem_data\n");
+			if (boot_data->nv_mem_data) {
+				skwboot_log(":free 2 nv_mem_data\n");
+				kfree(boot_data->nv_mem_data);
+				boot_data->nv_mem_data = NULL;
+			}
+		}
+		boot_data->dl_base_img = NULL;
+		boot_data->skw_nv_name = NULL;
+		boot_data->iram_file_path = NULL;
+		boot_data->dram_file_path = NULL;
+		boot_data->wifi_start = NULL;
+		boot_data->wifi_stop = NULL;
+		boot_data->bt_start = NULL;
+		boot_data->bt_stop = NULL;
+		boot_data->skw_dloader_module = NULL;
+		devm_kfree(&pdev->dev, boot_data);
+		boot_data = NULL;
+	} else {
+		skwboot_err("%s:boot_data is NULL\n", __func__);
+	}
+	mutex_destroy(&boot_mutex);
+	return 0;
+}
+extern void skw_modem_log_stop_rec(void);
+static void seekwave_boot_shutdown(struct platform_device *pdev)
+{
+	skwboot_log("%s enter ...\n", __func__);
+	skw_modem_log_stop_rec();
+	skw_reset_bus_dev();
+}
+static const struct of_device_id seekwave_match_table[] ={
+
+	{ .compatible = CHIP_DEV_NAME_COM},
+	{ },
+};
+
+static struct platform_driver seekwave_driver ={
+
+	.driver = {
+		.owner = THIS_MODULE,
+		.name  = CHIP_DEV_NAME,
+		.of_match_table = seekwave_match_table,
+	},
+	.probe = seekwave_boot_probe,
+	.remove = seekwave_boot_remove,
+	.shutdown = seekwave_boot_shutdown,
+};
+
+/***********************************************************************
+ *Description:chipen gpio pin reset
+ *Seekwave tech LTD
+ *Author:zongqiang.cheng
+ *Date:2025-3-18
+ *Modify:
+ ***********************************************************************/
+int skw_chipen_gpio_reset(int on)
+{
+	int chip_en = g_chipen_pin;
+	if (chip_en < 0){
+		printk("chip_en need set !!\n");
+		return -1;
+	}
+
+	if(on) {
+		gpio_set_value(chip_en, 1);
+		printk("seekwave power on !!\n");
+	} else {
+		gpio_set_value(chip_en, 0);
+		printk("seekwave power down !!\n");
+	}
+	return 0;
+}
+
+/***********************************************************************
+ *Description:BT download boot pdata
+ *Seekwave tech LTD
+ *Author:junwei.jiang
+ *Date:2021-11-3
+ *Modify:
+ ***********************************************************************/
+static int get_sleep_status(int portno, char *buffer, int size)
+{
+	memcpy(buffer, "WAKE", 4);
+	if (boot_data->host_gpio >=0) {
+		if (gpio_get_value(boot_data->host_gpio) == 0)
+			memcpy(buffer, "DOWN", 4);
+	}
+	return 4;
+}
+static int set_sleep_status(int portno, char *buffer, int size)
+{
+	int i, count;
+
+	for(i=0; i<2; i++) {
+		if (gpio_get_value(boot_data->host_gpio))
+			return 1;
+		if(buffer && !strncmp(buffer, "WAKE", 4)) {
+			gpio_set_value(boot_data->chip_gpio, 0);
+			udelay(10);
+			gpio_set_value(boot_data->chip_gpio, 1);
+		}
+		count = 0;
+		do {
+			if (count++ < 100)
+				udelay(20);
+		} while(gpio_get_value(boot_data->host_gpio) ==0);
+		if (gpio_get_value(boot_data->host_gpio))
+			return 1;
+		udelay(100);
+	}
+	if (gpio_get_value(boot_data->host_gpio)==0)
+		skwboot_log("wakeup CHIP timeout!!! \n");
+	return 1;
+}
+struct sv6160_platform_data boot_pdata = {
+	.data_port = 8,
+	.bus_type = SDIO_LINK,
+	.max_buffer_size = 0x800,
+	.align_value = 4,
+	.hw_sdma_rx = get_sleep_status,
+	.hw_sdma_tx = set_sleep_status,
+	.open_port = bt_start_service,
+	.close_port = bt_stop_service,
+};
+
+/***************************************************************
+ *Description:BT bind boot driver
+ *Seekwave tech LTD
+ *Author:junwei.jiang
+ *Date:2021-11-3
+ *Modify:
+***************************************************************/
+int skw_bind_boot_driver(struct device *dev)
+{
+	struct platform_device *pdev;
+	char	pdev_name[32];
+	int ret = 0;
+	sprintf(pdev_name, "skw_ucom");
+	if(!dev){
+		skwboot_err("%s the dev fail \n", __func__);
+		return -1;
+	}
+	if (btboot_pdev)
+		return ret;
+
+	pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+	if(!pdev)
+		return -ENOMEM;
+	pdev->dev.parent = dev;
+	pdev->dev.dma_mask = &port_dmamask;
+	pdev->dev.coherent_dma_mask = port_dmamask;
+	boot_pdata.port_name = "BTBOOT";
+	boot_pdata.data_port = 8;
+	//skw_get_chipid((char *)&boot_data->chip_id);
+	ret = platform_device_add_data(pdev, &boot_pdata, sizeof(boot_pdata));
+	if(ret) {
+		dev_err(dev, "failed to add boot data \n");
+		platform_device_put(pdev);
+		return ret;
+	}
+	ret = platform_device_add(pdev);
+	if(ret) {
+		platform_device_put(pdev);
+		skwboot_err("%s,line:%d the device add fail \n",__func__,__LINE__);
+		return ret;
+	}
+	btboot_pdev = pdev;
+	return ret;
+}
+/****************************************************************
+ *Description:the data Little Endian process interface
+ *Func:EndianConv_32
+ *Calls:None
+ *Call By:The img data process
+ *Input:value
+ *Output:the Endian data
+ *Return:value
+ *Others:
+ *Author:JUNWEI.JIANG
+ *Date:2021-08-26
+ * **************************************************************/
+static unsigned int EndianConv_32(unsigned int value)
+{
+#ifdef _LITTLE_ENDIAN
+	unsigned int nTmp = (value >>24 | value <<24);
+	nTmp |= ((value >> 8) & 0x0000FF00);
+	nTmp |= ((value << 8) & 0x00FF0000);
+	return nTmp;
+#else
+	return value;
+#endif
+}
+
+/****************************************************************
+ *Description:dram read the double img file
+ *Func:
+ *Calls:
+ *Call By:
+ *Input:the file path
+ *Output:download data and the data size dl_data image_size
+ *Return:0:pass other fail
+ *Others:
+ *Author:JUNWEI.JIANG
+ *Date:2022-02-07
+ * **************************************************************/
+static int skw_download_signal_ops(void)
+{
+	unsigned int tmp_signal = 0;
+	//download done flag ++
+	boot_data->dl_done_signal ++;
+	tmp_signal = boot_data->dl_done_signal;
+	boot_data->dl_done_signal = 0xff&tmp_signal;
+	boot_data->dl_acount_addr = SKW_SDIO_PD_DL_AP2CP_BSP;
+
+	//gpio need set high or low power interrupt to cp wakeup
+	boot_data->gpio_out = boot_data->chip_gpio;
+	if(boot_data->gpio_val)
+		boot_data->gpio_val =0;
+	else
+		boot_data->gpio_val =1;
+	skwboot_log("%s line:%d download data ops done the dl_count=%d \n", __func__, __LINE__,boot_data->dl_done_signal);
+	return 0;
+}
+/****************************************************************
+ *Description:analysis the double img dram iram
+ *Func:
+ *Calls:
+ *Call By:
+ *Input:the file path
+ *Output:download data and the data size dl_data image_size
+ *Return:0:pass other fail
+ *Others:
+ *Author:JUNWEI.JIANG
+ *Date:2022-02-07
+ * **************************************************************/
+static int skw_boot_init(struct seekwave_device *boot_data)
+{
+	int i =0;
+	int k =0;
+	unsigned int head_offset=0;
+	unsigned int tail_offset=0;
+	int ret = 0;
+	struct img_head_data_t dl_data_info;
+	unsigned int *data=NULL;
+	unsigned int *nvdata=NULL;
+	unsigned int *dl_addr_data=NULL;
+
+	ret = skw_request_firmwares(boot_data, boot_data->dram_file_path,
+				    boot_data->iram_file_path,
+				    boot_data->skw_nv_name);
+	if (ret == ENOENT) {
+		ret = skw_request_firmwares(boot_data,
+					    boot_data->dram_file_path,
+					    boot_data->iram_file_path,
+					    SEEKWAVE_NV_NAME);
+	}
+	skwboot_log("image_size=%d,%d, ret=%d\n", boot_data->iram_dl_size,
+		    boot_data->dram_dl_size, ret);
+	if (ret < 0){
+		skwboot_err("request image fail\n");
+		return ret;
+	}
+	boot_data->head_addr = 0;
+	boot_data->tail_addr = 0;
+	boot_data->bsp_head_addr = 0;
+	boot_data->bsp_tail_addr = 0;
+	boot_data->wifi_head_addr =0;
+	boot_data->wifi_tail_addr = 0;
+	boot_data->bt_head_addr = 0;
+	boot_data->bt_tail_addr = 0;
+	boot_data->nv_head_addr = 0;
+	boot_data->nv_tail_addr = 0;
+	boot_data->nv_data_size = 0;
+
+	if(boot_data->iram_img_data!=NULL){
+		/*analysis the img*/
+		for(i=0; i*IMG_HEAD_OPS_LEN<IMG_HEAD_INFOR_RANGE; i++)
+		{
+			if(!head_offset)
+			{
+				if((0==memcmp(CP_IMG_HEAD0, boot_data->iram_img_data+i*IMG_HEAD_OPS_LEN,IMG_HEAD_OPS_LEN))&&
+						(0==memcmp(CP_IMG_HEAD1,boot_data->iram_img_data+(i+1)*IMG_HEAD_OPS_LEN,IMG_HEAD_OPS_LEN)))
+					head_offset = (i+1)*IMG_HEAD_OPS_LEN;
+			}else if(!tail_offset){
+				if((0==memcmp(CP_IMG_TAIL0, boot_data->iram_img_data+i*IMG_HEAD_OPS_LEN, IMG_HEAD_OPS_LEN))&&
+						(0==memcmp(CP_IMG_TAIL1, boot_data->iram_img_data+(i+1)*IMG_HEAD_OPS_LEN, IMG_HEAD_OPS_LEN))){
+					tail_offset = (i-1)*IMG_HEAD_OPS_LEN;
+					break;
+				}
+			}
+		}
+
+		/*analysis the nv*/
+		for(k=0; k*IMG_HEAD_OPS_LEN<IMG_HEAD_INFOR_RANGE; k++)
+		{
+			if(!boot_data->nv_head_addr)
+			{
+				if(0==memcmp(CP_NV_HEAD, boot_data->iram_img_data+k*IMG_HEAD_OPS_LEN,IMG_HEAD_OPS_LEN))
+					boot_data->nv_head_addr = k*IMG_HEAD_OPS_LEN;
+			}else if(!boot_data->nv_tail_addr){
+				if((0==memcmp(CP_NV_TAIL, boot_data->iram_img_data+k*IMG_HEAD_OPS_LEN, IMG_HEAD_OPS_LEN))){
+					boot_data->nv_tail_addr = k*IMG_HEAD_OPS_LEN;
+					boot_data->nv_data_size = boot_data->nv_tail_addr - boot_data->nv_head_addr - IMG_HEAD_OPS_LEN;
+					nvdata = (u32 *) &boot_data->iram_img_data[boot_data->nv_head_addr];
+					break;
+				}
+			}
+		}
+		if(!tail_offset){
+			skwboot_err("%s,%d,the iram_img not need analysis!!! or Fail!! \n",__func__,__LINE__);
+			return -1;
+		}else{
+			//get the iram img addr and dram img addr
+			dl_addr_data = (unsigned int *)(boot_data->iram_img_data+head_offset+IMG_HEAD_OPS_LEN);
+			boot_data->iram_dl_addr = dl_addr_data[0];
+			boot_data->dram_dl_addr = dl_addr_data[1];
+			head_offset = head_offset+RAM_ADDR_OPS_LEN;//jump the ram addr data;
+
+			skwboot_log("%s line:%d,the tail_offset ---0x%x, the head_offset --0x%x ,iram_addr=0x%x,dram_addr=0x%x, \
+					nv_head_addr:0x%x,nv_tail_addr:0x%x,nv_size=%d\n",__func__, __LINE__,tail_offset, head_offset,
+					boot_data->iram_dl_addr,boot_data->dram_dl_addr,boot_data->nv_head_addr,boot_data->nv_tail_addr,
+					boot_data->nv_data_size);
+		}
+		/*need download the img bin for WIFI or BT service dl_module >0*/
+		head_offset = head_offset +IMG_HEAD_OPS_LEN;
+		/*get the img head tail offset*/
+		boot_data->head_addr = head_offset;
+		boot_data->tail_addr = tail_offset;
+
+		skwboot_log("%s line:%d analysis the img module\n", __func__, __LINE__);
+		for(i=0; i*MODULE_INFO_LEN<=(tail_offset-head_offset); i++)
+		{
+			data = (unsigned int *)(boot_data->iram_img_data +head_offset+i*MODULE_INFO_LEN);
+			dl_data_info.dl_addr=data[0];
+			dl_data_info.write_addr =data[2];
+			dl_data_info.index = 0x000000FF&EndianConv_32(data[1]);
+			dl_data_info.data_size = 0x00FFFFFF&data[1];
+			if(dl_data_info.index==1){
+				boot_data->bsp_index_count +=1;
+			}else if(dl_data_info.index ==2){
+				boot_data->wifi_index_count +=1;
+			}else if(dl_data_info.index ==3){
+				boot_data->bt_index_count +=1;
+			}
+			skwboot_log("%s line:%d dl_addr=0x%x, write_addr=0x%x, index=0x%x,data_size=0x%x\n", __func__,
+					__LINE__, dl_data_info.dl_addr,dl_data_info.write_addr,dl_data_info.index,dl_data_info.data_size);
+		}
+		skwboot_log("%s line:%d bsp_index count:%d, bt_index_count=%d, wifi_index_count=%d \n",
+				__func__, __LINE__,boot_data->bsp_index_count,boot_data->bt_index_count,boot_data->wifi_index_count);
+		//get the dl count for the service download module img
+		boot_data->wifi_dl_count = boot_data->bsp_index_count + boot_data->wifi_index_count;
+		boot_data->all_dl_count = boot_data->bsp_index_count + boot_data->wifi_index_count + boot_data->bt_index_count;
+		boot_data->bt_dl_count = boot_data->bsp_index_count + boot_data->bt_index_count;
+
+		if (boot_data->nv_mem_size > 20) {//new nv
+			if(boot_data->nv_mem_cmfg_size && (boot_data->nv_mem_cmfg_size <= boot_data->nv_data_size)){
+				memcpy((boot_data->iram_img_data+boot_data->nv_head_addr+4),boot_data->nv_mem_cmfg_data,boot_data->nv_mem_cmfg_size);
+				//kfree(boot_data->nv_mem_data);
+				//boot_data->nv_mem_data = NULL;
+			}
+		} else {//old nv
+			if(boot_data->nv_mem_size && (boot_data->nv_mem_size <= boot_data->nv_data_size)){
+				memcpy((boot_data->iram_img_data+boot_data->nv_head_addr+4),boot_data->nv_mem_data,boot_data->nv_mem_size);
+				kfree(boot_data->nv_mem_data);
+				boot_data->nv_mem_data = NULL;
+			}
+		}
+		//print_hex_dump(KERN_ERR, "nvcom ", 0, 16, 1,boot_data->iram_img_data+boot_data->nv_head_addr, boot_data->nv_data_size+8, 1);
+	 }
+	 return 0;
+}
+//debug cp boot for setvalue
+int gbl_count_flag=0;
+/****************************************************************
+ *Description:download the wifi bt service img,
+ *Func:
+ *Calls:
+ *Call By:
+ *Input:the service index
+ *Output:download data
+ *Return:0:pass other fail
+ *Others:
+ *Author:JUNWEI.JIANG
+ *Date:2024-02-07
+ * **************************************************************/
+static int skw_dloader_module(int service_index)
+{
+	int i =0;
+	int ret = 0;
+	struct img_head_data_t dl_data_info;
+	unsigned int *data=NULL;
+	int tmp_count=0;
+	int total_count=0;
+#if SKW_CPBOOT_DEBUG//for wifionly debug cp boot sleep no dl img setvalue 1;
+	skwboot_log("%s:the debug skw_dloader Enter \n",__func__);
+	return 0;
+#endif
+	//dl count flag setvalue
+	if(service_index==SKW_WIFI){
+		tmp_count = boot_data->wifi_dl_count;
+    }else if(service_index == SKW_BT){
+		tmp_count = boot_data->bt_dl_count;
+    }else if(service_index == SKW_ALL){
+		tmp_count = boot_data->all_dl_count;
+    }else{
+		skwboot_warn("%s No service index ops!!!\n",__func__);
+    }
+#if 1//debug cp boot for setvalue 1
+	if(gbl_count_flag==0){
+			boot_data->dl_done_signal=0;
+			gbl_count_flag=1;
+	}
+#endif
+	skwboot_log("the dl_count ---dl_bin_counts ===%d\n",tmp_count);
+	boot_data->service_ops = SKW_NO_SERVICE;
+	if(boot_data->iram_img_data!=NULL){
+		for(i=0; i*MODULE_INFO_LEN<=(boot_data->tail_addr-boot_data->head_addr); i++)
+		{
+			data = (unsigned int *)(boot_data->iram_img_data +boot_data->head_addr+i*MODULE_INFO_LEN);
+			dl_data_info.dl_addr=data[0];
+			dl_data_info.write_addr =data[2];
+			dl_data_info.index = 0x000000FF&EndianConv_32(data[1]);
+			dl_data_info.data_size = 0x00FFFFFF&data[1];
+			skwboot_log("%s line:%d dl_addr=0x%x, write_addr=0x%x, index=0x%x,data_size=0x%x\n", __func__,
+					__LINE__, dl_data_info.dl_addr,dl_data_info.write_addr,dl_data_info.index,dl_data_info.data_size);
+			if(service_index ==dl_data_info.index || SKW_BSP== dl_data_info.index || service_index == SKW_ALL){
+				total_count +=1;
+				boot_data->dl_base_addr = dl_data_info.write_addr;
+				boot_data->dl_size = dl_data_info.data_size;
+				if(dl_data_info.dl_addr >= boot_data->dram_dl_addr){
+					//iram data dload img
+					boot_data->dl_offset_addr =dl_data_info.dl_addr- boot_data->dram_dl_addr;
+					boot_data->dl_base_img = boot_data->dram_img_data;
+				}else {
+					//dram data dload img
+					boot_data->dl_offset_addr =dl_data_info.dl_addr- boot_data->iram_dl_addr;
+					boot_data->dl_base_img = boot_data->iram_img_data;
+				}
+				if(tmp_count == total_count){
+					skw_download_signal_ops();
+				}
+				ret=skw_boot_loader(boot_data);
+				if(ret){
+					skwboot_err("%s the load module=%d, fail,ret=%d \n", __func__, dl_data_info.index, ret);
+					break;
+				}
+			}else{
+					skwboot_err("%s not need to load module_index=%d,ret=%d \n", __func__, dl_data_info.index, ret);
+			}
+		}
+
+	 }
+	 return ret;
+}
+
+/***************************************************************************
+ *Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ **************************************************************************/
+int skw_start_wifi_service(void)
+{
+	int ret =0;
+
+	skwboot_log("%s Enter cp_state =%d \n",__func__, cp_exception_sts);
+	mutex_lock(&boot_mutex);
+	boot_data->service_ops = SKW_WIFI_START;
+#if defined(SKW_BOOT_MEMPOWERON)
+	boot_data->dl_module = NONE_BOOT;
+	boot_data->first_boot_flag =1;
+	boot_data->dl_base_img = NULL;
+#else
+	boot_data->first_dl_flag = 1;
+	boot_data->dl_module = SKW_WIFI_BOOT;
+	//download done flag ++
+	skw_download_signal_ops();
+#endif
+	ret = skw_boot_loader(boot_data);
+	mutex_unlock(&boot_mutex);
+	if(ret !=0)
+	{
+		skwboot_err("%s,line:%d boot fail \n", __func__,__LINE__);
+		return -1;
+	}
+
+	skwboot_log("%s wifi boot sucessfull\n", __func__);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(skw_start_wifi_service);
+/***************************************************************************
+ *Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ **************************************************************************/
+int skw_stop_wifi_service(void)
+{
+	int ret =0;
+	skwboot_log("%s Enter cp_state =%d \n",__func__, cp_exception_sts);
+	mutex_lock(&boot_mutex);
+	boot_data->service_ops = SKW_WIFI_STOP;
+#if defined(SKW_BOOT_MEMPOWERON)
+	boot_data->dl_module = NONE_BOOT;
+	boot_data->first_boot_flag =1;
+	boot_data->dl_base_img = NULL;
+#else
+	boot_data->dl_module = 0;
+	boot_data->first_dl_flag = 1;
+#endif
+	//download done flag ++
+	//gpio need set high or low power interrupt to cp wakeup
+	boot_data->gpio_out = boot_data->chip_gpio;
+	if(boot_data->gpio_val)
+		boot_data->gpio_val =0;
+	else
+		boot_data->gpio_val =1;
+	ret = skw_boot_loader(boot_data);
+	mutex_unlock(&boot_mutex);
+	if(ret !=0)
+	{
+		skwboot_warn("dload the img fail \n");
+		return -1;
+	}
+	skwboot_log("seekwave boot stop done:%s\n",__func__);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(skw_stop_wifi_service);
+/***************************************************************************
+ *Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ **************************************************************************/
+static int skw_start_bt_service(void)
+{
+	int ret=0;
+	skwboot_log("%s Enter cp_state =%d \n",__func__, cp_exception_sts);
+	mutex_lock(&boot_mutex);
+	boot_data->service_ops = SKW_BT_START;
+#if defined(SKW_BOOT_MEMPOWERON)
+	boot_data->first_boot_flag =1;
+	boot_data->dl_base_img = NULL;
+	boot_data->dl_module = NONE_BOOT;
+#else
+	boot_data->first_dl_flag = 1;
+	boot_data->dl_module = SKW_BT_BOOT;
+	//download done flag ++
+	skw_download_signal_ops();
+#endif
+	ret = skw_boot_loader(boot_data);
+	mutex_unlock(&boot_mutex);
+	if(ret !=0)
+	{
+		skwboot_err("%s boot fail \n", __func__);
+		return -1;
+	}
+
+	skwboot_log("%s line:%d , boot bt sucessfully!\n", __func__,__LINE__);
+	return 0;
+}
+
+/***************************************************************************
+ *Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ **************************************************************************/
+static int skw_stop_bt_service(void)
+{
+	int ret =0;
+	skwboot_log("%s Enter cp_state =%d \n",__func__, cp_exception_sts);
+	mutex_lock(&boot_mutex);
+	boot_data->service_ops = SKW_BT_STOP;
+#if defined(SKW_BOOT_MEMPOWERON)
+	boot_data->first_boot_flag = 1;
+	boot_data->dl_base_img = NULL;
+#else
+	boot_data->dl_module = 0;
+	boot_data->first_dl_flag = 1;
+#endif
+	//download done flag ++
+	boot_data->dl_module = NONE_BOOT;
+	//gpio need set high or low power interrupt to cp wakeup
+	boot_data->gpio_out = boot_data->chip_gpio;
+	if(boot_data->gpio_val)
+		boot_data->gpio_val =0;
+	else
+		boot_data->gpio_val =1;
+	ret = skw_boot_loader(boot_data);
+	mutex_unlock(&boot_mutex);
+	if(ret !=0)
+	{
+		skwboot_warn("dload the img fail \n");
+		return -1;
+	}
+	skwboot_log("seekwave boot stop done:%s\n",__func__);
+	return 0;
+}
+
+/****************************************************************
+ *Description:double iram dram img first boot cp
+ *Func:
+ *Calls:
+ *Call By:skw_first_boot
+ *Input:the file path
+ *Output:download data and the data size dl_data image_size
+ *Return:0:pass other fail
+ *Others:
+ *Author:JUNWEI.JIANG
+ *Date:2022-02-07
+ * **************************************************************/
+static int skw_first_boot(struct seekwave_device *boot_data)
+{
+	int ret =0;
+	//get the img data
+#ifdef DEBUG_SKWBOOT_TIME
+	ktime_t cur_time,last_time;
+	cur_time = ktime_get();
+#endif
+	//set download the value;
+	boot_data->service_ops = SKW_NO_SERVICE;
+	boot_data->save_setup_addr = SKW_SDIO_PD_DL_AP2CP_BSP; //160
+	boot_data->gpio_out = boot_data->chip_gpio;
+	boot_data->gpio_val = 0;
+	boot_data->dl_module = 0;
+	boot_data->first_dl_flag =0;
+	boot_data->gpio_in  = boot_data->host_gpio;
+	boot_data->dma_type_addr = SKW_SDIO_PLD_DMA_TYPE;
+	boot_data->slp_disable_addr = SKW_SDIO_CP_SLP_SWITCH;
+	boot_data->wifi_start = skw_start_wifi_service;
+	boot_data->wifi_stop = skw_stop_wifi_service;
+	boot_data->bt_start = skw_start_bt_service;
+	boot_data->bt_stop = skw_stop_bt_service;
+	boot_data->skw_dloader_module = skw_dloader_module;
+	ret = skw_boot_loader(boot_data);
+	if(ret < 0){
+		skwboot_err("%s firt boot cp fail \n", __func__);
+		return -1;
+	}
+	//download done set the download flag;
+	boot_data->first_dl_flag =1;
+	boot_data->first_boot_flag =1;
+
+	//download done tall cp acount;
+	boot_data->dl_done_signal &= 0xFF;
+	boot_data->dl_done_signal +=1;
+	skwboot_log("%s first boot pass\n", __func__);
+#ifdef DEBUG_SKWBOOT_TIME
+	last_time = ktime_get();
+	skwboot_log("%s,the download time start time %llu and the over time %llu \n",
+			__func__, cur_time, last_time);
+#endif
+	return ret;
+}
+int seekwave_boot_init(char *chip_id)
+{
+	int ret;
+
+	skwboot_log("%s: enter chip_id=%s\n",__func__,chip_id);
+	if(chip_id != NULL && strlen(chip_id)!=0){
+		local_chip_id = chip_id;
+	}
+	btboot_pdev = NULL;
+	skw_ucom_init();
+	ret = platform_driver_register(&seekwave_driver);
+	if (seekwave_device.name)
+		platform_device_register(&seekwave_device);
+	return ret;
+}
+void seekwave_boot_exit(void)
+{
+	skw_ucom_exit();
+
+	if (seekwave_device.name)
+		platform_device_unregister(&seekwave_device);
+	platform_driver_unregister(&seekwave_driver);
+}
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_boot.h b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_boot.h
new file mode 100755
index 0000000..92a97ec
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_boot.h
@@ -0,0 +1,297 @@
+/*****************************************************************
+ *Copyright (C) 2021 Seekwave Tech Inc.
+ *Filename : skw_sdio.h
+ *Authors:seekwave platform
+ *
+ * This software is licensed under the terms of the the GNU
+ * General Public License version 2, as published by the Free
+ * Software Foundation, and may be copied, distributed, and
+ * modified under those terms.
+ *
+ * This program is distributed in the hope that it will be usefull,
+ * but without any warranty;without even the implied warranty of
+ * merchantability or fitness for a partcular purpose. See the
+ * GUN General Public License for more details.
+ * **************************************************************/
+#ifndef __SKW_BOOT_H__
+#define __SKW_BOOT_H__
+
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#if  KERNEL_VERSION(4, 13, 0) <= LINUX_VERSION_CODE
+#include <uapi/linux/sched/types.h>
+#else
+#include <linux/sched.h>
+#endif
+
+#ifdef SKW_EXT_INC
+#include "skw_platform_data.h"
+#else
+#include <linux/platform_data/skw_platform_data.h>
+#endif
+
+#ifdef CONFIG_WAKELOCK
+#include <linux/wakelock.h>
+#else
+#include <linux/pm_wakeup.h>
+#endif
+#include "boot_config.h"
+#if KERNEL_VERSION(4, 14, 0) <= LINUX_VERSION_CODE
+#define skw_read_file	kernel_read
+#define skw_write_file  kernel_write
+#else
+#define skw_read_file	vfs_read
+#define skw_write_file  vfs_write
+#endif
+
+#if KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE
+#define skw_wakeup_source_register(x, y)		  wakeup_source_register(x,y)
+#else
+#define skw_wakeup_source_register(x, y)		  wakeup_source_register(y)
+#endif
+
+#if KERNEL_VERSION(4, 4, 0) <= LINUX_VERSION_CODE
+#define skw_reinit_completion(x)	  reinit_completion(&x)
+#define SKW_MIN_NICE					  MIN_NICE
+#else
+#define skw_reinit_completion(x)	  INIT_COMPLETION(x)
+#define SKW_MIN_NICE					  -20
+#endif
+#ifndef PLATFORM_DEVID_AUTO
+#define PLATFORM_DEVID_AUTO -1
+#endif
+
+#ifdef CONFIG_NO_GKI
+#if KERNEL_VERSION(5, 4, 0) <= LINUX_VERSION_CODE
+MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
+#endif
+#else
+#if KERNEL_VERSION(5, 4, 0) <= LINUX_VERSION_CODE
+MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
+#endif
+#endif
+
+/****************************************************************
+ *Description:the skwsdio log define and the skwsdio data debug,
+ *Func: skwsdio_log, skwsdio_err, skwsdio_data_pr;
+ *Calls:
+ *Call By:
+ *Input: skwsdio log debug informations
+ *Output:
+ *Return:
+ *Others:
+ *Author:JUNWEI.JIANG
+ *Date:2021-08-25
+ * **************************************************************/
+#define skwboot_log(fmt, args...) \
+	pr_info("[SKWBOOT]:" fmt, ## args)
+
+#define skwboot_err(fmt, args...) \
+	pr_err("[SKWBOOT_ERR]:" fmt, ## args)
+
+#define skwboot_warn(fmt, args...) \
+	pr_warn("[SKWBOOT_WARN]:" fmt, ## args)
+
+#define skwboot_data_pr(level, prefix_str, prefix_type, rowsize,\
+		groupsize, buf, len, asscii)\
+		do{if(loglevel) \
+			print_hex_dump(level, prefix_str, prefix_type, rowsize,\
+					groupsize, buf, len, asscii);\
+		}while(0)
+
+/**********************sdio boot interface start******************/
+
+#define SKW_BOOT_START_ADDR			0x100000
+#define SKW_CHIP_ID					0x40000000  //SV6160 chip id
+/*add the 32bit*4 128bit */
+struct img_head_data_t
+{
+	 unsigned int index;
+	 unsigned int dl_addr;
+	 unsigned int data_size;
+	 unsigned int write_addr;
+};
+/*add the 32bit*4 128bit */
+struct img_dl_data
+{
+	 unsigned int dl_addr;
+	 unsigned int dl_info; /*type and the size*/
+	 unsigned int write_addr;
+};
+#define CRC_16_L_SEED	0x80
+#define CRC_16_L_POLYNOMIAL  0x8000
+#define CRC_16_POLYNOMIAL  0x1021
+#define IRAM_CRC_OFFSET	 0
+#define DRAM_CRC_OFFSET	 0
+
+#define SKW_FIRST_BOOT  0
+#define SKW_BSP_BOOT	1
+#define SKW_WIFI_BOOT	2
+#define SKW_BT_BOOT		3
+#define RECOVERY_BOOT	4
+#define NONE_BOOT		5
+/*slp reg add the ap send the irq to cp reg*/
+#define SKW_SDIO_PD_DL_AP2CP_BSP		0x160 //download done  or first boot setup addrn
+#define SKW_SDIO_AP2CP_EXTI_SETVAL		0x161 //External Interrupt set val 1 or 2 ;3 is fail
+#define SDIOHAL_PD_DL_AP2CP_BT			0x162
+#define SDIOHAL_PD_DL_ALL				0x163
+#define SKW_SDIO_DL_POWERON_MODULE		0x164 //Poweron CP Moudle  1 WIFI 2:BT
+#define SKW_SDIO_PLD_DMA_TYPE			0x165
+#define SDIOHAL_CPLOG_TO_AP_SWITCH		0x166
+#define SKW_SDIO_CP_SLP_SWITCH  		0x167 //Turn on/off the CP slp feature 1:dis slp 0:enb slp
+#define SKW_SDIO_CREDIT_TO_CP			0x168
+
+// CP signal 3
+#define SKW_SDIO_RX_CHANNEL_FTL0		0x16C
+#define SKW_SDIO_RX_CHANNEL_FTL1		0x16D
+
+/*slp reg get the cp dl state reg*/
+#define SKW_SDIO_DL_CP2AP_BSP			0x180 //poweron OK ? 1: WIFI 2:BT
+#define SKW_SDIO_CP2AP_FIFO_IND			0x181 //CP_RX FIFO Empty Indiacation.
+#define SKW_SDIO_CP2AP_EXTI_GETVAL		0x182 //sdio External Interrupt get val 1 or 2 ;3 is fail
+#define SDIOHAL_PD_DL_CP2AP_ALL			0x183
+#define SDIOHAL_PD_DL_CP2AP_SIG4		0x184
+#define SDIOHAL_PD_DL_CP2AP_SIG5		0x185
+#define SDIOHAL_PD_DL_CP2AP_SIG6		0x186
+#define SDIOHAL_PD_DL_CP2AP_SIG7		0x187
+
+#define SKWSDIO_AP2CP_IRQ				0x1b0  //AP to CP interrupt and used BIT4 set 1 :fifth bit
+
+#define NV_CMFG_OFFSET	0x8
+#define NV_CMFG_SIZE	0xC
+#define NV_PNFG_OFFSET	0x10
+#define NV_PNFG_SIZE	0x14
+#define NV_HEADER_SIZE	0x20
+
+#define PN_CNT	20
+#define PN_FUNCSEL_ONEGRP_CNT	10
+#define PN_FUNC_SEL0_OFFSET	0x5c
+#define PN_FUNC_SEL1_OFFSET	0x60
+#define SKW_PINREG_BASE 0x40102000
+#define SKW_DL_FLAG_BASE 0x40100030
+#define SKW_DL_FLAG_BIT_MASK BIT(8)
+
+#define BIT_PN_DSLP_EN_START	17
+#define BIT_PN_DSLP_EN_END	21
+#define BIT_DRV_STREN_START	14
+#define BIT_DRV_STREN_END	16
+#define BIT_NORMAL_WP_START	8
+#define BIT_NORMAL_WP_END	10
+#define BIT_SCHMITT_START	11
+#define BIT_SCHMITT_END	11
+#define BIT_SLP_WP_START	2
+#define BIT_SLP_WP_END	4
+#define BIT_SLEEP_IE_OE_START	0
+#define BIT_SLEEP_IE_OE_END	1
+
+enum dma_type_en{
+	ADMA=1,
+	SDMA,
+};
+enum skw_service_ops {
+	SKW_NO_SERVICE =0,
+	SKW_WIFI_START,
+	SKW_WIFI_STOP,
+	SKW_BT_START,
+	SKW_BT_STOP,
+};
+
+enum skw_subsys{
+	SKW_BOOT=0,
+	SKW_BSP,
+	SKW_WIFI,
+	SKW_BT,
+	SKW_ALL,
+};
+
+struct seekwave_device {
+	struct  platform_device *pdev;
+	char *skw_nv_name;
+	char *iram_file_path;
+	char *dram_file_path;
+	char *iram_img_data;
+	char *dram_img_data;
+	char *dl_base_img;//
+	char *nv_mem_data;
+	char *nv_mem_cmfg_data;
+	char *nv_mem_pnfg_data;
+	int  (*wifi_start)(void);
+	int  (*bt_start)(void);
+	int  (*wifi_stop)(void);
+	int  (*bt_stop)(void);
+	int  (*skw_dloader_module)(int service_index);
+	u32 nv_mem_cmfg_size;
+	u32 nv_mem_pnfg_size;
+	unsigned short iram_crc_val;
+	unsigned short dram_crc_val;
+	unsigned short nvmem_crc_val;
+	unsigned int iram_dl_addr;
+	unsigned int iram_dl_size;
+	unsigned int iram_crc_offset;
+	unsigned int iram_crc_en;
+	unsigned int dram_dl_addr;
+	unsigned int dram_dl_size;
+	unsigned int dram_crc_offset;
+	unsigned int dram_crc_en;
+	unsigned int setup_addr;//setup address
+	unsigned int save_setup_addr;//send the setup address register
+	unsigned int first_dl_flag;
+	unsigned int first_boot_flag;
+	unsigned int dl_module;
+	unsigned int dma_type_addr;//1:ADMA,2:SDMA
+	unsigned int dma_type;//1:ADMA,2:SDMA
+	unsigned int slp_disable;//0:disable,1:enable
+	unsigned int slp_disable_addr;
+	unsigned int head_addr;
+	unsigned int tail_addr;
+	unsigned int bsp_head_addr;
+	unsigned int bsp_tail_addr;
+	unsigned int wifi_head_addr;
+	unsigned int wifi_tail_addr;
+	unsigned int bt_head_addr;
+	unsigned int bt_tail_addr;
+	unsigned int nv_mem_addr;
+	unsigned int nv_mem_size;
+	unsigned int nv_head_addr;
+	unsigned int nv_tail_addr;
+	unsigned int nv_data_size;
+	unsigned int nvmem_crc_en;
+	unsigned int nvmem_crc_offset;
+	unsigned int chip_id;
+	unsigned int fpga_debug;
+	unsigned int bt_antenna;
+	unsigned int dl_offset_addr;
+	unsigned int dl_base_addr;
+	unsigned int dl_addr;//
+	unsigned int dl_acount_addr;
+	unsigned int dl_size;
+	int img_size;
+	int bsp_index_count;
+	int bt_index_count;
+	int wifi_index_count;
+	int all_dl_count;
+	int bt_dl_count;
+	int wifi_dl_count;
+	int host_gpio;/*GPIO0_A3*/
+	int chip_gpio;/*GPIO2_D2*/
+	int chip_en;/*GPIO0_B1*/
+	int bt_service_state;
+	int wifi_service_state;
+	int service_ops;
+	int dl_done_signal;
+	int gpio_out;//host wakeup gpio0:/*GPIO0_A3*/,chip_wakeup gpio2:/*GPIO2_D2*/
+	int gpio_in;//host wakeup gpio 0
+	int gpio_val;
+	int gpio_next_val;
+
+};
+void seekwave_boot_exit(void);
+int seekwave_boot_init(char *chip_id);
+int skw_ucom_init(void);
+void skw_ucom_exit(void);
+int skw_bind_boot_driver(struct device *dev);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_dump_mem.c b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_dump_mem.c
new file mode 100755
index 0000000..c3dcf25
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_dump_mem.c
@@ -0,0 +1,284 @@
+
+#include "sv6621s_mem_map.h"
+#include "skw_log_to_file.h"
+
+struct memory_segment {
+	const char 	*name;
+	uint32_t 	address;
+	uint32_t 	size;
+};
+
+struct memory_segment cp_mem_seg[] = {
+	{ "CODE", 	CODE_MEM_BASE_ADDR, CODE_MEM_SIZE},
+	{ "DATA", 	DATA_MEM_BASE_ADDR, DATA_MEM_SIZE},
+	{ "AHBR", 	AHB_REG_BASE_ADDR, AHB_REG_SIZE},
+	{ "PPBM", 	UMEM_MEM_BASE_ADDR, UMEM_MEM_SIZE},
+	{ "SMEM", 	SMEM_MEM_BASE_ADDR, SMEM_MEM_SIZE},
+	{ "WFRF", 	0x40140000, 0x4000},
+	{ "CSCB", 	0xE000E000, 0x1000},
+	{ "WREG", 	WREG_MEM_BASE_ADDR, WREG_MEM_SIZE},
+	{ "PHYR", 	PHYR_MEM_BASE_ADDR, PHYR_MEM_SIZE},
+	{ "SDIO", 	SDIO_MEM_BASE_ADDR, SDIO_MEM_SIZE},
+	{ "BTRG", 	BTDM_MEM_BASE_ADDR, BTDM_MEM_SIZE},
+	{ "BTEM", 	BTEM_MEM_BASE_ADDR, BTEM_MEM_SIZE},
+	{ "BTGB", 	BTGB_MEM_BASE_ADDR, BTGB_MEM_SIZE},
+	{ "BTRF", 	BTRF_MEM_BASE_ADDR, BTRF_MEM_SIZE},
+	{ "RFTOP", 	RFTOP_MEM_BASE_ADDR, RFTOP_MEM_SIZE},
+	{ "RCLK", 	RCLK_MEM_BASE_ADDR, RCLK_MEM_SIZE},
+	{ "BBPLL", 	BBPLL_MEM_BASE_ADDR, BBPLL_MEM_SIZE}
+
+};
+static uint32_t skw_checksum(void *data, int data_len)
+{
+	uint32_t *d32 = data;
+	uint32_t checksum=0;
+	int i;
+
+	data_len = data_len >> 2;
+	for (i=0; i<data_len; i++)
+		checksum += d32[i];
+	return checksum;
+}
+int skw_dump_memory_into_buffer(struct ucom_dev *ucom, char *buffer, int length)
+{
+	struct memory_segment *mem_sg = &cp_mem_seg[0];
+	int offset=0;
+	uint16_t seq, packet_len;
+	uint32_t sg_size;
+	char *read_buf;
+	uint8_t sg_count;
+	int ret = 0;
+
+	if (!ucom || !ucom->pdata ||
+	    !ucom->pdata->skw_dump_mem)
+		return 0;
+	sg_count = sizeof(cp_mem_seg)/sizeof(cp_mem_seg[0]);
+	if (sg_count==0)
+		return 0;
+	packet_len = 0x800;
+	read_buf = kmalloc(packet_len, GFP_KERNEL);
+	if (read_buf==NULL)
+		return 0;
+	buffer[offset] = sg_count; //save total segment count
+	offset++;
+
+	do {
+		uint32_t source_addr;
+		sg_size = mem_sg->size;
+
+		memcpy(&buffer[offset], mem_sg->name, 5); //save segment name
+		offset += 5;
+		memcpy(&buffer[offset], &mem_sg->address, 4); //save segment base addrss
+		offset += 4;
+		memcpy(&buffer[offset], &mem_sg->size, 4); //save segment size
+		offset += 4;
+		memcpy(&buffer[offset], &packet_len, 2); //save segment size
+		offset += 2;
+		skwlog_log("%s %s:%d 0x%x 0x%x\n", __func__, mem_sg->name,
+			offset, mem_sg->address, mem_sg->size);
+		seq = 0;
+		source_addr = mem_sg->address;
+		do {
+			int read_len;
+			uint32_t sum;
+
+			memcpy(&buffer[offset], &seq, 2); //save segment size
+			seq++;
+			offset += 2;
+
+			if (sg_size > packet_len)
+				read_len = packet_len;
+			else
+				read_len = sg_size;
+			ret = ucom->pdata->skw_dump_mem(source_addr,(void *)read_buf,read_len);
+			if (ret != 0) {
+				skwlog_err("%s dump memory fail :%d \n", __func__, ret);
+				break;
+			}
+			source_addr += read_len;
+			memcpy(buffer+offset, read_buf, read_len); //save packet payload 
+			offset += read_len;
+
+			sum = skw_checksum(read_buf, read_len);
+			memcpy (buffer+offset, &sum, 4); //save checksum
+			offset += 4;
+
+			sg_size -= read_len;
+		} while (sg_size);
+		mem_sg++;
+		sg_count--;
+	} while (sg_count && (!ret));
+	kfree(read_buf);
+	return offset;
+}
+
+static int skw_ucom_dump_from_buffer(char __user *buf, size_t count, loff_t *pos)
+{
+	int len;
+	int ret;
+
+	len = 0;
+	ret = 0;
+	if (dump_log_size) {
+		if (*pos + count < dump_log_size)
+			len = count;
+		else if (*pos < dump_log_size)
+			len = dump_log_size - *pos;
+		if (len)
+			ret = copy_to_user(buf, &dump_memory_buffer[*pos], len);
+		if (ret ==0)
+			ret = len;
+	} else if (*pos == 0){
+		char assert_info[32]={0};
+		sprintf(assert_info,"modem_status=%d\n", cp_exception_sts);
+		ret = copy_to_user(buf, assert_info, strlen(assert_info));
+		len = strlen(assert_info);
+	}
+	*pos = *pos+len;
+	if (len==0)
+		skwboot_log("dump_log_size: %d offset %d count %d %p ret=%d\n", dump_log_size, (int)*pos, (int)count, dump_memory_buffer, ret);
+	return ret;
+}
+
+static int user_dump_open(struct inode *ip, struct file *fp)
+{
+	if (dump_buffer_size)
+		return 0;
+	return 0;
+}
+
+static ssize_t user_dump_read(struct file *fp, char __user *buf, size_t count, loff_t *pos)
+{
+	ssize_t ret = skw_ucom_dump_from_buffer(buf, count, pos);
+	if (ret == 0)
+		dump_log_size = 0;
+	return ret;
+
+}
+static int user_dump_release(struct inode *ip, struct file *fp)
+{
+	return 0;
+}
+
+static ssize_t user_dump_write(struct file *fp, const char __user *buf, size_t count, loff_t *pos)
+{
+	return 0;
+}
+static long user_dump_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	return 0;
+}
+static const struct file_operations skw_dump_ops = {
+	.owner	= THIS_MODULE,
+	.open	= user_dump_open,
+	.read	= user_dump_read,
+	.write	= user_dump_write,
+	.unlocked_ioctl = user_dump_ioctl,
+	.release= user_dump_release,
+};
+static int bt_state_event_notifier(struct notifier_block *nb, unsigned long action, void *data)
+{
+	struct ucom_dev *ucom = container_of(nb, struct ucom_dev, notifier);
+	//int status = cp_exception_sts;
+	switch(action)
+	{
+		case DEVICE_ASSERT_EVENT:
+		{
+			skwboot_log("BT BSPASSERT EVENT received!!!!\n");
+			cp_exception_sts = 1;
+			ucom = ucoms[log_portno];
+			if (dump_log_size){
+				skwboot_log(" dump done ,mem size: %d\n", dump_log_size);
+				return NOTIFY_OK;
+			}
+			if (!dump_memory_buffer && !atomic_read(&ucom->open) && SKW_DUMP_BUFFER_SIZE!=0)
+				dump_memory_buffer = kzalloc(SKW_DUMP_BUFFER_SIZE, GFP_KERNEL);
+			if(ucom->pdata && ucom->pdata->dump_modem_memory) {
+				dump_log_size = 0;
+				if (dump_memory_buffer) {
+					dump_buffer_size = SKW_DUMP_BUFFER_SIZE;
+					ucom->pdata->dump_modem_memory(dump_memory_buffer,
+							dump_buffer_size, &dump_log_size);
+				}
+			} else if (dump_memory_buffer) {
+				dump_buffer_size = SKW_DUMP_BUFFER_SIZE;
+				if (!atomic_read(&ucom->open))
+					dump_log_size = skw_dump_memory_into_buffer(ucom, dump_memory_buffer,dump_buffer_size);
+            }
+
+		}
+		break;
+		case DEVICE_BSPREADY_EVENT:
+		{
+			cp_exception_sts = 0;
+			skwboot_log("BT BSPREADY EVENT Comming in !!!!\n");
+		}
+		break;
+		case DEVICE_DUMPDONE_EVENT:
+		{
+			cp_exception_sts = 2;
+			skwboot_log("BT DUMPDONE EVENT Comming in !!!!\n");
+		}
+		break;
+		case DEVICE_BLOCKED_EVENT:
+		{
+			cp_exception_sts = 3;
+			skwboot_log("BT BLOCKED EVENT Comming in !!!!\n");
+			if (dump_log_size){
+				skwboot_log(" dump done ,mem size: %d\n", dump_log_size);
+				return NOTIFY_OK;
+			}
+			if (!dump_memory_buffer)
+				dump_memory_buffer = kzalloc(SKW_DUMP_BUFFER_SIZE, GFP_KERNEL);
+			if (dump_memory_buffer) {
+				dump_buffer_size = SKW_DUMP_BUFFER_SIZE;
+				dump_log_size = skw_dump_memory_into_buffer(ucom, dump_memory_buffer,dump_buffer_size);
+			}
+		}
+		break;
+		case DEVICE_DISCONNECT_EVENT:
+		{
+			cp_exception_sts = action;
+		}
+		break;
+		default:
+		break;
+
+	}
+	return NOTIFY_OK;
+}
+
+static int skw_bt_state_event_init(struct ucom_dev *ucom)
+{
+	int ret = 0;
+	int devno;
+	if (ucom->pdata->modem_register_notify && ucom->notifier.notifier_call == NULL) {
+		ucom->notifier.notifier_call = bt_state_event_notifier;
+		ucom->pdata->modem_register_notify(&ucom->notifier);
+	}
+	ret =__register_chrdev(skw_major, UCOM_PORTNO_MAX+1, 1,
+                                          "SKWDUMP", &skw_dump_ops);
+
+	devno = MKDEV(skw_major, UCOM_PORTNO_MAX+1);
+	if (ret==0)
+		device_create(skw_com_class, NULL, devno, NULL, "%s", "SKWDUMP");
+	skwlog_log("%s enter  ret=%d\n",__func__, ret);
+	return ret;
+}
+
+static int skw_bt_state_event_deinit(struct ucom_dev *ucom)
+{
+	int ret = 0;
+	int devno;
+	if(ucom) {
+		if((ucom->notifier.notifier_call)){
+			skwboot_log("%s :%d release the notifier \n", __func__,__LINE__);
+			ucom->notifier.notifier_call = NULL;
+			ucom->pdata->modem_unregister_notify(&ucom->notifier);
+		}
+		devno = MKDEV(skw_major, UCOM_PORTNO_MAX+1);
+		__unregister_chrdev(skw_major, MINOR(devno), 1,  "SKWDUMP");
+		device_destroy(skw_com_class, devno);
+	}
+	return ret;
+}
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_log_to_file.c b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_log_to_file.c
new file mode 100755
index 0000000..c8067da
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_log_to_file.c
@@ -0,0 +1,767 @@
+/*****************************************************************
+ *Copyright (C) 2021 Seekwave Tech Inc.
+ *Filename : skw_log_process.c
+ *Authors:seekwave platform
+ *
+ * This software is licensed under the terms of the the GNU
+ * General Public License version 2, as published by the Free
+ * Software Foundation, and may be copied, distributed, and
+ * modified under those terms.
+ *
+ * This program is distributed in the hope that it will be usefull,
+ * but without any warranty;without even the implied warranty of
+ * merchantability or fitness for a partcular purpose. See the
+ * GUN General Public License for more details.
+ * **************************************************************/
+
+/* #define DEBUG */
+/* #define VERBOSE_DEBUG */
+
+#include <linux/kernel.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+#include <linux/scatterlist.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/version.h>
+#include <linux/types.h>
+#include <linux/file.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include "skw_boot.h"
+
+#include "skw_log_to_file.h"
+#include "skw_mem_map.h"
+extern int cp_exception_sts;
+static char *log_path = "/data";
+module_param(log_path, charp, 0644);
+
+int skw_log_num = 2;
+#ifdef MIN_LOG_SIZE
+int skw_log_size = (1*1024*1024);
+#else
+int skw_log_size = (100*1024*1024);
+#endif
+#define SKW_LOG_READ_BUFFER_SIZE (8*1024)
+
+#ifndef CONFIG_NO_GKI
+#define CONFIG_NO_GKI
+#endif
+
+module_param(skw_log_size, int, 0644);
+module_param(skw_log_num, int, 0644);
+
+struct skw_log_data	{
+	spinlock_t lock;
+
+	int state;
+
+	/* synchronize access to our device file */
+	atomic_t open_excl;
+	/* to enforce only one ioctl at a time */
+	atomic_t ioctl_excl;
+
+
+	int rx_done;
+	/* for processing MTP_SEND_FILE, MTP_RECEIVE_FILE and
+	 * MTP_SEND_FILE_WITH_HEADER ioctls on a work queue
+	 */
+	struct workqueue_struct *wq;
+	struct work_struct log_to_file_work;
+	struct file *xfer_file;
+	loff_t xfer_file_offset;
+	int64_t xfer_file_length;
+	unsigned xfer_send_header;
+	uint16_t xfer_command;
+	uint32_t xfer_transaction_id;
+	int xfer_result;
+};
+
+struct log_com_dev	{
+	atomic_t open;
+	spinlock_t lock;
+	int 	rx_busy;
+	int	tx_busy;
+	int	devno;
+	int	portno;
+	struct sv6160_platform_data *pdata;
+	wait_queue_head_t wq;
+	struct cdev cdev;
+	char	*rx_buf;
+	char	*tx_buf;
+	struct notifier_block notifier;
+};
+struct skw_log_read_buffer	{
+	int 	lenth;
+	char	*buffer;
+};
+
+
+void skw_modem_log_to_file_work(struct work_struct *data);
+extern int skw_cp_exception_reboot(void);
+
+int skw_modem_save_dumpmem(void);
+static uint32_t record_flag = 0;
+static uint32_t cp_assert_status = 0;
+struct file *log_fp = NULL;
+struct log_com_dev *log_com = NULL;
+struct sv6160_platform_data *port_data = NULL;
+struct skw_log_data *skw_log_dev = NULL;
+struct skw_log_read_buffer log_read_buffer;
+
+char *log0_file = "log000";
+char *log1_file = "log111";
+char *log_file;
+
+char* skw_code_mem = "code_mem_100000_7a000";
+char* skw_data_mem = "data_mem_20200000_40000";
+char* skw_cscb_mem = "cscb_mem_e000ed00_300";
+char* skw_wreg_mem = "wreg_mem_40820000_4000";
+char* skw_phyr_mem = "phyr_mem_40830000_4000";
+char* skw_smem_mem = "smem_mem_40a00000_58000";
+char* skw_umem_mem = "umem_mem_40b00000_c000";
+char* skw_modem_mem = "sdio_mem_401e0000_800";
+char* skw_btdm_mem = "btdm_mem_41000000_400";
+char* skw_btbt_mem = "btbt_mem_41000400_400";
+char* skw_btle_mem = "btle_mem_41000800_400";
+char* skw_btem_mem = "btem_mem_41022000_c000";
+
+int modem_event_notifier(struct notifier_block *nb, unsigned long action, void *data)
+{
+	unsigned long flags;
+	skwboot_log("%s event = %d\n", __func__, (int)action);
+	switch(action)
+	{
+		case DEVICE_ASSERT_EVENT:
+		{
+			struct log_com_dev *ucom = log_com;
+			skwboot_log("the BSPASSERT EVENT Comming in !!!!\n");
+			skw_modem_log_start_rec();
+			spin_lock_irqsave(&ucom->lock, flags);
+			if(ucom->tx_busy) {
+				spin_unlock_irqrestore(&ucom->lock, flags);
+				skwboot_err("%s error 0\n", __func__);
+				return NOTIFY_OK;
+			}
+			ucom->tx_busy = 1;
+			spin_unlock_irqrestore(&ucom->lock, flags);
+			skw_modem_log_set_assert_status(1);
+		}
+		break;
+		case DEVICE_BSPREADY_EVENT:
+		{
+			skwboot_log("the BSPREADY EVENT Comming in !!!!\n");
+			skw_modem_log_start_rec();
+		}
+		break;
+		case DEVICE_DUMPDONE_EVENT:
+		{
+			skwboot_log("the DUMPDONE EVENT Comming in !!!!\n");
+			skw_modem_log_stop_rec();
+			skw_modem_log_set_assert_status(0);
+		}
+		break;
+		case DEVICE_BLOCKED_EVENT:
+		{
+			skwboot_log("the BLOCKED EVENT Comming in !!!!\n");
+			//skw_modem_dumpmodem_start_rec();
+		}
+		break;
+		case DEVICE_DUMPMEM_EVENT:
+		{
+			//skw_modem_dumpmodem_start_rec();
+		}
+		break;
+		default:
+		{
+
+		}
+		break;
+
+	}
+	return NOTIFY_OK;
+}
+
+void skw_modem_log_to_file_work(struct work_struct *data)
+{
+#ifdef CONFIG_NO_GKI
+	//int log_len = 0;
+	struct file *fp = log_fp;
+	struct file *log_store_fp = NULL;
+	loff_t offset =0;
+	loff_t log_store_offset =0;
+	unsigned long flags;
+	uint32_t *rx_data;
+	size_t count = 0;
+	int ret = 0;
+	int i = 0;
+	int log_cnt = 0;
+	int sdma_rx_error_cnt = 0;
+	char *log_store;
+	int  log_path_len;
+	char rd_buff[50];
+
+	if(record_flag)
+		return;
+	record_flag = 1;
+
+	skwlog_log("log path = %s \n", log_path);
+	log_path_len = strlen(log_path);
+
+	log_file = kzalloc(log_path_len + 16, GFP_KERNEL);
+
+	if(log_file == NULL)
+		return;
+	log_store = kzalloc(log_path_len + 16, GFP_KERNEL);
+	if(log_store == NULL) {
+		kfree(log_file);
+		return;
+	}
+
+	sprintf(log_store, "%s/log_store", log_path);
+	log_store_fp = filp_open(log_store, O_RDWR, 0777);
+	while(IS_ERR(log_store_fp))
+	{
+		skwlog_err("open log_store %s failed:%d	\n", log_store, (int)PTR_ERR(log_store_fp));
+		msleep(1000);
+		i++;
+		if(i > 8) {
+			kfree(log_store);
+			kfree(log_file);
+			return;
+		}
+		if(i > 5){
+			skwlog_err("%s open log_store file failed, create file:%s\n",__func__, log_store);
+			log_store_fp = filp_open(log_store, O_CREAT | O_RDWR | O_TRUNC, 0777);
+		}
+		else{
+			log_store_fp = filp_open(log_store, O_RDWR, 0777);
+		}
+	}
+
+	ret = skw_read_file(log_store_fp, rd_buff, 30, &log_store_offset);
+	if(ret < 0){
+		skwlog_err("%s Read file:%s failed, err:%d \n",__func__, log_store, ret);
+	}
+
+	rd_buff[0] = rd_buff[0]+1;
+	if(rd_buff[0] > skw_log_num) {
+		rd_buff[0] = 0;
+	}
+	sprintf(log_file, "%s/%s", log_path, log0_file);
+	log_path_len = strlen(log_file);
+	*(log_file + log_path_len - 1) = ((rd_buff[0]%10) | 0x30);
+	*(log_file + log_path_len - 2) = (((rd_buff[0]/10)%10) | 0x30);
+
+	log_store_offset = 0;
+	ret = skw_write_file(log_store_fp, rd_buff, 1, &log_store_offset);
+	if(ret < 0){
+		skwlog_err("%s write file:%s failed, err:%d \n",__func__, log_store, ret);
+	}
+	ret = skw_write_file(log_store_fp, log_file, strlen(log_file), &log_store_offset);
+	if(ret < 0){
+		skwlog_err("%s write f name to file:%s failed, err:%d \n",__func__, log_store, ret);
+	}
+
+	log_fp = filp_open(log_file, O_CREAT | O_RDWR | O_TRUNC, 0777);
+	fp = log_fp;
+
+	while(IS_ERR(fp))
+	{
+		skwlog_err("open rec file %s failed :%d	\n",log_file, (int)PTR_ERR(fp));
+		msleep(500);
+		i++;
+		if(i > 10) {
+			kfree(log_store);
+			kfree(log_file);
+			return;
+		}
+
+		log_fp = filp_open(log_file, O_CREAT | O_RDWR | O_TRUNC, 0777);
+		fp = log_fp;
+	}
+	atomic_inc(&log_com->open);
+	if (atomic_read(&log_com->open)==1) {
+		init_waitqueue_head(&log_com->wq);
+		spin_lock_init(&log_com->lock);
+		log_com->pdata->open_port(log_com->portno, NULL, NULL);
+	}
+
+	log_read_buffer.lenth = 0;
+	log_read_buffer.buffer = kzalloc(SKW_LOG_READ_BUFFER_SIZE, GFP_KERNEL);
+	if(!log_read_buffer.buffer){
+			kfree(log_store);
+			kfree(log_file);
+			return;
+	}
+	skwlog_log(" open %s for CP log record \n", log_file);
+	while(record_flag || cp_assert_status)
+	{
+		ret = 0;
+	//	if(log_com){
+check_rx_busy:
+			spin_lock_irqsave(&log_com->lock, flags);
+			if(log_com->rx_busy) {
+				spin_unlock_irqrestore(&log_com->lock, flags);
+				mdelay(5);
+				goto check_rx_busy;
+			}
+			log_com->rx_busy = 1;
+			count = log_com->pdata->max_buffer_size;
+			spin_unlock_irqrestore(&log_com->lock, flags);
+			ret = log_com->pdata->hw_sdma_rx(log_com->portno, (log_read_buffer.buffer + log_read_buffer.lenth), count);
+
+			if(ret > 0){
+				log_cnt++;
+				sdma_rx_error_cnt = 0;
+				log_read_buffer.lenth = log_read_buffer.lenth + ret;
+				//skwlog_log("hw_sdma_rx:%s read len:%d buffer len:%d \n", skw_log, ret, log_read_buffer.lenth);
+				if(ret >= 0x1000)
+					skwlog_err("%s get too long data , err:%d \n",__func__, ret);
+
+				if(log_cnt > 1000){
+					skwlog_log("%s log_file:%s offset:%lld data:0x%x 0x%x 0x%x 0x%x 0x%x	\n",__func__, log_file, offset, *(log_read_buffer.buffer),
+						*(log_read_buffer.buffer+1), *(log_read_buffer.buffer+2), *(log_read_buffer.buffer+3), *(log_read_buffer.buffer+4));
+					log_cnt = 0;
+				}
+			}
+			else{
+				skwlog_err("%s read log data err:%d \n",__func__, ret);
+				sdma_rx_error_cnt++;
+				if(sdma_rx_error_cnt > 5){
+					skwlog_err("%s sdma_rx_error_cnt over:%d, stop log work \n",__func__, sdma_rx_error_cnt);
+					skw_modem_log_set_assert_status(0);
+					skw_modem_log_stop_rec();
+				}
+			}
+
+			if (port_data->bus_type == USB_LINK) {
+				if(ret < 0){
+					skwlog_err("%s read log data err:%d, stop log work \n",__func__, ret);
+					skw_modem_log_set_assert_status(0);
+					skw_modem_log_stop_rec();
+				}
+			}
+			else if (port_data->bus_type == SDIO_LINK) {
+				if(ret == -ENOTCONN){
+					skw_modem_log_set_assert_status(0);
+					skw_modem_log_stop_rec();
+				}
+			}
+			//skwlog_log("read log from SDIO len:%d  ----- \n", log_read_buffer.lenth);
+			log_com->rx_busy = 0;
+			rx_data = (uint32_t *)log_read_buffer.buffer;	
+	//	}
+
+		if(((log_read_buffer.lenth > 0) && cp_assert_status) 
+			|| ((SKW_LOG_READ_BUFFER_SIZE - log_read_buffer.lenth) <= (log_com->pdata->max_buffer_size))){
+			//skwlog_log("skw_write_file:%s offset:%lld lenth:%d \n", skw_log, offset, log_read_buffer.lenth);
+			ret = skw_write_file(fp, log_read_buffer.buffer, log_read_buffer.lenth, &offset);
+			if(ret < 0){
+				skwlog_err("%s write file failed, err:%d \n",__func__, ret);
+			}
+
+			log_read_buffer.lenth = 0;
+			if(ret == -ENOSPC){
+				skwlog_err("%s no space, stop CP log record \n",__func__);
+				skw_modem_log_stop_rec();
+			}
+
+			if(offset > skw_log_size && (!cp_assert_status)){
+				if(!IS_ERR(fp))
+					filp_close(fp, NULL);
+
+				rd_buff[0] = rd_buff[0]+1;
+				if(rd_buff[0] > skw_log_num) {
+					rd_buff[0] = 0;
+				}
+				sprintf(log_file, "%s/%s", log_path, log0_file);
+				log_path_len = strlen(log_file);
+				*(log_file + log_path_len - 1) = ((rd_buff[0]%10) | 0x30);
+				*(log_file + log_path_len - 2) = (((rd_buff[0]/10)%10) | 0x30);
+
+				log_store_offset = 0;
+				ret = skw_write_file(log_store_fp, rd_buff, 1, &log_store_offset);
+				if(ret < 0){
+					skwlog_err("%s write file:%s failed, err:%d \n",__func__, log_store, ret);
+				}
+				ret = skw_write_file(log_store_fp, log_file, strlen(log_file), &log_store_offset);
+				if(ret < 0){
+					skwlog_err("%s write f name to file:%s failed, err:%d \n",__func__, log_store, ret);
+				}
+
+				log_fp = filp_open(log_file, O_CREAT | O_RDWR | O_TRUNC, 0777);
+				fp = log_fp;
+				if(IS_ERR(fp)){
+					skwlog_err("%s switch record file to:%s failed: %d \n",__func__, log_file, (int)PTR_ERR(fp));
+					kfree(log_file);
+					kfree(log_store);
+					kfree(log_read_buffer.buffer);
+					return;
+				}
+				else{
+					skwlog_err("%s switch record file to:%s sucess \n",__func__, log_file);
+				}
+
+				offset = 0;
+			}
+		}
+	}
+	atomic_dec(&log_com->open);
+	log_com->pdata->close_port(log_com->portno);
+
+	if(!IS_ERR(fp)){
+		filp_close(fp, NULL);
+		skwlog_log("%s close file %s before stop work.\n",__func__, log_file);
+		log_fp = NULL;
+		fp = log_fp;
+	}
+
+	if(!IS_ERR(log_store_fp)){
+		filp_close(log_store_fp, NULL);
+		skwlog_log("%s close file %s before stop work.\n",__func__, log_store);
+		log_store_fp = NULL;
+	}
+	kfree(log_file);
+	kfree(log_store);
+	kfree(log_read_buffer.buffer);
+	skwlog_log("%s work exit\n",__func__);
+#endif
+	return;
+}
+
+/***************************************************************************
+ *Description:dump modem memory
+ *Seekwave tech LTD
+ *Author:JunWei Jiang
+ *Date:2022-11-14
+ *Modify:
+ **************************************************************************/
+int skw_modem_save_mem(char *mem_path,unsigned int mem_len, unsigned int mem_addr)
+{
+	int ret =0;
+#ifdef CONFIG_NO_GKI
+	char dump_mem_file[128];
+	//dump code data
+	char *data_mem = NULL;
+	struct file *fp =NULL;
+	loff_t pos = 0;
+	int nwrite = 0;
+	unsigned int read_len=0;
+	unsigned int mem_size = mem_len;
+
+	memset(dump_mem_file, 0, sizeof(dump_mem_file));
+	sprintf(dump_mem_file,"%s/%s", log_path, mem_path);
+	data_mem = kzalloc(SKW_MAX_BUF_SIZE, GFP_KERNEL);
+	if(!data_mem){
+		skwlog_log("the kzalloc dump buffer fail");
+		return -2;
+	}
+	/* open file to write */
+	fp = filp_open(dump_mem_file, O_CREAT | O_RDWR | O_TRUNC, 0777);
+	if (IS_ERR(fp)) {
+		skwlog_log("open file %s fail try again!!!\n", dump_mem_file);
+		fp = filp_open(mem_path, O_CREAT | O_RDWR | O_TRUNC, 0777);
+		if(IS_ERR(fp)){
+			skwlog_log("open file error\n");
+			ret = -1;
+			goto exit;
+		}
+	}
+
+	while(mem_size)
+	{
+		if(mem_size<SKW_MAX_BUF_SIZE)
+		{
+			read_len = mem_size;
+		}else{
+			read_len = SKW_MAX_BUF_SIZE;
+		}
+		//skwlog_log("the read_len =0x%x mem_size= 0x%x\n", read_len, mem_size);
+		ret = log_com->pdata->skw_dump_mem(mem_addr+(mem_len-mem_size),(void *)data_mem,read_len);
+		if(ret< 0)
+			break;
+
+		//print_hex_dump(KERN_ERR, "img data ", 0, 16, 1,data_mem, 32, 1);
+		//pos=(unsigned long)offset;
+		/* Write buf to file */
+		nwrite=skw_write_file(fp, data_mem,read_len, &pos);
+		//offset +=nwrite;
+
+		if(mem_size>=SKW_MAX_BUF_SIZE)
+		{
+			mem_size=mem_size-SKW_MAX_BUF_SIZE;
+		}else{
+			mem_size =0;
+		}
+	}
+	skwlog_log("Dump %s memory done !!\n", mem_path);
+	if(fp)
+	{
+		filp_close(fp,NULL);
+		skwlog_log("the file close!!!\n");
+	}
+exit:
+	if (fp)
+		filp_close(fp, NULL);
+	kfree(data_mem);
+#endif
+	return ret;
+}
+
+/***************************************************************************
+ *Description:dump modem memory
+ *Seekwave tech LTD
+ *Author:JunWei Jiang
+ *Date:2022-11-14
+ *Modify:
+ **************************************************************************/
+int skw_modem_save_dumpmem(void)
+{
+	int ret =0;
+	if (!log_com->pdata->skw_dump_mem)
+		return 0;
+	skwlog_log("%s [Enter]\n",__func__);
+	//DATA MEM
+	ret = skw_modem_save_mem(skw_data_mem,DATA_MEM_SIZE, DATA_MEM_BASE_ADDR);
+	if(ret !=0)
+	{
+		skwlog_log("%s dump fail ret: %d\n", skw_data_mem, ret);
+		return -1;
+	}
+	//CODE MEM
+	ret = skw_modem_save_mem(skw_code_mem,CODE_MEM_SIZE, CODE_MEM_BASE_ADDR);
+	if(ret !=0)
+	{
+		skwlog_log("%s dump fail ret: %d\n", skw_code_mem,ret);
+		return -1;
+	}
+	//CSCB MEM
+	ret = skw_modem_save_mem(skw_cscb_mem,CSCB_MEM_SIZE, CSCB_MEM_BASE_ADDR);
+	if(ret !=0)
+	{
+		skwlog_log("%s dump fail ret: %d\n", skw_cscb_mem,ret);
+		return -1;
+	}
+
+	//UMEM MEM
+	ret = skw_modem_save_mem(skw_umem_mem,UMEM_MEM_SIZE, UMEM_MEM_BASE_ADDR);
+	if(ret !=0)
+	{
+		skwlog_log("%s dump fail ret: %d\n", skw_umem_mem,ret);
+		return -1;
+	}
+	//SDIO MEM
+	ret = skw_modem_save_mem(skw_modem_mem,SDIO_MEM_SIZE, SDIO_MEM_BASE_ADDR);
+	if(ret !=0)
+	{
+		skwlog_log("%s dump fail ret: %d\n",skw_modem_mem,ret);
+		return -1;
+	}
+	//BTDM MEM
+	ret = skw_modem_save_mem(skw_btdm_mem,BTDM_MEM_SIZE, BTDM_MEM_BASE_ADDR);
+	if(ret !=0)
+	{
+		skwlog_log("%s dump fail ret: %d\n", skw_btdm_mem,ret);
+		return -1;
+	}
+	//BTBT MEM
+	ret = skw_modem_save_mem(skw_btbt_mem,BTBT_MEM_SIZE, BTBT_MEM_BASE_ADDR);
+	if(ret !=0)
+	{
+		skwlog_log("%s dump fail ret: %d\n", skw_btbt_mem,ret);
+		return -1;
+	}
+	//BTLE MEM
+	ret = skw_modem_save_mem(skw_btle_mem,BTLE_MEM_SIZE, BTLE_MEM_BASE_ADDR);
+	if(ret !=0)
+	{
+		skwlog_log("%s dump fail ret: %d\n", skw_btle_mem,ret);
+		return -1;
+	}
+	//BTEM MEM
+	ret = skw_modem_save_mem(skw_btem_mem,BTEM_MEM_SIZE, BTEM_MEM_BASE_ADDR);
+	if(ret !=0){
+		skwlog_log("%s dump fail ret: %d\n",skw_btem_mem,ret);
+		return -1;
+	}
+	//WREG MEM
+	ret = skw_modem_save_mem(skw_wreg_mem,WREG_MEM_SIZE, WREG_MEM_BASE_ADDR);
+	if(ret !=0)
+	{
+		skwlog_log("%s dump fail ret: %d\n", skw_wreg_mem,ret);
+		return -1;
+	}
+	//PHYR MEM
+	ret = skw_modem_save_mem(skw_phyr_mem,PHYR_MEM_SIZE, PHYR_MEM_BASE_ADDR);
+	if(ret !=0)
+	{
+		skwlog_log("%s dump fail ret: %d\n", skw_phyr_mem,ret);
+		return -1;
+	}
+	//SMEM MEM
+	ret = skw_modem_save_mem(skw_smem_mem,SMEM_MEM_SIZE, SMEM_MEM_BASE_ADDR);
+	if(ret !=0)
+	{
+		skwlog_log("%s dump fail ret: %d\n", skw_smem_mem,ret);
+		return -1;
+	}
+	return ret;
+}
+
+int skw_modem_log_init(struct sv6160_platform_data *p_data, struct file *fp, void *ucom)
+{
+
+	int ret = 0;
+#ifdef CONFIG_NO_GKI
+	if (skw_log_dev)
+		return ret;
+	if(skw_log_size > (200*1024*1024))
+		skw_log_size = (200*1024*1024);
+	if(skw_log_size < (512*1024))
+		skw_log_size = (512*1024);
+	if((skw_log_size*skw_log_num) > (1024*1024*1024))
+		skw_log_num = (1024*1024*1024)/skw_log_size;
+	if(skw_log_num > 99)
+		skw_log_num = 99;
+
+	skwlog_log("%s enter skw_log_num:%d skw_log_size:%d   \n",__func__, skw_log_num, skw_log_size);
+	log_fp = fp;
+	log_com = ucom;
+	port_data = p_data;
+	
+	skw_log_dev = (struct skw_log_data *)kzalloc(sizeof(*skw_log_dev), GFP_KERNEL);
+	if (!skw_log_dev){
+		ret = -ENOMEM;
+		skwlog_err("%s line:%d,can't malloc \n", __func__, __LINE__);
+		goto err1;
+	}
+
+	spin_lock_init(&skw_log_dev->lock);
+	atomic_set(&skw_log_dev->open_excl, 0);
+	atomic_set(&skw_log_dev->ioctl_excl, 0);
+	//INIT_LIST_HEAD(&skw_log_dev->tx_idle);
+
+
+	skw_log_dev->wq = create_singlethread_workqueue("skw_log");
+	if (!skw_log_dev->wq) {
+		ret = -ENOMEM;
+		goto err2;
+	}
+	INIT_WORK(&skw_log_dev->log_to_file_work, skw_modem_log_to_file_work);
+
+	if (log_com->pdata->modem_register_notify) {
+		if(log_com->notifier.notifier_call == NULL){
+			log_com->notifier.notifier_call = modem_event_notifier;
+			log_com->pdata->modem_register_notify(&log_com->notifier);
+		}
+	}
+
+	skw_modem_log_start_rec();
+	return 1;
+
+err2:
+	if (skw_log_dev->wq) {
+		destroy_workqueue(skw_log_dev->wq);
+		skw_log_dev->wq = NULL;
+	}
+	kfree(skw_log_dev);
+	skwlog_err("mtp gadget driver failed to initialize\n");
+
+err1:
+#else
+	skwlog_warn("GKI Not support log2file!\n");
+#endif
+	return ret;
+}
+
+void skw_modem_log_set_assert_status(uint32_t cp_assert)
+{
+	cp_assert_status = cp_assert;
+	if(cp_assert_status){
+		skwlog_log("%s CP in ASSERT, dump log in %s \n",__func__, log_file);
+	}
+}
+
+void skw_modem_log_start_rec(void)
+{
+#ifdef CONFIG_NO_GKI
+	skwlog_log("%s enter  \n",__func__);
+	if(atomic_read(&log_com->open) > 1){
+		skwlog_warn("%s:log port is busy\n",__func__);
+		return;
+	}
+	if(!skw_log_dev){
+		skwlog_warn("%s no mem ready, can't start \n",__func__);
+		return;
+	}
+	if(record_flag){
+		skwlog_warn("%s lof2file already \n",__func__);
+		return;
+	}
+	cp_assert_status = 0;
+	queue_work(skw_log_dev->wq, &skw_log_dev->log_to_file_work);
+#else
+	skwlog_warn("GKI Not support log2file!\n");
+#endif
+
+}
+
+/***************************************************************************
+ *Description:dump modem memory
+ *Seekwave tech LTD
+ *Author:JunWei Jiang
+ *Date:2022-11-14
+ *Modify:
+ **************************************************************************/
+void skw_modem_dumpmodem_start_rec(void)
+{
+#ifdef CONFIG_NO_GKI
+	skw_modem_save_dumpmem();
+#else
+	skwlog_warn("GKI Not support dump!\n");
+#endif
+
+}
+
+/***************************************************************************
+ *Description:dump modem memory
+ *Seekwave tech LTD
+ *Author:JunWei Jiang
+ *Date:2022-11-14
+ *Modify:
+ **************************************************************************/
+
+void skw_modem_log_stop_rec(void)
+{
+	skwlog_log("%s enter \n",__func__);
+	if(record_flag)
+		record_flag = 0;
+	if(log_com && log_com->pdata && log_com->pdata->close_port)
+		log_com->pdata->close_port(log_com->portno);
+	return;
+}
+
+void skw_modem_log_exit(void)
+{
+#ifdef CONFIG_NO_GKI
+	skwlog_log("%s enter\n",__func__);
+	if(!log_com) return;
+	log_com->pdata->modem_unregister_notify(&log_com->notifier);
+	skw_modem_log_stop_rec();
+	cancel_work_sync(&skw_log_dev->log_to_file_work);
+	destroy_workqueue(skw_log_dev->wq);
+	kfree(skw_log_dev);
+	skw_log_dev = NULL;
+	log_com = NULL;
+#endif
+}
+
+//DECLARE_USB_FUNCTION_INIT(mtp, mtp_alloc_inst, mtp_alloc);
+MODULE_LICENSE("GPL");
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_log_to_file.h b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_log_to_file.h
new file mode 100755
index 0000000..bae2e64
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_log_to_file.h
@@ -0,0 +1,51 @@
+/*****************************************************************
+ *Copyright (C) 2021 Seekwave Tech Inc.
+ *Filename : skw_sdio.h
+ *Authors:seekwave platform
+ *
+ * This software is licensed under the terms of the the GNU
+ * General Public License version 2, as published by the Free
+ * Software Foundation, and may be copied, distributed, and
+ * modified under those terms.
+ *
+ * This program is distributed in the hope that it will be usefull,
+ * but without any warranty;without even the implied warranty of
+ * merchantability or fitness for a partcular purpose. See the
+ * GUN General Public License for more details.
+ * **************************************************************/
+#ifndef __SKW_LOG_H__
+#define __SKW_LOG_H__
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+
+/****************************************************************
+ *Description:the skwsdio log define and the skwsdio data debug,
+ *Func: skwsdio_log, skwsdio_err, skwsdio_data_pr;
+ *Calls:
+ *Call By:
+ *Input: skwsdio log debug informations
+ *Output:
+ *Return:
+ *Others:
+ *Author:JUNWEI.JIANG
+ *Date:2022-07-18
+ * **************************************************************/
+#define skwlog_log(fmt, args...) \
+    pr_info("[SKWLOG]:" fmt, ## args)
+
+#define skwlog_err(fmt, args...) \
+    pr_err("[SKWLOG_ERR]:" fmt, ## args)
+
+#define skwlog_warn(fmt, args...) \
+    pr_warn("[SKWLOG_WARN]:" fmt, ## args)
+
+int skw_modem_log_init(struct sv6160_platform_data *p_data, struct file *fp, void *ucom);
+void skw_modem_log_set_assert_status(uint32_t cp_assert);
+void skw_modem_dumpmodem_start_rec(void);
+void skw_modem_log_start_rec(void);
+void skw_modem_log_stop_rec(void);
+void skw_modem_log_exit(void);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_mem_map.h b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_mem_map.h
new file mode 100755
index 0000000..e0e083a
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_mem_map.h
@@ -0,0 +1,116 @@
+/*
+ * skw_mem_map.h
+ * Copyright (C) 2022 cfig <junwei.jiang@seekwavetech.com>
+ *
+ * Distributed under terms of the MIT license.
+ */
+
+#ifndef SKW_MEM_MAP_H
+#define SKW_MEM_MAP_H
+
+//#define SKW_MAX_BUF_SIZE    0x400 //1K
+#define SKW_MAX_BUF_SIZE    0x100 //256B
+
+/*---------------CODE MEM SECTION-------------------------*/
+#define CODE_MEM_BASE_ADDR		  0x100000
+#define CODE_MEM_SIZE			   0x7A000//488K
+/*-------------------------------------------------------*/
+
+/*----------------DATA MEM SECTION-----------------------*/
+#define DATA_MEM_BASE_ADDR		  0x20200000
+#define DATA_MEM_SIZE			   0x40000//256K
+/*-------------------------------------------------------*/
+
+/*----------------CSCB MEM SECTION-----------------------*/
+#define CSCB_MEM_BASE_ADDR		  0xE000ED00
+#define CSCB_MEM_SIZE			   0x300//0.75K
+/*-------------------------------------------------------*/
+
+
+/*----------------WREG MEM SECTION-----------------------*/
+#define WREG_MEM_BASE_ADDR		  0x40820000
+#define WREG_MEM_SIZE			   0x4000//16K
+/*-------------------------------------------------------*/
+
+
+/*----------------PHYR MEM SECTION-----------------------*/
+#define PHYR_MEM_BASE_ADDR		  0x40830000
+#define PHYR_MEM_SIZE			   0x4000//16K
+/*-------------------------------------------------------*/
+
+
+/*----------------SMEM MEM SECTION-----------------------*/
+#define SMEM_MEM_BASE_ADDR		  0x40A00000
+#define SMEM_MEM_SIZE			   0x58000//352K
+/*-------------------------------------------------------*/
+
+
+/*----------------UMEM MEM SECTION-----------------------*/
+#define UMEM_MEM_BASE_ADDR		  0x40B00000
+#define UMEM_MEM_SIZE			   0xC000//48K
+/*-------------------------------------------------------*/
+
+
+/*----------------SDIO MEM SECTION-----------------------*/
+#define SDIO_MEM_BASE_ADDR		  0x401E0000
+#define SDIO_MEM_SIZE			   0x800//2K
+/*-------------------------------------------------------*/
+
+
+/*----------------BTDM MEM SECTION-----------------------*/
+#define BTDM_MEM_BASE_ADDR		  0x41000000
+#define BTDM_MEM_SIZE			   0x400//1K
+/*-------------------------------------------------------*/
+
+
+/*----------------BTBT MEM SECTION-----------------------*/
+#define BTBT_MEM_BASE_ADDR		  0x41000400
+#define BTBT_MEM_SIZE			   0x400//1K
+/*-------------------------------------------------------*/
+
+
+/*----------------BTLE MEM SECTION-----------------------*/
+#define BTLE_MEM_BASE_ADDR		  0x41000800
+#define BTLE_MEM_SIZE			   0x400//1K
+/*-------------------------------------------------------*/
+
+
+/*----------------BTEM MEM SECTION-----------------------*/
+#define BTEM_MEM_BASE_ADDR		  0x41010000
+#define BTEM_MEM_SIZE			   0xC000//48K
+/*-------------------------------------------------------*/
+
+
+/*----------------BTGB MEM SECTION-----------------------*/
+#define BTGB_MEM_BASE_ADDR		  0x41022000
+#define BTGB_MEM_SIZE			   0x40//64B
+/*-------------------------------------------------------*/
+
+
+/*----------------BTRF MEM SECTION-----------------------*/
+#define BTRF_MEM_BASE_ADDR		  0x41024000
+#define BTRF_MEM_SIZE			   0x510//1K 272B
+/*-------------------------------------------------------*/
+
+/*-------------------------------------------------------*/
+//SV6316
+/*-------------------------------------------------------*/
+#define HIF_EDMA_BASE_ADDR      0x40188000
+#define HIF_EDMA_SIZE           0x1080
+/*-------------------------------------------------------*/
+
+/*-------------------------------------------------------*/
+#define WFRF_BASE_ADDR      0x40144000
+#define WFRF_MEM_SIZE           0x5000
+/*-------------------------------------------------------*/
+
+/*-------------------------------------------------------*/
+#define RFGLB_BASE_ADDR      0x40150000
+#define RFGLB_MEM_SIZE           0x600
+/*-------------------------------------------------------*/
+
+/*-------------------------------------------------------*/
+
+
+
+#endif /* !SKW_MEM_MAP_H */
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_user_com.c b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_user_com.c
new file mode 100755
index 0000000..aa2eba5
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/skw_user_com.c
@@ -0,0 +1,534 @@
+#include <linux/kernel.h>
+#include <linux/cdev.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/compat.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+#include <linux/scatterlist.h>
+ #include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include "skw_boot.h"
+#include "skw_log_to_file.h"
+#define UCOM_PORTNO_MAX		13
+#define UCOM_DEV_PM_OPS NULL
+int cp_exception_sts=0;
+static int log_start;
+static unsigned int tmp_chipid = 0;
+static int log_portno = 0;
+static int	skw_major = 0;
+struct mutex ucom_mutex;
+static struct class *skw_com_class = NULL;
+struct ucom_dev	{
+	atomic_t open;
+	spinlock_t lock;
+	int 	rx_busy;
+	int	tx_busy;
+	int	devno;
+	int	portno;
+	struct sv6160_platform_data *pdata;
+	wait_queue_head_t wq;
+	struct cdev cdev;
+	char	*rx_buf;
+	char	*tx_buf;
+	struct notifier_block notifier;
+};
+static struct ucom_dev *ucoms[UCOM_PORTNO_MAX];
+static char *dump_memory_buffer;
+static int  dump_buffer_size, dump_log_size;
+#include "skw_dump_mem.c"
+static int user_boot_open(struct inode *ip, struct file *fp)
+{
+	struct cdev *char_dev;
+	int ret = -EIO, i;
+	struct ucom_dev *ucom=NULL;
+
+	char_dev = ip->i_cdev;
+	for(i=0; i< UCOM_PORTNO_MAX; i++) {
+		if(ucoms[i] && (ucoms[i]->devno == char_dev->dev)) {
+			ucom = ucoms[i];
+			ret = 0;
+			break;
+		}
+	}
+
+	if(cp_exception_sts)
+		ret = -EIO;
+	if(ucom && !cp_exception_sts) {
+		if(atomic_read(&ucom->open))
+			return -EBUSY;
+		if(!cp_exception_sts)
+			ret=ucom->pdata->open_port(ucom->portno, NULL, NULL);
+
+		if (ret == 0) {
+			atomic_inc(&ucom->open);
+			fp->private_data = ucom;
+		}
+	}
+	skwboot_log("Open user_boot device: ret=%d task %d\n", ret, (int)current->pid);
+
+	return ret;
+}
+
+static int user_boot_release(struct inode *ip, struct file *fp)
+{
+	struct ucom_dev *ucom = fp->private_data;
+	int count=100;
+	while(cp_exception_sts &&count--)
+		msleep(10);
+
+	if(ucom){
+		if(!cp_exception_sts)
+			ucom->pdata->close_port(ucom->portno);
+		atomic_dec(&ucom->open);
+	}
+
+	return 0;
+}
+
+static int ucom_open(struct inode *ip, struct file *fp)
+{
+	struct cdev *char_dev;
+	int ret = -EIO, i;
+	struct ucom_dev *ucom=NULL;
+
+	char_dev = ip->i_cdev;
+	for(i=0; i< UCOM_PORTNO_MAX; i++) {
+		if(ucoms[i] && (ucoms[i]->devno == char_dev->dev)) {
+			ucom = ucoms[i];
+			ret = 0;
+			break;
+		}
+	}
+	if(ucom) {
+		if(cp_exception_sts && strncmp(ucom->pdata->port_name, "LOG", 3)){
+			skwboot_log("%s line:%d the modem assert \n", __func__,__LINE__);
+			return -EIO;
+		}
+		if(atomic_read(&ucom->open) > 1){
+			skwboot_log("%s ,%d\n", __func__, __LINE__);
+			return -EBUSY;
+		}
+		atomic_inc(&ucom->open);
+		if (atomic_read(&ucom->open)==1) {
+			init_waitqueue_head(&ucom->wq);
+			spin_lock_init(&ucom->lock);
+			ucom->pdata->open_port(ucom->portno, NULL, NULL);
+		}
+		fp->private_data = ucom;
+		skwboot_log("%s: ucom[%d] %s(0x%x)\n", __func__, i, ucom->pdata->port_name, ucom->portno);
+
+		if(!strncmp((char *)ucom->pdata->chipid,"SV6160",6))
+			tmp_chipid = 0x6160;
+		else if(!strncmp((char *)ucom->pdata->chipid,"SV6316", 6))
+			tmp_chipid = 0x6316;
+		else if(!strncmp((char *)ucom->pdata->chipid,"SV6160LITE", 10))
+			tmp_chipid = 0x6161;
+
+		skwboot_log("the portno=%d - chipid = 0x%lx \n",ucom->portno, (unsigned long)tmp_chipid);
+	}
+
+	return ret;
+}
+static int ucom_release(struct inode *ip, struct file *fp)
+{
+	struct ucom_dev *ucom = fp->private_data;
+	int i;
+
+	for(i=0; i< UCOM_PORTNO_MAX; i++) {
+		if(ucoms[i] == ucom)
+			break;
+	}
+	fp->private_data = NULL;
+	if(ucom && (i<UCOM_PORTNO_MAX)) {
+		skwboot_log("%s: ucom%p %s(0x%x)\n", __func__, ucom, ucom->pdata->port_name, ucom->devno);
+		if (atomic_read(&ucom->open)) {
+			atomic_dec(&ucom->open);
+			// Don't close LOG/AT port once open it, otherwise RX transfer lost.
+			if (strncmp(ucom->pdata->port_name, "LOG", 3) &&
+			    strncmp(ucom->pdata->port_name, "ATC", 3))
+				ucom->pdata->close_port(ucom->portno);
+			else if (!log_start && !strncmp(ucom->pdata->port_name, "LOG", 3))
+				ucom->pdata->close_port(ucom->portno);
+			wake_up(&ucom->wq);
+		} else
+			kfree(ucom);
+	}
+	return 0;
+}
+
+static ssize_t ucom_read(struct file *fp, char __user *buf, size_t count, loff_t *pos)
+{
+	struct ucom_dev *ucom = fp->private_data;
+	ssize_t r = count;
+	int ret;
+	unsigned long flags;
+	uint32_t *data;
+
+	if(atomic_read(&ucom->open)==0)
+		return -EIO;
+	if (strncmp(ucom->pdata->port_name, "LOG", 3) && cp_exception_sts)
+		return -EIO;
+
+	if (!strncmp(ucom->pdata->port_name, "LOG", 3) && dump_log_size) {
+		return skw_ucom_dump_from_buffer(buf, count, pos);
+	}
+
+	spin_lock_irqsave(&ucom->lock, flags);
+	if(ucom->rx_busy) {
+		spin_unlock_irqrestore(&ucom->lock, flags);
+		return -EAGAIN;
+	}
+	ucom->rx_busy = 1;
+	if(count > ucom->pdata->max_buffer_size)
+		count = ucom->pdata->max_buffer_size;
+	spin_unlock_irqrestore(&ucom->lock, flags);
+	ret = ucom->pdata->hw_sdma_rx(ucom->portno, ucom->rx_buf, count);
+	ucom->rx_busy = 0;
+	data = (uint32_t *)ucom->rx_buf;
+
+	if(ret > 0) {
+		r = ret;
+		if(ret > count)
+			ret = copy_to_user(buf, ucom->rx_buf, count);
+		else
+			ret = copy_to_user(buf, ucom->rx_buf, ret);
+		if(ret > 0)
+			return -EFAULT;
+	} else	r = ret;
+	pr_debug("%s %s ret = %d\n", __func__,current->comm, (int)r);
+	return r;
+}
+
+static ssize_t ucom_write(struct file *fp, const char __user *buf, size_t count, loff_t *pos)
+{
+	struct ucom_dev *ucom = fp->private_data;
+	int ret = 0;
+	ssize_t r = count;
+	ssize_t size;
+	unsigned long flags;
+
+	if(cp_exception_sts || atomic_read(&ucom->open)==0)
+		return -EIO;
+	spin_lock_irqsave(&ucom->lock, flags);
+	if(ucom->tx_busy) {
+		spin_unlock_irqrestore(&ucom->lock, flags);
+		skwboot_log("%s error 0\n", __func__);
+		return -EAGAIN;
+	}
+	ucom->tx_busy = 1;
+	spin_unlock_irqrestore(&ucom->lock, flags);
+	while(count){
+		if(count > ucom->pdata->max_buffer_size)
+			size =  ucom->pdata->max_buffer_size;
+		else
+			size = count;
+		if(copy_from_user(ucom->tx_buf, buf, size))
+			return -EFAULT;
+
+		if(ucom->pdata->port_name && !strncmp(ucom->pdata->port_name, "LOG", 3)){
+			if(!strncmp(ucom->tx_buf, "START", 5)){
+				skwboot_log("%s START log to file \n", __func__);
+				log_start = skw_modem_log_init(ucom->pdata, NULL, (void *)ucom);
+
+			}
+			else if(!strncmp(ucom->tx_buf, "STOP", 4)){
+				skwboot_log("%s STOP log to file \n", __func__);
+				skw_modem_log_exit();
+				log_start = 0;
+			}
+			else
+				skwboot_log("%s LOG write string:%s \n", __func__, ucom->tx_buf);
+			ucom->tx_busy = 0;
+			return r;
+		}
+		if (ucom->pdata->port_name && !strncmp(ucom->pdata->port_name, "ATC", 3)) {
+			 ucom->tx_buf[size++] = 0x0D;
+			 ucom->tx_buf[size++] = 0x0A;
+			 count += 2;
+		}
+		ret = ucom->pdata->hw_sdma_tx(ucom->portno, ucom->tx_buf, size);
+
+		if(ret < 0){
+			skwboot_log("the close the ucom tx_busy=0");
+			ucom->tx_busy=0;
+			return ret;
+		}
+		count -= ret;
+		buf += ret;
+	}
+	ucom->tx_busy = 0;
+	pr_debug("%s %s ret = %d\n", __func__,current->comm,(int)r);
+	return r;
+}
+
+static long ucom_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	struct ucom_dev *ucom = fp->private_data;
+	int i;
+
+	for(i=0; i< UCOM_PORTNO_MAX; i++) {
+		if(ucoms[i] == ucom)
+			break;
+	}
+	if((i<UCOM_PORTNO_MAX) && atomic_read(&ucom->open)) {
+		skwboot_log("%s ucom_%p rx_busy=%d\n", __func__, ucom, ucom->rx_busy);
+		if (ucom->pdata && ucom->rx_busy)
+			ucom->pdata->close_port(ucom->portno);
+	}
+	return 0;
+}
+#ifdef CONFIG_COMPAT
+static long ucom_compat_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	return ucom_ioctl(fp, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+static ssize_t boot_read(struct file *fp, char __user *buf, size_t count, loff_t *pos)
+{
+	u32 status;
+	struct ucom_dev *ucom = fp->private_data;
+	int ret = 0;
+
+	ret = ucom->pdata->hw_sdma_rx(ucom->portno, (char *)&status, 4);
+	if (ret > 0)
+		ret = copy_to_user(buf, &status, ret);
+	return ret;
+}
+
+
+static ssize_t boot_write(struct file *fp, const char __user *buf, size_t count, loff_t *pos)
+{
+	struct ucom_dev *ucom = fp->private_data;
+	int ret = 0;
+	ret = ucom->pdata->hw_sdma_tx(ucom->portno, "WAKE", 4);
+	if(ret > 0)
+		return count;
+
+	return ret;
+}
+static long boot_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	struct ucom_dev *ucom = fp->private_data;
+	unsigned char cp_log_state = 0;
+	int ret = 0;
+	switch (cmd) {
+		case 0:
+			ret = copy_to_user((char*)arg, (char*)&tmp_chipid, 4);
+			skwboot_log("the orgchip = %s ,the chipid = 0x%x\n",(char *)ucom->pdata->chipid, tmp_chipid);
+			break;
+		case _IOWR('S', 1, uint8_t *):
+			break;
+		case _IOWR('S', 2, uint8_t *):
+#ifdef CONFIG_SEEKWAVE_PLD_RELEASE
+			cp_log_state =1; //close log
+			//skw_sdio_cp_log(1);
+#else
+			cp_log_state = 2;//open log
+#endif
+			ret = copy_to_user((char*)arg, (char*)&cp_log_state, 1);
+			skwboot_log("the cp_log_state = %d \n", cp_log_state);
+			break;
+	}
+	return 0;
+}
+
+#ifdef CONFIG_COMPAT
+static long boot_compat_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	return boot_ioctl(fp, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+
+static const struct file_operations skw_ucom_ops = {
+	.owner	= THIS_MODULE,
+	.open	= ucom_open,
+	.read	= ucom_read,
+	.write	= ucom_write,
+	.unlocked_ioctl = ucom_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = ucom_compat_ioctl,
+#endif
+	.release= ucom_release,
+};
+static const struct file_operations skw_user_boot_ops = {
+	.owner	= THIS_MODULE,
+	.open	= user_boot_open,
+	.read	= boot_read,
+	.write	= boot_write,
+	.unlocked_ioctl = boot_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = boot_compat_ioctl,
+#endif
+	.release= user_boot_release,
+};
+
+static int skw_ucom_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct sv6160_platform_data *pdata = dev->platform_data;
+	struct ucom_dev	*ucom;
+	int ret = 0;
+
+	mutex_lock(&ucom_mutex);
+	if(skw_com_class == NULL) {
+		skw_com_class = class_create(THIS_MODULE, "btcom");
+		if(IS_ERR(skw_com_class)) {
+			skwboot_log("skw_ucom_probe: class prt = %p\n", skw_com_class);
+			ret =  PTR_ERR(skw_com_class);
+			skw_com_class = NULL;
+			mutex_unlock(&ucom_mutex);
+			return ret;
+		}else{
+			skwboot_log(
+				"skw_ucom_probe:class prt = %p, name = %s\n",
+				skw_com_class, skw_com_class->name);
+		}
+	}
+	mutex_unlock(&ucom_mutex);
+
+	if (pdata) {
+		ucom = kzalloc(sizeof(struct ucom_dev), GFP_KERNEL);
+		if(!ucom)
+			return -ENOMEM;
+		if (strncmp(pdata->port_name, "BTBOOT", 6)) {
+			ucom->rx_buf = kzalloc(pdata->max_buffer_size, GFP_KERNEL);
+			if(ucom->rx_buf) {
+				ucom->tx_buf = kzalloc(pdata->max_buffer_size, GFP_KERNEL);
+				if(!ucom->tx_buf) {
+					kfree(ucom->rx_buf);
+					kfree(ucom);
+					return -ENOMEM;
+				}
+			}else{
+				kfree(ucom);
+				return -ENOMEM;
+			}
+			ret =__register_chrdev(skw_major, pdata->data_port+1, 1, pdata->port_name, &skw_ucom_ops);
+		} else {
+			pdata->data_port = UCOM_PORTNO_MAX - 1;
+			ret =__register_chrdev(skw_major, UCOM_PORTNO_MAX, 1,
+					pdata->port_name, &skw_user_boot_ops);
+		}
+		if(ret < 0) {
+			kfree(ucom->rx_buf);
+			kfree(ucom->tx_buf);
+			kfree(ucom);
+			return ret;
+		}
+		if(skw_major == 0)
+			skw_major = ret;
+		ucom->devno = MKDEV(skw_major, pdata->data_port+1);
+		ucom->pdata = pdata;
+		ucom->portno = pdata->data_port;
+		atomic_set(&ucom->open, 0);
+		platform_set_drvdata(pdev, ucom);
+		ucoms[ucom->portno] = ucom;
+		device_create(skw_com_class, NULL, ucom->devno, NULL, "%s", pdata->port_name);
+		if(!strncmp(ucom->pdata->port_name, "ATC", 3))
+			skw_bt_state_event_init(ucom);
+                if (!strncmp(ucom->pdata->port_name, "LOG", 3)) {
+			log_portno = ucom->portno;
+#ifndef CONFIG_SEEKWAVE_PLD_RELEASE
+			log_start = skw_modem_log_init(ucom->pdata, NULL, (void *)ucom);
+#endif
+		}
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int skw_ucom_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct sv6160_platform_data *pdata = dev->platform_data;
+	struct ucom_dev *ucom;
+	int ret, i;
+	int devno;
+
+	ucom = platform_get_drvdata(pdev);
+
+	if(ucom) {
+		if(!strncmp(ucom->pdata->port_name, "LOG", 3)) {
+			skw_modem_log_exit();
+			log_start = 0;
+		}
+
+		if(!strncmp(ucom->pdata->port_name, "BTCMD", 5)) {
+			if (ucom->rx_busy) {
+				ucom->pdata->close_port(ucom->portno);
+			}
+			cp_exception_sts = 0;
+		}
+		if (!strncmp(ucom->pdata->port_name, "ATC", 3)) {
+			skw_bt_state_event_deinit(ucom);
+		}
+		ret = wait_event_interruptible_timeout(ucom->wq,
+				(!atomic_read(&ucom->open)),
+				msecs_to_jiffies(1000));
+		if (ret <= 0) {
+			skwboot_warn(
+				"%s: open timeout ucom%p %s(0x%x) -- open_count=%d \n",
+				__func__, ucom, ucom->pdata->port_name,
+				ucom->devno, atomic_read(&ucom->open));
+		}
+		skwboot_log("%s: ucom%p %s(0x%x) -- open_count=%d \n", __func__, ucom,
+			ucom->pdata->port_name, ucom->devno,atomic_read(&ucom->open));
+
+		devno = ucom->devno;
+		device_destroy(skw_com_class, devno);
+		ucoms[ucom->portno] = NULL;
+		kfree(ucom->rx_buf);
+		ucom->rx_buf = NULL;
+		kfree(ucom->tx_buf);
+		ucom->tx_buf = NULL;
+		if (!atomic_read(&ucom->open)) {
+			kfree(ucom);
+			ucom = NULL;
+		}else
+			atomic_set(&ucom->open, 0);
+		__unregister_chrdev(MAJOR(devno), MINOR(devno), 1,  pdata->port_name);
+		platform_set_drvdata(pdev, NULL);
+	}
+	for(i=0; i<UCOM_PORTNO_MAX; i++) {
+		if(ucoms[i])
+			break;
+	}
+	if (i >= UCOM_PORTNO_MAX && skw_com_class) {
+		class_destroy(skw_com_class);
+		skw_com_class = NULL;
+	}
+	return 0;
+}
+
+static struct platform_driver skw_ucom_driver = {
+	.driver = {
+		.name	= (char*)"skw_ucom",
+		.bus	= &platform_bus_type,
+		.pm	= UCOM_DEV_PM_OPS,
+	},
+	.probe = skw_ucom_probe,
+	.remove = skw_ucom_remove,
+};
+
+int skw_ucom_init(void)
+{
+	dump_log_size = 0;
+	log_start = 0;  
+	mutex_init(&ucom_mutex);
+	platform_driver_register(&skw_ucom_driver);
+	return 0;
+}
+
+void skw_ucom_exit(void)
+{
+	if (dump_memory_buffer)
+		kfree(dump_memory_buffer);
+	dump_memory_buffer  = NULL;
+	dump_log_size = 0;
+	cp_exception_sts=0;
+	mutex_destroy(&ucom_mutex);
+	platform_driver_unregister(&skw_ucom_driver);
+}
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/sv6621s_mem_map.h b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/sv6621s_mem_map.h
new file mode 100755
index 0000000..6c9003e
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/skwutil/sv6621s_mem_map.h
@@ -0,0 +1,114 @@
+/*
+ * sv6160_mem_map.h
+ * Copyright (C) 2022 cfig <junwei.jiang@seekwavetech.com>
+ *
+ * Distributed under terms of the MIT license.
+ */
+
+#ifndef SV6160_MEM_MAP_H
+#define SV6160_MEM_MAP_H
+
+//#define SKW_MAX_BUF_SIZE    0x400 //1K
+#define SKW_MAX_BUF_SIZE    0x100 //256B
+
+/*---------------CODE MEM SECTION-------------------------*/
+#define CODE_MEM_BASE_ADDR		  0x100000
+#define CODE_MEM_SIZE			   0x7A000//488K
+/*-------------------------------------------------------*/
+
+/*----------------DATA MEM SECTION-----------------------*/
+#define DATA_MEM_BASE_ADDR		  0x20200000
+#define DATA_MEM_SIZE			   0x40000//256K
+/*-------------------------------------------------------*/
+
+/*----------------CSCB MEM SECTION-----------------------*/
+#define AHB_REG_BASE_ADDR		  0x40000000
+#define AHB_REG_SIZE			  0x400
+/*-------------------------------------------------------*/
+
+
+/*----------------WREG MEM SECTION-----------------------*/
+#define WREG_MEM_BASE_ADDR		  0x40820000
+#define WREG_MEM_SIZE			   0xC000//48K
+/*-------------------------------------------------------*/
+
+
+/*----------------PHYR MEM SECTION-----------------------*/
+#define PHYR_MEM_BASE_ADDR		  0x40830000
+#define PHYR_MEM_SIZE			   0x4000//16K
+/*-------------------------------------------------------*/
+
+
+/*----------------SMEM MEM SECTION-----------------------*/
+#define SMEM_MEM_BASE_ADDR		  0x40A00000
+#define SMEM_MEM_SIZE			   0x58000//352K
+/*-------------------------------------------------------*/
+
+/*----------------UMEM MEM SECTION-----------------------*/
+#define UMEM_MEM_BASE_ADDR		  0x401E0000
+#define UMEM_MEM_SIZE			  0xC000//48K
+/*-------------------------------------------------------*/
+
+
+/*----------------SDIO MEM SECTION-----------------------*/
+#define SDIO_MEM_BASE_ADDR		  0x401D0000
+#define SDIO_MEM_SIZE			   0x800//2K
+/*-------------------------------------------------------*/
+
+
+/*----------------BTDM MEM SECTION-----------------------*/
+#define BTDM_MEM_BASE_ADDR		  0x41000000
+#define BTDM_MEM_SIZE			   0xC00//3K
+/*-------------------------------------------------------*/
+
+
+/*----------------BTBT MEM SECTION-----------------------*/
+#define BTBT_MEM_BASE_ADDR		  0x41000400
+#define BTBT_MEM_SIZE			   0x400//1K
+/*-------------------------------------------------------*/
+
+
+/*----------------BTLE MEM SECTION-----------------------*/
+#define BTLE_MEM_BASE_ADDR		  0x41000800
+#define BTLE_MEM_SIZE			   0x400//1K
+/*-------------------------------------------------------*/
+
+
+/*----------------BTEM MEM SECTION-----------------------*/
+#define BTEM_MEM_BASE_ADDR		  0x41010000
+#define BTEM_MEM_SIZE			   0xC000//48K
+/*-------------------------------------------------------*/
+
+
+/*----------------BTGB MEM SECTION-----------------------*/
+#define BTGB_MEM_BASE_ADDR		  0x41022000
+#define BTGB_MEM_SIZE			   0x40//64B
+/*-------------------------------------------------------*/
+
+
+/*----------------BTRF MEM SECTION-----------------------*/
+#define BTRF_MEM_BASE_ADDR		  0x41024000
+#define BTRF_MEM_SIZE			   0x510//1K 272B
+/*-------------------------------------------------------*/
+
+/*----------------BTRF MEM SECTION-----------------------*/
+#define RFTOP_MEM_BASE_ADDR		  0x40148000
+#define RFTOP_MEM_SIZE			   0xC20//
+/*-------------------------------------------------------*/
+
+
+/*----------------BTRF MEM SECTION-----------------------*/
+#define RCLK_MEM_BASE_ADDR		  0x40150000
+#define RCLK_MEM_SIZE			   0x200//512B
+/*-------------------------------------------------------*/
+
+/*----------------BTRF MEM SECTION-----------------------*/
+#define BBPLL_MEM_BASE_ADDR		  0x40150400
+#define BBPLL_MEM_SIZE			   0x200//512B
+/*-------------------------------------------------------*/
+/*-------------------------------------------------------*/
+/*-------------------------------------------------------*/
+
+
+
+#endif /* !SV6160_MEM_MAP_H */
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/Kconfig b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/Kconfig
new file mode 100755
index 0000000..ce99b1c
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/Kconfig
@@ -0,0 +1,10 @@
+config SKW_USB
+	tristate "SeekWave USB Support"
+	depends on USB ||COMPILE_TEST
+	default n
+	help
+	  Enable this module for WCN_USB
+	  chip usb interface bus Support.
+	  Please insmod this module before any other
+	  WCN subsystems. Thanks.
+
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/README.md b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/README.md
new file mode 100755
index 0000000..8d12f18
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/README.md
@@ -0,0 +1,2 @@
+#seekwave tech usb drivers
+
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb.h b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb.h
new file mode 100755
index 0000000..c25821d
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb.h
@@ -0,0 +1,55 @@
+/*****************************************************************
+ *Copyright (C) 2021 Seekwave Tech Inc.
+ *Filename : skw_usb.h
+ *Authors:seekwave platform
+ *
+ * This software is licensed under the terms of the the GNU
+ * General Public License version 2, as published by the Free
+ * Software Foundation, and may be copied, distributed, and
+ * modified under those terms.
+ *
+ * This program is distributed in the hope that it will be usefull,
+ * but without any warranty;without even the implied warranty of
+ * merchantability or fitness for a partcular purpose. See the
+ * GUN General Public License for more details.
+ * **************************************************************/
+#ifndef WCN_USB_H
+#define WCN_USB_H
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/bitops.h>
+#include <linux/kthread.h>
+#include <linux/notifier.h>
+#include "../skwutil/skw_boot.h"
+
+#define skwusb_log(fmt, args...) \
+	pr_info("[SKW_USB]:" fmt, ## args)
+
+#define skwusb_err(fmt, args...) \
+	pr_err("[SKW_USB_ERR]:" fmt, ## args)
+
+#define skwusb_data_pr(level, prefix_str, prefix_type, rowsize,\
+		groupsize, buf, len, asscii)\
+		do{if(loglevel) \
+			print_hex_dump(level, prefix_str, prefix_type, rowsize,\
+					groupsize, buf, len, asscii);\
+		}while(0)
+
+
+#define USB_RX_TASK_PRIO 90
+#define SKW_CHIP_ID_LENGTH			16  //SV6160 chip id lenght
+
+int skw_usb_recovery_debug(int disable);
+int skw_usb_recovery_debug_status(void);
+void reboot_to_change_bt_uart1(char *mode);
+int skw_usb_debug_log_open(void);
+int skw_usb_debug_log_close(void);
+
+#endif /* WCN_USB_H */
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_debugfs.c b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_debugfs.c
new file mode 100755
index 0000000..b391eee
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_debugfs.c
@@ -0,0 +1,123 @@
+/*****************************************************************************
+ * Copyright(c) 2020-2030  Seekwave Corporation.
+ * SEEKWAVE TECH LTD..CO
+ *Seekwave Platform the usb log debug fs
+ *FILENAME:skw_usb_debugfs.c
+ *DATE:2022-04-11
+ *MODIFY:
+ *
+ **************************************************************************/
+
+#include "skw_usb_debugfs.h"
+#include "skw_usb_log.h"
+#include "skw_usb.h"
+
+static struct proc_dir_entry *skw_usb_proc_root;
+
+static int skw_usb_proc_show(struct seq_file *seq, void *v)
+{
+#define SKW_BSP_CONFIG_INT(conf)                                          \
+	do {                                                          \
+		seq_printf(seq, "%s=%d\n", #conf, conf);              \
+	} while (0)
+
+#define SKW_BSP_CONFIG_BOOL(conf)                                         \
+	do {                                                          \
+		if (IS_ENABLED(conf))                                 \
+			seq_printf(seq, "%s=y\n", #conf);             \
+		else                                                  \
+			seq_printf(seq, "# %s is not set\n", #conf);  \
+	} while (0)
+
+#define SKW_BSP_CONFIG_STRING(conf)                                       \
+	do {                                                          \
+		seq_printf(seq, "%s=\"%s\"\n", #conf, conf);          \
+	} while (0)
+
+	seq_puts(seq, "\n");
+	seq_printf(seq, "Kernel Version:  \t%s\n",
+			UTS_RELEASE);
+	seq_puts(seq, "\n");
+
+	SKW_BSP_CONFIG_BOOL(CONFIG_SEEKWAVE_BSP_DRIVERS);
+	SKW_BSP_CONFIG_BOOL(CONFIG_SKW_USB);
+	SKW_BSP_CONFIG_BOOL(CONFIG_SEEKWAVE_PLD_RELEASE);
+
+	seq_puts(seq, "\n");
+
+	return 0;
+}
+
+static int skw_usb_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, skw_usb_proc_show, NULL);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_usb_profile_proc_fops = {
+	.proc_open = skw_usb_proc_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+};
+#else
+static const struct file_operations skw_usb_profile_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_usb_proc_open,
+	.read = seq_read,
+	.release = single_release,
+};
+#endif
+
+struct proc_dir_entry *skw_usb_procfs_file(struct proc_dir_entry *parent,
+				       const char *name, umode_t mode,
+				       const void *fops, void *data)
+{
+	struct proc_dir_entry *dentry = parent ? parent : skw_usb_proc_root;
+
+	if (!dentry)
+		return NULL;
+
+	return proc_create_data(name, mode, dentry, fops, data);
+}
+
+int skw_usb_proc_init_ex(const char *name, umode_t mode,
+				       const void *fops, void *data)
+{
+	if (!skw_usb_proc_root)
+		return -1;
+	skw_usb_procfs_file(skw_usb_proc_root, name, mode, fops, NULL);
+	return 0;
+}
+
+int skw_usb_proc_init(void)
+{
+	skw_usb_proc_root = proc_mkdir("skwusb", NULL);
+	if (!skw_usb_proc_root){
+		pr_err("creat proc skwusb failed\n");
+		return -1;
+	}
+
+	skw_usb_procfs_file(skw_usb_proc_root,"profile", 0666, &skw_usb_profile_proc_fops, NULL);
+
+	return 0;
+}
+
+void skw_usb_proc_deinit(void)
+{
+	if (!skw_usb_proc_root)
+		return;
+	proc_remove(skw_usb_proc_root);
+}
+
+int skw_usb_debugfs_init(void)
+{
+	skw_usb_dbg("%s :traced\n", __func__);
+	skw_usb_proc_init();
+	return 0;
+}
+
+void skw_usb_debugfs_deinit(void)
+{
+	skw_usb_dbg("%s :traced\n", __func__);
+	skw_usb_proc_deinit();
+}
\ No newline at end of file
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_debugfs.h b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_debugfs.h
new file mode 100755
index 0000000..6c13915
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_debugfs.h
@@ -0,0 +1,24 @@
+/******************************************************************************
+ *
+ * Copyright(c) 2020-2030  Seekwave Corporation.
+ *
+ *****************************************************************************/
+#ifndef __SKW_USB_DEBUGFS_H__
+#define __SKW_USB_DEBUGFS_H__
+
+#include <linux/fs.h>
+#include <linux/debugfs.h>
+#include <linux/proc_fs.h>
+#include <linux/scatterlist.h>
+#include <generated/utsrelease.h>
+#include "boot_config.h"
+#include "skw_boot.h"
+
+int skw_usb_debugfs_init(void);
+void skw_usb_debugfs_deinit(void);
+struct proc_dir_entry *skw_usb_procfs_file(struct proc_dir_entry *parent,
+				       const char *name, umode_t mode,
+				       const void *proc_fops, void *data);
+int skw_usb_proc_init_ex(const char *name, umode_t mode,
+				       const void *fops, void *data);
+#endif
\ No newline at end of file
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_io.c b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_io.c
new file mode 100755
index 0000000..3a9dcbd
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_io.c
@@ -0,0 +1,2825 @@
+/************************************************************************
+ *Copyright(C) 2020-2021: Seekwave tech LTD 		China
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/dma-mapping.h>
+#include <linux/version.h>
+#include <linux/notifier.h>
+#include <linux/semaphore.h>
+#include <linux/pm_runtime.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/err.h>
+#include <linux/wait.h>
+#include <linux/gpio.h>
+#include "skw_usb.h"
+#include "skw_usb_log.h"
+#include "skw_usb_debugfs.h"
+#if  LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)
+#include <linux/bits.h>
+#else
+#include <linux/bitops.h>
+#endif
+#ifndef GENMASK
+#ifdef CONFIG_64BIT
+#define BITS_PER_LONG 64
+#else
+#define BITS_PER_LONG 32
+#endif /* CONFIG_64BIT */
+#define GENMASK(h, l) \
+	(((~0UL) - (1UL << (l)) + 1) & (~0UL >> (BITS_PER_LONG - 1 - (h))))
+#endif
+
+#define MAX_BUFFER_SIZE 20*1024
+#define MAX_MSG_SIZE	MAX_BUFFER_SIZE
+
+#define VENDOR_MSG_MODEM_ASSERT 0xA5
+#define VENDOR_MSG_SERVICE_CTRL 0xA6
+#define VENDOR_MSG_PACKET_COUNT 0xA7
+#define VENDOR_MSG_LOG_SWITCH	0xA8
+#define VENDOR_MSG_MODEM_RESET  0xA9
+#define VENDOR_MSG_MODEM_SUSP   0xAA
+
+#define	WIFI_SERVICE	0
+#define BT_SERVICE	  1
+
+#define SERVICE_START	0
+#define SERVICE_STOP	1
+
+#define MODEM_OFF		0
+#define MODEM_ON		1
+#define MODEM_HALT		2
+#define MODEM_DOWNLOAD_FAILED   4
+
+#define WIFI_PORT_SHARE_FLAG	0x4000
+//#define MODEM_WDT_SUPPORT       0x40
+#define USB_HOST_RESUME_SUPPORT 0x20
+
+#define MAX_USB_PORT MAX_PORT_COUNT
+#define MAX_PACKET_COUNT 20
+static struct delayed_work skw_except_work;
+static struct work_struct add_device_work;
+static struct work_struct dump_memory_worker;
+static struct work_struct usb_control_worker;
+static struct platform_device *wifi_data_pdev;
+static u64 port_dmamask = DMA_BIT_MASK(32);
+static u32 service_state_map = 0;
+static int cp_log_status = 0;
+static char *firmware_data;
+static int	firmware_size;
+static int	firmware_addr;
+static struct seekwave_device *usb_boot_data;
+static struct completion download_done;
+static struct completion loop_completion;
+static BLOCKING_NOTIFIER_HEAD(modem_notifier_list);
+static int chip_en_gpio;
+static int host_wake_gpio;
+static int modem_status;
+static int usb_speed_switching;
+static int cls_recovery_mode_en;
+static char *skw_chipid;
+static u32 last_sent_wifi_cmd[3];
+static u32 last_recv_wifi_evt[3];
+static u32 last_recv_wifi_ack[3];
+static u64 last_sent_time, last_ack_time;
+static struct scatterlist *sgs;
+static int nr_sgs;
+static int start_service_flag = 0;
+static u16 transfer_index;
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+
+static const struct usb_device_id skw_usb_io_id_table[] = {
+	{ USB_VENDOR_AND_INTERFACE_INFO(0x3607, 0x02, 0x02, 0) },
+	{ USB_DEVICE(0x0483, 0x5720) },
+	{ USB_DEVICE(0x0483, 0x5721) },
+	{ USB_DEVICE(0x3607, 0x6316) },
+	{ USB_DEVICE(0x3607, 0x6621) },
+	{ USB_DEVICE(0x3607, 0x6160) },
+	{} /* Terminating entry */
+};
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:XW
+ *
+ ********************************************************************* */
+static struct recovery_data{
+	struct mutex except_mutex;
+	int cp_state;
+} g_recovery_data;
+
+#ifdef CONFIG_SKW_DL_TIME_STATS
+	ktime_t cur_time,last_time;
+#endif
+
+#define SKW_USB_GET_RECOVERY_DATA() &g_recovery_data
+
+static struct usb_port_struct {
+	struct work_struct work;
+	struct platform_device *pdev;
+	int	portno;
+	struct usb_interface *interface;
+	struct usb_device *udev;
+	struct urb *read_urb;
+	struct usb_endpoint_descriptor *epin;
+	struct urb *write_urb;
+	struct usb_endpoint_descriptor *epout;
+	char *read_buffer;
+	char	*write_buffer;
+	int		buffer_size;
+	struct usb_anchor read_submitted;
+	struct usb_anchor write_submitted;
+	struct task_struct *thread;
+	rx_submit_fn rx_submit;
+	adma_callback adma_tx_callback;
+	sdma_callback sdma_tx_callback;
+	void *rx_data;
+	void *tx_data;
+	int	state;
+	int  ep_mps;
+	int  max_packet_count;
+	struct semaphore sem;
+	int	is_dloader;
+	int	sent_packet_count;
+	int	req_tx_packet;
+	wait_queue_head_t	rx_wait;
+	wait_queue_head_t	tx_wait;
+	struct tasklet_struct tasklet;
+	struct list_head rx_urb_list;
+	struct list_head tx_urb_list;
+	struct list_head rx_done_urb_list;
+	struct list_head suspend_urb_list;
+	spinlock_t rx_urb_lock;
+	spinlock_t tx_urb_lock;
+	int	tx_urb_count;
+	int	rx_packet_count;
+	int     suspend;
+	u64 	tx_done_time;
+	u64 	rx_done_time;
+} *usb_ports[MAX_USB_PORT];
+
+static int modem_assert(void);
+static int skw_recovery_mode(void);
+static struct usb_port_struct *log_port;
+int skw_reset_bus_dev(void);
+extern void kernel_restart(char *cmd);
+static int bulkin_read_timeout(int portno, char *buffer, int size, int *actual, int timeout);
+static int bulkout_write_timeout(int portno, char *buffer, int size, int *actual, int timeout);
+static void bulkout_async_complete(struct urb *urb);
+static void bulkin_async_complete(struct urb *urb);
+static int assert_info_print;
+static int usb_bt_rx_entry(void *para);
+char firmware_version[256];
+#ifdef CONFIG_BT_SEEKWAVE
+static int	bt_audio_port;
+#endif
+static struct platform_device *bluetooth_pdev = NULL;
+static int wifi_port_share;
+static int bulk_async_read;
+static int dump_memory_done;
+static char* dump_memory_buffer=NULL;
+static int dump_buffer_size=0;
+static int* dump_log_size=NULL;
+static int usb_bus_num;
+static int usb_port_num;
+
+void skw_get_port_statistic(char *buffer, int size)
+{
+	int ret = 0;
+	int i;
+
+	if(!buffer)
+		return;
+
+	ret += sprintf(&buffer[ret], "%s", firmware_version);
+	for(i=0; i<MAX_USB_PORT; i++) {
+		if(ret >= size)
+			break;
+
+		if (usb_ports[i]) {
+			ret += sprintf(&buffer[ret],
+				"port%d: req_tx %d tx_done %d, rx %d: tx_time: 0x%x rx_time: 0x%x\n",
+				i, usb_ports[i]->req_tx_packet,	usb_ports[i]->sent_packet_count,
+		       		usb_ports[i]->rx_packet_count, (u32)usb_ports[i]->tx_done_time,
+				(u32)usb_ports[i]->rx_done_time);
+		}
+	}
+}
+
+#include "usb_boot.c"
+void modem_register_notify(struct notifier_block *nb)
+{
+	blocking_notifier_chain_register(&modem_notifier_list, nb);
+}
+void modem_unregister_notify(struct notifier_block *nb)
+{
+	blocking_notifier_chain_unregister(&modem_notifier_list, nb);
+}
+void modem_notify_event(int event)
+{
+	blocking_notifier_call_chain(&modem_notifier_list, event, NULL);
+}
+void skw_usb_exception_work(struct work_struct *work)
+{
+	struct recovery_data *recovery = SKW_USB_GET_RECOVERY_DATA();
+	skw_usb_info(" enter cp_state=%d...\n", recovery->cp_state);
+	mutex_lock(&recovery->except_mutex);
+	if(recovery->cp_state!=1)
+	{
+		mutex_unlock(&recovery->except_mutex);
+		return;
+	}
+	recovery->cp_state = DEVICE_BLOCKED_EVENT;
+	mutex_unlock(&recovery->except_mutex);
+	modem_notify_event(DEVICE_BLOCKED_EVENT);
+	service_state_map=0;
+	skw_recovery_mode();
+}
+
+int skw_usb_recovery_debug(int disable)
+{
+	cls_recovery_mode_en = disable;
+	skw_usb_info("the recovery status =%d\n", cls_recovery_mode_en);
+	return 0;
+}
+
+int skw_usb_recovery_debug_status(void)
+{
+	skw_usb_info("the recovery val =%d\n", cls_recovery_mode_en);
+	return cls_recovery_mode_en;
+}
+
+
+static void usb_setup_service_devices(void)
+{
+#ifdef CONFIG_BT_SEEKWAVE
+	struct usb_port_struct *bt_port;
+#endif
+	int ret;
+
+	skw_bind_boot_driver(&usb_ports[0]->udev->dev);
+	if(usb_ports[1]->pdev){
+		if(wifi_data_pdev==NULL) {
+			wifi_data_pdev = usb_ports[1]->pdev;                  
+			ret = platform_device_add(usb_ports[1]->pdev);
+			if(ret) {
+				skw_usb_err("the fail to register WIFI device\n");
+				wifi_data_pdev = NULL;
+				platform_device_put(usb_ports[1]->pdev);
+			} else {
+				skw_usb_info("add WIFI devices done\n");
+			}
+		 }
+	} else
+		 skw_usb_err("NOT suppport WIFI service\n");
+#ifdef CONFIG_BT_SEEKWAVE
+	if (bluetooth_pdev) {
+		bt_port = usb_ports[bt_audio_port];
+		bt_port->pdev = bluetooth_pdev;
+		bluetooth_pdev = NULL;
+		ret = platform_device_add(bt_port->pdev);
+		if(ret) {
+			skw_usb_err("failt to register Bluetooth device\n");
+			platform_device_put(bt_port->pdev);
+			bt_port->pdev = NULL;
+		} else
+			skw_usb_info("add Bluetooth devices done\n");
+	}
+#endif
+
+}
+void add_devices_work(struct work_struct *work)
+{
+	if (usb_ports[0])
+		usb_setup_service_devices();
+}
+void skw_set_bt_suspend_flag(void)
+{
+}
+static void usb_port_alloc_recv_urbs(struct usb_port_struct *port, struct usb_endpoint_descriptor *epd, int count, int buffer_size)
+{
+	int i;
+	struct urb *urb;
+
+	for(i=0; i<count; i++) {
+		urb = usb_alloc_urb(0, GFP_KERNEL);
+		if(!urb)
+			break;
+		if(!buffer_size) {
+			urb->transfer_buffer = NULL;
+			urb->transfer_buffer_length = 0;
+		} else{
+			urb->transfer_buffer = kzalloc(buffer_size, GFP_KERNEL);
+			if(!urb->transfer_buffer) {
+				usb_free_urb(urb);
+				break;
+			}
+			urb->transfer_buffer_length = buffer_size;
+		}
+		usb_fill_bulk_urb(urb, port->udev,usb_rcvbulkpipe(port->udev, epd->bEndpointAddress),
+			urb->transfer_buffer, buffer_size, bulkin_async_complete, NULL);
+		list_add_tail(&urb->urb_list, &port->rx_urb_list);
+	}
+	skw_usb_dbg(" urb cout %d\n", i);
+}
+
+static void usb_port_alloc_xmit_urbs(struct usb_port_struct *port, struct usb_endpoint_descriptor *epd, int count, int buffer_size)
+{
+	int i;
+	struct urb *urb;
+
+	for(i=0; i<count; i++) {
+		urb = usb_alloc_urb(0, GFP_KERNEL);
+		if(!urb)
+			break;
+		if(!buffer_size) {
+			urb->transfer_buffer = NULL;
+			urb->transfer_buffer_length = 0;
+		} else{
+			urb->transfer_buffer = kzalloc(buffer_size, GFP_KERNEL);
+			if(!urb->transfer_buffer) {
+				usb_free_urb(urb);
+				break;
+			}
+			urb->transfer_buffer_length = buffer_size;
+		}
+		usb_fill_bulk_urb(urb, port->udev,usb_sndbulkpipe(port->udev, epd->bEndpointAddress),
+			urb->transfer_buffer, buffer_size, bulkout_async_complete, NULL);
+		list_add_tail(&urb->urb_list, &port->tx_urb_list);
+	}
+	skw_usb_dbg(" urb cout %d\n", i);
+}
+
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+int open_usb_port(int id, void *callback, void *data)
+{
+	struct usb_port_struct *port;
+
+	if(id >= MAX_USB_PORT)
+		return -EINVAL;
+
+	port = usb_ports[id];
+	if(port->state==0)
+		return -EIO;
+	skw_usb_info("port%d\n", id);
+	if (port->state==1) {
+		if(port->read_urb && !port->read_urb->context)
+			init_usb_anchor(&port->read_submitted);
+		if(port->write_urb && !port->write_urb->context)
+			init_usb_anchor(&port->write_submitted);
+	}
+	port->state = 2;
+	port->rx_submit = callback;
+	port->rx_data = data;
+	if(callback && data && !port->thread) {
+		sema_init(&port->sem, 0);
+		port->thread = kthread_create(usb_bt_rx_entry, port, port->interface->cur_altsetting->string);
+		if(port->thread)
+			wake_up_process(port->thread);
+	}
+	if (port->interface && modem_status==MODEM_ON) {
+		struct usb_host_interface *iface_desc;
+		iface_desc = port->interface->cur_altsetting;
+		if (iface_desc && iface_desc->string &&
+		    !strncmp(iface_desc->string, "LOG", 3))
+			skw_usb_cp_log(0);
+	}
+	return 0;
+}
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int  bulkin_read(struct usb_port_struct *port, void *buffer, int size)
+{
+	int retval = -1;
+	DECLARE_COMPLETION_ONSTACK(done);
+	if(port->state==0)
+		return -EIO;
+
+	if(port == log_port){
+		memset(buffer, 0 , size);
+	}
+
+	if(port->read_urb) {
+		port->read_urb->transfer_buffer = buffer;
+		port->read_urb->transfer_buffer_length = size;
+		port->read_urb->context = &done;
+		usb_anchor_urb(port->read_urb, &port->read_submitted);
+		retval = usb_submit_urb(port->read_urb,GFP_KERNEL);
+		if(retval==0) {
+			retval = wait_for_completion_interruptible(&done);
+			if(retval == -ERESTARTSYS)
+				usb_kill_urb(port->read_urb);
+			else if(port->read_urb->status)
+				retval = port->read_urb->status;
+			else if(retval==0)
+				retval = port->read_urb->actual_length;
+			port->read_urb->context = NULL;
+		} else {
+			 if (retval < 0)
+				 usb_unanchor_urb(port->read_urb);
+			port->read_urb->context = NULL;
+		}
+	}
+	if(port == log_port) {
+		if(assert_info_print && assert_info_print<28 && retval<100) {
+			assert_info_print++;
+			if(retval > 4)
+				skw_usb_info("%s", (char *)buffer);
+		}
+		if(retval == 4)
+			assert_info_print = 28;
+	}
+	return retval;
+}
+int skw_bus_version(void)
+{
+	skw_usb_info("USB bus Version1.0\n");
+	return 0;
+}
+/*******************************************************
+ * bulkin_read_async - async read from bulk in endpoint
+ * @port: pointer to struct usb_port_struct
+ *
+ * Submit a read urb for bulk in endpoint asynchronously.
+ *
+ * Return: 0 on success, negative value on error
+ ******************************************************/
+int bulkin_read_async(struct usb_port_struct *port)
+{
+	int	 retval = -1;
+	unsigned long flags;
+	struct urb *urb;
+
+	spin_lock_irqsave(&port->rx_urb_lock, flags);
+	urb = list_first_entry(&port->rx_urb_list, struct urb, urb_list);
+	list_del_init(&urb->urb_list);
+	spin_unlock_irqrestore(&port->rx_urb_lock, flags);
+	if(urb->context) {
+		skw_usb_info("port is busy!!!\n");
+		return -EBUSY;
+	}
+
+	urb->complete = bulkin_async_complete;
+	urb->context = port;
+	if (port->suspend)
+		list_add_tail(&urb->urb_list, &port->suspend_urb_list);
+	else {
+		usb_anchor_urb(urb, &port->read_submitted);
+		bulk_async_read++;
+		retval = usb_submit_urb(urb, GFP_ATOMIC);
+		if (retval < 0) {
+			bulk_async_read--;
+			usb_unanchor_urb(urb);
+			urb->context = NULL;
+			skw_usb_info(" is error!!! %d\n", retval);
+			list_add_tail(&urb->urb_list, &port->suspend_urb_list);
+		}
+	}
+	return retval;
+}
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int bulkout_write(struct usb_port_struct *port, void *buffer, int size)
+{
+	int retval = -1;
+	DECLARE_COMPLETION_ONSTACK(done);
+
+
+	if (port && port->write_urb && !port->write_urb->context) {
+		if (port->suspend) {
+		skw_usb_info("port%d is suspended\n", port->portno);
+			return -EOPNOTSUPP;
+		}
+		port->write_urb->context = &done;
+		port->write_urb->transfer_buffer = buffer;
+		port->write_urb->transfer_buffer_length = size;
+		if(size%port->ep_mps == 0)
+			port->write_urb->transfer_flags |= URB_ZERO_PACKET;
+		usb_anchor_urb(port->write_urb, &port->write_submitted);
+		retval = usb_submit_urb(port->write_urb,GFP_KERNEL);
+		if(retval==0) {
+			retval = wait_for_completion_interruptible(&done);
+			if(retval==-ERESTARTSYS)
+				usb_kill_urb(port->write_urb);
+			else if (port->write_urb->status)
+				retval = port->write_urb->status;
+			else
+				retval = port->write_urb->actual_length;
+			port->write_urb->context = NULL;
+		} else {
+
+			if (retval < 0) {
+				usb_unanchor_urb(port->write_urb);
+				skw_usb_info("is error!!! %d\n", retval);
+			}
+			port->write_urb->context = NULL;
+		}
+	}
+	return retval;
+}
+
+int bulkout_write_async(struct usb_port_struct *port, void *buffer, int size)
+{
+	int retval = -1;
+	struct urb *urb;
+	unsigned long flags;
+
+	if (port->suspend) {
+		skw_usb_info("port%d is suspended\n", port->portno);
+		return -EOPNOTSUPP;
+	}
+	spin_lock_irqsave(&port->tx_urb_lock, flags);
+	if(list_empty(&port->tx_urb_list)) {
+		spin_unlock_irqrestore(&port->tx_urb_lock, flags);
+		retval = wait_event_interruptible(port->tx_wait, (!list_empty(&port->tx_urb_list)));
+		spin_lock_irqsave(&port->tx_urb_lock, flags);
+	}
+	urb = list_first_entry(&port->tx_urb_list, struct urb, urb_list);
+	list_del_init(&urb->urb_list);
+	port->tx_urb_count++;
+	spin_unlock_irqrestore(&port->tx_urb_lock, flags);
+
+	usb_fill_bulk_urb(urb, port->udev,usb_sndbulkpipe(port->udev, port->epout->bEndpointAddress),
+		buffer, size, bulkout_async_complete, port);
+	if(size%port->ep_mps == 0)
+		urb->transfer_flags |= URB_ZERO_PACKET;
+		
+	usb_anchor_urb(urb, &port->write_submitted);
+	retval = usb_submit_urb(urb,GFP_KERNEL);
+	if (retval < 0) {
+		usb_unanchor_urb(urb);
+		spin_lock_irqsave(&port->tx_urb_lock, flags);
+		port->tx_urb_count--;
+		list_add_tail(&urb->urb_list, &port->tx_urb_list);
+		spin_unlock_irqrestore(&port->tx_urb_lock, flags);
+		skw_usb_info("is error!!! %d\n", retval);
+	}
+	skw_usb_dbg(" portno %d wait done %d %d\n", port->portno, retval, port->tx_urb_count);
+	return retval;
+}
+void check_sgs_headers(struct scatterlist *sgs, int sg_num, int total)
+{
+	int i,size;
+	struct skw_packet2_header *header;
+
+	size = 0;
+	for (i=0; i<sg_num; i++) {
+		header = (struct skw_packet2_header *)sg_virt(sgs + i);
+		size += header->len;
+		if (header->len > 2048 || size > total)
+			skw_usb_err("invalid packet: (%d - %d):( %d-%d-%d)\n", total, sg_num, i, header->len, size);
+	}
+}
+
+int bulkout_write_sg_async(struct usb_port_struct *port, struct scatterlist *sgs, int sg_num, int total)
+{
+	struct urb *urb;
+	unsigned long flags;
+	int retval = -1;
+
+	check_sgs_headers(sgs, sg_num, total);
+	spin_lock_irqsave(&port->tx_urb_lock, flags);
+	if(list_empty(&port->tx_urb_list)) {
+		spin_unlock_irqrestore(&port->tx_urb_lock, flags);
+		retval = wait_event_interruptible(port->tx_wait, (!list_empty(&port->tx_urb_list)));
+		spin_lock_irqsave(&port->tx_urb_lock, flags);
+	}
+	if (port->state==0)
+		return -EIO;
+	urb = list_first_entry(&port->tx_urb_list, struct urb, urb_list);
+	port->tx_urb_count++;
+	list_del_init(&urb->urb_list);
+	port->req_tx_packet += sg_num;
+	spin_unlock_irqrestore(&port->tx_urb_lock, flags);
+	urb->transfer_buffer = NULL;
+	urb->transfer_buffer_length = 0;
+	usb_fill_bulk_urb(urb, port->udev,usb_sndbulkpipe(port->udev, port->epout->bEndpointAddress),
+		NULL, 0, bulkout_async_complete, port);
+	urb->sg = sgs;
+	urb->num_sgs = sg_num;
+	urb->transfer_buffer_length = total;
+	if(total%port->ep_mps == 0)
+		urb->transfer_flags |= URB_ZERO_PACKET;
+	usb_anchor_urb(urb, &port->write_submitted);
+	//skw_usb_info("portno %d submit  %d\n", port->portno, port->tx_urb_count);
+	retval = usb_submit_urb(urb,GFP_KERNEL);
+	if (retval < 0) {
+		usb_unanchor_urb(urb);
+		spin_lock_irqsave(&port->tx_urb_lock, flags);
+		port->tx_urb_count--;
+		list_add_tail(&urb->urb_list, &port->tx_urb_list);
+		spin_unlock_irqrestore(&port->tx_urb_lock, flags);
+	}
+	return retval;
+
+}
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int bulkout_write_sg(struct usb_port_struct *port, struct scatterlist *sgs, int sg_num, int total)
+{
+	int	 retval = -1;
+	DECLARE_COMPLETION_ONSTACK(done);
+
+	if(!port->write_urb)
+		return -ENODEV;
+	if(port->write_urb->context) {
+		skw_usb_info("port is busy!!!\n");
+		return -EBUSY;
+	}
+	if(port->write_urb) {
+		port->write_urb->sg = sgs;
+		port->write_urb->num_sgs = sg_num;
+		port->write_urb->transfer_buffer_length = total;
+		if(total%port->ep_mps == 0)
+			port->write_urb->transfer_flags |= URB_ZERO_PACKET;
+		port->write_urb->context = &done;
+		port->req_tx_packet += port->write_urb->num_sgs;
+		usb_anchor_urb(port->write_urb, &port->write_submitted);
+		retval = usb_submit_urb(port->write_urb,GFP_KERNEL);
+		if(retval==0) {
+			retval = wait_for_completion_interruptible(&done);
+			if(retval==0)
+				retval = port->write_urb->actual_length;
+			port->write_urb->context = NULL;
+			port->sent_packet_count += sg_num;
+
+		} else {
+			skw_port_log(port->portno, "%s retval = %d\n", __func__, retval);
+			usb_unanchor_urb(port->write_urb);
+			port->write_urb->context = NULL;
+		}
+	}
+	if(retval > 0)
+		return 0;
+	return retval;
+}
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int send_data(int portno, char *buffer, int total)
+{
+	struct usb_port_struct *port;
+	u32 *data;
+
+	if(total==0)
+		return 0;
+	if (modem_status != MODEM_ON)
+		return -EIO;
+	port = usb_ports[portno];
+	if(!port || !port->state)
+		return -EIO;
+	if (port->suspend) {
+		skw_usb_info("port%d is suspended\n", portno);
+		return -EOPNOTSUPP;
+	}
+	if (portno == 0) {
+		data = (u32 *)buffer;
+		memcpy(last_sent_wifi_cmd, data, 12);
+		last_sent_time = jiffies;
+		last_sent_wifi_cmd[0] =  bulk_async_read;
+	}
+	return bulkout_write(port, buffer, total);
+}
+static void check_first_header(char *buffer, int total)
+{
+	struct skw_packet_header *header;
+
+	header = (struct skw_packet_header *)buffer;
+	if (header->len > 1536 || header->len > total)
+		skw_usb_err("invalid packet: (%d - %d)\n", total, header->len);
+}
+static int send_data_async(int portno, char *buffer, int total)
+{
+	struct usb_port_struct *port;
+	int ret;
+
+	if(total==0)
+		return 0;
+	if (modem_status != MODEM_ON)
+		return -EIO;
+	port = usb_ports[portno];
+	if(!port || !port->state)
+		return -EIO;
+	ret = bulkout_write_async(port, buffer, total);
+	check_first_header(buffer, total);
+	return ret;
+}
+
+int recv_data(int portno, char *buffer, int total)
+{
+	struct usb_port_struct *port;
+
+	if(total==0)
+		return 0;
+
+	port = usb_ports[portno];
+	if(!port || !port->state)
+		return -EIO;
+	return bulkin_read(port, buffer, total);
+}
+
+int close_usb_port(int portno)
+{
+	struct usb_port_struct *port;
+
+	port = usb_ports[portno];
+
+	skw_usb_info("port%d\n", portno);
+	if (port) {
+		port->state = 1;
+		if(port->write_urb && port->write_urb->context)
+			usb_kill_urb(port->write_urb);
+		if(port->read_urb && port->read_urb->context)
+			usb_kill_urb(port->read_urb);
+		if(port->thread && down_interruptible(&port->sem))
+			skw_usb_info("port%d rx thread exit\n", portno);
+		port->thread = NULL;
+		if (port->interface) {
+			struct usb_host_interface *iface_desc;
+			iface_desc = port->interface->cur_altsetting;
+			if (iface_desc && iface_desc->string &&
+			    !strncmp(iface_desc->string, "LOG", 3))
+				skw_usb_cp_log(1);
+		}
+	}
+	return 0;
+}
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+int wifi_send_cmd(int portno, struct scatterlist *sg, int sg_num, int total)
+{
+	struct usb_port_struct *port;
+	u32 *data;
+	int ret;
+
+	if(total==0)
+		return 0;
+	if (modem_status != MODEM_ON)
+		return -EIO;
+	if(portno >= MAX_USB_PORT)
+		return -EINVAL;
+	port = usb_ports[portno];
+	if(!port || !port->state)
+		return -EIO;
+	if (port->suspend) {
+		skw_usb_info("port%d is suspended\n", portno);
+		return -EOPNOTSUPP;
+	}
+	if (portno == 0) {
+		data = (u32 *)sg_virt(sg);
+		memcpy(last_sent_wifi_cmd, data, 12);
+		last_sent_time = jiffies;
+		last_sent_wifi_cmd[0] =  bulk_async_read;
+	}
+	ret = bulkout_write_sg(port, sg, sg_num, total);
+	return ret;
+}
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+int wifi_send_cmd_async(int portno, struct scatterlist *sg, int sg_num, int total)
+{
+	struct usb_port_struct *port;
+	u32 *data;
+
+	if(total==0)
+		return 0;
+	if (modem_status != MODEM_ON)
+		return -EIO;
+	if(portno >= MAX_USB_PORT)
+		return -EINVAL;
+	port = usb_ports[portno];
+	if(!port || !port->state)
+		return -EIO;
+
+	if (port->suspend) {
+		skw_usb_info("port%d is suspended\n", portno);
+		return -EOPNOTSUPP;
+	}
+	if (portno == 0) {
+		data = (u32 *)sg_virt(sg);
+		memcpy(last_sent_wifi_cmd, data, 12);
+		last_sent_time = jiffies;
+		last_sent_wifi_cmd[0] =  bulk_async_read;
+	}
+	return bulkout_write_sg_async(port, sg, sg_num, total);
+}
+
+/************************************************************************
+ *Decription: manual assert modem
+ *Author:jiayong.yang
+ *Date:2021-08-03
+ *Modfiy:
+ *Notes: this function must not be invoked in IRQ context.
+ ************************************************************************/
+static int modem_assert_work(void)
+{
+	struct usb_port_struct *port;
+	struct recovery_data *recovery = SKW_USB_GET_RECOVERY_DATA();
+	int ret = -1;
+	 u32 *cmd = last_sent_wifi_cmd;
+
+	if(modem_status == MODEM_HALT){
+		skw_usb_info("modem in recovery mode \n");
+		return 0;
+	}
+	port = usb_ports[0];
+	if(port && port->state) {
+		recovery->cp_state =1;
+		ret = usb_control_msg(port->udev, usb_sndctrlpipe(port->udev, 0),
+				VENDOR_MSG_MODEM_ASSERT, USB_DIR_OUT| USB_TYPE_VENDOR|USB_RECIP_DEVICE,
+				0,0,NULL,0,1000);
+		skw_usb_err("SND ASSERT CMD ret = %d cmd: 0x%x 0x%x 0x%x: ACK 0x%x-0x%x-0x%x EVT: 0x%x 0x%x 0x%x \n",
+				ret, cmd[0], cmd[1], cmd[2],
+			       	last_recv_wifi_ack[0],last_recv_wifi_ack[1],last_recv_wifi_ack[2],
+			       	last_recv_wifi_evt[0],last_recv_wifi_evt[1],last_recv_wifi_evt[2]);
+		modem_status = MODEM_HALT;
+#ifdef CONFIG_SEEKWAVE_PLD_RELEASE
+		schedule_delayed_work(&skw_except_work , msecs_to_jiffies(2000));
+#else
+		schedule_delayed_work(&skw_except_work , msecs_to_jiffies(6000));
+#endif
+	}
+	return ret;
+}
+static void usb_control_work(struct work_struct *work)
+{
+	modem_assert_work();
+}
+static int modem_assert(void)
+{
+	struct usb_port_struct *port;
+
+	port = usb_ports[0];
+	if (port)
+		schedule_work(&usb_control_worker);
+	return 0;
+}
+int wifi_service_start(void)
+{
+	int ret = 0;
+
+	if(!usb_boot_data)
+		return -ENODEV;
+	ret=usb_boot_data->wifi_start();
+
+	return ret;
+}
+
+int wifi_service_stop(void)
+{
+	int ret = 0;
+	if(!usb_boot_data)
+		return -ENODEV;
+	ret=usb_boot_data->wifi_stop();
+	return ret;
+}
+
+int bt_service_start(void)
+{
+	int ret = 0;
+
+	if(!usb_boot_data)
+		return -ENODEV;
+	ret=usb_boot_data->bt_start();
+	return ret;
+}
+
+int bt_service_stop(void)
+{
+	int ret = 0;
+
+	if(!usb_boot_data)
+		return -ENODEV;
+	ret=usb_boot_data->bt_stop();
+	return ret;
+}
+static int send_modem_service_command(u16 service, u16 command)
+{
+	struct recovery_data *recovery = SKW_USB_GET_RECOVERY_DATA();
+	struct usb_port_struct *port;
+	int ret = -1;
+	int timeout = 1000;
+	port = usb_ports[1];
+#ifndef MODEM_WDT_SUPPORT
+	if(usb_boot_data->chip_en < 0){
+		skw_usb_err("chip_en = %d Invalid Pls check HW !!\n", usb_boot_data->chip_en);
+		return ret;
+	}
+#endif
+	if(port)
+		skw_usb_info("(%d,%d) cp_state= %d\n",
+			service, command, recovery->cp_state);
+	if (recovery->cp_state)
+		return ret;
+
+	if(port && port->state) {
+		skw_reinit_completion(download_done);
+		ret = usb_control_msg(port->udev, usb_sndctrlpipe(port->udev, 0),
+				VENDOR_MSG_SERVICE_CTRL, USB_DIR_OUT| USB_TYPE_VENDOR|USB_RECIP_DEVICE,
+				service, command, NULL, 0, 1000);
+	}
+	if((command & 0x01) == SERVICE_START) {
+		skw_usb_info("ret = %d\n", ret);
+		complete(&loop_completion);
+		start_service_flag = 1;
+		wait_for_completion_interruptible_timeout(&download_done, msecs_to_jiffies(timeout + 1000*service));
+		service_state_map |= (1<<service);
+	} else {
+		if(service==BT_SERVICE && modem_status==MODEM_ON)
+			wait_for_completion_interruptible_timeout(&download_done, msecs_to_jiffies(1000));
+		service_state_map &= ~(1<<service);
+	}
+	return ret;
+}
+
+static int skw_get_packet_count(u8 portno)
+{
+	struct usb_port_struct *port;
+	int ret = -1;
+	u16 *packet_count, size=2;
+
+	port = usb_ports[portno];
+	if(port && port->state) {
+		ret = usb_control_msg(port->udev, usb_rcvctrlpipe(port->udev, 0),
+				VENDOR_MSG_PACKET_COUNT, USB_DIR_IN| USB_TYPE_VENDOR|USB_RECIP_DEVICE,
+				portno, 0, port->read_buffer, size, 1000);
+
+		packet_count = (u16 *)port->read_buffer;
+		if(ret < 0)
+			skw_port_log(portno,"%s (%d,%d) ret = %d\n", __func__, portno, *packet_count, ret);
+		if(ret==size)
+			port->max_packet_count = *packet_count;
+		else
+			port->max_packet_count = MAX_PACKET_COUNT;
+	}
+	return ret;
+}
+
+void skw_usb_cp_log(int disable)
+{
+	struct usb_port_struct *port;
+	int ret = -1;
+	port = usb_ports[0];
+	if(port && port->state) {
+		ret = usb_control_msg(port->udev, usb_rcvctrlpipe(port->udev, 0),
+				VENDOR_MSG_LOG_SWITCH, USB_DIR_IN| USB_TYPE_VENDOR|USB_RECIP_DEVICE,
+				disable, 0, NULL, 0, 10);
+
+		skw_usb_info("(disable=%d) ret = %d\n", disable, ret);
+	}
+	cp_log_status = disable;
+}
+/************************************************************************
+ *Decription:send BT start command to modem.
+ *Author:jiayong.yang
+ *Date:2021-08-30
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int skw_BT_service_start(void)
+{
+	u16 cmd = SERVICE_START;
+
+	if (!wifi_data_pdev)
+		return -ENODEV;
+
+	skw_usb_info("Enter modem_status=%d\n", modem_status);
+	if (service_state_map & (1<<BT_SERVICE))
+		return 0;
+
+#if defined(CONFIG_SEEKWAVE_PLD_RELEASE) && defined(MODEM_WDT_SUPPORT)
+	if (chip_en_gpio < 0){
+		cmd |= MODEM_WDT_SUPPORT;
+	}
+#endif
+	return send_modem_service_command(BT_SERVICE, cmd);
+}
+
+/************************************************************************
+ *Decription:send BT stop command to modem.
+ *Author:jiayong.yang
+ *Date:2021-08-30
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int skw_BT_service_stop(void)
+{
+	skw_usb_info("Enter modem_status=%d\n", modem_status);
+	if (!wifi_data_pdev)
+		return -ENODEV;
+
+	if (service_state_map & (1<<BT_SERVICE)){
+		return send_modem_service_command(BT_SERVICE, SERVICE_STOP);
+	}
+	return 0;
+}
+/************************************************************************
+ *Decription:send WIFI start command to modem.
+ *Author:jiayong.yang
+ *Date:2021-08-30
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int skw_WIFI_service_start(void)
+{
+	int count=90;
+	u16 cmd = SERVICE_START;
+	skw_usb_info("Enter STARTWIFI---modem_status=%d, 0x%x\n",
+			modem_status, service_state_map);
+	if (modem_status == MODEM_HALT) {
+		while(!usb_ports[1] && count--)
+			msleep(10);
+	}
+	if (service_state_map & (1<<WIFI_SERVICE))
+		return 0;
+	cmd |= WIFI_PORT_SHARE_FLAG;
+#if defined(CONFIG_SEEKWAVE_PLD_RELEASE) && defined(MODEM_WDT_SUPPORT)
+	if (chip_en_gpio < 0){
+		cmd |= MODEM_WDT_SUPPORT;
+	}
+#endif
+	return send_modem_service_command(WIFI_SERVICE, cmd);
+}
+
+/************************************************************************
+ *Decription: send WIFI stop command to modem.
+ *Author:jiayong.yang
+ *Date:2021-08-30
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int skw_WIFI_service_stop(void)
+{
+	int count=10;
+	int portno;
+	struct usb_port_struct *port;
+
+	skw_usb_info("Enter,STOPWIFI--- modem status %d, 0x%x\n",
+			modem_status, service_state_map);
+	for (portno=0; portno<2; portno++) {
+		port = usb_ports[portno];
+		if (port && port->write_urb && port->write_urb->context) {
+			usb_kill_anchored_urbs(&port->write_submitted);
+		} else if (port && port->tx_urb_count) {
+			usb_kill_anchored_urbs(&port->write_submitted);
+		}
+	}
+	if (modem_status == MODEM_HALT) {
+		service_state_map &= ~(1<<WIFI_SERVICE);
+		while(!usb_ports[1] && count--)
+			msleep(10);
+		return 0;
+	}
+	if (service_state_map & (1<<WIFI_SERVICE))
+		return send_modem_service_command(WIFI_SERVICE, SERVICE_STOP);
+	return 0;
+}
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-08-30
+ *Modfiy:
+ *
+ ********************************************************************* */
+static void bulkin_complete(struct urb *urb)
+{
+	struct usb_port_struct *port;
+	int portno;
+
+	if(!urb)
+		return;
+
+	portno = usb_pipeendpoint(urb->pipe) - 1;
+	port = usb_ports[portno];
+	port->rx_done_time = jiffies;
+	port->rx_packet_count++;
+	if(urb) {
+		if(urb->status) {
+			skw_usb_info("endpoint%d actual = %d status %d\n",
+				usb_pipeendpoint(urb->pipe), urb->actual_length, urb->status);
+		}
+		if(urb->status == -ENOENT && port && port!=log_port  && port->suspend)
+			list_add_tail(&urb->urb_list, &port->suspend_urb_list);
+		else if (urb->context)
+			complete(urb->context);
+	}
+}
+static void bulkin_async_complete(struct urb *urb)
+{
+	struct usb_port_struct *port;
+
+	if(!urb)
+		return;
+	port = urb->context;
+	if(!port)
+		return;
+	port->rx_done_time = jiffies;
+	bulk_async_read--;
+	if(urb->status) {
+		skw_usb_info("endpoint%d actual = %d status %d\n",
+			usb_pipeendpoint(urb->pipe), urb->actual_length, urb->status);
+	}
+	if(urb->status == -ENOENT && port && port->suspend)
+		list_add_tail(&urb->urb_list, &port->suspend_urb_list);
+	else if (port) {
+		urb->context = NULL;
+		spin_lock(&port->rx_urb_lock);
+		if (port->state)
+			list_add_tail(&urb->urb_list, &port->rx_done_urb_list);
+		else
+			list_add_tail(&urb->urb_list, &port->rx_urb_list);
+		spin_unlock(&port->rx_urb_lock);
+		if (port->state)
+			tasklet_hi_schedule(&port->tasklet);
+	}
+}
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static void bulkout_complete(struct urb *urb)
+{
+	struct usb_port_struct *port;
+	int portno;
+
+	portno = usb_pipeendpoint(urb->pipe) - 1;
+	port = usb_ports[portno];
+
+	if(urb->status)
+		skw_usb_info("endpoint%d actual = %d status %d\n",
+			usb_pipeendpoint(urb->pipe),  urb->actual_length, urb->status);
+
+	if (port) {
+		port->tx_done_time = jiffies;
+		port->sent_packet_count++;
+	}
+	if (urb->context)
+		complete(urb->context);
+}
+
+static void bulkout_async_complete(struct urb *urb)
+{
+	struct usb_port_struct *port = urb->context;
+
+	if(urb->status) {
+		port->sent_packet_count += urb->num_sgs;
+		if(urb->sg && port->adma_tx_callback)
+			port->adma_tx_callback(port->portno, urb->sg, urb->num_sgs, port->tx_data, urb->status);
+		else if(urb->transfer_buffer && port->sdma_tx_callback)
+			port->sdma_tx_callback(port->portno, urb->transfer_buffer, urb->transfer_buffer_length, port->tx_data, urb->status);
+		skw_usb_info("port%d endpoint%d actual = %d status %d\n",
+			port->portno, usb_pipeendpoint(urb->pipe), urb->actual_length, urb->status);
+	} else if(urb->sg && port->adma_tx_callback) {
+		port->adma_tx_callback(port->portno, urb->sg, urb->num_sgs, port->tx_data, 0);
+		port->sent_packet_count += urb->num_sgs;
+	} else if(urb->transfer_buffer && port->sdma_tx_callback)
+		port->sdma_tx_callback(port->portno, urb->transfer_buffer, urb->transfer_buffer_length, port->tx_data, 0);
+	urb->context = NULL;
+	port->tx_done_time = jiffies;
+	spin_lock(&port->tx_urb_lock);
+	list_add_tail(&urb->urb_list, &port->tx_urb_list);
+	port->tx_urb_count--;
+	if(port->tx_urb_count==0 && port->sent_packet_count!=port->req_tx_packet)
+		skw_usb_info(" port[%d]= %d %d\n", port->portno, port->sent_packet_count, port->req_tx_packet);
+	spin_unlock(&port->tx_urb_lock);
+	wake_up_interruptible(&port->tx_wait);
+}
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+int bulkin_read_timeout(int portno, char *buffer, int size, int *actual, int timeout)
+{
+	struct usb_port_struct *port;
+	unsigned int pipe;
+	int	ret;
+
+	if(portno >= MAX_USB_PORT || !buffer || !size)
+		return -EINVAL;
+	port = usb_ports[portno];
+	if(!port->state)
+		return -EIO;
+	if(actual)
+		*actual = 0;
+	pipe = usb_rcvbulkpipe(port->udev, port->epin->bEndpointAddress);
+	ret = usb_bulk_msg(port->udev, pipe, buffer, size, actual,timeout);
+
+	if(port == log_port && actual) {
+		if(assert_info_print && assert_info_print<28 && *actual<100) {
+			assert_info_print++;
+			if(*actual > 4)
+				skw_usb_info("%s", (char *)buffer);
+		}
+		if(*actual == 4)
+			assert_info_print = 28;
+	}
+	if(ret)
+		return ret;
+
+	if(actual)
+		return *actual;
+	return ret;
+}
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+int bulkout_write_timeout(int portno, char *buffer, int size, int *actual, int timeout)
+{
+	struct usb_port_struct *port;
+	unsigned int pipe;
+	int	ret;
+
+	if(portno >= MAX_USB_PORT || !buffer || !size)
+		return -EINVAL;
+	port = usb_ports[portno];
+	
+	if(!port->state)
+		return -EIO;
+	if(actual)
+		*actual = 0;
+	pipe = usb_sndbulkpipe(port->udev, port->epout->bEndpointAddress);
+	ret = usb_bulk_msg(port->udev, pipe, buffer, size, actual,timeout);
+	if(ret)
+		return ret;
+	if(actual)
+		return *actual;
+	return ret;
+}
+
+static void kick_rx_thread(void)
+{
+	struct usb_port_struct *port;
+
+	skw_usb_info("submitted urb %d\n", bulk_async_read);
+	port = usb_ports[1];
+	if ((bulk_async_read == 0) && port &&
+		(!list_empty(&port->rx_urb_list)))
+		bulkin_read_async(port);
+	else if (port && list_empty(&port->rx_urb_list))
+		skw_usb_info("urb list is empty \n");
+}
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int register_rx_callback(int id, void *func, void *para);
+static int register_tx_callback(int id, void *func, void *para);
+static struct sv6160_platform_data wifi_pdata = {
+	.data_port = 0,
+	.cmd_port = 1,
+#ifdef CONFIG_SEEKWAVE_PLD_RELEASE
+	.bus_type = USB_LINK|TX_SDMA|RX_SDMA|TX_ASYN|CP_RLS,
+#else
+	.bus_type = USB_LINK|TX_SDMA|RX_SDMA|TX_ASYN|CP_DBG,
+#endif
+	.max_buffer_size = MAX_BUFFER_SIZE,
+	.align_value = 512,
+	.hw_adma_tx = wifi_send_cmd,
+	.hw_sdma_tx = send_data,
+	.hw_adma_tx_async = wifi_send_cmd_async,
+	.hw_sdma_tx_async = send_data_async,
+	.callback_register = register_rx_callback,
+	.modem_assert = modem_assert,
+	.service_start = wifi_service_start,
+	.service_stop = wifi_service_stop,
+	.modem_register_notify = modem_register_notify,
+	.modem_unregister_notify = modem_unregister_notify,
+	.at_ops = {
+		.port = 2,
+		.open = open_usb_port,
+		.close = close_usb_port,
+		.read = recv_data,
+		.write = send_data,
+		.read_tm = bulkin_read_timeout,
+		.write_tm = bulkout_write_timeout,
+	},
+	.tx_callback_register = register_tx_callback,
+	.rx_thread_wakeup = kick_rx_thread,
+};
+
+void usb_handle(unsigned long tsk_data)
+{
+	int	size, read, ret;
+	int  transfer_count = 0, sg_count, offset;
+	u16  data_flag = 0x8000;
+	unsigned long flags;
+	char *buffer;
+	int  *data;
+	struct usb_port_struct *port = (struct usb_port_struct *) tsk_data;
+	struct scatterlist *sg;
+	struct urb *urb;
+	u16  pcount, sequence;
+
+	if (!strncmp(skw_chipid, "SV6316", 6) || !strlen(skw_chipid)
+            || !strncmp(skw_chipid, "SV6160LITE", 10))
+		data_flag = 2;
+
+	while(!list_empty(&port->rx_done_urb_list)) {
+
+		if (!port->state || port!=usb_ports[1])
+			break;
+		spin_lock_irqsave(&port->rx_urb_lock, flags);
+		urb = list_first_entry(&port->rx_done_urb_list, struct urb, urb_list);
+		list_del_init(&urb->urb_list);
+		list_add_tail(&urb->urb_list, &port->rx_urb_list);
+		spin_unlock_irqrestore(&port->rx_urb_lock, flags);
+
+		sg_init_table(sgs, nr_sgs);
+		read = urb->actual_length;
+		buffer = urb->transfer_buffer;
+		transfer_count++;
+		if(urb->status < 0 || !port->state) {
+			skw_usb_err(" bulkin read status=%d state=%d\n", urb->status, port->state);
+			return ;
+		}
+		if(port->rx_submit) {
+			int is_cmd;
+			u32 d32;
+
+			data = (int *)buffer;
+			d32 = data[0];
+			sequence = d32 & 0xffff;
+			pcount = d32 >> 16;
+			pcount &= 0x7fff;
+			offset = 0;
+			sg_count = 0;
+			sg = sgs;
+			is_cmd = 0;
+			if (d32 & 0x80000000) {
+				if (transfer_index != sequence)
+					skw_usb_info("data lost: recved %d, expect:%d\n", transfer_index,sequence);
+				transfer_index = sequence + 1;
+			}
+			while (offset+12 < read) {
+				sg_count++;
+				if(sg_count > nr_sgs) {
+					skw_usb_warn("packet count is overflow %d : %d : %d : %d!!!\n",
+							offset, read, sg_count, nr_sgs);
+					sg_count--;
+					break;
+				}
+				size = data[2] >> 16;
+				size += 3;
+				size = size & 0xfffffffc;
+				if(data[2] & data_flag) {
+					if (sg_count > 1 && !is_cmd)
+						size = -1;
+					else
+						is_cmd = 1;
+				}
+				if (size + offset > read || size > 2048 || size <= 12) {
+					skw_usb_warn("Invalid packet size=%d: %d : %d :%d  0x%x:0x%x!!!\n",
+							size, offset, read, sg_count, d32, data[2]);
+					if (cls_recovery_mode_en) {
+						print_hex_dump(KERN_ERR, "PACKET1::", 0, 16, 1,
+								urb->transfer_buffer, offset+12, 1);
+						modem_assert();
+					}
+					if (sg_count > 0)
+						sg_count--;
+					break;
+				}
+				sg_set_buf(sg,  &buffer[offset], size);
+				sg++;
+				offset  += size;
+				if (is_cmd) {
+					if (modem_status != MODEM_ON)
+						skw_usb_info("rx_submit(0x%x): command: 0x%x 0x%x: 0x%x 0x%x readlen=%d\n", (u32)jiffies,
+							       	data[2], data[3], last_recv_wifi_ack[1], last_recv_wifi_ack[2], read);
+					if ((data[3] & 0xff) == 0x10) {
+						last_ack_time = jiffies;
+						memcpy(last_recv_wifi_ack, &data[1], 12);
+					} else
+						memcpy(last_recv_wifi_evt, &data[1], 12);
+				}
+				data = (int *)&buffer[offset];
+			}
+			if (d32 & 0x80000000) {
+				if (sg_count != pcount)
+					skw_usb_info("packet lost:%d %d\n", sg_count, pcount);
+			}
+			if(sg_count >15)
+				skw_usb_info("rx_submit: port%d packet count %d\n",
+					port->portno, sg_count);
+			if(is_cmd)
+				port = usb_ports[wifi_pdata.cmd_port];
+			else
+				port = usb_ports[wifi_pdata.data_port];
+			if (port->rx_submit)
+				port->rx_submit(port->portno, sgs, sg_count, port->rx_data);
+			port->rx_packet_count += sg_count;
+			if (modem_status != MODEM_ON)
+				return ;
+			port = usb_ports[wifi_pdata.data_port];
+		}
+	}
+
+	while(!list_empty(&port->rx_urb_list)) {
+		if (port->state==0)
+			break;
+		ret = bulkin_read_async(port);
+	}
+}
+
+/**********************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ **********************************************************************/
+int usb_port_async_entry(void *para)
+{
+	struct usb_port_struct *port = para;
+	struct sched_param param;
+	unsigned long flags;
+	//int	size, read, ret;
+	int ret;
+	u16	mpc;
+	struct urb *urb;
+	u16  data_flag = 0x8000;
+
+	if(port->portno == 0) {
+		param.sched_priority = USB_RX_TASK_PRIO;
+#if KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE
+		sched_set_fifo_low(current);
+#else
+		sched_setscheduler(current, SCHED_FIFO, &param);
+#endif
+	}
+	if(port->max_packet_count)
+		mpc = port->max_packet_count;
+	else
+		mpc = 2;
+
+	if (!strncmp(skw_chipid, "SV6316", 6) || !strlen(skw_chipid)
+            || !strncmp(skw_chipid, "SV6160LITE", 10))
+		data_flag = 2;
+
+	nr_sgs = mpc+1;
+	sgs = kzalloc((nr_sgs)*sizeof(struct scatterlist), GFP_KERNEL);
+	if (!sgs)
+		return -ENOMEM;
+	bulk_async_read = 0;
+	if (mpc<=13)
+		usb_port_alloc_recv_urbs(port, port->epin, 3, 20*1024);
+	else
+		usb_port_alloc_recv_urbs(port, port->epin, 3, 24*1024);
+	usb_port_alloc_xmit_urbs(port, port->epout,10,0);
+	msleep(300);
+	transfer_index = 0;
+	skw_usb_info(" port %d running packet %d %s 0x%x...\n",port->portno, mpc, skw_chipid, data_flag);
+	if (!list_empty(&port->rx_urb_list)) {
+		ret = bulkin_read_async(port);
+	}
+
+	wait_event_interruptible(port->rx_wait, (!port->state));
+	skw_usb_info(" port %d stoped\n", port->portno);
+	msleep(50);
+	kfree(sgs);
+
+	if(port->write_urb) {
+		usb_kill_anchored_urbs(&port->write_submitted);
+	}
+	if(port->read_urb) {
+		usb_kill_anchored_urbs(&port->read_submitted);
+	}
+
+	if(port->write_urb && port->write_urb->context)
+		wait_for_completion_interruptible(port->write_urb->context);
+
+	spin_lock_irqsave(&port->rx_urb_lock, flags);
+	while(!list_empty(&port->rx_urb_list)) {
+		urb = list_first_entry(&port->rx_urb_list, struct urb, urb_list);
+		list_del_init(&urb->urb_list);
+		if(urb->transfer_buffer)
+			kfree(urb->transfer_buffer);
+		usb_free_urb(urb);
+	}
+	spin_unlock_irqrestore(&port->rx_urb_lock, flags);
+	while(!list_empty(&port->tx_urb_list)) {
+		urb = list_first_entry(&port->tx_urb_list, struct urb, urb_list);
+		list_del_init(&urb->urb_list);
+		usb_free_urb(urb);
+	}
+	up(&port->sem);
+	return 0;
+}
+
+static void skw_usb_kill_wifi_threads(struct usb_port_struct *p)
+{
+	int i;
+	struct usb_port_struct *port;
+	for(i=0; i<3; i++) {
+		port = usb_ports[i];
+		if(port==NULL)
+			break;
+		if(port && port->thread) {
+			port->state = 0;
+		}
+	}
+}
+static void skw_usb_dump_memory(char *buffer, int size, int *log_size)
+{
+	if (log_port->state==2)
+		return;
+	if (size && buffer && log_size) {
+		dump_memory_buffer = buffer;
+		dump_buffer_size = size;
+		dump_log_size = log_size;
+		skw_usb_info("dump_memory : %p-%d\n", buffer, size);
+		schedule_work(&dump_memory_worker);
+	}
+}
+
+static void show_assert_context(void)
+{
+	int read;
+	int error_count;
+	int total_size;
+	int dump_memory_size = 0;
+
+	if(log_port && log_port->state!=2) {
+		char *buffer;
+		buffer = kzalloc(1024, GFP_KERNEL);
+		if (!buffer)
+			return;
+		open_usb_port(log_port->portno, 0, 0);
+		dump_memory_done = 0;
+		error_count=0;
+		total_size = 0;
+		do {
+			if (modem_status == MODEM_ON)
+				break;
+			if (!dump_memory_done)
+				read = bulkin_read_timeout(log_port->portno, buffer, 1024, &read, 10);
+			if (read > 0) {
+				if (total_size + read < dump_buffer_size) {
+					memcpy(&dump_memory_buffer[total_size], buffer, read);
+					dump_memory_size = total_size + read;
+				}
+				total_size += read;
+				memset(buffer, 0, read);
+			}
+			if(read == 4 || read < 0) {
+				break;
+			}
+		} while (assert_info_print<100 && !dump_memory_done);
+		while (!dump_memory_done) {
+			if (modem_status == MODEM_ON)
+				break;
+			read = bulkin_read_timeout(log_port->portno, buffer, 1024, &read, 10);
+			if (read <= 0) {
+				error_count++;
+				skw_usb_info("%s read = %d : total %d done=%d\n", current->comm, read, total_size, dump_memory_done);
+				if(error_count >1)
+					break;
+			} else {
+				if (total_size + read < dump_buffer_size) {
+					memcpy(&dump_memory_buffer[total_size], buffer, read);
+					dump_memory_size = total_size + read;
+				}
+				total_size += read;
+			}
+		}
+		close_usb_port(log_port->portno);
+		skw_usb_info("dump memory size: %d buffer_size: %d\n", dump_memory_size, dump_buffer_size);
+		if (dump_log_size)
+			*dump_log_size = dump_memory_size;
+		kfree(buffer);
+	}
+}
+static void dump_memory_work(struct work_struct *work)
+{
+	if(dump_log_size && *dump_log_size==0) {
+		skw_usb_info(" running...\n");
+		show_assert_context();
+	}
+}
+
+int skw_pin_config(struct usb_port_struct *port, char *buffer)
+{
+	int i;
+	u32 val;
+	int func_index = 0;
+	int g_offset = 0;
+	u32 pin_group_index = 0;
+	u32 func_group_index = 0;
+	u32 func_sel_val[2] = {0};
+	u32 func_sel_off[] = {PN_FUNC_SEL0_OFFSET, PN_FUNC_SEL1_OFFSET};
+	u32 *pin_val;
+	u8 pin_off[PN_CNT] = {0};
+	char value_str[9];
+	char result_str[15]; //e.g. PN:10:12345678
+	char off_str[3];
+
+	pin_val = (u32 *)kzalloc(PN_CNT * sizeof(u32), GFP_KERNEL);
+
+	while (g_offset < usb_boot_data->nv_mem_pnfg_size) {
+		//1. pin offset
+		pin_off[pin_group_index] = usb_boot_data->nv_mem_pnfg_data[g_offset];
+
+		//2. func_sel value
+		val = usb_boot_data->nv_mem_pnfg_data[g_offset + 1];
+		func_sel_val[func_group_index] |= (val << (3 * func_index)) & (7 << (3 * func_index));
+
+		//3. pin config value
+		pin_val[pin_group_index] |=
+				((u32)usb_boot_data->nv_mem_pnfg_data[g_offset + 2] << BIT_PN_DSLP_EN_START) & \
+				GENMASK(BIT_PN_DSLP_EN_END, BIT_PN_DSLP_EN_START);//dslp_en
+		pin_val[pin_group_index] |=
+				((u32)usb_boot_data->nv_mem_pnfg_data[g_offset + 3] << BIT_DRV_STREN_START) & \
+				GENMASK(BIT_DRV_STREN_END, BIT_DRV_STREN_START);//drv_strength
+		pin_val[pin_group_index] |=
+				((u32)usb_boot_data->nv_mem_pnfg_data[g_offset + 4] << BIT_NORMAL_WP_START) & \
+				GENMASK(BIT_NORMAL_WP_END, BIT_NORMAL_WP_START);//normal:wpu/wpd
+		pin_val[pin_group_index] |=
+				((u32)usb_boot_data->nv_mem_pnfg_data[g_offset + 5] << BIT_SCHMITT_START) & \
+				GENMASK(BIT_SCHMITT_END, BIT_SCHMITT_START);//schmitt trigger enable
+		pin_val[pin_group_index] |=
+				((u32)usb_boot_data->nv_mem_pnfg_data[g_offset + 6] << BIT_SLP_WP_START) & \
+				GENMASK(BIT_SLP_WP_END, BIT_SLP_WP_START);//sleep:wpu/wpd
+		pin_val[pin_group_index] |=
+				((u32)usb_boot_data->nv_mem_pnfg_data[g_offset + 7]) & \
+				GENMASK(BIT_SLEEP_IE_OE_END, BIT_SLEEP_IE_OE_START);//sleep:ie/oe
+
+		g_offset += 8; // 1(offset) + 1(pin sel) + 6(pin config)
+		func_index++;
+		pin_group_index++;
+		if (func_index == PN_FUNCSEL_ONEGRP_CNT){
+			func_group_index++;
+			func_index = 0;
+		}
+	}
+
+	//function select
+	for (i = 0;i < sizeof(func_sel_off) / sizeof(u32);i++) {
+		sprintf(value_str, "%08x", func_sel_val[i]);
+		sprintf(off_str, "%02x", func_sel_off[i]);
+		sprintf(result_str, "PN:%s:%s", off_str, value_str);
+		skw_usb_dbg("func_sel[%d]:%s\n", i, result_str);
+		memcpy(buffer+256, result_str, 14);
+		bulkout_write(port, buffer+256, 14);
+	}
+
+	//pin config
+	for (i = 0;i < PN_CNT;i++) {
+		sprintf(value_str, "%08x", pin_val[i]);
+		sprintf(off_str, "%02x", pin_off[i]);
+		sprintf(result_str, "PN:%s:%s", off_str, value_str);
+		skw_usb_dbg("pin_conf[%d]:%s\n", i, result_str);
+		memcpy(buffer+256, result_str, 14);
+		bulkout_write(port, buffer+256, 14);
+	}
+	kfree(pin_val);
+
+	return 0;
+}
+
+static int usb_loopcheck_entry(void *para)
+{
+	struct usb_port_struct *port = para;
+	char *buffer;
+	int read, size;
+	int count= 0, timeout=300;
+	struct recovery_data *recovery = SKW_USB_GET_RECOVERY_DATA();
+	size = 512;
+	buffer = kzalloc(size, GFP_KERNEL);
+	schedule_delayed_work(&skw_except_work , msecs_to_jiffies(6000));
+	while(port->state && buffer){
+		read = 0;
+		memset(buffer,0,512);
+		do{
+			if(port->state==0)
+				break;
+			read = bulkin_read(port, buffer, 256);
+		}while(!read);
+
+		if (port->suspend) {
+			msleep(500);
+			continue;
+		}
+		if(read < 0 || !port->state) {
+			skw_usb_err("bulkin read_len=%d\n",read);
+			break;
+		}
+		if(strncmp(buffer, "BSPREADY", read))
+			skw_usb_info("recv(%d):%s %s\n", read, skw_chipid, buffer);
+		memcpy(buffer+256, "LOOPCHECK", 9);
+		if (read==8 && !strncmp(buffer, "BSPREADY", read)) {
+			if (start_service_flag)
+				continue;
+			bulkout_write(port, buffer+256, 9);
+			//bulkout_write_timeout(port->portno, buffer+256,9, &size, 300);
+		} else if (read==9 && !strncmp(buffer, "WIFIREADY", read)) {
+			start_service_flag = 0;
+			service_state_map |= (1<<WIFI_SERVICE);
+			complete(&download_done);
+			bulkout_write(port, buffer+256, 9);
+			//bulkout_write_timeout(port->portno, buffer+256,9, &size, 300);
+		} else if (read==6 && !strncmp(buffer, "BTEXIT", read)) {
+			complete(&download_done);
+			//bulkout_write(port, buffer+256, 9);
+			bulkout_write_timeout(port->portno, buffer+256,9, &size, 300);
+		} else if (read==7 && !strncmp(buffer, "BTREADY", read)) {
+			start_service_flag = 0;
+			service_state_map |= (1<<BT_SERVICE);
+			complete(&download_done);
+			bulkout_write(port, buffer+256, 9);
+			//bulkout_write_timeout(port->portno, buffer+256,9, &size, 300);
+		} else if (!strncmp(buffer, "BSPASSERT", 9)) {
+			sprintf(firmware_version, "%s\n%s\n", firmware_version, buffer);
+			skw_usb_err("cmd:0x%x 0x%x 0x%x ack:%x %x:%x event:0x%x:0x%x:0x%x time:0x%x:0x%x:0x%x\n",
+			       last_sent_wifi_cmd[0],last_sent_wifi_cmd[1],last_sent_wifi_cmd[2],
+			       last_recv_wifi_ack[0],last_recv_wifi_ack[1],last_recv_wifi_ack[2],
+			       last_recv_wifi_evt[0],last_recv_wifi_evt[1],last_recv_wifi_evt[2],
+			       (u32)jiffies,(u32)last_sent_time, (u32)last_ack_time);
+			if(recovery->cp_state==1)
+				cancel_delayed_work_sync(&skw_except_work);
+
+			mutex_lock(&recovery->except_mutex);
+			if(recovery->cp_state==DEVICE_BLOCKED_EVENT){
+				mutex_unlock(&recovery->except_mutex);
+				break;
+			}
+			recovery->cp_state = 1;
+			mutex_unlock(&recovery->except_mutex);
+
+			assert_info_print = 1;
+			memset(buffer, 0, read);
+			skw_usb_kill_wifi_threads(port);
+			modem_status = MODEM_HALT;
+			modem_notify_event(DEVICE_ASSERT_EVENT);
+			if (log_port->state!=2)
+				schedule_work(&dump_memory_worker);
+			memset(buffer, 0, 256);
+			read = bulkin_read_timeout(port->portno, buffer, 256, &read, 1000);
+			if (read > 0)
+				skw_usb_info("bspassert after recv(%d): %s\n", read, buffer);
+			dump_memory_done = 1;
+			modem_notify_event(DEVICE_DUMPDONE_EVENT);
+			msleep(100);
+			skw_recovery_mode();
+			service_state_map =0;
+
+			break;
+		} else if (!strncmp("trunk_W", buffer, 7)) {
+#ifdef CONFIG_SKW_DL_TIME_STATS
+			last_time = ktime_get();
+			skw_usb_info(",the download time start time %llu and lasttime %llu ,lose_time=%llu\n",
+				cur_time, last_time,(last_time-cur_time));
+#endif
+			cancel_delayed_work_sync(&skw_except_work);
+			recovery->cp_state = 0;
+			assert_info_print = 0;
+			modem_status = MODEM_ON;
+			memset(firmware_version, 0 , sizeof(firmware_version));
+			strncpy(firmware_version, buffer, read);
+
+			modem_notify_event(DEVICE_BSPREADY_EVENT);
+			count = 0;
+			service_state_map =0;
+			//usb_setup_service_devices();
+			schedule_work(&add_device_work);
+			bulkout_write_timeout(port->portno, buffer+256,9, &size, 300);
+			if (usb_boot_data->nv_mem_pnfg_data != NULL && usb_boot_data->nv_mem_pnfg_size != 0) {
+				skw_usb_info("UPDATE '%s' PINCFG from %s\n", skw_chipid, usb_boot_data->skw_nv_name);
+				skw_pin_config(port, buffer);
+			}
+		}
+		wait_for_completion_interruptible_timeout(&loop_completion, msecs_to_jiffies(timeout));
+		skw_reinit_completion(loop_completion);
+	}
+	skw_usb_info(" -port%d is stopped\n", port->portno);
+	if(port->read_urb && port->read_urb->context) {
+		usb_kill_anchored_urbs(&port->read_submitted);
+	}
+
+	if(port->write_urb && port->write_urb->context) {
+		usb_kill_anchored_urbs(&port->write_submitted);
+		if(port->write_urb->context)
+			wait_for_completion_interruptible(port->write_urb->context);
+	}
+	kfree(buffer);
+	up(&port->sem);
+	return 0;
+}
+
+static int usb_bt_rx_entry(void *para)
+{
+	struct usb_port_struct *port = para;
+	char *buffer;
+	int read, size;
+
+	size = 2048;
+	buffer = kzalloc(size, GFP_KERNEL);
+	while(port->state==2 && buffer){
+		read = 0;
+		memset(buffer,0,size);
+		do{
+			if(port->state != 2)
+				break;
+			read = bulkin_read(port, buffer, size);
+		}while(!read);
+
+		if(read < 0) {
+			skw_usb_err("bulkin read_len=%d\n",read);
+			break;
+		}
+		if(port->rx_submit)
+			port->rx_submit(port->portno, port->rx_data, read, buffer);
+	}
+	skw_usb_info("-port%d is stopped\n", port->portno);
+	if(port->write_urb && port->write_urb->context) {
+		usb_kill_anchored_urbs(&port->write_submitted);
+	}
+	if(port->read_urb && port->read_urb->context) {
+		usb_kill_anchored_urbs(&port->read_submitted);
+	}
+
+	if(buffer)
+		kfree(buffer);
+	up(&port->sem);
+	return 0;
+}
+
+
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static struct sv6160_platform_data ucom_pdata = {
+	.max_buffer_size =0x800,
+	.bus_type = USB_LINK,
+	.hw_sdma_tx = send_data,
+	.hw_sdma_rx = recv_data,
+	.open_port = open_usb_port,
+	.close_port = close_usb_port,
+	.modem_assert = modem_assert,
+	.service_start = bt_service_start,
+	.service_stop = bt_service_stop,
+	.modem_register_notify = modem_register_notify,
+	.modem_unregister_notify = modem_unregister_notify,
+	.dump_modem_memory = skw_usb_dump_memory,
+};
+
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+
+static int register_rx_callback(int id, void *func, void *para)
+{
+	if(id >= MAX_USB_PORT)
+		return -EINVAL;
+
+	if(usb_ports[id] == NULL)
+		return -EIO;
+	if(func && !usb_ports[id]->rx_submit) {
+		usb_ports[id]->rx_submit = func;
+		usb_ports[id]->rx_data = para;
+		if(id==1)
+			skw_WIFI_service_start();
+		return 0;
+	} else if(!func && usb_ports[id]->rx_submit) {
+		if(id==1)
+			skw_WIFI_service_stop();
+
+		usb_ports[id]->rx_submit = func;
+		usb_ports[id]->rx_data = para;
+		return 0;
+	}
+	if(wifi_pdata.bus_type & TX_ASYN) {
+		if(wifi_pdata.bus_type & TX_SDMA)
+			usb_ports[id]->sdma_tx_callback = func;
+		else
+			usb_ports[id]->adma_tx_callback = func;
+	}
+	usb_ports[id]->tx_data = para;
+	return 0;
+}
+
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int register_tx_callback(int id, void *func, void *para)
+{
+	if(id >= MAX_USB_PORT)
+		return -EINVAL;
+
+	if(usb_ports[id] == NULL)
+		return -EIO;
+	if(wifi_pdata.bus_type & TX_ASYN) {
+		if(wifi_pdata.bus_type & TX_SDMA)
+			usb_ports[id]->sdma_tx_callback = func;
+		else
+			usb_ports[id]->adma_tx_callback = func;
+	}
+	usb_ports[id]->tx_data = para;
+	return 0;
+}
+
+
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int skw_usb_io_probe(struct usb_interface *interface,
+				const struct usb_device_id *id)
+{
+	struct recovery_data *recovery = SKW_USB_GET_RECOVERY_DATA();
+	struct usb_port_struct *port;
+	struct usb_host_interface *iface_desc;
+	struct usb_endpoint_descriptor *epd;
+	struct platform_device *pdev;
+	struct usb_device *udev = interface_to_usbdev(interface);
+	char	pdev_name[32], names[32];
+	int	i, ret, dloader=0;
+
+	memset(names, 0 ,sizeof(names));
+	iface_desc = interface->cur_altsetting;
+	if (iface_desc->string == NULL)
+		return -EINVAL;
+	sprintf(names, "%s", iface_desc->string);
+
+	if (!strncmp(names, "Boot", 4))
+		dloader = 1;
+
+#ifndef CONFIG_SV6160_LITE_FPGA
+	if ((udev->descriptor.idProduct != 0x6316) && !dloader
+		&& chip_en_gpio < 0 && modem_status == MODEM_OFF) {
+#ifndef MODEM_WDT_SUPPORT
+		return -EINVAL;
+#endif
+	}
+	if (chip_en_gpio < 0) {
+		skw_usb_warn(" descriptor.idProduct = 0x%x CHIP_ID= %.100s\n",
+			udev->descriptor.idProduct,udev->product);
+	}
+#endif
+	port = kzalloc(sizeof(*port), GFP_KERNEL);
+	if (!port)
+		return -ENOMEM;
+
+	mutex_lock(&recovery->except_mutex);
+	if (usb_bus_num == 0xff && usb_port_num == 0xff) {
+		skw_usb_info("bus[%x].port[%d]\n", udev->bus->busnum, udev->portnum);
+		usb_bus_num = udev->bus->busnum;
+		usb_port_num = udev->portnum;
+	} else if (usb_bus_num != udev->bus->busnum || usb_port_num != udev->portnum) {
+		mutex_unlock(&recovery->except_mutex);
+		if (port)
+			kfree(port);
+		return -EBUSY;
+	}
+	mutex_unlock(&recovery->except_mutex);
+
+	pdev = NULL;
+	if (!strncmp(names, "WIFITCMD", 8))
+		wifi_port_share = 1;
+	usb_ports[iface_desc->desc.bInterfaceNumber] = port;
+	INIT_LIST_HEAD(&port->rx_urb_list);
+	INIT_LIST_HEAD(&port->tx_urb_list);
+	INIT_LIST_HEAD(&port->rx_done_urb_list);
+	INIT_LIST_HEAD(&port->suspend_urb_list);
+	spin_lock_init(&port->rx_urb_lock);
+	spin_lock_init(&port->tx_urb_lock);
+	port->tx_urb_count = 0;
+	init_waitqueue_head(&port->rx_wait);
+	init_waitqueue_head(&port->tx_wait);
+	if(dloader)
+		dloader = 1;
+	else if (iface_desc->desc.bInterfaceNumber == 1) {
+		if (!strncmp(skw_chipid, "SV6160LITE", 10)) {
+#ifdef SV6621S_WIRELESS
+			sprintf(pdev_name, "%s%d", SV6621S_WIRELESS,
+				iface_desc->desc.bInterfaceNumber);
+#else
+			sprintf(pdev_name, "%s%d", SV6160_WIRELESS,
+				iface_desc->desc.bInterfaceNumber);
+#endif
+		} else if (!strncmp(skw_chipid, "SV6160", 6)) {
+			sprintf(pdev_name, "%s%d", SV6160_WIRELESS,
+				iface_desc->desc.bInterfaceNumber);
+		} else if (!strncmp(skw_chipid, "SV6316", 6)) {
+			sprintf(pdev_name, "%s%d", SV6316_WIRELESS,
+				iface_desc->desc.bInterfaceNumber);
+		} else {
+			skw_usb_err(
+				"unknow chip id!!! pls check you porting code!! and the connect the seekwave!!\n");
+			if (skw_chipid)
+				skw_usb_err("unknow chip id: %s\n", skw_chipid);
+
+			sprintf(pdev_name, "%s%d", SV6316_WIRELESS,
+				iface_desc->desc.bInterfaceNumber);
+		}
+		if(!wifi_data_pdev)
+			pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+		else
+			pdev = wifi_data_pdev;
+		if(!pdev)
+			return -ENOMEM;
+	} else {
+#ifdef CONFIG_BT_SEEKWAVE
+		if (!strncmp(names, "DATA", 4)) {
+			ucom_pdata.data_port = 0;
+		} else if(!strncmp(names, "BTDATA", 6))
+			ucom_pdata.data_port = iface_desc->desc.bInterfaceNumber;
+		else if(!strncmp(names, "BTCMD", 5))
+			ucom_pdata.cmd_port = iface_desc->desc.bInterfaceNumber;
+		else if(!strncmp(names, "BTISOC", 6)) {
+			ucom_pdata.audio_port = iface_desc->desc.bInterfaceNumber;
+			sprintf(pdev_name, "%s", "btseekwave");
+			ucom_pdata.port_name = "BTHCI";
+			bluetooth_pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+			if (!bluetooth_pdev)
+				return -ENOMEM;
+			bluetooth_pdev->dev.parent = &udev->dev;
+			bluetooth_pdev->dev.dma_mask = &port_dmamask;
+			bluetooth_pdev->dev.coherent_dma_mask = port_dmamask;
+			bt_audio_port = iface_desc->desc.bInterfaceNumber;
+			memcpy(ucom_pdata.chipid, skw_chipid, SKW_CHIP_ID_LENGTH);
+			ret = platform_device_add_data(bluetooth_pdev, &ucom_pdata, sizeof(ucom_pdata));
+			if(ret) {
+				skw_usb_err("failed to add platform data \n");
+				platform_device_put(pdev);
+				kfree(port);
+				return ret;
+			}
+			skw_usb_info("add the bt devices \n");
+		}else if(!strncmp(names, "AUDIO", 5)) {
+			ucom_pdata.audio_port = 0;
+			sprintf(pdev_name, "%s", "btseekwave");
+			ucom_pdata.port_name = "BTHCI";
+			bluetooth_pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+			if (!bluetooth_pdev)
+				return -ENOMEM;
+			bluetooth_pdev->dev.parent = &udev->dev;
+			bluetooth_pdev->dev.dma_mask = &port_dmamask;
+			bluetooth_pdev->dev.coherent_dma_mask = port_dmamask;
+			bt_audio_port = iface_desc->desc.bInterfaceNumber;
+			memcpy(ucom_pdata.chipid, skw_chipid, SKW_CHIP_ID_LENGTH);
+			ret = platform_device_add_data(bluetooth_pdev, &ucom_pdata, sizeof(ucom_pdata));
+			if(ret) {
+				skw_usb_err("failed to add platform data \n");
+				platform_device_put(pdev);
+				kfree(port);
+				return ret;
+			}
+		} else
+#endif
+		if (iface_desc->desc.bInterfaceNumber && strncmp(names, "LOOP", 4)) {
+			sprintf(pdev_name, "%s", "skw_ucom");
+			ucom_pdata.port_name = iface_desc->string;
+			ucom_pdata.data_port = iface_desc->desc.bInterfaceNumber;
+			memcpy(ucom_pdata.chipid, skw_chipid, SKW_CHIP_ID_LENGTH);
+			pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_AUTO);
+			if(!pdev)
+				return -ENOMEM;
+		}
+	}
+	if(!dloader) {
+		if (1==iface_desc->desc.bInterfaceNumber && wifi_data_pdev) {
+			struct sv6160_platform_data *pdata;
+			pdev = wifi_data_pdev;
+			//pdev->dev.parent = NULL;
+			pdata = pdev->dev.platform_data;
+			if (pdata) {
+				pdata->align_value = iface_desc->endpoint[0].desc.wMaxPacketSize;
+				wifi_pdata.align_value = iface_desc->endpoint[0].desc.wMaxPacketSize;
+			}
+			port->pdev = pdev;
+		} else if (iface_desc->desc.bInterfaceNumber && pdev) {
+			if (1==iface_desc->desc.bInterfaceNumber &&
+			    usb_boot_data && usb_boot_data->pdev) {
+				pdev->dev.parent = &usb_boot_data->pdev->dev;
+			} else
+				pdev->dev.parent = &udev->dev;
+			pdev->dev.dma_mask = &port_dmamask;
+			pdev->dev.coherent_dma_mask = port_dmamask;
+
+			if(iface_desc->desc.bInterfaceNumber == 1) {
+				wifi_pdata.align_value = iface_desc->endpoint[0].desc.wMaxPacketSize;
+				if(usb_boot_data && usb_boot_data->iram_dl_size >0x50000)
+					wifi_pdata.at_ops.port = 4;
+				else
+					wifi_pdata.at_ops.port = 2;
+				if(udev->config->string && !strncmp(udev->config->string, "ECOM", 4)) {
+					wifi_pdata.bus_type &= ~TYPE_MASK;
+					wifi_pdata.bus_type |= USB2_LINK;
+				}
+				ret = platform_device_add_data(pdev, &wifi_pdata, sizeof(wifi_pdata));
+				modem_status = MODEM_ON;
+			} else{
+				memcpy(ucom_pdata.chipid, skw_chipid, SKW_CHIP_ID_LENGTH);
+				ret = platform_device_add_data(pdev, &ucom_pdata, sizeof(ucom_pdata));
+			}
+			if(ret) {
+				skw_usb_err("failed to add platform data \n");
+				platform_device_put(pdev);
+				kfree(port);
+				return ret;
+			}
+			if(iface_desc->desc.bInterfaceNumber>1){
+				ret = platform_device_add(pdev);
+				if(ret) {
+					skw_usb_err("failt to register platform device\n");
+					platform_device_put(pdev);
+					kfree(port);
+					return ret;
+				}
+			}
+			port->pdev = pdev;
+		}
+	}
+	usb_set_intfdata(interface, port);
+
+	port->interface = usb_get_intf(interface);
+	port->udev = usb_get_dev(udev);
+	/* register struct wcn_usb_intf */
+	skw_usb_dbg("intf[%x] is registerred: ep count %d %s\n",
+			iface_desc->desc.bInterfaceNumber,
+			iface_desc->desc.bNumEndpoints,
+			iface_desc->string);
+	ret = -ENOMEM;
+	for(i=0; i<iface_desc->desc.bNumEndpoints; i++) {
+
+		epd = &iface_desc->endpoint[i].desc;
+		port->buffer_size = 5120;
+		port->ep_mps = epd->wMaxPacketSize;
+		if(usb_endpoint_is_bulk_in(epd)) {
+			port->epin = epd;
+			port->read_urb = usb_alloc_urb(0, GFP_KERNEL);
+			if(!port->read_urb)
+				goto err0;
+			if(iface_desc->desc.bInterfaceNumber > 1) {
+				port->read_buffer = NULL;
+				port->buffer_size = 0;
+			} else {
+				port->read_buffer = kzalloc(port->buffer_size , GFP_KERNEL);
+				if(!port->read_buffer)
+					goto err0;
+			}
+			usb_fill_bulk_urb(port->read_urb, udev,
+				usb_rcvbulkpipe(udev, epd->bEndpointAddress),
+				port->read_buffer, port->buffer_size,
+				bulkin_complete, port);
+			port->read_urb->context = NULL;
+			init_usb_anchor(&port->read_submitted);
+			skw_usb_dbg("BulkinEP = 0x%x rp=%p\n",
+					epd->bEndpointAddress, port->read_buffer);
+		} else if(usb_endpoint_is_bulk_out(epd)) {
+			port->epout = epd;
+			port->write_urb = usb_alloc_urb(0, GFP_KERNEL);
+			if(!port->write_urb)
+				goto err0;
+			if(iface_desc->desc.bInterfaceNumber > 1) {
+				port->write_buffer = NULL;
+				port->buffer_size = 0;
+			} else{
+				port->write_buffer = kzalloc(port->buffer_size, GFP_KERNEL);
+				if(!port->write_buffer)
+					goto err0;
+			}
+			usb_fill_bulk_urb(port->write_urb, udev,
+				usb_sndbulkpipe(udev, epd->bEndpointAddress),
+				port->write_buffer, port->buffer_size, bulkout_complete,port);
+			port->write_urb->context = NULL;
+			init_usb_anchor(&port->write_submitted);
+			skw_usb_dbg("BulkoutEP = 0x%x wp =%p context %p\n",
+					epd->bEndpointAddress, port->write_buffer, port->write_urb->context);
+		}
+	}
+	if(!dloader) {
+		port->portno = iface_desc->desc.bInterfaceNumber;
+		port->state = 1;
+		if (port->portno<=1) {
+			if (!strncmp(names, "WIFIDATA", 8)) {
+				skw_get_packet_count(port->portno);
+				wifi_pdata.cmd_port = 1 - port->portno;
+				wifi_pdata.data_port = port->portno;
+				port->thread = kthread_create(usb_port_async_entry, port, iface_desc->string);
+
+				tasklet_init(&port->tasklet, usb_handle, (unsigned long) port);
+			} else {
+				wifi_pdata.cmd_port = port->portno;
+				wifi_pdata.data_port = 1 - port->portno;
+			}
+			if(port->thread) {
+				sema_init(&port->sem, 0);
+				wake_up_process(port->thread);
+			} else
+				sema_init(&port->sem, 1);
+		} else if(!strncmp(names, "LOOP", 4)) {
+			sema_init(&port->sem, 0);
+			port->thread = kthread_create(usb_loopcheck_entry, port, iface_desc->string);
+			if (port->thread)
+				wake_up_process(port->thread);
+		} else	sema_init(&port->sem, 1);
+	} else {
+		port->state = 1;
+		assert_info_print = 0;
+		INIT_WORK(&port->work, dloader_work);
+		if (usb_boot_data &&
+		    usb_boot_data->iram_dl_size &&
+		    usb_boot_data->dram_dl_size) {
+			skw_usb_info("schedule boot-work: 0x%x:0x%x\n",
+				usb_boot_data->dram_dl_size,usb_boot_data->iram_dl_size);
+			schedule_work(&port->work);
+			modem_status = MODEM_ON;
+		}
+		port->is_dloader = 1;
+	}
+	if (!strncmp(names, "LOG", 3))
+		log_port = port;
+	return 0;
+err0:
+	skw_usb_err("no memory  to register device\n");
+	if(port->write_buffer)
+		kfree(port->write_buffer);
+	if(port->read_buffer)
+		kfree(port->read_buffer);
+	if(port->write_urb)
+		usb_free_urb(port->write_urb);
+	if(port->read_urb)
+		usb_free_urb(port->read_urb);
+	if(port->pdev)
+		platform_device_unregister(port->pdev);
+	usb_ports[iface_desc->desc.bInterfaceNumber] = NULL;
+	kfree(port);
+	return ret;
+}
+
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int launch_download_work(char *data, int size,int addr)
+{
+	int chk_ports =0;
+	firmware_size = size;//link to usb_download size
+	firmware_data	= data;//link to usb_download dl_data
+	firmware_addr = addr;
+	do{
+		if((usb_ports[0] !=NULL)&&(usb_ports[0]->state)) {
+			chk_ports = 1;
+			break;
+		}
+		msleep(10);
+	}while(!chk_ports);
+	schedule_work(&usb_ports[0]->work);
+	return 0;
+}
+
+
+static int skw_recovery_mode(void)
+{
+	int ret=0;
+	skw_usb_info("[+]\n");
+	if(!cls_recovery_mode_en){
+		ret = skw_reset_bus_dev();
+	} else {
+	    skw_usb_info("No Need To Recovery!\n");
+	}
+	skw_usb_info("[-]\n");
+	return ret;
+}
+
+void get_bt_antenna_mode(char *mode)
+{
+}
+
+void reboot_to_change_bt_antenna_mode(char *mode)
+{
+}
+
+void get_USB_speed_mode(char *mode)
+{
+}
+
+void reboot_to_change_USB_speed_mode(char *mode)
+{
+}
+
+void reboot_to_change_bt_uart1(char *mode)
+{
+}
+static irqreturn_t skw_gpio_irq_handler(int irq, void *dev_id)
+{
+	return IRQ_HANDLED;
+}
+/************************************************************************
+ *Decription:
+ *Author:JUNWEI.JIANG
+ *Date:2021-12-20
+ *Modfiy:
+ *
+ ********************************************************************* */
+int skw_boot_loader(struct seekwave_device *boot_data)
+{
+	int ret = 1;
+
+	if (usb_ports[0] && usb_ports[0]->suspend)
+		return -EOPNOTSUPP;
+	usb_boot_data= boot_data;
+	skw_usb_info("status:%d , chip_en_gpio=%d, gpio_in=%d", modem_status,
+		       	usb_boot_data->chip_en, usb_boot_data->gpio_in);
+	chip_en_gpio = usb_boot_data->chip_en;
+#ifdef CONFIG_SKW_DL_TIME_STATS
+	cur_time = ktime_get();
+#endif
+	if (host_wake_gpio < 0 && usb_boot_data->gpio_in>=0) {
+		int irq_num;
+
+		host_wake_gpio = usb_boot_data->gpio_in;
+		irq_num = gpio_to_irq(host_wake_gpio);		
+		ret = request_irq(irq_num, skw_gpio_irq_handler,
+				IRQF_TRIGGER_RISING | IRQF_ONESHOT, "skw-gpio-irq", NULL);
+		skw_usb_info("request_gpio_irq ret=%d\n", ret);
+		if (ret == 0)
+			enable_irq_wake(irq_num);
+	}
+	if(!boot_data->first_dl_flag ){
+		if (usb_ports[0] && !usb_ports[0]->is_dloader) {
+			//usb_setup_service_devices();
+			schedule_work(&add_device_work);
+		} else if(boot_data->iram_img_data !=NULL && boot_data->dram_img_data!=NULL){
+			skw_usb_info("USB FIRST BOOT... \n");
+			ret=launch_download_work(boot_data->iram_img_data,boot_data->iram_dl_size,boot_data->iram_dl_addr);
+		}else{
+			skw_usb_info("The CPBOOT not download from AP!!!!\n");
+		}
+	}
+	if(boot_data->dl_module==RECOVERY_BOOT){
+		skw_recovery_mode();
+		return 0;
+	}
+	if(boot_data->service_ops==SKW_WIFI_START){
+		//skw_WIFI_service_start();
+		//skw_usb_info("----WIFI-SERVICE-----START!!!\n");
+	}else if(boot_data->service_ops== SKW_WIFI_STOP &&
+			(service_state_map & (1<<WIFI_SERVICE))){
+		skw_WIFI_service_stop();
+		//skw_usb_info("----WIFI-SERVICE-----STOP!!!\n");
+	}else if(boot_data->service_ops == SKW_BT_START){
+		skw_usb_info("----BT-SERVICE-----START!!!\n");
+		ret=skw_BT_service_start();
+	}else if(boot_data->service_ops==SKW_BT_STOP &&
+			(service_state_map & (1<<BT_SERVICE))){
+		skw_usb_info("----BT-SERVICE-----STOP!!!\n");
+		ret=skw_BT_service_stop();
+	}
+	if(ret < 0)
+		return -1;
+	else
+		return 0;
+}
+void *skw_get_bus_dev(void)
+{
+	int time_count=0;
+	if(modem_status == MODEM_OFF && !usb_ports[0]) {
+		skw_usb_err(" power on USB\n");
+		do{
+			msleep(10);
+			time_count++;
+		}while(!usb_ports[0] && time_count < 50);
+	}
+	if(!usb_ports[0] || !usb_ports[0]->state || !usb_ports[0]->udev){
+		skw_usb_err(" the port open device fail !!!\n");
+		return NULL;
+	}
+	return &usb_ports[0]->udev->dev;
+}
+
+/************************************************************************
+ *Decription:check dev ready for boot
+ *Author:junwei.jiang
+ *Date:2022-06-07
+ *Modfiy:
+ *
+ ********************************************************************* */
+int skw_reset_bus_dev(void)
+{
+	struct usb_port_struct *port;
+	int ret = -1;
+
+	if(chip_en_gpio >= 0) {
+		skw_chip_power_reset();
+		return 0;
+	}
+	port = usb_ports[0];
+	if (port == NULL)
+		return 0;
+
+	ret = usb_control_msg(port->udev, usb_sndctrlpipe(port->udev, 0),
+			VENDOR_MSG_MODEM_RESET, USB_DIR_OUT| USB_TYPE_VENDOR|USB_RECIP_DEVICE,
+			0,0,NULL,0,100);
+	skw_usb_info("ret = %d\n", ret);
+	if (ret == -ETIMEDOUT) {
+		usb_reset_device(port->udev);
+	}
+	return ret;
+}
+
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int skw_usb_io_free_suspend_urbs(struct usb_interface *interface)
+{
+	struct usb_port_struct *port;
+	struct urb *urb;
+
+	port = usb_get_intfdata(interface);
+
+	port->suspend = 0;
+	while(!list_empty(&port->suspend_urb_list)) {
+		urb = list_first_entry(&port->suspend_urb_list, struct urb, urb_list);
+		list_del_init(&urb->urb_list);
+		if (!list_empty(&port->suspend_urb_list))
+			list_add_tail(&urb->urb_list, &port->rx_urb_list);
+		else {
+			urb->status = -EIO;
+			urb->complete(urb);
+		}
+	}
+	return 0;
+}
+
+static void skw_usb_io_disconnect(struct usb_interface *interface)
+{
+	int infno = interface->cur_altsetting->desc.bInterfaceNumber;
+	struct recovery_data *recovery = SKW_USB_GET_RECOVERY_DATA();
+	struct usb_port_struct *port;
+	unsigned long flags;
+	struct urb *urb;
+
+	port = usb_get_intfdata(interface);
+	if(!port)
+		return;
+	log_port = NULL;
+	port->state = 0;
+	skw_usb_info("interface[%x] disconnected %d\n", infno, modem_status);
+	if(!port->is_dloader) {
+		if (infno > 1)
+			platform_device_unregister(port->pdev);
+		if (infno == 1) {
+			wake_up_interruptible(&port->rx_wait);
+			wake_up_interruptible(&port->tx_wait);
+		}
+		if (modem_status==MODEM_ON) {
+			if(wifi_data_pdev && &port->udev->dev == wifi_data_pdev->dev.parent) {
+				if(recovery->cp_state == 0)
+					modem_notify_event(DEVICE_DISCONNECT_EVENT);
+				platform_device_unregister(wifi_data_pdev);
+				wifi_data_pdev = NULL;
+				skw_usb_info("WIFI device disconnected1!!!\n");
+			}
+		}
+		if (port->pdev == wifi_data_pdev && port->suspend) {
+			modem_notify_event(DEVICE_DISCONNECT_EVENT);
+			tasklet_kill(&port->tasklet);
+		}
+		skw_usb_io_free_suspend_urbs(interface);
+		if(port->read_urb && port->read_urb->context)
+			usb_kill_anchored_urbs(&port->read_submitted);
+		if(port->write_urb && port->write_urb->context)
+			usb_kill_anchored_urbs(&port->write_submitted);		
+		if(port->thread && !port->suspend&& down_timeout(&port->sem, 1000))
+			skw_usb_info("start  to unregister interface[%x]\n", infno);
+	} else
+		flush_work(&port->work);
+	if(port->read_urb && !port->read_urb->context) {
+		kfree(port->read_urb);
+		port->read_urb = NULL;
+	} else skw_usb_err(" memory leak port.r%d!!!!!!!!\n", infno);
+	if(port->write_urb && !port->write_urb->context) {
+		kfree(port->write_urb);
+		port->write_urb = NULL;
+	} else skw_usb_err(" memory leak port.w%d!!!!!!!!\n", infno);
+	if(port->read_buffer)
+		kfree(port->read_buffer);
+	if(port->write_buffer)
+		kfree(port->write_buffer);
+	spin_lock_irqsave(&port->rx_urb_lock, flags);
+	while(!list_empty(&port->rx_done_urb_list)) {
+		urb = list_first_entry(&port->rx_done_urb_list, struct urb, urb_list);
+		list_del_init(&urb->urb_list);
+		if(urb->transfer_buffer)
+			kfree(urb->transfer_buffer);
+		usb_free_urb(urb);
+	}
+	spin_unlock_irqrestore(&port->rx_urb_lock, flags);
+	usb_ports[infno] = NULL;
+	usb_set_intfdata(interface, NULL);
+	usb_put_dev(port->udev);
+	usb_put_intf(interface);
+	kfree(port);
+	if (chip_en_gpio >= 0 && MODEM_DOWNLOAD_FAILED == modem_status) {
+		modem_status = MODEM_HALT;
+		msleep(50);
+		gpio_set_value(chip_en_gpio, 1);
+		skw_usb_info("retry to boot device\n");
+	}
+}
+
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int skw_usb_io_pre_reset(struct usb_interface *interface)
+{
+	/* there is a lock to prevent we reset a interface when
+	 * urb submit
+	 */
+	struct usb_port_struct *port;
+
+	port = usb_get_intfdata(interface);
+
+	return 0;
+}
+
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static int skw_usb_io_post_reset(struct usb_interface *interface)
+{
+	struct usb_port_struct *port;
+
+	port = usb_get_intfdata(interface);
+	return 0;
+}
+#ifdef CONFIG_PM
+static int skw_usb_io_suspend(struct usb_interface *interface, pm_message_t message)
+{
+	struct usb_port_struct *port;
+	struct recovery_data *recovery = SKW_USB_GET_RECOVERY_DATA();
+
+	port = usb_get_intfdata(interface);
+
+	if(usb_ports[1] == port) {
+		int ret;
+		u16 *count = (u16 *)port->read_buffer;
+		
+		modem_notify_event(DEVICE_SUSPEND_EVENT);
+		ret = usb_control_msg(port->udev, usb_rcvctrlpipe(port->udev, 0),
+				VENDOR_MSG_MODEM_SUSP, USB_DIR_IN| USB_TYPE_VENDOR|USB_RECIP_DEVICE,
+				1, 0, port->read_buffer, 2, 10);
+		skw_usb_info("RET = %d  packet suspended = %d\n", ret, *count);
+		if (*count)
+			msleep(10);
+	}
+	if (port->tx_urb_count)
+		usb_kill_anchored_urbs(&port->write_submitted);
+	port->suspend = 1;
+
+	if(port->portno == 1 || port->read_urb->context)
+		usb_kill_anchored_urbs(&port->read_submitted);
+	if(port->write_urb->context)
+		usb_kill_anchored_urbs(&port->write_submitted);
+	if (port->portno==0 && recovery->cp_state) {
+		recovery->cp_state = 0;
+		cancel_delayed_work_sync(&skw_except_work);
+	}
+#if KERNEL_VERSION(3, 2, 0) <= LINUX_VERSION_CODE
+	skw_usb_info("port%d %s MSG\n", port->portno, PMSG_IS_AUTO(message)? "Auto":"None-auto");
+#else
+	skw_usb_info("port%d MSG not supported print!!\n", port->portno);
+#endif
+	return 0;
+}
+static int skw_usb_io_resume(struct usb_interface *interface)
+{
+	int	 retval = -1;
+	struct usb_port_struct *port;
+	struct urb *urb;
+	port = usb_get_intfdata(interface);
+
+	skw_usb_info("port%d enter...\n", port->portno);
+	while(!list_empty(&port->suspend_urb_list)) {
+		urb = list_first_entry(&port->suspend_urb_list, struct urb, urb_list);
+		list_del_init(&urb->urb_list);
+		if(port->portno == wifi_pdata.data_port)
+			urb->context = port;
+		usb_anchor_urb(urb, &port->read_submitted);
+		retval = usb_submit_urb(urb, GFP_KERNEL);
+		if (retval < 0) {
+			usb_unanchor_urb(urb);
+			skw_usb_info("is error!!! %d\n", retval);
+			return retval;
+		}
+	}
+	if (usb_ports[1]==port && port->suspend) {
+		port->suspend = 0;
+		modem_notify_event(DEVICE_RESUME_EVENT);
+	}
+	port->suspend = 0;
+	return 0;
+}
+static int skw_usb_io_reset_resume(struct usb_interface *interface)
+{
+	struct usb_port_struct *port;
+
+	skw_usb_info("enter...\n");
+	port = usb_get_intfdata(interface);
+	if (port)
+		port->suspend++;
+	skw_usb_io_resume(interface);
+	return 0;
+}
+#endif
+
+/************************************************************************
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+struct usb_driver skw_usb_io_driver = {
+	.name = "skw_usb_io",
+	.probe = skw_usb_io_probe,
+	.disconnect = skw_usb_io_disconnect,
+#ifdef CONFIG_PM
+        .suspend   = skw_usb_io_suspend,
+        .resume    = skw_usb_io_resume,
+        .reset_resume = skw_usb_io_reset_resume,
+#endif	
+	.pre_reset = skw_usb_io_pre_reset,
+	.post_reset = skw_usb_io_post_reset,
+	.id_table = skw_usb_io_id_table,
+	.supports_autosuspend = 1,
+};
+
+/**
+ * wcn_usb_io_init() - init wcn_usb_io's memory and register this driver.
+ * @void: void.
+ */
+static int __init skw_usb_io_init(void)
+{
+	usb_bus_num = 0xff;
+	usb_port_num = 0xff;
+	wifi_data_pdev = NULL;
+	bluetooth_pdev = NULL;
+	log_port = NULL;
+	usb_boot_data = NULL;
+#ifndef CONFIG_SEEKWAVE_PLD_RELEASE
+	cls_recovery_mode_en = 1;
+#else
+	cls_recovery_mode_en = 0;
+#endif
+	wifi_port_share = 0;
+	usb_speed_switching = 0;
+	memset(usb_ports, 0, sizeof(usb_ports));
+	init_completion(&download_done);
+	init_completion(&loop_completion);
+	skw_usb_debugfs_init();
+	skw_usb_log_level_init();
+	chip_en_gpio = -1;
+	modem_status = MODEM_OFF;
+	skw_chipid = wifi_pdata.chipid;
+	mutex_init(&g_recovery_data.except_mutex);
+	INIT_DELAYED_WORK(&skw_except_work, skw_usb_exception_work);
+	INIT_WORK(&add_device_work, add_devices_work);
+	INIT_WORK(&dump_memory_worker, dump_memory_work);
+	INIT_WORK(&usb_control_worker, usb_control_work);
+	dump_memory_buffer = NULL;
+	dump_log_size=NULL;
+	dump_buffer_size = 0;
+	usb_register(&skw_usb_io_driver);
+	return seekwave_boot_init(NULL);;
+}
+
+/************************************************************************
+ *Copyright(C) 2020-2021: Seekwave tech LTD 		China
+ *Decription:
+ *Author:jiayong.yang
+ *Date:2021-05-27
+ *Modfiy:
+ *
+ ********************************************************************* */
+static void __exit skw_usb_io_exit(void)
+{
+	int ret;
+
+	if (chip_en_gpio >=0) {
+		skw_chip_set_power(0);
+		msleep(50);
+	}
+	if (usb_ports[0] && usb_ports[0]->udev) {
+		skw_usb_info("reset SKWUSB device");
+		skw_reset_bus_dev();
+	}
+	if (usb_boot_data && usb_boot_data->pdev && wifi_data_pdev &&
+	    wifi_data_pdev->dev.parent == &usb_boot_data->pdev->dev) {
+		skw_usb_info("unregister WIFI device\n");
+		platform_device_unregister(wifi_data_pdev);
+		wifi_data_pdev = NULL;
+		ret = 0;
+	}
+	seekwave_boot_exit();
+	skw_usb_debugfs_deinit();
+	cancel_delayed_work_sync(&skw_except_work);
+	cancel_work_sync(&add_device_work);
+	cancel_work_sync(&dump_memory_worker);
+	cancel_work_sync(&usb_control_worker);
+	dump_memory_buffer = NULL;
+	dump_log_size=NULL;
+	mutex_destroy(&g_recovery_data.except_mutex);
+	if(bluetooth_pdev)
+		platform_device_put(bluetooth_pdev);
+	usb_deregister(&skw_usb_io_driver);
+	if(wifi_data_pdev)
+		platform_device_put(wifi_data_pdev);
+}
+module_init(skw_usb_io_init)
+module_exit(skw_usb_io_exit)
+MODULE_LICENSE("GPL v2");
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_log.c b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_log.c
new file mode 100755
index 0000000..bfe9f5d
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_log.c
@@ -0,0 +1,452 @@
+/*****************************************************************************
+ * Copyright(c) 2020-2030  Seekwave Corporation.
+ * SEEKWAVE TECH LTD..CO
+ *
+ *Seekwave Platform the usb log debug fs
+ *FILENAME:skw_usb_log.c
+ *DATE:2022-04-11
+ *MODIFY:
+ *Author:Jones.Jiang
+ **************************************************************************/
+#include <linux/uaccess.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/seq_file.h>
+#include "skw_usb_log.h"
+#include "skw_usb.h"
+#include "skw_usb_debugfs.h"
+
+static unsigned long skw_usb_dbg_level;
+
+extern char firmware_version[];
+extern void reboot_to_change_bt_antenna_mode(char *mode);
+extern void get_bt_antenna_mode(char *mode);
+unsigned long skw_usb_log_level(void)
+{
+	return skw_usb_dbg_level;
+}
+
+static void skw_usb_set_log_level(int level)
+{
+	unsigned long dbg_level;
+
+	dbg_level = skw_usb_log_level() & 0xffff0000;
+	dbg_level |= ((level << 1) - 1);
+
+	xchg(&skw_usb_dbg_level, dbg_level);
+}
+
+static void skw_usb_enable_func_log(int func, bool enable)
+{
+	unsigned long dbg_level = skw_usb_log_level();
+
+	if (enable)
+		dbg_level |= func;
+	else
+		dbg_level &= (~func);
+
+	xchg(&skw_usb_dbg_level, dbg_level);
+}
+
+static int skw_usb_log_show(struct seq_file *seq, void *data)
+{
+#define SKW_USB_LOG_STATUS(s) (level & (s) ? "enable" : "disable")
+
+	int i;
+	u32 level = skw_usb_log_level();
+	u8 *log_name[] = {"NONE", "ERROR", "WARNNING", "INFO", "DEBUG"};
+
+	for (i = 0; i < 5; i++) {
+		if (!(level & BIT(i)))
+			break;
+	}
+	if (i >= 5)
+		return 0;
+	seq_printf(seq, "\nlog   level: %s\n", log_name[i]);
+
+	seq_puts(seq, "\n");
+	seq_printf(seq, "port0 log: %s\n", SKW_USB_LOG_STATUS(SKW_USB_PORT0));
+	seq_printf(seq, "port1 log: %s\n", SKW_USB_LOG_STATUS(SKW_USB_PORT1));
+	seq_printf(seq, "port2 log: %s\n", SKW_USB_LOG_STATUS(SKW_USB_PORT2));
+	seq_printf(seq, "port3 log: %s\n", SKW_USB_LOG_STATUS(SKW_USB_PORT3));
+	seq_printf(seq, "port4 log: %s\n", SKW_USB_LOG_STATUS(SKW_USB_PORT4));
+	seq_printf(seq, "port5 log: %s\n", SKW_USB_LOG_STATUS(SKW_USB_PORT5));
+	seq_printf(seq, "port6 log: %s\n", SKW_USB_LOG_STATUS(SKW_USB_PORT6));
+	seq_printf(seq, "port7 log: %s\n", SKW_USB_LOG_STATUS(SKW_USB_PORT7));
+	seq_printf(seq, "savelog  : %s\n", SKW_USB_LOG_STATUS(SKW_USB_SAVELOG));
+	seq_printf(seq, "dump  log: %s\n", SKW_USB_LOG_STATUS(SKW_USB_DUMP));
+
+	return 0;
+}
+
+static int skw_usb_log_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, &skw_usb_log_show, inode->i_private);
+}
+
+static int skw_usb_log_control(const char *cmd, bool enable)
+{
+	if (!strcmp("dump", cmd))
+		skw_usb_enable_func_log(SKW_USB_DUMP, enable);
+	else if (!strcmp("port0", cmd))
+		skw_usb_enable_func_log(SKW_USB_PORT0, enable);
+	else if (!strcmp("port1", cmd))
+		skw_usb_enable_func_log(SKW_USB_PORT1, enable);
+	else if (!strcmp("port2", cmd))
+		skw_usb_enable_func_log(SKW_USB_PORT2, enable);
+	else if (!strcmp("port3", cmd))
+		skw_usb_enable_func_log(SKW_USB_PORT3, enable);
+	else if (!strcmp("port4", cmd))
+		skw_usb_enable_func_log(SKW_USB_PORT4, enable);
+	else if (!strcmp("port5", cmd))
+		skw_usb_enable_func_log(SKW_USB_PORT5, enable);
+    else if (!strcmp("port6", cmd))
+		skw_usb_enable_func_log(SKW_USB_PORT6, enable);
+	else if (!strcmp("port7", cmd))
+		skw_usb_enable_func_log(SKW_USB_PORT7, enable);
+    else if (!strcmp("savelog", cmd))
+		skw_usb_enable_func_log(SKW_USB_SAVELOG, enable);
+	else if (!strcmp("debug", cmd))
+		skw_usb_set_log_level(SKW_USB_DEBUG);
+	else if (!strcmp("info", cmd))
+		skw_usb_set_log_level(SKW_USB_INFO);
+	else if (!strcmp("warn", cmd))
+		skw_usb_set_log_level(SKW_USB_WARNING);
+	else if (!strcmp("error", cmd))
+		skw_usb_set_log_level(SKW_USB_ERROR);
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static ssize_t skw_usb_log_write(struct file *fp, const char __user *buffer,
+				size_t len, loff_t *offset)
+{
+	int i, idx;
+	char cmd[32];
+	bool enable = false;
+
+	for (idx = 0, i = 0; i < len; i++) {
+		char c;
+
+		if (get_user(c, buffer))
+			return -EFAULT;
+
+		switch (c) {
+		case ' ':
+			break;
+
+		case ':':
+			cmd[idx] = 0;
+			if (!strcmp("enable", cmd))
+				enable = true;
+			else
+				enable = false;
+
+			idx = 0;
+			break;
+
+		case '|':
+		case '\0':
+		case '\n':
+			cmd[idx] = 0;
+			skw_usb_log_control(cmd, enable);
+			idx = 0;
+			break;
+
+		default:
+			cmd[idx++] = c;
+			idx %= 32;
+
+			break;
+		}
+
+		buffer++;
+	}
+
+	return len;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_usb_log_proc_fops = {
+	.proc_open = skw_usb_log_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+	.proc_write = skw_usb_log_write,
+};
+#else
+static const struct file_operations skw_usb_log_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_usb_log_open,
+	.read = seq_read,
+	.release = single_release,
+	.write = skw_usb_log_write,
+};
+#endif
+
+static int skw_version_show(struct seq_file *seq, void *data)
+{
+	seq_printf(seq, "firmware info:\n %s\n", firmware_version );
+	return 0;
+}
+
+static int skw_version_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, &skw_version_show, inode->i_private);
+}
+
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_version_proc_fops = {
+	.proc_open = skw_version_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+};
+#else
+static const struct file_operations skw_version_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_version_open,
+	.read = seq_read,
+	.release = single_release,
+};
+#endif
+
+static int skw_port_statistic_show(struct seq_file *seq, void *data)
+{
+	char *statistic = kzalloc(2048, GFP_KERNEL);
+
+	skw_get_port_statistic(statistic, 2048);
+	seq_printf(seq, "Statistic:\n%s", statistic );
+	kfree(statistic);
+	return 0;
+}
+
+static int skw_port_statistic_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, &skw_port_statistic_show, inode->i_private);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_port_statistic_proc_fops = {
+	.proc_open = skw_port_statistic_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+};
+#else
+static const struct file_operations skw_port_statistic_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_port_statistic_open,
+	.read = seq_read,
+	.release = single_release,
+};
+#endif
+
+static int skw_bluetooth_antenna_show(struct seq_file *seq, void *data)
+{
+	char result[32];
+
+	memset(result, 0, sizeof(result));
+	get_bt_antenna_mode(result);
+	if(strlen(result))
+		seq_printf(seq, result);
+	return 0;
+}
+static int skw_bluetooth_antenna_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, &skw_bluetooth_antenna_show, inode->i_private);
+}
+
+
+static ssize_t skw_bluetooth_antenna_write(struct file *fp, const char __user *buffer,
+                                size_t len, loff_t *offset)
+{
+	char cmd[32]={0};
+
+	if (len >= sizeof(cmd))
+			return -EINVAL;
+	if (copy_from_user(cmd, buffer, len))
+		return -EFAULT;
+	if (!strncmp("switch", cmd, 6)) {
+		memset(cmd, 0, sizeof(cmd));
+		reboot_to_change_bt_antenna_mode(cmd);
+		skw_usb_info("%s\n", cmd);
+	}
+	return len;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_bluetooth_antenna_proc_fops = {
+	.proc_open = skw_bluetooth_antenna_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+	.proc_write = skw_bluetooth_antenna_write,
+};
+#else
+static const struct file_operations skw_bluetooth_antenna_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_bluetooth_antenna_open,
+	.read = seq_read,
+	.release = single_release,
+	.write = skw_bluetooth_antenna_write,
+};
+#endif
+
+static int skw_USB_speed_show(struct seq_file *seq, void *data)
+{
+	char result[32];
+
+	memset(result, 0, sizeof(result));
+	get_USB_speed_mode(result);
+	if(strlen(result))
+		seq_printf(seq, result);
+	return 0;
+}
+static int skw_USB_speed_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, &skw_USB_speed_show, inode->i_private);
+}
+
+
+static ssize_t skw_USB_speed_write(struct file *fp, const char __user *buffer,
+                                size_t len, loff_t *offset)
+{
+	char cmd[32]={0};
+
+	if (len >= sizeof(cmd))
+		return -EINVAL;
+	if (copy_from_user(cmd, buffer, len))
+		return -EFAULT;
+	if (!strncmp("HIGH", cmd, 4)) {
+		memset(cmd, 0, sizeof(cmd));
+		reboot_to_change_USB_speed_mode(cmd);
+		skw_usb_info("%s\n", cmd);
+	}
+	return len;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_USB_speed_proc_fops = {
+	.proc_open = skw_USB_speed_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+	.proc_write = skw_USB_speed_write,
+};
+#else
+static const struct file_operations skw_USB_speed_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_USB_speed_open,
+	.read = seq_read,
+	.release = single_release,
+	.write = skw_USB_speed_write,
+};
+#endif
+
+static int skwusb_recovery_debug_show(struct seq_file *seq, void *data)
+{
+    if (skw_usb_recovery_debug_status())
+        seq_printf(seq, "Disabled");
+    else
+        seq_printf(seq, "Enabled");
+
+    return 0;
+}
+static int skwusb_recovery_debug_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, &skwusb_recovery_debug_show, inode->i_private);
+}
+
+static ssize_t skwusb_recovery_debug_write(struct file *fp, const char __user *buffer,
+                size_t len, loff_t *offset)
+{
+    char cmd[16]={0};
+
+    if (len >= sizeof(cmd))
+        return -EINVAL;
+    if (copy_from_user(cmd, buffer, len))
+        return -EFAULT;
+    if (!strncmp("disable", cmd, 7))
+        skw_usb_recovery_debug(1);
+    else if (!strncmp("enable", cmd, 6))
+        skw_usb_recovery_debug(0);
+
+    return len;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skwusb_recovery_debug_proc_fops = {
+    .proc_open = skwusb_recovery_debug_open,
+    .proc_read = seq_read,
+    .proc_release = single_release,
+    .proc_write = skwusb_recovery_debug_write,
+};
+#else
+static const struct file_operations skwusb_recovery_debug_proc_fops = {
+	.owner = THIS_MODULE,
+    .open = skwusb_recovery_debug_open,
+    .read = seq_read,
+    .release = single_release,
+    .write = skwusb_recovery_debug_write,
+};
+#endif
+
+static int skw_bluetooth_UART1_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, NULL, inode->i_private);
+}
+
+
+static ssize_t skw_bluetooth_UART1_write(struct file *fp, const char __user *buffer,
+				size_t len, loff_t *offset)
+{
+	char cmd[32]={0};
+
+	if (len >= sizeof(cmd))
+		return -EINVAL;
+	if (copy_from_user(cmd, buffer, len))
+		return -EFAULT;
+	if (!strncmp("enable", cmd, 6)) {
+		memset(cmd, 0, sizeof(cmd));
+		reboot_to_change_bt_uart1(cmd);
+		skw_usb_info("%s UART-HCI\n", cmd);
+	}
+	return len;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_bluetooth_UART1_proc_fops = {
+	.proc_open = skw_bluetooth_UART1_open,
+	.proc_release = single_release,
+	.proc_write = skw_bluetooth_UART1_write,
+};
+#else
+static const struct file_operations skw_bluetooth_UART1_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_bluetooth_UART1_open,
+	.release = single_release,
+	.write = skw_bluetooth_UART1_write,
+};
+#endif
+
+void skw_usb_log_level_init(void)
+{
+	skw_usb_set_log_level(SKW_USB_INFO);
+
+	skw_usb_enable_func_log(SKW_USB_DUMP, false);
+	skw_usb_enable_func_log(SKW_USB_PORT0, false);
+	skw_usb_enable_func_log(SKW_USB_PORT1, false);
+	skw_usb_enable_func_log(SKW_USB_PORT2, false);
+	skw_usb_enable_func_log(SKW_USB_PORT3, false);
+	skw_usb_enable_func_log(SKW_USB_PORT4, false);
+	skw_usb_enable_func_log(SKW_USB_PORT5, false);
+	skw_usb_enable_func_log(SKW_USB_PORT6, false);
+	skw_usb_enable_func_log(SKW_USB_SAVELOG, false);
+	skw_usb_enable_func_log(SKW_USB_PORT7, false);
+	skw_usb_proc_init_ex("log_level", 0666, &skw_usb_log_proc_fops, NULL);
+	skw_usb_proc_init_ex("Version", 0666, &skw_version_proc_fops, NULL);
+	skw_usb_proc_init_ex("Statistic", 0666, &skw_port_statistic_proc_fops, NULL);
+	skw_usb_proc_init_ex("BT_ANT", 0666, &skw_bluetooth_antenna_proc_fops, NULL);
+	skw_usb_proc_init_ex("recovery", 0666, &skwusb_recovery_debug_proc_fops, NULL);
+	skw_usb_proc_init_ex("USB_SPEED", 0666, &skw_USB_speed_proc_fops, NULL);
+	skw_usb_proc_init_ex("BT_UART1", 0666, &skw_bluetooth_UART1_proc_fops, NULL);
+}
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_log.h b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_log.h
new file mode 100755
index 0000000..606e9f9
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/skw_usb_log.h
@@ -0,0 +1,86 @@
+/******************************************************************************
+ *
+ * Copyright(c) 2020-2030  Seekwave Corporation.
+ * DATE: 2022-07-18
+ * MODIFY:
+ * Author:junwei.jiang
+ *
+ *****************************************************************************/
+#ifndef __SKW_USB_LOG_H__
+#define __SKW_USB_LOG_H__
+
+#define SKW_USB_ERROR    BIT(0)
+#define SKW_USB_WARNING  BIT(1)
+#define SKW_USB_INFO     BIT(2)
+#define SKW_USB_DEBUG    BIT(3)
+
+#define SKW_USB_CMD      BIT(16)
+#define SKW_USB_EVENT    BIT(17)
+#define SKW_USB_SCAN     BIT(18)
+#define SKW_USB_TIMER    BIT(19)
+#define SKW_USB_STATE    BIT(20)
+
+#define SKW_USB_PORT0     BIT(21)
+#define SKW_USB_PORT1     BIT(22)
+#define SKW_USB_PORT2     BIT(23)
+#define SKW_USB_PORT3     BIT(24)
+#define SKW_USB_PORT4     BIT(25)
+#define SKW_USB_PORT5     BIT(26)
+#define SKW_USB_PORT6     BIT(27)
+#define SKW_USB_PORT7     BIT(28)
+#define SKW_USB_SAVELOG     BIT(29)
+#define SKW_USB_DUMP     BIT(31)
+
+unsigned long skw_usb_log_level(void);
+void skw_usb_cp_log(int disable);
+int skw_usb_cp_log_status(void);
+void skw_get_port_statistic(char *buffer, int size);
+void reboot_to_change_USB_speed_mode(char *mode);
+void get_USB_speed_mode(char *mode);
+void modem_notify_event(int event);
+#define skw_usb_log(level, fmt, ...) \
+	do { \
+		if (skw_usb_log_level() & level) \
+			pr_err(fmt,  ##__VA_ARGS__); \
+	} while (0)
+
+#define skw_usb_port_log(port_num, fmt, ...) \
+	do { \
+		if (skw_usb_log_level() &(SKW_USB_PORT0<<port_num)) \
+			pr_err(fmt,  ##__VA_ARGS__); \
+	} while (0)
+
+#define skw_port_log(port_num,fmt, ...) \
+	skw_usb_log((SKW_USB_PORT0<<port_num), "[PORT_LOG] %s: "fmt, __func__, ##__VA_ARGS__)
+
+#define skw_usb_err(fmt, ...) \
+	skw_usb_log(SKW_USB_ERROR, "[SKWUSB ERROR] %s: "fmt, __func__, ##__VA_ARGS__)
+
+#define skw_usb_warn(fmt, ...) \
+	skw_usb_log(SKW_USB_WARNING, "[SKWUSB WARN] %s: "fmt, __func__, ##__VA_ARGS__)
+
+#define skw_usb_info(fmt, ...) \
+	skw_usb_log(SKW_USB_INFO, "[SKWUSB INFO] %s: "fmt, __func__, ##__VA_ARGS__)
+
+#define skw_usb_dbg(fmt, ...) \
+	skw_usb_log(SKW_USB_DEBUG, "[SKWUSB DBG] %s: "fmt, __func__, ##__VA_ARGS__)
+
+#define skw_usb_hex_dump(prefix, buf, len) \
+	do { \
+		if (skw_usb_log_level() & SKW_USB_DUMP) { \
+			u8 str[32] = {0};  \
+			snprintf(str, sizeof(str), "[SKWUSB DUMP] %s", prefix); \
+			print_hex_dump(KERN_ERR, str, \
+				DUMP_PREFIX_OFFSET, 16, 1, buf, len, true); \
+		} \
+	} while (0)
+#if 0
+#define skw_usb_port_log(port_num, fmt, ...) \
+	do { \
+		if (skw_usb_log_level() &(SKW_USB_PORT0<<port_num)) \
+			pr_err("[PORT_LOG] %s:"fmt,__func__,  ##__VA_ARGS__); \
+	} while (0)
+
+#endif
+void skw_usb_log_level_init(void);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/usb_boot.c b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/usb_boot.c
new file mode 100755
index 0000000..78a31bb
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/usb_boot.c
@@ -0,0 +1,469 @@
+/*************************************************************************************
+ *Description: usb download
+ *Seekwave tech LTD
+ *Author: jiayong.yang/junwei.jiang
+ *Date:20210527
+ *Modify:
+ * ***********************************************************************************/
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/errno.h>
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+#endif
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#include <linux/ctype.h>
+#include "skw_usb_log.h"
+#include "usb_boot.h"
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+static int dl_mps;
+static int dloader_port = 0;
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+static int check_modem_status_from_connect_message(void)
+{
+	struct connect_ack *ack = (void *)&connect_ack[12];
+	memcpy(skw_chipid,ack->chip_id,16);
+	dl_mps = ack->packet_size;
+	if(ack->flags.bitmap.boot)
+		return NORMAL_BOOT;
+	else
+		return NORMAL_BOOT;
+}
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+static int dloader_write(char *msg, int msg_len, int *actual, int timeout)
+{
+	return bulkout_write_timeout(dloader_port, msg, msg_len, actual, timeout);
+}
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+static int dloader_read(char *msg, int msg_len, int *actual, int timeout)
+{
+	return bulkin_read_timeout(dloader_port, msg, msg_len, actual, timeout);
+}
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+static int compare_msg(const char *src, const char *dst, size_t count)
+{
+	unsigned char c1, c2;
+
+	while (count) {
+		c1 = *src++;
+		c2 = *dst++;
+		if (c1 != c2)
+			return c1 < c2 ? -1 : 1;
+		count--;
+	}
+	return 0;
+}
+
+static int dloader_send_data(const char *command, int command_len, const char *ack, int ack_len)
+{
+	int actual_len = 0;
+	int ret;
+	void *data;
+	int data_size = 128;
+
+	data = kzalloc(data_size, GFP_KERNEL);
+
+	if (!data)
+		return -ENOMEM;
+	/* send command */
+	ret = dloader_write((char *)command, command_len, &actual_len, 3000);
+	if (ret <0 || actual_len != command_len) {
+		skw_usb_err(" send cmd error ret %d actual_len %d command_len %d\n",
+				ret, actual_len, command_len);
+	} else {
+		if (ack == NULL)
+			goto OUT;
+
+		/* read ack and check it */
+		ret = dloader_read(data, data_size, &actual_len, 3000);
+		if (ret <0 || ack_len > actual_len || compare_msg(ack, data, ack_len)) {
+			skw_usb_err(" ack is NACK:ret == %d\n", ret);
+			print_hex_dump(KERN_ERR, "ACK ERR:", 0, 16, 1, data, ack_len, 1);
+			ret = -EIO;
+		}
+	}
+OUT:
+	kfree(data);
+	return ret;
+}
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+static int dloader_send_command(const char *command,  int command_len, const char *ack, int ack_len)
+{
+	int actual_len = 0;
+	int ret;
+	void *data;
+	int data_size = 128;
+
+	data = kzalloc(data_size, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	/* send command */
+	memcpy(data, (char*)command, command_len);
+	ret = dloader_write(data, command_len, &actual_len, 3000);
+	if (ret <0 || actual_len != command_len) {
+		skw_usb_err(" send cmd error ret %d actual_len %d command_len %d\n",
+				ret, actual_len, command_len);
+	} else {
+		/* read ack */
+		ret = dloader_read(data, data_size, &actual_len, 3000);
+		if (ret <0) {
+			skw_usb_warn(" ack is NACK: acklen ===%d- actual_len ==%d--ret == %d\n",
+				ret, ack_len, actual_len);
+		}
+	}
+	if ((command_len > 8) &&(0 == command[8])){
+		if(actual_len > sizeof(connect_ack))
+			actual_len = sizeof(connect_ack);
+		memcpy(connect_ack, data, actual_len);
+	}
+	kfree(data);
+	return ret;
+}
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+static unsigned short crc16_calculate(unsigned char *buf, int len)
+{
+	unsigned int i;
+	unsigned short crc = 0;
+
+	while (len-- != 0) {
+		for (i = 0x80; i != 0; i = i >> 1) {
+			if ((crc & 0x8000) != 0) {
+				crc = crc << 1;
+				crc = crc ^ 0x1021;
+			} else {
+				crc = crc << 1;
+			}
+			if ((*buf & i) != 0)
+				crc = crc ^ 0x1021;
+		}
+		buf++;
+	}
+	return crc;
+}
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+int dloader_command_start_download(unsigned int addr, unsigned int len)
+{
+
+	int command_len = 20;
+	char command[20] = {0x7E, 0x7E, 0x7E, 0x7E,/* head */
+		0x08, 0x00, 0x00, 0x00, /*length */
+		0x01, 0x00, /* message type, 01: start command */
+		0x00, 0x00, /*crc for data body, excludes message header */
+		0x00, 0x00, 0x10, 0x00,/*addr*/
+		0x60, 0xb3, 0x06, 0x00 /*image size*/};
+
+	*((u32 *)&command[12]) = addr;
+	*((u32 *)&command[16]) = len;
+
+	*((u16 *)&command[10]) = cpu_to_be16(crc16_calculate(&command[12], command_len - 12));
+	return dloader_send_command(command, command_len, common_ack, sizeof(common_ack));
+}
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+int dloader_command_exec(unsigned int addr)
+{
+	unsigned short command_len = 16;
+
+	char command[16] = {0x7E,0x7E,0x7E,0x7E, /* head */
+		0x04, 0x00, 0x00, 0x00,
+		0x04, 0x00, /*command type */
+		0x43, 0x63, /*command len */
+		0x00, 0x00, 0x10, 0x00 /*addr*/
+		};
+	*((u32 *)&command[12]) = addr;
+	*((u16 *)&command[10]) = crc16_calculate(&command[12], 4);
+	return dloader_send_command(command, command_len, exec_ack, sizeof(exec_ack));
+}
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+int dloader_setup_usb_connection(struct usb_port_struct *port)
+{
+	int ret;
+
+	dloader_command_client_probe();
+	if (ret < 0) {
+		dev_err(&port->udev->dev, "get version error\n");
+		return ret;
+	}
+	dloader_command_connect();
+	if (ret < 0) {
+		dev_err(&port->udev->dev, "connection  error\n");
+		return ret;
+	}
+	dev_info(&port->udev->dev,"dloader connect susscess...\n");
+	return 0;
+}
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+int dloader_execute_image(struct usb_port_struct *port,unsigned int addr)
+{
+	int ret;
+
+	ret = dloader_command_exec(addr);
+	if (ret < 0) {
+		dev_err(&port->udev->dev, "exec command is error\n");
+		return ret;
+	}
+	return 0;
+}
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:junwei.jiang
+ *Date:
+ *Modify:
+ * ************************************************************************/
+static unsigned int dloader_send_pdata(char* buf, const void *pdata, unsigned int len)
+{
+	PACKET_T *packet_ptr = (PACKET_T *)buf;
+	int command_len = len + PACKET_HEADER_SIZE;
+
+	packet_ptr->magic = PACKET_MAGIC;
+	packet_ptr->type = 0x0002;
+	packet_ptr->size = len;
+	packet_ptr->crc = 0x0000;
+	memset(packet_ptr->content, 0 , len);
+	memcpy(packet_ptr->content,pdata, len);
+
+	//crc check sum
+	packet_ptr->crc = cpu_to_be16(crc16_calculate((char*)(&(packet_ptr->content)), command_len));
+
+	return command_len;
+}
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+int usb_download_image(struct usb_port_struct *port, unsigned int addr, unsigned int len)
+{
+	int ret;
+	int size;
+	int offset = 0;
+	int img_size = 0;
+	int temp_size = 0;
+
+	/*the first command connect*/
+	ret = dloader_command_start_download(addr, len);
+	if (ret < 0) {
+		dev_err(&port->udev->dev,"start download command failed\n");
+		return ret;
+	}
+	/*get the data and the sv6160.bin size*/
+	img_size = len;
+
+	while (img_size > 0) {
+		temp_size = MIN(dl_mps, img_size-offset);
+		if(!temp_size)
+			return 0;
+		size  = dloader_send_pdata(port->read_buffer, (void*)(firmware_data)+offset, temp_size);
+		if (size%512==0 && temp_size < dl_mps) {
+			temp_size = temp_size>>1;
+			size  = dloader_send_pdata(port->read_buffer, (void*)(firmware_data)+offset, temp_size);
+		}  
+		ret = dloader_send_data(port->read_buffer, size, common_ack, sizeof(common_ack));
+		if (ret < 0) {
+			dev_err(&port->udev->dev, "donwload img  error\n");
+			return ret;
+		}
+		offset += temp_size;
+	}
+	return 0;
+}
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+int dloader_get_chip_id(void *buf, unsigned int buf_size)
+{
+	int len = strlen(usb_ports[0]->udev->product);
+	memcpy(buf, usb_ports[0]->udev->product, strlen(usb_ports[0]->udev->product));
+	return len;
+}
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+int dloader_dump_from_romcode_usb(unsigned int addr, void *buf, int len)
+{
+	int ret;
+	unsigned short command_len = 16;
+	char command[20] = {0x7E, 0x7E, 0x7E, 0x7E,/* head */
+		0x08, 0x00, 0x00, 0x00, /*for command data len */
+		0x00, 0x09, /*command type */
+		0x00, 0x00, /*for crc*/
+		0x00, 0x00, 0x00, 0x00,/*addr*/
+		0x00, 0x00, 0x00, 0x00,/*data len*/
+		};
+	int actual_len = 0;
+	int size;
+
+	//*((u32 *)&command[12]) = cpu_to_be32(addr);
+	//*((u32 *)&command[16]) = cpu_to_be32(len);
+	*((u32 *)&command[12]) = addr;
+	*((u32 *)&command[16]) = len;
+
+	*((u16 *)&command[10]) = cpu_to_be16(crc16_calculate(&command[1], command_len - 4));
+
+
+	ret = dloader_send_command(command, command_len, NULL, 0);
+	if (ret < 0) {
+		skw_usb_err(" send command error\n");
+		return -EIO;
+	}
+
+	size = dl_mps;
+	while(len > 0) {
+		if (len < size)
+			size = len;
+		ret = dloader_read(buf, size, &actual_len, 3000);
+		if (ret < 0)
+			skw_usb_err("dloader_read_ack dump memory error\n");
+		else len -= actual_len;
+	}
+	return ret;
+}
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+static int dloader_dump_read_usb(struct usb_port_struct *port)
+{
+	int ret;
+	ret = dloader_dump_from_romcode_usb(START_ADDR, port->read_buffer, MAX_IMAGE_SIZE);
+	return ret;
+}
+
+/***************************************************************************
+ * Description:
+ *Seekwave tech LTD
+ *Author:
+ *Date:
+ *Modify:
+ * ************************************************************************/
+static void dloader_work(struct work_struct *work)
+{
+	struct usb_port_struct *port = container_of(work, struct usb_port_struct, work);
+	int ret;
+	dloader_port = port->portno;
+	dloader_setup_usb_connection(port);
+
+	ret = check_modem_status_from_connect_message();
+	if (ret == HANG_REBOOT){
+		dloader_dump_read_usb(port);
+	}
+	if(usb_boot_data->dram_dl_size > 0){
+		firmware_data = usb_boot_data->dram_img_data;
+		ret = usb_download_image(port, usb_boot_data->dram_dl_addr, usb_boot_data->dram_dl_size);
+		if(ret <0)
+			skw_usb_warn(" dram download img fail !!!!\n");
+	}
+
+	if(!ret && usb_boot_data->iram_dl_size > 0){
+		firmware_data = usb_boot_data->iram_img_data;
+		ret = usb_download_image(port, usb_boot_data->iram_dl_addr, usb_boot_data->iram_dl_size);
+	}
+	modem_notify_event(DEVICE_BOOTUP_EVENT);
+	if (!ret)
+		ret = dloader_execute_image(port, START_ADDR);
+
+	if (ret < 0 && chip_en_gpio >= 0) {
+		modem_status = MODEM_DOWNLOAD_FAILED;
+		skw_usb_info("download failed! power off device\n");
+		gpio_set_value(chip_en_gpio, 0);
+		msleep(10);
+	}
+}
diff --git a/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/usb_boot.h b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/usb_boot.h
new file mode 100755
index 0000000..e50180e
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/misc/seekwaveplatform_lite/usb/usb_boot.h
@@ -0,0 +1,94 @@
+#define MAX_PACKET_SIZE		0x400		//4K
+#define PACKET_MAGIC		0x7e7e7e7e
+/******************************************************************
+ * **Description:download image,addrress and img size
+ * Seekwave tech LTD
+ * *	StartDownload 	0x0001
+ * *
+ * * MAGIC 4B	Length 4B MessageType 2B  	2B	CRC 0x7E7E7E7E
+ * **
+ * ******************************************************************/
+
+struct connect_ack {
+   unsigned int packet_size;
+   union packet_attr_tag
+   {
+		   struct connect_attr_map
+		   {
+				   unsigned int check_sum	   :1;
+				   unsigned int smp			 :1;
+				   unsigned int boot			:1;
+				   unsigned int res0			:1;
+				   unsigned int strapin		 :2;
+				   unsigned int usb_sdio_dis	:2;
+				   unsigned int res1			:24;
+		   }bitmap;
+		   unsigned int dwValue;
+   }flags;
+   unsigned int  chip_id[4];
+};
+
+typedef struct PACKET_BODY_tag{
+	unsigned int magic;	 //magic
+	unsigned int size;			  //length,length - 12
+	unsigned short type;			  //type,the type defferent cmd
+	unsigned short crc;		//checksum
+	unsigned char content[MAX_PACKET_SIZE];
+}PACKET_T;
+#define PACKET_HEADER_SIZE		(sizeof(struct PACKET_BODY_tag) - MAX_PACKET_SIZE)
+#ifndef MIN
+#define MIN(a,b)			(((a) < (b)) ? (a) : (b))
+#endif
+#ifndef MAX
+#define MAX(a,b)			(((a) > (b)) ? (a) : (b))
+#endif
+#define DEBUG
+#define NORMAL_BOOT 0
+#define HANG_REBOOT 1
+#define START_ADDR	0x100000
+#define MAX_IMAGE_SIZE	0x7a000
+
+static const char client_version[] = { 0x7E, 0x7E, 0x7E, 0x7E }; /* magic only to probe client */
+static const char client_version_ack[] = {0x7E, 0x7E, 0x7E, 0x7E,/* magic */
+	0x18, 0x00, 0x00, 0x00,/*size*/
+	0x81, 0x00, /*message type*/
+	0x00, 0x00, /* crc16 = 0 */
+	0x42, 0x6f, 0x6f, 0x74, 0x20, 0x4c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x20,
+	0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, 0x30, 0x00};
+
+static const char connect[] = {0x7E, 0x7E, 0x7E, 0x7E,/* magic */
+	0x18, 0x00, 0x00, 0x00,/*size*/
+	0x00, 0x00, /* message type, 0: connect command */
+	0x00, 0x00, /*crc16*/
+	0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static  char connect_ack[] = {0x7E, 0x7E, 0x7E, 0x7E,/* magic */
+	0x18, 0x00, 0x00, 0x00,/*size*/
+	0x80, 0x00, /*message type*/
+	0x00, 0x00, /* crc16 */
+	0x00, 0x10, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x53, 0x56, 0x36, 0x31,
+	0x36, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const char common_ack[] = { 0x7e, 0x7e, 0x7e, 0x7e, /* magic */
+	0x00, 0x00, 0x00, 0x00,/*size*/
+	0x80, 0x00, /*message type*/
+	0x00, 0x00};/* crc16 */
+
+static const char exec_ack[] = { 0x7e, 0x7e, 0x7e, 0x7e,
+	0x00, 0x00, 0x00, 0x00,/*size*/
+	0x80, 0x00,/*message type*/
+	0x00, 0x00};/* crc16 */
+
+static int dloader_send_command(const char *command,  int command_len, const char *ack, int ack_len);
+
+#define dloader_command_client_probe()				\
+    do {                         \
+	ret = dloader_send_command(client_version, sizeof(client_version),	\
+			client_version_ack, sizeof(client_version_ack)); \
+    }while(0)
+
+#define dloader_command_connect()				\
+    do {                         \
+	ret = dloader_send_command(connect, sizeof(connect), connect_ack, sizeof(connect_ack)); \
+    }while(0)
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/Kconfig b/longan/kernel/linux-4.9/drivers/net/wireless/Kconfig
index eae7ede..e31f1f3 100644
--- a/longan/kernel/linux-4.9/drivers/net/wireless/Kconfig
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/Kconfig
@@ -126,4 +126,5 @@
 	  This option adds support for ethernet connections to appear as if they
 	  are wifi connections through a special rtnetlink device.
 
+source "drivers/net/wireless/swt6621s_wifi/Kconfig"
 endif # WLAN
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/Makefile b/longan/kernel/linux-4.9/drivers/net/wireless/Makefile
index 68da845..a4a6442 100644
--- a/longan/kernel/linux-4.9/drivers/net/wireless/Makefile
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/Makefile
@@ -36,3 +36,4 @@
 obj-$(CONFIG_AIC_WLAN_SUPPORT) += aic8800/
 obj-$(CONFIG_RTL8723DU) += rtl8723du/
 obj-$(CONFIG_RTL8822BS) += rtl8822bs/
+obj-$(CONFIG_WLAN_VENDOR_SWT6621S) += swt6621s_wifi/
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/.gitignore b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/.gitignore
new file mode 100755
index 0000000..6702033
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/.gitignore
@@ -0,0 +1 @@
+version.h
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/Kconfig b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/Kconfig
new file mode 100755
index 0000000..46ccf93
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/Kconfig
@@ -0,0 +1,145 @@
+config WLAN_VENDOR_SWT6621S
+	tristate "SWT6621S chipset support"
+	depends on CFG80211
+	select CFG80211_WEXT_EXPORT
+	help
+	This module adds support for SeekWave 802.11ax wireless adapter chipset.
+
+	If you have a wireless card belonging to this class, say Y.
+
+	Note that the answer to this question doesn't directly affect the
+	kernel: saying N will just cause the configurator to skip all the
+	questions about these cards. If you say Y, you will be asked for
+	your specific card in the following questions.
+
+if WLAN_VENDOR_SWT6621S
+config SWT6621S_LEGACY_P2P
+	bool "SWT6621S Legacy P2P"
+	default n
+	help
+	Say Y here to create interface p2p0 default
+
+config SWT6621S_REPEATER_MODE
+        bool "SWT6621S Repeater Mode"
+        default n
+        help
+        Say Y here to support repeater mode
+
+config SWT6621S_EXTERNAL_REGDB
+	bool "Use External Regdomain Database"
+	default n
+	help
+	Say Y here disable regdomain database in driver
+
+config SWT6621S_TDLS
+	bool "SWT6621S TDLS"
+	default n
+	help
+	Say Y here to enable TDLS
+
+config SWT6621S_DFS_MASTER
+	bool "SWT6621S DFS Master"
+	depends on SWT6621S_EXTERNAL_REGDB
+	default n
+	help
+	Say Y here to support DFS master
+
+config SWT6621S_OFFCHAN_TX
+	bool "Seekwave OFFCHAN TX"
+	default n
+	help
+	Say Y here to support off-channel TX
+
+config SWT6621S_6GHZ
+	bool "Seekwave 6GHz"
+	default n
+	help
+	Say Y here to support 6GHz
+
+config SWT6621S_USB3_WORKAROUND
+	bool "SWT6621S USB 3.0 workaround"
+	default n
+	help
+	Say Y here to enable workaround for USB 3.0
+
+config SWT6621S_RX_REORDER_TIMEOUT
+	int "SWT6621S reorder timeout (50-500)"
+	range 50 500
+	default 100
+
+config SWT6621S_SKB_RECYCLE
+	bool "SWT6621S skb recycle"
+	default n
+	help
+	Say Y here to support skb recycle
+
+config SWT6621S_DEFAULT_COUNTRY
+	string "SWT6621S Default Country Code"
+	default ""
+	help
+	Set Default Country Code
+
+config SWT6621S_CHIP_ID
+	string "SWT6621S Chip ID"
+	default ""
+	help
+	Set Chip ID
+
+config SWT6621S_PROJECT_NAME
+	string "SWT6621S Default Project Name"
+	default "SEEKWAVE"
+	help
+	Set Default Project Name
+
+config SWT6621S_NOT_WAKEUP_HOST
+	bool "SWT6621S Wakeup Host Enable"
+	default n
+	help
+	Say Y here to support wakeup host
+
+config SWT6621S_WDS
+	bool "SWT6621S Wds Support"
+	default n
+	help
+	Say Y here to support WDS
+
+config SWT6621S_PRESUSPEND_SUPPORT
+	bool "SWT6621S Presuspend Support"
+	default n
+	help
+	Say Y here to support presuspend
+
+choice
+	prompt "SWT6621S Log Level"
+	default SWT6621S_LOG_DEBUG
+	help
+	SEEKWAVE Log Level
+
+config SWT6621S_LOG_ERROR
+	bool "ERROR"
+	help
+	This feature set Error log level
+
+config SWT6621S_LOG_WARN
+	bool "WARN"
+	help
+	This feature set Warnning log level
+
+config SWT6621S_LOG_INFO
+	bool "INFO"
+	help
+	This feature set INFO log level
+
+config SWT6621S_LOG_DEBUG
+	bool "DEBUG"
+	help
+	This feature set DEBUG log level
+
+config SWT6621S_LOG_DETAIL
+	bool "DETAIL"
+	help
+	This feature set DETAIL log level
+
+endchoice
+
+endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/Makefile b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/Makefile
new file mode 100755
index 0000000..98b10af
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/Makefile
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_WLAN_VENDOR_SWT6621S) += swt6621s_wifi.o
+
+swt6621s_wifi-objs += skw_core.o
+swt6621s_wifi-objs += skw_iface.o
+swt6621s_wifi-objs += skw_cfg80211.o
+swt6621s_wifi-objs += skw_msg.o
+swt6621s_wifi-objs += skw_rx.o
+swt6621s_wifi-objs += skw_tx.o
+swt6621s_wifi-objs += skw_regd.o
+swt6621s_wifi-objs += skw_mlme.o
+swt6621s_wifi-objs += skw_timer.o
+swt6621s_wifi-objs += skw_log.o
+swt6621s_wifi-objs += skw_iw.o
+swt6621s_wifi-objs += skw_work.o
+swt6621s_wifi-objs += skw_mbssid.o
+swt6621s_wifi-objs += skw_dentry.o
+swt6621s_wifi-objs += skw_util.o
+swt6621s_wifi-objs += skw_config.o
+swt6621s_wifi-objs += skw_recovery.o
+swt6621s_wifi-objs += skw_vendor.o
+swt6621s_wifi-objs += trace.o
+swt6621s_wifi-objs += skw_db.o
+
+# swt6621s_wifi-$(CONFIG_SWT6621S_CALIB_DPD) += skw_calib.o
+# swt6621s_wifi-$(CONFIG_SWT6621S_EDMA) += skw_edma.o
+swt6621s_wifi-$(CONFIG_SWT6621S_DFS_MASTER) += skw_dfs.o
+swt6621s_wifi-$(CONFIG_SWT6621S_TDLS) += skw_tdls.o
+
+ccflags-y += -DCONFIG_SWT6621S_STA_SME_EXT
+ccflags-y += -DCONFIG_SWT6621S_SAP_SME_EXT
+ccflags-y += -DCONFIG_SWT6621S_SCAN_RANDOM_MAC
+ccflags-y += -DCONFIG_SWT6621S_TX_WORKQUEUE
+ccflags-y += -DCONFIG_SWT6621S_HIGH_PRIORITY
+ccflags-y += -DCONFIG_SWT6621S_CALIB_APPEND_BUS_ID
+ccflags-y += -DCONFIG_SWT6621S_CALIB_APPEND_MODULE_ID
+
+ifneq ($(CONFIG_SWT6621S_EXTERNAL_REGDB),y)
+ccflags-y += -DCONFIG_SWT6621S_REGD_SELF_MANAGED
+endif
+
+ifeq ($(CONFIG_ARCH_ROCKCHIP),y)
+ccflags-y += -DCONFIG_PLATFORM_ROCKCHIP
+endif
+
+ifneq ($(filter y,$(CONFIG_ANDROID_BINDER_IPC) $(CONFIG_SKW_ANDROID)),)
+ccflags-y += -D__SKW_ANDROID__
+endif
+
+ifneq ($(skw_extra_flags),)
+ccflags-y += $(skw_extra_flags)
+endif
+
+ifneq ($(skw_extra_symbols),)
+KBUILD_EXTRA_SYMBOLS += $(skw_extra_symbols)
+endif
+
+ccflags-y += -I$(srctree)/include/linux/platform_data/
+
+CFLAGS_trace.o := -I$(src)
+$(obj)/skw_core.o : $(obj)/version.h
+
+skw_abs_path := $(addprefix $(abspath $(srctree))/,$(filter-out /%,$(src)))$(filter /%,$(src))
+$(obj)/version.h: $(skw_abs_path)/genver.pl
+	@$(PERL) -s $(skw_abs_path)/genver.pl $@
+
+clean-files := version.h
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/genver.pl b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/genver.pl
new file mode 100755
index 0000000..b116cf6
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/genver.pl
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0
+#!/usr/bin/perl -s
+
+use POSIX qw(strftime);
+use File::Basename;
+
+$skw_branch = "swt6621s_dev";
+$skw_version = "2.0.250611.022f652";
+
+$output = shift;
+open (OUTPUT, ">$output") || die "$0 : can't open $output for writing\n";
+
+print OUTPUT "#ifndef __SKW_VERSION_H__\n";
+print OUTPUT "#define __SKW_VERSION_H__\n";
+
+print OUTPUT "\n";
+
+print OUTPUT "#define SKW_BRANCH     \"$skw_branch\"\n";
+print OUTPUT "#define SKW_VERSION    \"$skw_version\"\n";
+
+print OUTPUT "\n";
+
+print OUTPUT "#endif";
+
+close (OUTPUT);
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_calib.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_calib.c
new file mode 100755
index 0000000..9630ffa
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_calib.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/crc32c.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/firmware.h>
+
+#include "skw_core.h"
+#include "skw_msg.h"
+#include "skw_log.h"
+#include "skw_cfg80211.h"
+#include "skw_util.h"
+#include "skw_calib.h"
+
+static int skw_dpd_chn_to_index(u16 chn, u8 bw)
+{
+	int index = 0;
+
+	switch (chn) {
+	case 1 ... 5:
+		/* these ch cp use ch3 instead */
+		if (bw == 0)
+			index = 2;
+		else
+			index = 67;
+		break;
+
+	case 6 ... 9:
+		/* these ch cp use ch7 instead */
+		if (bw == 0)
+			index = 6;
+		else
+			index = 68;
+		break;
+
+	case 10 ... 14:
+		/* these ch cp use ch11 instead */
+		if (bw == 0)
+			index = 10;
+		else
+			index = 69;
+		break;
+
+	case 36 ... 48:
+		index = ((chn - 36) >> 1) + 14;
+		break;
+
+	case 52 ... 64:
+		index = ((chn - 52) >> 1) + 21;
+		break;
+
+	case 100 ... 144:
+		index = ((chn - 100) >> 1) + 28;
+		break;
+
+	case 149 ... 177:
+		index = ((chn - 149) >> 1) + 51;
+		break;
+
+	default:
+		index = -1;
+		break;
+	}
+
+	return index;
+}
+
+int skw_dpd_set_coeff_params(struct wiphy *wiphy,
+	struct net_device *ndev, u8 chn, u8 center_chan,
+	u8 center_chan2, u8 bandwidth)
+{
+
+	int ret = 0;
+	int index;
+	struct skw_core *skw = NULL;
+	struct skw_rf_rxdpd_data *para;
+	struct skw_rf_rxdpd_param param;
+
+	skw_dbg("chan: %d, center_chan: %d, center_chan2: %d, band width: %d\n",
+		chn, center_chan, center_chan2, bandwidth);
+
+	skw = wiphy_priv(wiphy);
+	if (!skw) {
+		skw_err("skw->dpd skw null");
+		return -EINVAL;
+	}
+
+	para = skw->dpd.resource;
+	if (!para) {
+		skw_err("skw->dpd.resource null");
+		return -EINVAL;
+	}
+
+	param.size = 2 * sizeof(struct skw_rf_rxdpd_train);
+
+	index = skw_dpd_chn_to_index(center_chan, bandwidth);
+	skw_dbg("ch_idx:%d\n", index);
+	if (index < 0 || index > DPD_CHAN_CNT - 1) {
+		skw_err("chn %d not found\n", center_chan);
+		return -ERANGE;
+	}
+
+	memcpy(&param.train[0], &para->data[index][0],
+		sizeof(struct skw_rf_rxdpd_train) * 2);
+
+	if (center_chan <= 14) {
+		param.train[0].chan = center_chan;
+		param.train[1].chan = center_chan;
+	}
+
+	skw_hex_dump("dpdresultcmd", &param,
+			sizeof(struct skw_rf_rxdpd_param), false);
+
+	ret = skw_send_msg(wiphy, ndev, SKW_CMD_SET_DPD_RESULT,
+		&param, sizeof(param), NULL, 0);
+	if (ret)
+		skw_err("Send dpd result failed, ret: %d", ret);
+
+	return ret;
+}
+
+int skw_dpd_result_handler(struct skw_core *skw, void *buf, int len)
+{
+	int index;
+	struct skw_rf_rxdpd_train *res = buf;
+	struct skw_rf_rxdpd_data *para;
+
+	if (res->done)
+		skw_dbg("ch:%d rf:%d bw:%d done:%d\n", res->chan,
+			res->rf_idx, res->cbw, res->done);
+
+	if (res->rf_idx > DPD_RF_CNT - 1) {
+		skw_err("invalid rf_idx: %d\n", res->rf_idx);
+
+		skw_hw_assert(skw, false);
+		return -ERANGE;
+	}
+
+	index = skw_dpd_chn_to_index(res->chan, res->cbw);
+	if (index < 0 || index > DPD_CHAN_CNT - 1) {
+		skw_err("chn %d not found\n", res->chan);
+		return -ERANGE;
+	}
+
+	para = skw->dpd.resource;
+	if (!para) {
+		skw_err("skw->dpd.resource null");
+		return -EINVAL;
+	}
+
+	if (para->data[index][res->rf_idx].chan ||
+		para->data[index][res->rf_idx].cbw) {
+		skw_hex_dump("havedata", &para->data[index][res->rf_idx],
+				sizeof(struct skw_rf_rxdpd_train), true);
+		skw_err("ch:%d rf:%d cbw:%d index:%d had data\n",
+			res->chan, res->rf_idx, res->cbw, index);
+		skw_hw_assert(skw, false);
+		return -EBUSY;
+	}
+
+	memcpy(&para->data[index][res->rf_idx], res,
+		sizeof(struct skw_rf_rxdpd_train));
+
+	skw_hex_dump("data", &para->data[index][res->rf_idx],
+			sizeof(struct skw_rf_rxdpd_train), false);
+	return 0;
+}
+
+int skw_dpd_init(struct skw_dpd *dpd)
+{
+	dpd->size = sizeof(struct skw_rf_rxdpd_data);
+
+	dpd->resource = SKW_ZALLOC(dpd->size, GFP_KERNEL);
+	if (!dpd->resource) {
+		skw_err("malloc dpd resource failed, size: %d\n",
+			dpd->size);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+void skw_dpd_zero(struct skw_dpd *dpd)
+{
+	memset(dpd->resource, 0, dpd->size);
+}
+
+void skw_dpd_deinit(struct skw_dpd *dpd)
+{
+	SKW_KFREE(dpd->resource);
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_calib.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_calib.h
new file mode 100755
index 0000000..afed862
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_calib.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_CALIB_H__
+#define __SKW_CALIB_H__
+
+#include <linux/ieee80211.h>
+#include <net/cfg80211.h>
+
+#define DPD_CHAN_CNT               70
+#define DPD_RF_CNT                 2
+
+struct skw_dpd {
+	void *resource;
+	int size;
+};
+
+#ifdef CONFIG_SWT6621S_CALIB_DPD
+struct skw_rf_rxdpd_coeff {
+	u32 low_dpd_lut_coeff[4][12];
+	u32 high_dpd_lut_coeff[4][16];
+} __packed;
+
+struct skw_rf_rxdpd_train {
+	u16 chan;
+	u8 done:1;
+	u8 rf_idx:3;
+	u8 cbw:4;
+	u8 dpd_rf_gain_max;
+	u8 delta_gain_q2[8];
+	u32 dpd_tpc_info[8];
+	struct skw_rf_rxdpd_coeff dpd_coeff;
+} __packed;
+
+struct skw_rf_rxdpd_data {
+	struct skw_rf_rxdpd_train data[DPD_CHAN_CNT][DPD_RF_CNT];
+} __packed;
+
+struct skw_rf_rxdpd_param {
+	u32 size;
+	struct skw_rf_rxdpd_train train[2];
+} __packed;
+
+int skw_dpd_set_coeff_params(struct wiphy *wiphy, struct net_device *ndev,
+			     u8 chn, u8 center_chan,
+			     u8 center_chan2, u8 bandwidth);
+
+int skw_dpd_result_handler(struct skw_core *skw, void *buf, int len);
+int skw_dpd_init(struct skw_dpd *dpd);
+void skw_dpd_deinit(struct skw_dpd *dpd);
+void skw_dpd_zero(struct skw_dpd *dpd);
+#else
+static inline int skw_dpd_set_coeff_params(struct wiphy *wiphy,
+			struct net_device *ndev, u8 chn, u8 center_chan,
+			u8 center_two_chan2, u8 bandwidth)
+{
+	return 0;
+}
+
+static inline int skw_dpd_result_handler(struct skw_core *skw, void *buf, int len)
+{
+	return 0;
+}
+
+static inline int skw_dpd_init(struct skw_dpd *dpd)
+{
+	return 0;
+}
+
+static inline void skw_dpd_deinit(struct skw_dpd *dpd)
+{
+}
+
+static inline void skw_dpd_zero(struct skw_dpd *dpd)
+{
+}
+
+#endif
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_cfg80211.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_cfg80211.c
new file mode 100755
index 0000000..2146a27
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_cfg80211.c
@@ -0,0 +1,6186 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/ieee80211.h>
+#include <net/cfg80211.h>
+#include <linux/inetdevice.h>
+#include <net/addrconf.h>
+#include <linux/if_tunnel.h>
+
+#include "skw_core.h"
+#include "skw_iface.h"
+#include "skw_msg.h"
+#include "skw_cfg80211.h"
+#include "skw_regd.h"
+#include "skw_mlme.h"
+#include "skw_timer.h"
+#include "skw_work.h"
+#include "skw_tdls.h"
+#include "skw_calib.h"
+#include "skw_recovery.h"
+#include "skw_dfs.h"
+
+#define SKW_BIT_ULL(nr)        (1ULL << (nr))
+
+int to_skw_bw(enum nl80211_chan_width bw)
+{
+	switch (bw) {
+	case NL80211_CHAN_WIDTH_20:
+	case NL80211_CHAN_WIDTH_20_NOHT:
+		return SKW_CHAN_WIDTH_20;
+
+	case NL80211_CHAN_WIDTH_40:
+		return SKW_CHAN_WIDTH_40;
+
+	case NL80211_CHAN_WIDTH_80:
+		return SKW_CHAN_WIDTH_80;
+
+	case NL80211_CHAN_WIDTH_80P80:
+		return SKW_CHAN_WIDTH_80P80;
+
+	case NL80211_CHAN_WIDTH_160:
+		return SKW_CHAN_WIDTH_160;
+
+	default:
+		break;
+	}
+
+	return SKW_CHAN_WIDTH_MAX;
+}
+
+static int to_skw_gtk(u8 key_index)
+{
+	switch (key_index) {
+	case 0 ... 3:
+		return SKW_KEY_TYPE_GTK;
+	case 4 ... 5:
+		return SKW_KEY_TYPE_IGTK;
+	case 6:
+		return SKW_KEY_TYPE_BIGTK;
+	default:
+		break;
+	}
+
+	return SKW_KEY_TYPE_GTK;
+}
+
+static int to_skw_cipher_type(u32 cipher)
+{
+#define SKW_CASE_CIPHER_TYPE(c)                        \
+	{                                              \
+		case SKW_CIPHER_SUITE_##c:             \
+			return SKW_CIPHER_TYPE_##c;    \
+	}
+
+	switch (cipher) {
+	SKW_CASE_CIPHER_TYPE(WEP40);
+	SKW_CASE_CIPHER_TYPE(WEP104);
+	SKW_CASE_CIPHER_TYPE(SMS4);
+	SKW_CASE_CIPHER_TYPE(TKIP);
+	SKW_CASE_CIPHER_TYPE(CCMP);
+	SKW_CASE_CIPHER_TYPE(CCMP_256);
+	SKW_CASE_CIPHER_TYPE(AES_CMAC);
+	SKW_CASE_CIPHER_TYPE(BIP_CMAC_256);
+	SKW_CASE_CIPHER_TYPE(BIP_GMAC_128);
+	SKW_CASE_CIPHER_TYPE(BIP_GMAC_256);
+	SKW_CASE_CIPHER_TYPE(GCMP);
+	SKW_CASE_CIPHER_TYPE(GCMP_256);
+
+	default:
+		break;
+	}
+#undef SKW_CASE_CIPHER_TYPE
+
+	return SKW_CIPHER_TYPE_INVALID;
+}
+
+static const struct ieee80211_iface_limit skw_iface_limits[] = {
+	{
+		.max = 1,
+		.types = BIT(NL80211_IFTYPE_STATION),
+	},
+	{
+		.max = 1,
+		.types = BIT(NL80211_IFTYPE_AP),
+	},
+	{
+		.max = 1,
+		.types = BIT(NL80211_IFTYPE_P2P_GO) |
+			 BIT(NL80211_IFTYPE_P2P_CLIENT),
+	},
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 15, 10)
+	{
+		.max = 1,
+		.types = BIT(NL80211_IFTYPE_P2P_DEVICE),
+	},
+#endif
+};
+
+static const struct ieee80211_iface_limit skw_iface_limits_change[] = {
+	{
+		.max = 3,
+		.types = BIT(NL80211_IFTYPE_STATION),
+	},
+	{
+		.max = 1,
+		.types = BIT(NL80211_IFTYPE_AP)     |
+			 BIT(NL80211_IFTYPE_P2P_GO) |
+			 BIT(NL80211_IFTYPE_P2P_CLIENT),
+	},
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 15, 10)
+	{
+		.max = 1,
+		.types = BIT(NL80211_IFTYPE_P2P_DEVICE),
+	},
+#endif
+};
+
+static const struct ieee80211_iface_limit skw_iface_limits_aps[] = {
+	{
+		.max = 1,
+		.types = BIT(NL80211_IFTYPE_STATION) |
+			 BIT(NL80211_IFTYPE_P2P_CLIENT),
+	},
+	{
+		.max = 2,
+		.types = BIT(NL80211_IFTYPE_AP),
+	},
+};
+
+static const struct ieee80211_iface_limit skw_iface_limits_monitor[] = {
+	{
+		.max = 2,
+		.types = BIT(NL80211_IFTYPE_MONITOR),
+	},
+};
+
+#ifdef CONFIG_SWT6621S_DFS_MASTER
+static const struct ieee80211_iface_limit skw_iface_limits_dfs[] = {
+	{
+		.max = 1,
+		.types = BIT(NL80211_IFTYPE_STATION),
+	},
+	{
+		.max = 1,
+		.types = BIT(NL80211_IFTYPE_AP),
+	},
+};
+
+static const struct ieee80211_iface_limit skw_iface_limits_dfs_change[] = {
+	{
+		.max = 2,
+		.types = BIT(NL80211_IFTYPE_STATION),
+	},
+};
+#endif
+
+static const struct ieee80211_iface_combination skw_iface_combos[] = {
+	{
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 15, 10)
+		.max_interfaces = 4,
+#else
+		.max_interfaces = 3,
+#endif
+		.num_different_channels = 2,
+		.limits = skw_iface_limits,
+		.n_limits = ARRAY_SIZE(skw_iface_limits),
+	},
+	{
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 15, 10)
+		.max_interfaces = 4,
+#else
+		.max_interfaces = 3,
+#endif
+		.num_different_channels = 2,
+		.limits = skw_iface_limits_change,
+		.n_limits = ARRAY_SIZE(skw_iface_limits_change),
+	},
+	{
+		.max_interfaces = 3,
+		.num_different_channels = 1,
+		.limits = skw_iface_limits_aps,
+		.n_limits = ARRAY_SIZE(skw_iface_limits_aps),
+	},
+	{
+		.max_interfaces = 2,
+		.num_different_channels = 1,
+		.limits = skw_iface_limits_monitor,
+		.n_limits = ARRAY_SIZE(skw_iface_limits_monitor),
+	},
+#ifdef CONFIG_SWT6621S_DFS_MASTER
+	{
+		.max_interfaces = 2,
+		.num_different_channels = 1,
+		.limits = skw_iface_limits_dfs,
+		.n_limits = ARRAY_SIZE(skw_iface_limits_dfs),
+		.radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) |
+				       BIT(NL80211_CHAN_WIDTH_20) |
+				       BIT(NL80211_CHAN_WIDTH_40) |
+				       BIT(NL80211_CHAN_WIDTH_80),
+	},
+	{
+		.max_interfaces = 2,
+		.num_different_channels = 1,
+		.limits = skw_iface_limits_dfs_change,
+		.n_limits = ARRAY_SIZE(skw_iface_limits_dfs_change),
+		.radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) |
+				       BIT(NL80211_CHAN_WIDTH_20) |
+				       BIT(NL80211_CHAN_WIDTH_40) |
+				       BIT(NL80211_CHAN_WIDTH_80),
+	},
+#endif
+};
+
+static const struct
+ieee80211_txrx_stypes skw_mgmt_stypes[NUM_NL80211_IFTYPES] = {
+	[NL80211_IFTYPE_ADHOC] = {
+		.tx = 0xffff,
+		.rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
+			BIT(IEEE80211_STYPE_AUTH >> 4) |
+			BIT(IEEE80211_STYPE_DEAUTH >> 4) |
+			BIT(IEEE80211_STYPE_PROBE_REQ >> 4),
+	},
+	[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_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),
+	},
+};
+
+#define SKW_CHAN2G(_channel, _freq, _flags) {		\
+	.band			= NL80211_BAND_2GHZ,	\
+	.center_freq		= (_freq),		\
+	.hw_value		= (_channel),		\
+	.flags			= (_flags),		\
+	.max_antenna_gain	= 0,			\
+	.max_power		= 30,			\
+}
+
+static struct ieee80211_channel skw_2ghz_chan[] = {
+	SKW_CHAN2G(1, 2412, 0),
+	SKW_CHAN2G(2, 2417, 0),
+	SKW_CHAN2G(3, 2422, 0),
+	SKW_CHAN2G(4, 2427, 0),
+	SKW_CHAN2G(5, 2432, 0),
+	SKW_CHAN2G(6, 2437, 0),
+	SKW_CHAN2G(7, 2442, 0),
+	SKW_CHAN2G(8, 2447, 0),
+	SKW_CHAN2G(9, 2452, 0),
+	SKW_CHAN2G(10, 2457, 0),
+	SKW_CHAN2G(11, 2462, 0),
+	SKW_CHAN2G(12, 2467, 0),
+	SKW_CHAN2G(13, 2472, 0),
+	SKW_CHAN2G(14, 2484, 0),
+};
+#undef SKW_CHAN2G
+
+#define SKW_CHAN5G(_channel, _flags) {			    \
+	.band			= NL80211_BAND_5GHZ,	    \
+	.center_freq		= 5000 + (5 * (_channel)),  \
+	.hw_value		= (_channel),		    \
+	.flags			= (_flags),		    \
+	.max_antenna_gain	= 0,			    \
+	.max_power		= 30,			    \
+}
+
+static struct ieee80211_channel skw_5ghz_chan[] = {
+	SKW_CHAN5G(36, 0),
+	SKW_CHAN5G(40, 0),
+	SKW_CHAN5G(44, 0),
+	SKW_CHAN5G(48, 0),
+	SKW_CHAN5G(52, 0),
+	SKW_CHAN5G(56, 0),
+	SKW_CHAN5G(60, 0),
+	SKW_CHAN5G(64, 0),
+	SKW_CHAN5G(100, 0),
+	SKW_CHAN5G(104, 0),
+	SKW_CHAN5G(108, 0),
+	SKW_CHAN5G(112, 0),
+	SKW_CHAN5G(116, 0),
+	SKW_CHAN5G(120, 0),
+	SKW_CHAN5G(124, 0),
+	SKW_CHAN5G(128, 0),
+	SKW_CHAN5G(132, 0),
+	SKW_CHAN5G(136, 0),
+	SKW_CHAN5G(140, 0),
+	SKW_CHAN5G(144, 0),
+	SKW_CHAN5G(149, 0),
+	SKW_CHAN5G(153, 0),
+	SKW_CHAN5G(157, 0),
+	SKW_CHAN5G(161, 0),
+	SKW_CHAN5G(165, 0),
+	SKW_CHAN5G(169, 0),
+	SKW_CHAN5G(173, 0),
+	SKW_CHAN5G(177, 0),
+	SKW_CHAN5G(181, 0),
+};
+#undef SKW_CHAN5G
+
+#ifdef CONFIG_SWT6621S_6GHZ
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)
+#define SKW_CHAN6G(_channel, _flags) {                  \
+	.band = NL80211_BAND_6GHZ,                      \
+	.center_freq = 5950 + (5 * (_channel)),         \
+	.hw_value = (_channel),                         \
+	.flags = (_flags),                              \
+	.max_antenna_gain = 0,                          \
+	.max_power = 30,                                \
+}
+
+static struct ieee80211_channel skw_6ghz_chan[] = {
+	SKW_CHAN6G(1, 0),
+	SKW_CHAN6G(2, 0),
+	SKW_CHAN6G(5, 0),
+	SKW_CHAN6G(9, 0),
+	SKW_CHAN6G(13, 0),
+	SKW_CHAN6G(17, 0),
+	SKW_CHAN6G(21, 0),
+	SKW_CHAN6G(25, 0),
+	SKW_CHAN6G(29, 0),
+	SKW_CHAN6G(33, 0),
+	SKW_CHAN6G(37, 0),
+	SKW_CHAN6G(41, 0),
+	SKW_CHAN6G(45, 0),
+	SKW_CHAN6G(49, 0),
+	SKW_CHAN6G(53, 0),
+	SKW_CHAN6G(57, 0),
+	SKW_CHAN6G(61, 0),
+	SKW_CHAN6G(65, 0),
+	SKW_CHAN6G(69, 0),
+	SKW_CHAN6G(73, 0),
+	SKW_CHAN6G(77, 0),
+	SKW_CHAN6G(81, 0),
+	SKW_CHAN6G(85, 0),
+	SKW_CHAN6G(89, 0),
+	SKW_CHAN6G(93, 0),
+	SKW_CHAN6G(97, 0),
+	SKW_CHAN6G(101, 0),
+	SKW_CHAN6G(105, 0),
+	SKW_CHAN6G(109, 0),
+	SKW_CHAN6G(113, 0),
+	SKW_CHAN6G(117, 0),
+	SKW_CHAN6G(121, 0),
+	SKW_CHAN6G(125, 0),
+	SKW_CHAN6G(129, 0),
+	SKW_CHAN6G(133, 0),
+	SKW_CHAN6G(137, 0),
+	SKW_CHAN6G(141, 0),
+	SKW_CHAN6G(145, 0),
+	SKW_CHAN6G(149, 0),
+	SKW_CHAN6G(153, 0),
+	SKW_CHAN6G(157, 0),
+	SKW_CHAN6G(161, 0),
+	SKW_CHAN6G(165, 0),
+	SKW_CHAN6G(169, 0),
+	SKW_CHAN6G(173, 0),
+	SKW_CHAN6G(177, 0),
+	SKW_CHAN6G(181, 0),
+	SKW_CHAN6G(185, 0),
+	SKW_CHAN6G(189, 0),
+	SKW_CHAN6G(193, 0),
+	SKW_CHAN6G(197, 0),
+	SKW_CHAN6G(201, 0),
+	SKW_CHAN6G(205, 0),
+	SKW_CHAN6G(209, 0),
+	SKW_CHAN6G(213, 0),
+	SKW_CHAN6G(217, 0),
+	SKW_CHAN6G(221, 0),
+	SKW_CHAN6G(225, 0),
+	SKW_CHAN6G(229, 0),
+	SKW_CHAN6G(233, 0),
+};
+#undef SKW_CHAN6G
+#endif
+#endif
+
+#define SKW_RATETAB_ENT(_rate, _rateid, _flags)     \
+{                                                   \
+	.bitrate        = (_rate),                  \
+	.hw_value       = (_rateid),                \
+	.flags          = (_flags),                 \
+}
+
+static struct ieee80211_rate skw_rates[] = {
+	SKW_RATETAB_ENT(10, 0x1, 0),
+	SKW_RATETAB_ENT(20, 0x2, 0),
+	SKW_RATETAB_ENT(55, 0x5, 0),
+	SKW_RATETAB_ENT(110, 0xb, 0),
+	SKW_RATETAB_ENT(60, 0x6, 0),
+	SKW_RATETAB_ENT(90, 0x9, 0),
+	SKW_RATETAB_ENT(120, 0xc, 0),
+	SKW_RATETAB_ENT(180, 0x12, 0),
+	SKW_RATETAB_ENT(240, 0x18, 0),
+	SKW_RATETAB_ENT(360, 0x24, 0),
+	SKW_RATETAB_ENT(480, 0x30, 0),
+	SKW_RATETAB_ENT(540, 0x36, 0),
+};
+
+#undef SKW_RATETAB_ENT
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)
+static const struct ieee80211_sband_iftype_data skw_he_capa_2ghz = {
+	.types_mask = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP),
+	.he_cap = {
+		.has_he = true,
+		.he_cap_elem = {
+			.mac_cap_info[0] = SKW_HE_MAC_CAP0_HTC_HE,
+			.mac_cap_info[1] = SKW_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US |
+				SKW_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_8,
+			.mac_cap_info[2] = SKW_HE_MAC_CAP2_BSR |
+				SKW_HE_MAC_CAP2_MU_CASCADING |
+				SKW_HE_MAC_CAP2_ACK_EN,
+			.mac_cap_info[3] = SKW_HE_MAC_CAP3_OMI_CONTROL |
+				SKW_HE_MAC_CAP3_GRP_ADDR_MULTI_STA_BA_DL_MU |
+				SKW_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_VHT_2,
+			.mac_cap_info[4] = SKW_HE_MAC_CAP4_AMDSU_IN_AMPDU,
+			.phy_cap_info[0] = SKW_HE_PHY_CAP0_DUAL_BAND |
+				SKW_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G,
+			.phy_cap_info[1] = SKW_HE_PHY_CAP1_DEVICE_CLASS_A |
+				SKW_HE_PHY_CAP1_PREAMBLE_PUNC_RX_MASK |
+				SKW_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD |
+				SKW_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS,
+			.phy_cap_info[2] = SKW_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO |
+				SKW_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US |
+				SKW_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ |
+				SKW_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ |
+				SKW_HE_PHY_CAP2_UL_MU_PARTIAL_MU_MIMO,
+		},
+		.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),
+		},
+	},
+};
+
+static const struct ieee80211_sband_iftype_data skw_he_capa_5ghz = {
+	.types_mask = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP),
+	.he_cap = {
+		.has_he = true,
+		.he_cap_elem = {
+			.mac_cap_info[0] = SKW_HE_MAC_CAP0_HTC_HE,
+			.mac_cap_info[1] = SKW_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US |
+				SKW_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_8,
+			.mac_cap_info[2] = SKW_HE_MAC_CAP2_BSR |
+				SKW_HE_MAC_CAP2_MU_CASCADING |
+				SKW_HE_MAC_CAP2_ACK_EN,
+			.mac_cap_info[3] = SKW_HE_MAC_CAP3_OMI_CONTROL |
+				SKW_HE_MAC_CAP3_GRP_ADDR_MULTI_STA_BA_DL_MU |
+				SKW_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_VHT_2,
+			.mac_cap_info[4] = SKW_HE_MAC_CAP4_AMDSU_IN_AMPDU,
+
+			.phy_cap_info[0] = SKW_HE_PHY_CAP0_DUAL_BAND |
+				SKW_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G,
+			.phy_cap_info[1] = SKW_HE_PHY_CAP1_DEVICE_CLASS_A |
+				SKW_HE_PHY_CAP1_PREAMBLE_PUNC_RX_MASK |
+				SKW_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD |
+				SKW_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS,
+			.phy_cap_info[2] = SKW_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US |
+				SKW_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ |
+				SKW_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ |
+				SKW_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO |
+				SKW_HE_PHY_CAP2_UL_MU_PARTIAL_MU_MIMO,
+		},
+		.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),
+		},
+	},
+};
+
+#endif
+
+#define skw_a_rates       (skw_rates + 4)
+#define skw_a_rates_size  8
+#define skw_g_rates       (skw_rates + 0)
+#define skw_g_rates_size  12
+
+static struct ieee80211_supported_band skw_band_2ghz = {
+	.channels = skw_2ghz_chan,
+	.n_channels = ARRAY_SIZE(skw_2ghz_chan),
+	.bitrates = skw_g_rates,
+	.n_bitrates = skw_g_rates_size,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)
+	.n_iftype_data = 1,
+	.iftype_data = &skw_he_capa_2ghz,
+#endif
+};
+
+static struct ieee80211_supported_band skw_band_5ghz = {
+	.channels = skw_5ghz_chan,
+	.n_channels = ARRAY_SIZE(skw_5ghz_chan),
+	.bitrates = skw_a_rates,
+	.n_bitrates = skw_a_rates_size,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)
+	.n_iftype_data = 1,
+	.iftype_data = &skw_he_capa_5ghz,
+#endif
+};
+
+#ifdef CONFIG_SWT6621S_6GHZ
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)
+static struct ieee80211_supported_band skw_band_6ghz = {
+	.channels = skw_6ghz_chan,
+	.n_channels = ARRAY_SIZE(skw_6ghz_chan),
+	.bitrates = skw_a_rates,
+	.n_bitrates = skw_a_rates_size,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)
+	.n_iftype_data = 1,
+	.iftype_data = &skw_he_capa_5ghz, //TBD:check it
+#endif
+};
+#endif
+#endif
+
+static const u32 skw_cipher_suites[] = {
+	/* keep WEP first, it may be removed below */
+	SKW_CIPHER_SUITE_WEP40,
+	SKW_CIPHER_SUITE_TKIP,
+	SKW_CIPHER_SUITE_CCMP,
+	SKW_CIPHER_SUITE_WEP104,
+	SKW_CIPHER_SUITE_AES_CMAC,
+	SKW_CIPHER_SUITE_GCMP,
+
+	SKW_CIPHER_SUITE_CCMP_256,
+	SKW_CIPHER_SUITE_GCMP_256,
+	SKW_CIPHER_SUITE_BIP_CMAC_256,
+	SKW_CIPHER_SUITE_BIP_GMAC_128,
+	SKW_CIPHER_SUITE_BIP_GMAC_256,
+
+	SKW_CIPHER_SUITE_SMS4,
+};
+
+#ifdef CONFIG_SWT6621S_WDS
+static int skw_set_wds_mib(struct wiphy *wiphy, bool enable)
+{
+	return skw_util_set_mib_enable(wiphy, 0,
+			SKW_MIB_SET_WDS_ENABLE, enable);
+}
+#endif
+
+static int skw_set_once_noa_mib(struct wiphy *wiphy, u8 en, u8 pre_time, u8 abs_time)
+{
+	int ret;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+	struct skw_once_noa_enable_mib once_noa;
+
+	skw_dbg("once_noa: %d %d %d\n", en, pre_time, abs_time);
+
+	once_noa.en = en;
+	once_noa.go_pre_time = pre_time;
+	once_noa.go_abs_time = abs_time;
+
+	ret = skw_tlv_alloc(&conf, 128, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_ONCE_NOA_ENABLE, &once_noa, sizeof(once_noa))) {
+		skw_err("set once noa enable failed\n");
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	*plen = conf.total_len;
+
+	ret = skw_msg_xmit(wiphy, 0, SKW_CMD_SET_MIB, conf.buff,
+			   conf.total_len, NULL, 0);
+	if (ret)
+		skw_warn("failed, ret: %d\n", ret);
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+static int skw_set_ratio_level_mib(struct wiphy *wiphy,  u8 valid, u8 level)
+{
+	int ret;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+	struct skw_noa_ratio_mib ratio;
+
+	skw_dbg("ratio level: %d %d\n", valid, level);
+
+	ratio.valid = valid;
+	ratio.ratio_lv = level;
+
+	ret = skw_tlv_alloc(&conf, 128, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_NOA_RATIO_TYPE, &ratio, sizeof(ratio))) {
+		skw_err("set ratio level failed\n");
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	*plen = conf.total_len;
+
+	ret = skw_msg_xmit(wiphy, 0, SKW_CMD_SET_MIB, conf.buff,
+			   conf.total_len, NULL, 0);
+	if (ret)
+		skw_warn("failed, ret: %d\n", ret);
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+int skw_set_dot11k_mib(struct wiphy *wiphy, struct net_device *dev,
+			    bool enable)
+{
+	struct skw_iface *iface;
+
+	if (!dev) {
+		skw_err("dev is null\n");
+
+		return -EINVAL;
+	}
+
+	iface = netdev_priv(dev);
+
+	return skw_util_set_mib_enable(wiphy, iface->id,
+			SKW_MIB_SET_11K_TLV_ID, enable);
+}
+
+int skw_set_dot11v_mib(struct wiphy *wiphy, struct net_device *dev,
+			    bool enable)
+{
+	struct skw_iface *iface;
+
+	if (!dev) {
+		skw_err("dev is null\n");
+
+		return -EINVAL;
+	}
+
+	iface = netdev_priv(dev);
+
+	return skw_util_set_mib_enable(wiphy, iface->id,
+			SKW_MIB_SET_11V_TLV_ID, enable);
+}
+
+int skw_set_dot11r_mib(struct wiphy *wiphy, struct net_device *dev,
+			    bool enable)
+{
+	struct skw_iface *iface;
+
+	if (!dev) {
+		skw_err("dev is null\n");
+
+		return -EINVAL;
+	}
+
+	iface = netdev_priv(dev);
+
+	return skw_util_set_mib_enable(wiphy, iface->id,
+			SKW_MIB_SET_11R_TLV_ID, enable);
+}
+
+int skw_set_5ghz_band_mib(struct wiphy *wiphy, u32 band)
+{
+	int ret;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	skw_dbg("5ghz_band: %d\n", band);
+
+	ret = skw_tlv_alloc(&conf, 128, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_BAND_5G, &band, sizeof(band))) {
+		skw_err("set 5ghz band failed\n");
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	*plen = conf.total_len;
+
+	ret = skw_msg_xmit(wiphy, 0, SKW_CMD_SET_MIB, conf.buff,
+			   conf.total_len, NULL, 0);
+	if (ret)
+		skw_warn("failed, ret: %d\n", ret);
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+static inline void skw_iftype_dump(int iftype_num[NUM_NL80211_IFTYPES])
+{
+	int i;
+
+	for (i = 0; i < NUM_NL80211_IFTYPES; i++) {
+		if (iftype_num[i])
+			skw_info("%s: %d\n", skw_iftype_name(i), iftype_num[i]);
+	}
+}
+
+static void skw_count_iftype(struct wiphy *wiphy, int num[NUM_NL80211_IFTYPES])
+{
+	int i;
+	struct skw_iface *iface;
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	spin_lock_bh(&skw->vif.lock);
+
+	for (i = 0; i < SKW_NR_IFACE; i++) {
+		iface = skw->vif.iface[i];
+		if (!iface ||
+		    (iface->flags & SKW_IFACE_FLAG_LEGACY_P2P_DEV) ||
+		    (iface->wdev.iftype == NL80211_IFTYPE_P2P_DEVICE))
+			continue;
+
+		num[iface->wdev.iftype]++;
+	}
+
+	spin_unlock_bh(&skw->vif.lock);
+}
+
+static struct wireless_dev *skw_add_virtual_intf(struct wiphy *wiphy,
+		const char *name, unsigned char name_assign_type,
+		enum nl80211_iftype type, u32 *flags, struct vif_params *params)
+{
+	int ret;
+	struct skw_iface *iface;
+	u8 vif_id = SKW_INVALID_ID;
+	int iftype_num[NUM_NL80211_IFTYPES] = {0};
+
+	skw_dbg("%s(%s), mac: %pM\n", name, skw_iftype_name(type),
+		params->macaddr);
+
+	skw_count_iftype(wiphy, iftype_num);
+	ret = skw_compat_check_combs(wiphy, 0, 0, iftype_num);
+	if (ret) {
+		skw_err("check combinations failed, %s(%s)\n",
+			name, skw_iftype_name(type));
+
+		skw_iftype_dump(iftype_num);
+
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (type == NL80211_IFTYPE_P2P_DEVICE)
+		vif_id = SKW_LAST_IFACE_ID;
+
+	iface = skw_add_iface(wiphy, name, type, params->macaddr, vif_id,
+				type != NL80211_IFTYPE_P2P_DEVICE);
+	if (IS_ERR(iface)) {
+		skw_err("failed, %ld\n", PTR_ERR(iface));
+		return ERR_CAST(iface);
+	}
+
+	return &iface->wdev;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
+static struct wireless_dev *skw_cfg80211_add_virtual_intf(struct wiphy *wiphy,
+			const char *name, unsigned char name_assign_type,
+			enum nl80211_iftype type, struct vif_params *params)
+{
+	u32 flags = 0;
+
+	return skw_add_virtual_intf(wiphy, name, name_assign_type,
+			type, &flags, params);
+}
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
+static struct wireless_dev *skw_cfg80211_add_virtual_intf(struct wiphy *wiphy,
+			const char *name, unsigned char name_assign_type,
+			enum nl80211_iftype type, u32 *flags,
+			struct vif_params *params)
+{
+	return skw_add_virtual_intf(wiphy, name, name_assign_type,
+			type, flags, params);
+}
+#else
+static struct wireless_dev *skw_cfg80211_add_virtual_intf(struct wiphy *wiphy,
+			const char *name, enum nl80211_iftype type, u32 *flags,
+			struct vif_params *params)
+{
+	return skw_add_virtual_intf(wiphy, name, 0, type, flags, params);
+}
+#endif
+
+static int skw_cfg80211_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev)
+{
+	struct skw_iface *iface = SKW_WDEV_TO_IFACE(wdev);
+
+	skw_dbg("iftype: %d, iface id: %d\n", wdev->iftype, iface->id);
+
+	return skw_del_iface(wiphy, iface);
+}
+
+static int skw_change_intf(struct wiphy *wiphy, struct net_device *dev,
+			   enum nl80211_iftype type, u32 *flags,
+			   struct vif_params *params)
+{
+	u8 *mac;
+	int ret;
+	int iftype_num[NUM_NL80211_IFTYPES] = {0};
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_dbg("%s (inst: %d), %s -> %s, mac: %pM, 4addr: %d, flags: 0x%x\n",
+		netdev_name(dev), iface->id,
+		skw_iftype_name(dev->ieee80211_ptr->iftype),
+		skw_iftype_name(type), params->macaddr,
+		params->use_4addr, iface->flags);
+
+	if (iface->flags & SKW_IFACE_FLAG_LEGACY_P2P_DEV)
+		iface->wdev.iftype = type;
+
+	if (iface->wdev.iftype == type)
+		return 0;
+
+	skw_count_iftype(wiphy, iftype_num);
+	iftype_num[type]++;
+	iftype_num[iface->wdev.iftype]--;
+	ret = skw_compat_check_combs(wiphy, 0, 0, iftype_num);
+	if (ret) {
+		skw_err("check combinations failed, %s(inst: %d), %s -> %s\n",
+			netdev_name(dev), iface->id,
+			skw_iftype_name(dev->ieee80211_ptr->iftype),
+			skw_iftype_name(type));
+
+		skw_iftype_dump(iftype_num);
+
+		return ret;
+	}
+
+	if (iface->ndev)
+		netif_tx_stop_all_queues(dev);
+
+	ret = skw_iface_teardown(wiphy, iface);
+	if (ret) {
+		skw_err("teardown failed, %s (inst: %d), ret: %d\n",
+			skw_iftype_name(iface->wdev.iftype), iface->id, ret);
+
+		goto out;
+	}
+
+#ifdef CONFIG_SWT6621S_WDS
+	skw_set_wds_mib(wiphy, params->use_4addr);
+#endif
+
+	if (is_valid_ether_addr(params->macaddr))
+		mac = params->macaddr;
+	else
+		mac = (u8 *)wdev_address(dev->ieee80211_ptr);
+
+	ret = skw_iface_setup(wiphy, dev, iface, mac, type, iface->id);
+	if (ret) {
+		skw_err("open dev failed, %s (inst: %d)\n",
+			skw_iftype_name(type), iface->id);
+
+			ret = skw_iface_setup(wiphy, dev, iface, iface->addr,
+				iface->wdev.iftype, iface->id);
+			if (ret)
+				skw_err("open dev failed, %s (inst: %d)\n",
+					skw_iftype_name(iface->wdev.iftype), iface->id);
+	}
+
+out:
+	if (iface->ndev)
+		netif_tx_start_all_queues(dev);
+
+	return ret;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
+static int skw_cfg80211_change_intf(struct wiphy *wiphy, struct net_device *dev,
+		enum nl80211_iftype type, struct vif_params *params)
+{
+	u32 flags = 0;
+
+	return skw_change_intf(wiphy, dev, type, &flags, params);
+}
+#else
+static int skw_cfg80211_change_intf(struct wiphy *wiphy, struct net_device *dev,
+		enum nl80211_iftype type, u32 *flags, struct vif_params *params)
+{
+	return skw_change_intf(wiphy, dev, type, flags, params);
+}
+#endif
+
+static int skw_get_key(struct wiphy *wiphy, struct net_device *dev,
+		int link_id, u8 key_idx, bool pairwise,
+		const u8 *mac_addr, void *cookie,
+		void (*callback)(void *cookie, struct key_params *params))
+{
+	skw_dbg("dev: %s, link: %d, key_idx: %d, pairwise: %d, mac: %pM\n",
+		netdev_name(dev), link_id, key_idx, pairwise, mac_addr);
+
+	return 0;
+}
+
+static int skw_cmd_add_key(struct wiphy *wiphy, struct net_device *dev,
+			   int cipher, u8 key_idx, int key_type,
+			   const u8 *key, int key_len, const u8 *addr)
+{
+	struct skw_key_params params;
+	struct skw_iface *iface = netdev_priv(dev);
+	u8 wapi_tx_pn[] = {0x36, 0x5c, 0x36, 0x5c, 0x36, 0x5c};
+
+	memset(&params, 0x0, sizeof(params));
+
+	if (addr)
+		skw_ether_copy(params.mac_addr, addr);
+	else
+		memset(params.mac_addr, 0xff, ETH_ALEN);
+
+	memcpy(params.key, key, key_len);
+
+	params.key_type = key_type;
+	params.key_len = key_len;
+	params.key_id = key_idx;
+	params.cipher_type = cipher;
+	params.pn[0] = 1;
+
+	switch (cipher) {
+	case SKW_CIPHER_TYPE_SMS4:
+		memcpy(params.pn, wapi_tx_pn, SKW_PN_LEN);
+
+		if (is_skw_ap_mode(iface))
+			params.pn[0] += 1;
+
+		break;
+
+	case SKW_CIPHER_TYPE_TKIP:
+		if (is_skw_ap_mode(iface))
+			memcpy(&params.key[0], key, 32);
+		else {
+			memcpy(&params.key[0], key, 16);
+			memcpy(&params.key[16], key + 24, 8);
+			memcpy(&params.key[24], key + 16, 8);
+		}
+
+		break;
+
+	default:
+		break;
+	}
+
+	if (is_skw_ap_mode(iface) && iface->buf_keys_idx < SKW_MAX_BUF_KEYS &&
+		!SKW_TEST(iface->flags, SKW_IFACE_FLAG_AP_STARTED)) {
+		memcpy(&iface->buf_keys[iface->buf_keys_idx++],
+			 &params, sizeof(params));
+		SKW_SET(iface->flags, SKW_IFACE_FLAG_BUF_KEY);
+		skw_dbg("lead key buffered, idx: %d\n", iface->buf_keys_idx - 1);
+		return 0;
+	} else
+		return skw_send_msg(wiphy, dev, SKW_CMD_ADD_KEY, &params,
+			sizeof(params), NULL, 0);
+}
+
+static int skw_set_key(struct wiphy *wiphy, struct net_device *dev,
+			struct skw_key_conf *conf, u8 key_idx, int key_type,
+			const u8 *addr, struct key_params *params)
+{
+	int i, cipher, ret;
+	struct skw_key *key, *old_key;
+
+	cipher = to_skw_cipher_type(params->cipher);
+	if (cipher == SKW_CIPHER_TYPE_INVALID) {
+		skw_warn("cipher 0x%x unsupported\n", params->cipher);
+		return -ENOTSUPP;
+	}
+
+	key = SKW_ZALLOC(sizeof(struct skw_key), GFP_KERNEL);
+	if (!key)
+		return -ENOMEM;
+
+	key->key_len = params->key_len;
+	memcpy(key->key_data, params->key, params->key_len);
+
+	if (params->seq) {
+		skw_hex_dump("seq", params->seq, params->seq_len, false);
+
+		for (i = 1; i < IEEE80211_NUM_TIDS; i++)
+			memcpy(key->rx_pn[i], params->seq, SKW_PN_LEN);
+	}
+
+	conf->skw_cipher = cipher;
+
+	old_key = rcu_dereference_protected(conf->key[key_idx],
+			lockdep_is_held(&conf->lock));
+
+	rcu_assign_pointer(conf->key[key_idx], key);
+
+	SKW_SET(conf->installed_bitmap, BIT(key_idx));
+
+	if (old_key)
+		kfree_rcu(old_key, rcu);
+
+	if (cipher == SKW_CIPHER_TYPE_WEP40 ||
+	    cipher == SKW_CIPHER_TYPE_WEP104) {
+		SKW_SET(conf->flags, SKW_KEY_FLAG_WEP_SHARE);
+		return 0;
+	}
+
+	ret = skw_cmd_add_key(wiphy, dev, cipher, key_idx, key_type,
+			params->key, params->key_len, addr);
+	if (ret) {
+		RCU_INIT_POINTER(conf->key[key_idx], NULL);
+		SKW_CLEAR(conf->installed_bitmap, BIT(key_idx));
+		kfree_rcu(key, rcu);
+	}
+
+	return ret;
+}
+
+static int skw_add_key(struct wiphy *wiphy, struct net_device *dev,
+		       int link_id, u8 key_idx, bool pairwise,
+		       const u8 *addr, struct key_params *params)
+{
+	const u8 *mac;
+	int ret, key_type;
+	struct skw_key_conf *conf;
+	struct skw_peer_ctx *ctx;
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_dbg("%s, key_idx: %d, cipher: 0x%x, pairwise: %d, mac: %pM\n",
+		netdev_name(dev), key_idx, params->cipher, pairwise, addr);
+
+	key_type = pairwise ? SKW_KEY_TYPE_PTK : to_skw_gtk(key_idx);
+
+	if (addr) {
+		ctx = skw_peer_ctx(iface, addr);
+		if (!ctx) {
+			skw_warn("%pM not linked\n", addr);
+			return -ENOLINK;
+		}
+
+		skw_peer_ctx_lock(ctx);
+
+		if (!ctx->peer) {
+			skw_peer_ctx_unlock(ctx);
+			return 0;
+		}
+
+		if (pairwise)
+			conf = &ctx->peer->ptk_conf;
+		else
+			conf = &ctx->peer->gtk_conf;
+
+		mutex_lock(&conf->lock);
+
+		ret = skw_set_key(wiphy, dev, conf, key_idx,
+				  key_type, addr, params);
+
+		mutex_unlock(&conf->lock);
+
+		skw_peer_ctx_unlock(ctx);
+
+	} else {
+		if (is_skw_ap_mode(iface))
+			mac = NULL;
+		else
+			mac = iface->sta.core.bss.bssid;
+
+		conf = &iface->key_conf;
+
+		mutex_lock(&conf->lock);
+
+		ret = skw_set_key(wiphy, dev, conf, key_idx,
+				  key_type, mac, params);
+
+		mutex_unlock(&conf->lock);
+	}
+
+	if (ret)
+		skw_err("failed, cipher: 0x%x, ptk: %d, idx: %d, ret: %d\n",
+			params->cipher, pairwise, key_idx, ret);
+
+	return ret;
+}
+
+static int skw_cmd_del_key(struct wiphy *wiphy, struct net_device *dev,
+			u8 key_idx, int key_type, int cipher, const u8 *addr)
+{
+	struct skw_key_params params;
+
+	memset(&params, 0x0, sizeof(params));
+
+	if (addr)
+		skw_ether_copy(params.mac_addr, addr);
+	else
+		memset(params.mac_addr, 0xff, ETH_ALEN);
+
+	params.key_type = key_type;
+	params.cipher_type = cipher;
+	params.key_id = key_idx;
+
+	return skw_send_msg(wiphy, dev, SKW_CMD_DEL_KEY, &params,
+			   sizeof(params), NULL, 0);
+}
+
+static int skw_remove_key(struct wiphy *wiphy, struct net_device *dev,
+			struct skw_key_conf *conf, u8 key_idx,
+			int key_type, const u8 *addr)
+{
+	int ret;
+	struct skw_key *key;
+
+	if (SKW_TEST(conf->installed_bitmap, BIT(key_idx))) {
+		ret = skw_cmd_del_key(wiphy, dev, key_idx, key_type,
+				conf->skw_cipher, addr);
+		if (ret)
+			skw_err("failed, ret: %d\n", ret);
+	}
+
+	key = rcu_dereference_protected(conf->key[key_idx],
+			lockdep_is_held(&conf->lock));
+
+	RCU_INIT_POINTER(conf->key[key_idx], NULL);
+
+	SKW_CLEAR(conf->installed_bitmap, BIT(key_idx));
+
+	if (SKW_TEST(conf->flags, SKW_KEY_FLAG_WEP_SHARE)) {
+		SKW_CLEAR(conf->flags, SKW_KEY_FLAG_WEP_SHARE);
+		SKW_CLEAR(conf->flags, SKW_KEY_FLAG_WEP_UNICAST);
+		SKW_CLEAR(conf->flags, SKW_KEY_FLAG_WEP_MULTICAST);
+	}
+
+	if (key)
+		kfree_rcu(key, rcu);
+
+	return 0;
+}
+
+static int skw_del_key(struct wiphy *wiphy, struct net_device *dev,
+			int link_id, u8 key_idx, bool pairwise, const u8 *addr)
+{
+	int ret, key_type;
+	struct skw_key_conf *conf;
+	const u8 *mac = NULL;
+	struct skw_peer_ctx *ctx = NULL;
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_dbg("link: %d, key_idx: %d, pairwise: %d, mac: %pM\n",
+		link_id, key_idx, pairwise, addr);
+
+	if (key_idx >= SKW_NUM_MAX_KEY) {
+		skw_err("key index %d out of bounds\n", key_idx);
+		return -EINVAL;
+	}
+
+	key_type = pairwise ? SKW_KEY_TYPE_PTK : to_skw_gtk(key_idx);
+
+	if (addr) {
+		ctx = skw_peer_ctx(iface, addr);
+		if (!ctx)
+			return 0;
+
+		skw_peer_ctx_lock(ctx);
+
+		if (!ctx->peer) {
+			skw_peer_ctx_unlock(ctx);
+			return 0;
+		}
+
+		if (pairwise)
+			conf = &ctx->peer->ptk_conf;
+		else
+			conf = &ctx->peer->gtk_conf;
+
+		mutex_lock(&conf->lock);
+
+		ret = skw_remove_key(wiphy, dev, conf, key_idx, key_type, addr);
+
+		mutex_unlock(&conf->lock);
+
+		skw_peer_ctx_unlock(ctx);
+
+	} else {
+
+		conf = &iface->key_conf;
+
+		if (is_skw_sta_mode(iface))
+			mac = iface->sta.core.bss.bssid;
+
+		mutex_lock(&conf->lock);
+
+		ret = skw_remove_key(wiphy, dev, conf, key_idx, key_type, mac);
+
+		mutex_unlock(&conf->lock);
+	}
+
+	return ret;
+}
+
+/* for WEP keys */
+static int skw_set_default_key(struct wiphy *wiphy, struct net_device *dev,
+			int link_id, u8 key_idx, bool unicast, bool multicast)
+{
+	int ret = 0, key_len;
+	struct skw_key *key;
+	const u8 *mac = NULL;
+	u8 key_data[WLAN_MAX_KEY_LEN] = {0};
+	struct skw_iface *iface = netdev_priv(dev);
+	struct skw_key_conf *conf = &iface->key_conf;
+
+	skw_dbg("dev: %s, link: %d, key_idx: %d, unicast: %d, multicast: %d\n",
+		netdev_name(dev), link_id, key_idx, unicast, multicast);
+
+	if (!(conf->installed_bitmap & BIT(key_idx)))
+		return 0;
+
+	if (is_skw_sta_mode(iface))
+		mac = iface->sta.core.bss.bssid;
+
+	rcu_read_lock();
+
+	key = conf->key[key_idx];
+	if (key) {
+		memcpy(key_data, key->key_data, key->key_len);
+		key_len = key->key_len;
+	}
+
+	rcu_read_unlock();
+
+	if (!key)
+		return 0;
+
+	conf->wep_idx = key_idx;
+
+	if (unicast) {
+		ret = skw_cmd_add_key(wiphy, dev, conf->skw_cipher,
+				      key_idx, SKW_KEY_TYPE_PTK,
+				      key_data, key_len, mac);
+
+		if (ret)
+			SKW_SET(conf->flags, SKW_KEY_FLAG_WEP_UNICAST);
+	}
+
+	if (multicast) {
+		ret = skw_cmd_add_key(wiphy, dev, conf->skw_cipher,
+				      key_idx, SKW_KEY_TYPE_GTK,
+				      key_data, key_len, mac);
+
+		if (ret)
+			SKW_SET(conf->flags, SKW_KEY_FLAG_WEP_MULTICAST);
+	}
+
+	return 0;
+}
+
+/* for 11w */
+static int skw_set_default_mgmt_key(struct wiphy *wiphy, struct net_device *dev,
+				int link_id, u8 key_index)
+{
+	skw_dbg("%s, key index: %d\n", netdev_name(dev), key_index);
+
+	return 0;
+}
+
+static int skw_set_mac_acl(struct wiphy *wiphy, struct net_device *dev,
+			const struct cfg80211_acl_data *acl)
+{
+	int size;
+	struct skw_iface *iface = netdev_priv(dev);
+
+	if (!acl)
+		return 0;
+
+	skw_dbg("dev: %s, nr_entries: %d\n",
+		netdev_name(dev), acl->n_acl_entries);
+
+	if (!acl->n_acl_entries) {
+		SKW_KFREE(iface->sap.acl);
+		return 0;
+	}
+
+	size = acl->n_acl_entries * sizeof(struct mac_address);
+	size += sizeof(struct cfg80211_acl_data);
+
+	SKW_KFREE(iface->sap.acl);
+
+	iface->sap.acl = SKW_ZALLOC(size, GFP_KERNEL);
+	if (!iface->sap.acl)
+		return -ENOMEM;
+
+	memcpy(iface->sap.acl, acl, size);
+
+	skw_queue_work(wiphy, netdev_priv(dev), SKW_WORK_ACL_CHECK, NULL, 0);
+
+	return 0;
+}
+
+static bool skw_channel_allowed(struct wiphy *wiphy, u16 channel)
+{
+#define BITMAP_SIZE ((164 + BITS_PER_LONG) / BITS_PER_LONG)
+	int i, nr_channel;
+	struct skw_iface *iface;
+	bool extra_chn = false;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	int iftype_num[NUM_NL80211_IFTYPES] = {0};
+	long channel_map[BITMAP_SIZE] = {0};
+
+	spin_lock_bh(&skw->vif.lock);
+
+	for (nr_channel = 0, i = 0; i < SKW_NR_IFACE; i++) {
+		struct ieee80211_channel *chan = NULL;
+
+		iface = skw->vif.iface[i];
+		if (!iface)
+			continue;
+
+		switch (iface->wdev.iftype) {
+		case NL80211_IFTYPE_AP:
+		case NL80211_IFTYPE_P2P_GO:
+			chan = iface->sap.cfg.channel;
+			break;
+
+		case NL80211_IFTYPE_STATION:
+			if (atomic_read(&iface->actived_ctx) > 1)
+				extra_chn = true;
+
+			/* fall through */
+			skw_fallthrough;
+		case NL80211_IFTYPE_P2P_CLIENT:
+			chan = iface->sta.core.bss.channel;
+			break;
+
+		default:
+			break;
+		}
+
+		if (chan && !test_and_set_bit(chan->hw_value, channel_map))
+			nr_channel++;
+	}
+
+	spin_unlock_bh(&skw->vif.lock);
+
+	for (i = 0; extra_chn && (i < SKW_MAX_PEER_SUPPORT); i++) {
+		struct skw_peer_ctx *ctx = &skw->hw.lmac[iface->lmac_id].peer_ctx[i];
+
+		skw_peer_ctx_lock(ctx);
+
+		if (ctx->peer && ctx->peer->channel &&
+		    !test_and_set_bit(ctx->peer->channel, channel_map))
+			nr_channel++;
+
+		skw_peer_ctx_unlock(ctx);
+	}
+
+	if (!test_bit(channel, channel_map))
+		nr_channel++;
+
+	if (!skw_compat_check_combs(wiphy, nr_channel, 0, iftype_num))
+		return true;
+
+	skw_err("channel %d not allowed, total:%d\n", channel, nr_channel);
+	skw_hex_dump("channels", channel_map, sizeof(channel_map), true);
+
+	return false;
+}
+
+int skw_sap_set_mib(struct wiphy *wiphy, struct net_device *dev,
+		const u8 *ies, int ies_len)
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+	struct skw_iface *iface = netdev_priv(dev);
+	u32 val_zero = 0;
+	u32 val_one = 1;
+
+	ret = skw_tlv_alloc(&conf, 512, GFP_KERNEL);
+	if (ret)
+		return ret;
+
+	plen = skw_tlv_reserve(&conf, 2);
+
+	if (iface->extend.wireless_mode) {
+		skw_dbg("wireless_mode: %d\n", iface->extend.wireless_mode);
+
+		switch (iface->extend.wireless_mode) {
+		case SKW_WIRELESS_11G_ONLY:
+			if (skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_HE, &val_zero, 4) ||
+			    skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_VHT, &val_zero, 4) ||
+			    skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_HT, &val_zero, 4) ||
+			    skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_B, &val_zero, 4) ||
+			    skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_A, &val_zero, 4) ||
+			    skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_G, &val_one, 4)) {
+				skw_err("set 11G mode failed\n");
+				skw_tlv_free(&conf);
+			}
+
+			break;
+
+		case SKW_WIRELESS_11N_ONLY:
+			if (skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_HE, &val_zero, 4) ||
+			    skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_VHT, &val_zero, 4) ||
+			    skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_HT, &val_one, 4) ||
+			    skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_B, &val_zero, 4) ||
+			    skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_A, &val_zero, 4) ||
+			    skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_G, &val_zero, 4)) {
+				skw_err("set 11N mode failed\n");
+				skw_tlv_free(&conf);
+			}
+
+			break;
+
+		default:
+			break;
+		}
+	} else if (ies && ies_len) {
+		u8 ext_eid;
+		const u8 *ie;
+		int ht_enable = 0, vht_enable = 0, he_enable = 0;
+
+		ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, ies, ies_len);
+		if (ie && ie[1])
+			ht_enable = 1;
+
+		ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, ies, ies_len);
+		if (ie && ie[1])
+			vht_enable = 1;
+
+		ext_eid = SKW_WLAN_EID_EXT_HE_CAPABILITY;
+		ie = skw_find_ie_match(SKW_WLAN_EID_EXTENSION, ies, ies_len, &ext_eid, 1, 2);
+		if (ie && ie[1])
+			he_enable = 1;
+
+		skw_dbg("ht_enable: %d, vht_enable: %d, he_enable: %d\n",
+			ht_enable, vht_enable, he_enable);
+
+		if (skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_HT, &ht_enable, 4) ||
+		    skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_VHT, &vht_enable, 4) ||
+		    skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_HE, &he_enable, 4)) {
+			skw_err("set beacon capa failed\n");
+			skw_tlv_free(&conf);
+		}
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+				  conf.total_len, NULL, 0);
+		if (ret)
+			skw_err("failed, ret: %d\n", ret);
+
+	}
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+#ifdef CONFIG_SWT6621S_USB3_WORKAROUND
+static int
+skw_switch_usb3_to_usb2_using_2G(struct skw_iface *iface, enum nl80211_band band)
+{
+	struct skw_core *skw = iface->skw;
+	char mode[20];
+	int ret = 0;
+
+	skw_dbg("bus:%d align_value:%d band:%d\n", skw->hw.bus,
+		skw->hw_pdata->align_value, band);
+	if (skw->hw.bus == SKW_BUS_USB &&
+		skw->hw_pdata->align_value == 1024 &&
+		band == NL80211_BAND_2GHZ &&
+		skw->hw_pdata->usb_speed_switch) {
+		if (test_bit(SKW_FLAG_SWITCHING_USB_MODE, &skw->flags)) {
+			skw_dbg("already in switching\n");
+			return -EBUSY;
+		}
+		set_bit(SKW_FLAG_SWITCHING_USB_MODE, &skw->flags);
+		skw->hw_pdata->usb_speed_switch(mode);
+		skw_dbg("change usb mode to %s\n", mode);
+
+		skw_dbg("waiting for the switch completion");
+		if (wait_for_completion_interruptible_timeout(&skw->usb_switch_done,
+					SKW_RECOVERY_TIMEOUT) == 0) {
+			skw_err("switch timeout\n");
+			ret = -ETIME;
+		}
+	}
+
+	return ret;
+}
+#endif
+
+static int skw_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
+			struct cfg80211_ap_settings *settings)
+{
+	int ret, bw;
+	int total, fixed, offset = 0;
+	struct skw_startap_resp resp = {};
+	struct skw_startap_param *param = NULL;
+	struct skw_iface *iface = netdev_priv(dev);
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct cfg80211_beacon_data *bcn = &settings->beacon;
+	struct cfg80211_chan_def *chandef = &settings->chandef;
+	struct skw_key_conf *conf = &iface->key_conf;
+
+	skw_info("ndev: %s\n", netdev_name(dev));
+	skw_dbg("       * ssid: %s\n", settings->ssid);
+	skw_dbg("       * bssid: %pM\n", iface->addr);
+	skw_dbg("       * channel: %d band:%d (BW: %d)\n", chandef->chan->hw_value,
+			chandef->chan->band, chandef->width);
+	skw_dbg("       * auth type: %d\n", settings->auth_type);
+	skw_dbg("       * akm_suites: %d\n", settings->crypto.n_akm_suites);
+
+	if (!skw_channel_allowed(wiphy, chandef->chan->hw_value))
+		return -ENOTSUPP;
+
+	bw = to_skw_bw(settings->chandef.width);
+	if (bw == SKW_CHAN_WIDTH_MAX) {
+		skw_err("BW %d not support\n", settings->chandef.width);
+		return -ENOTSUPP;
+	}
+
+#ifdef CONFIG_SWT6621S_USB3_WORKAROUND
+	ret = skw_switch_usb3_to_usb2_using_2G(iface, chandef->chan->band);
+	if (ret)
+		return ret;
+#endif
+
+	skw_sap_set_mib(wiphy, dev, bcn->tail, bcn->tail_len);
+
+	fixed = sizeof(struct skw_startap_param);
+	total = fixed +
+		bcn->head_len +
+		bcn->tail_len +
+		bcn->probe_resp_len;
+
+	param = SKW_ZALLOC(total, GFP_KERNEL);
+	if (!param) {
+		skw_err("malloc failed, size: %d\n", total);
+		return -ENOMEM;
+	}
+
+	param->chan_width = bw;
+	param->chan = chandef->chan->hw_value;
+	param->band = to_skw_band(chandef->chan->band);
+	param->center_chn1 = skw_freq_to_chn(chandef->center_freq1);
+	param->center_chn2 = skw_freq_to_chn(chandef->center_freq2);
+
+	param->beacon_int = settings->beacon_interval;
+	param->dtim_period = settings->dtim_period;
+	param->ssid_len = settings->ssid_len;
+	memcpy(param->ssid, settings->ssid, settings->ssid_len);
+
+	if (settings->hidden_ssid)
+		param->flags |= settings->hidden_ssid;
+
+	if (bcn->head) {
+		skw_hex_dump("beacon_head", bcn->head, bcn->head_len, false);
+
+		param->beacon_head_len = bcn->head_len;
+		param->beacon_head_offset = offset + fixed;
+
+		memcpy(param->ies + offset, bcn->head, bcn->head_len);
+		offset += bcn->head_len;
+	}
+
+	if (bcn->tail) {
+		skw_hex_dump("beacon_tail", bcn->tail, bcn->tail_len, false);
+
+		param->beacon_tail_offset = offset + fixed;
+		param->beacon_tail_len = bcn->tail_len;
+
+		memcpy(param->ies + offset, bcn->tail, bcn->tail_len);
+		offset += bcn->tail_len;
+
+		skw_iface_set_wmm_capa(iface, bcn->tail, bcn->tail_len);
+	}
+
+	if (bcn->probe_resp) {
+		skw_hex_dump("probe_resp", bcn->probe_resp,
+				bcn->probe_resp_len, false);
+
+		param->probe_rsp_ies_offset = offset + fixed;
+		param->probe_rsp_ies_len = bcn->probe_resp_len;
+
+		memcpy(param->ies + offset, bcn->probe_resp,
+			bcn->probe_resp_len);
+
+		offset += bcn->probe_resp_len;
+
+		if (iface->sap.probe_resp) {
+			memcpy(iface->sap.probe_resp, bcn->probe_resp,
+					bcn->probe_resp_len);
+			iface->sap.probe_resp_len = bcn->probe_resp_len;
+		}
+	}
+
+	if (skw_recovery_data_update(iface, param, total)) {
+		skw_err("build recovery failed\n");
+
+		SKW_KFREE(param);
+		return -ENOMEM;
+	}
+
+	skw_edma_mask_irq(skw, iface->lmac_id);
+	ret = skw_send_msg(wiphy, dev, SKW_CMD_START_AP, param, total,
+			   &resp, sizeof(resp));
+	if (ret) {
+		skw_err("failed, ret: %d\n", ret);
+
+		skw_recovery_data_clear(iface);
+		SKW_KFREE(param);
+
+		return ret;
+	}
+
+	skw_startap_resp_handler(skw, iface, &resp);
+
+	if (SKW_TEST(conf->flags, SKW_KEY_FLAG_WEP_UNICAST) ||
+	    SKW_TEST(conf->flags, SKW_KEY_FLAG_WEP_MULTICAST))
+		skw_set_default_key(wiphy, dev, 0, conf->wep_idx,
+				SKW_TEST(conf->flags, SKW_KEY_FLAG_WEP_UNICAST),
+				SKW_TEST(conf->flags, SKW_KEY_FLAG_WEP_MULTICAST));
+
+	if (skw_compat_cfg80211_chandef_dfs_required(wiphy, chandef, iface->wdev.iftype)) {
+		ret = skw_dfs_chan_init(wiphy, dev, chandef, 0);
+		if (ret) {
+			skw_err("dfs channel init failed, ret: %d\n", ret);
+			SKW_KFREE(param);
+
+			return ret;
+		}
+
+		ret = skw_dfs_start_monitor(wiphy, dev);
+		if (ret) {
+			skw_dbg("start dfs monitor failed, ret: %d\n", ret);
+			SKW_KFREE(param);
+
+			return ret;
+		}
+	}
+
+	skw_dpd_set_coeff_params(wiphy, dev, param->chan,
+		param->center_chn1, param->center_chn2, param->chan_width);
+
+	skw_set_mac_acl(wiphy, dev, settings->acl);
+
+	skw_set_ratio_level_mib(wiphy, skw->config.fw.noa_ratio_en,
+		 skw->config.fw.noa_ratio_idx);
+	skw_set_once_noa_mib(wiphy, skw->config.fw.once_noa_en,
+		 skw->config.fw.once_noa_pre, skw->config.fw.once_noa_abs);
+
+	memcpy(iface->sap.cfg.ssid, settings->ssid, settings->ssid_len);
+	iface->sap.cfg.ssid_len = settings->ssid_len;
+
+	iface->sap.cfg.auth_type = settings->auth_type;
+	iface->sap.cfg.channel = chandef->chan;
+	iface->sap.cfg.width = bw;
+
+	skw_ether_copy(iface->sap.cfg.bssid, iface->addr);
+	memcpy(&iface->sap.cfg.crypto, &settings->crypto,
+		sizeof(settings->crypto));
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
+	iface->sap.cfg.ht_cap = SKW_KMEMDUP(settings->ht_cap,
+				sizeof(*settings->ht_cap), GFP_KERNEL);
+	iface->sap.cfg.vht_cap = SKW_KMEMDUP(settings->vht_cap,
+				sizeof(*settings->vht_cap), GFP_KERNEL);
+
+	iface->sap.ht_required = settings->ht_required;
+	iface->sap.vht_required = settings->vht_required;
+
+	iface->sap.cfg.crypto.wep_keys = NULL;
+	iface->sap.cfg.crypto.psk = NULL;
+#else
+	iface->sap.cfg.ht_cap = NULL;
+	iface->sap.cfg.vht_cap = NULL;
+
+	iface->sap.ht_required = false;
+	iface->sap.vht_required = false;
+#endif
+
+	if (iface->skw->hw.bus == SKW_BUS_PCIE) {
+		if (skw_edma_get_refill((void *)iface->skw, iface->lmac_id) == 0)
+			skw_edma_init_data_chan((void *)iface->skw, iface->lmac_id);
+		else
+			skw_edma_inc_refill((void *)iface->skw, iface->lmac_id);
+	}
+
+	SKW_CLEAR(iface->flags, SKW_IFACE_FLAG_DEAUTH);
+	netif_carrier_on(dev);
+
+	if (SKW_TEST(iface->flags, SKW_IFACE_FLAG_BUF_KEY)) {
+		int i;
+
+		for (i = 0; i < iface->buf_keys_idx; i++) {
+			skw_send_msg(wiphy, dev, SKW_CMD_ADD_KEY,
+				&iface->buf_keys[i], sizeof(struct skw_key_params), NULL, 0);
+			skw_dbg("send key buffered, idx: %d\n", i);
+			memset(&iface->buf_keys[i], 0, sizeof(struct skw_key_params));
+		}
+
+		iface->buf_keys_idx = 0;
+		SKW_CLEAR(iface->flags, SKW_IFACE_FLAG_BUF_KEY);
+	}
+
+	SKW_SET(iface->flags, SKW_IFACE_FLAG_AP_STARTED);
+
+	SKW_KFREE(param);
+
+	return 0;
+}
+
+static int skw_sap_del_sta(struct wiphy *wiphy, struct net_device *dev,
+			struct skw_peer_ctx *ctx, u8 subtype, u16 reason)
+{
+	int ret;
+	bool tx = true;
+	const u8 *mac = NULL;
+	struct skw_iface *iface = netdev_priv(dev);
+
+	if (!ctx)
+		return 0;
+
+	skw_peer_ctx_lock(ctx);
+
+	if (ctx->peer) {
+		mac = ctx->peer->addr;
+		__skw_peer_ctx_transmit(ctx, false);
+		skw_set_state(&ctx->peer->sm, SKW_STATE_NONE);
+
+		tx = !(ctx->peer->flags & SKW_PEER_FLAG_DEAUTHED);
+		SKW_SET(ctx->peer->flags, SKW_PEER_FLAG_DEAUTHED);
+	}
+
+	skw_peer_ctx_unlock(ctx);
+
+	if (!mac)
+		return 0;
+
+	skw_mlme_ap_remove_client(iface, mac);
+
+	ret = skw_cmd_del_sta(wiphy, dev, mac, subtype, reason, tx);
+	if (!ret)
+		skw_peer_ctx_bind(iface, ctx, NULL);
+
+	return ret;
+}
+
+static void skw_sap_flush_sta(struct wiphy *wiphy, struct skw_iface *iface,
+				u8 subtype, u16 reason)
+{
+	int idx;
+	struct skw_peer_ctx *ctx;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	u32 peer_map = atomic_read(&iface->peer_map);
+
+	while (peer_map) {
+		idx = ffs(peer_map) - 1;
+		SKW_CLEAR(peer_map, BIT(idx));
+
+		ctx = &skw->hw.lmac[iface->lmac_id].peer_ctx[idx];
+		if (!ctx)
+			continue;
+
+		skw_sap_del_sta(wiphy, iface->ndev, ctx, subtype, reason);
+	}
+}
+
+static int skw_stop_ap(struct wiphy *wiphy, struct net_device *dev,
+			unsigned int link_id)
+{
+	int ret = 0;
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_info("ndev: %s, link id: %d\n", netdev_name(dev), link_id);
+
+	if (iface->skw->hw.bus == SKW_BUS_PCIE)
+		skw_edma_dec_refill((void *)iface->skw, iface->lmac_id);
+
+	netif_carrier_off(dev);
+
+	skw_sap_flush_sta(wiphy, iface, 12, SKW_LEAVE);
+
+	// set flag for tx thread to filter out skb in tx cache
+	// mutex_lock(&skw->txrx.lock);
+	// SKW_CLEAR(skw->txrx.tx_map, BIT(iface->id));
+	// mutex_unlock(&skw->txrx.lock);
+
+	// WARN_ON(iface->sta_list.count);
+	skw_purge_key_conf(&iface->key_conf);
+	skw_recovery_data_clear(iface);
+
+	SKW_SET(iface->flags, SKW_IFACE_FLAG_DEAUTH);
+
+	skw_dfs_deinit(wiphy, dev);
+
+	ret = skw_send_msg(wiphy, dev, SKW_CMD_STOP_AP, NULL, 0, NULL, 0);
+	if (ret) {
+		SKW_CLEAR(iface->flags, SKW_IFACE_FLAG_DEAUTH);
+		skw_err("failed, ret = %d\n", ret);
+		return ret;
+	}
+
+	SKW_KFREE(iface->sap.acl);
+	SKW_KFREE(iface->sap.cfg.ht_cap);
+	SKW_KFREE(iface->sap.cfg.vht_cap);
+	SKW_CLEAR(iface->flags, SKW_IFACE_FLAG_AP_STARTED);
+
+	skw_lmac_unbind_iface(wiphy_priv(wiphy), iface->lmac_id, iface->id);
+
+	return 0;
+}
+
+static int skw_change_beacon(struct wiphy *wiphy, struct net_device *dev,
+				struct cfg80211_beacon_data *bcn)
+{
+	int ret = -1;
+	int total, fixed, offset = 0;
+	struct skw_iface *iface = netdev_priv(dev);
+	struct skw_beacon_params *param = NULL;
+
+	skw_dbg("dev: %s\n", netdev_name(dev));
+	if (!bcn)
+		return -EINVAL;
+
+	fixed = sizeof(struct skw_beacon_params);
+	total = fixed +
+		bcn->head_len +
+		bcn->tail_len +
+		bcn->probe_resp_len;
+
+	param = SKW_ZALLOC(total, GFP_KERNEL);
+	if (!param) {
+		skw_err("malloc failed, size: %d\n", total);
+		return -ENOMEM;
+	}
+
+	if (bcn->head) {
+		skw_hex_dump("beacon_head", bcn->head, bcn->head_len, false);
+
+		param->beacon_head_len = bcn->head_len;
+		param->beacon_head_offset = fixed + offset;
+		memcpy(param->ies + offset, bcn->head, bcn->head_len);
+		offset += bcn->head_len;
+	}
+
+	if (bcn->tail) {
+		skw_hex_dump("beacon_tail", bcn->tail, bcn->tail_len, false);
+
+		param->beacon_tail_offset = fixed + offset;
+		param->beacon_tail_len = bcn->tail_len;
+		memcpy(param->ies + offset, bcn->tail, bcn->tail_len);
+		offset += bcn->tail_len;
+	}
+
+	if (bcn->probe_resp) {
+		skw_hex_dump("probe_resp", bcn->probe_resp, bcn->probe_resp_len, false);
+
+		param->probe_rsp_offset = fixed + offset;
+		param->probe_rsp_len = bcn->probe_resp_len;
+		memcpy(param->ies + offset, bcn->probe_resp,
+				bcn->probe_resp_len);
+		offset += bcn->probe_resp_len;
+
+		if (iface->sap.probe_resp) {
+			memcpy(iface->sap.probe_resp, bcn->probe_resp,
+				bcn->probe_resp_len);
+
+			iface->sap.probe_resp_len = bcn->probe_resp_len;
+		}
+	}
+
+	ret = skw_send_msg(wiphy, dev, SKW_CMD_CHANGE_BEACON,
+			param, total, NULL, 0);
+	if (ret)
+		skw_err("failed, ret: %d\n", ret);
+
+	SKW_KFREE(param);
+
+	return ret;
+}
+
+void skw_set_state(struct skw_sm *sm, enum SKW_STATES state)
+{
+	skw_log(SKW_STATE, "[%s] inst: %d, %s -> %pM, state: %s -> %s\n",
+		SKW_TAG_STATE, sm->inst, skw_iftype_name(sm->iface_iftype),
+		sm->addr, skw_state_name(sm->state), skw_state_name(state));
+
+	sm->state = state;
+}
+
+int skw_change_station(struct wiphy *wiphy, struct net_device *dev,
+			const u8 *mac, struct station_parameters *params)
+{
+	struct skw_iface *iface = netdev_priv(dev);
+	u32 flags_set = params->sta_flags_set;
+	struct skw_peer_ctx *ctx = NULL;
+
+	skw_dbg("%s(%s), mac: %pM, flags_set: 0x%x\n",
+		netdev_name(dev), skw_iftype_name(dev->ieee80211_ptr->iftype),
+		mac, params->sta_flags_set);
+
+	ctx = skw_peer_ctx(iface, mac);
+	if (!ctx)
+		return -EINVAL;
+
+	skw_peer_ctx_lock(ctx);
+
+	switch (dev->ieee80211_ptr->iftype) {
+	case NL80211_IFTYPE_AP:
+	case NL80211_IFTYPE_P2P_GO:
+	case NL80211_IFTYPE_ADHOC:
+
+		if (flags_set & BIT(NL80211_STA_FLAG_ASSOCIATED)) {
+			__skw_peer_ctx_transmit(ctx, true);
+			skw_set_state(&ctx->peer->sm, SKW_STATE_ASSOCED);
+
+			if (iface->sap.cfg.crypto.n_akm_suites == 0)
+				flags_set |= BIT(NL80211_STA_FLAG_AUTHORIZED);
+
+		}
+
+		if (flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED)) {
+			skw_set_state(&ctx->peer->sm, SKW_STATE_COMPLETED);
+			atomic_set(&ctx->peer->rx_filter, SKW_RX_FILTER_NONE);
+		}
+
+		break;
+
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_P2P_CLIENT:
+		if (flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED)) {
+			skw_set_state(&iface->sta.core.sm, SKW_STATE_COMPLETED);
+			atomic_set(&ctx->peer->rx_filter, SKW_RX_FILTER_NONE);
+			skw_set_ip_to_fw(wiphy, dev);
+		}
+
+		break;
+
+	default:
+		break;
+	}
+
+	skw_peer_ctx_unlock(ctx);
+
+	return 0;
+}
+
+static int skw_cfg80211_change_station(struct wiphy *wiphy, struct net_device *dev,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
+			const u8 *mac,
+#else
+			u8 *mac,
+#endif
+			struct station_parameters *params)
+{
+	return skw_change_station(wiphy, dev, (const u8 *)mac, params);
+}
+
+static int skw_set_sta_wep_key(struct wiphy *wiphy, struct skw_iface *iface,
+			const u8 *mac, enum SKW_KEY_TYPE key_type)
+{
+	int idx;
+	struct skw_key_params key_params;
+	struct skw_key *key;
+	struct skw_key_conf *conf = &iface->key_conf;
+
+	skw_dbg("addr: %pM, key type: %d\n", mac, key_type);
+
+	memset(&key_params, 0x0, sizeof(key_params));
+
+	idx = skw_key_idx(conf->installed_bitmap);
+	if (idx == SKW_INVALID_ID)
+		return -EINVAL;
+
+	rcu_read_lock();
+	key = rcu_dereference(conf->key[idx]);
+	rcu_read_unlock();
+
+	key_params.cipher_type = conf->skw_cipher;
+	key_params.key_id = idx;
+	key_params.key_len = key->key_len;
+	key_params.key_type = key_type;
+
+	memcpy(key_params.key, key->key_data, key->key_len);
+	skw_ether_copy(key_params.mac_addr, mac);
+
+	if (is_skw_ap_mode(iface) && iface->buf_keys_idx < SKW_MAX_BUF_KEYS &&
+		!SKW_TEST(iface->flags, SKW_IFACE_FLAG_AP_STARTED)) {
+		memcpy(&iface->buf_keys[iface->buf_keys_idx++],
+			 &key_params, sizeof(key_params));
+		SKW_SET(iface->flags, SKW_IFACE_FLAG_BUF_KEY);
+		skw_dbg("lead key buffered, idx: %d\n", iface->buf_keys_idx - 1);
+		return 0;
+	} else
+		return skw_send_msg(wiphy, iface->ndev, SKW_CMD_ADD_KEY,
+			&key_params, sizeof(key_params), NULL, 0);
+}
+
+int skw_cmd_del_sta(struct wiphy *wiphy, struct net_device *dev,
+		const u8 *mac, u8 type, u16 reason, bool tx_frame)
+{
+	struct skw_del_sta_param params;
+
+	skw_dbg("%s: addr: %pM, reason: %d, tx frame: %d\n",
+		netdev_name(dev), mac, reason, tx_frame);
+
+	params.reason_code = reason;
+	skw_ether_copy(params.mac, mac);
+	params.tx_frame = tx_frame;
+
+	return  skw_send_msg(wiphy, dev, SKW_CMD_DEL_STA, &params,
+			    sizeof(params), NULL, 0);
+}
+
+int skw_del_station(struct wiphy *wiphy, struct net_device *dev,
+			const u8 *mac, u8 subtype, u16 reason)
+{
+	struct skw_peer_ctx *ctx;
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_info("subtype: %d, reason: %d, mac: %pM\n", subtype, reason, mac);
+
+	if (!mac || is_broadcast_ether_addr(mac)) {
+		skw_sap_flush_sta(wiphy, iface, subtype, reason);
+
+		return 0;
+	}
+
+	ctx = skw_peer_ctx(iface, mac);
+	if (!ctx)
+		return -ENOENT;
+
+	return skw_sap_del_sta(wiphy, dev, ctx, subtype, reason);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+static int skw_cfg80211_del_station(struct wiphy *wiphy, struct net_device *dev,
+			   struct station_del_parameters *params)
+{
+	return skw_del_station(wiphy, dev, params->mac,
+			params->subtype, params->reason_code);
+}
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
+static int skw_cfg80211_del_station(struct wiphy *wiphy,
+		struct net_device *dev, const u8 *mac)
+{
+	return skw_del_station(wiphy, dev, mac,
+				12,  /* Deauth */
+				WLAN_REASON_DEAUTH_LEAVING);
+}
+#else
+static int skw_cfg80211_del_station(struct wiphy *wiphy,
+		struct net_device *dev, u8 *mac)
+{
+	return skw_del_station(wiphy, dev, (const u8 *)mac,
+				12,  /* Deauth */
+				WLAN_REASON_DEAUTH_LEAVING);
+}
+#endif
+
+int skw_add_station(struct wiphy *wiphy, struct net_device *dev,
+		    const u8 *mac, struct station_parameters *params)
+{
+	struct skw_iface *iface = netdev_priv(dev);
+	struct skw_peer_ctx *ctx;
+	struct skw_peer *peer;
+	int ret;
+	u8 idx;
+
+	skw_dbg("ndev: %s, mac: %pM, flags: 0x%x\n",
+		netdev_name(dev), mac, params->sta_flags_set);
+
+	ctx = skw_peer_ctx(iface, mac);
+	if (!ctx) {
+		peer = skw_peer_alloc();
+		if (!peer) {
+			skw_err("failed, addr: %pM\n", mac);
+			return -ENOMEM;
+		}
+
+		ret = skw_send_msg(wiphy, dev, SKW_CMD_ADD_STA, (void *)mac,
+				   ETH_ALEN, &idx, sizeof(idx));
+		if (ret) {
+			skw_err("command failed, addr: %pM, ret: %d\n",
+				mac, ret);
+
+			SKW_KFREE(peer);
+			return ret;
+		}
+
+		skw_peer_init(peer, mac, idx);
+		ctx = skw_get_ctx(iface->skw, iface->lmac_id, idx);
+		ret = skw_peer_ctx_bind(iface, ctx, peer);
+		if (ret) {
+			skw_cmd_del_sta(wiphy, dev, mac, 12, SKW_LEAVE, false);
+			SKW_KFREE(peer);
+			return -EINVAL;
+		}
+	}
+
+	skw_peer_ctx_lock(ctx);
+
+	__skw_peer_ctx_transmit(ctx, false);
+	skw_set_state(&ctx->peer->sm, SKW_STATE_AUTHED);
+
+	skw_peer_ctx_unlock(ctx);
+
+	if (iface->key_conf.flags & SKW_KEY_FLAG_WEP_SHARE)
+		skw_set_sta_wep_key(wiphy, iface, mac, SKW_KEY_TYPE_PTK);
+
+	return 0;
+}
+
+static int skw_cfg80211_add_station(struct wiphy *wiphy, struct net_device *dev,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
+		    const u8 *mac,
+#else
+		    u8 *mac,
+#endif
+		    struct station_parameters *params)
+{
+	return skw_add_station(wiphy, dev, (const u8 *)mac, params);
+}
+
+static void skw_set_rate_info(struct skw_rate *rate, struct rate_info *rinfo)
+{
+#if 0
+	skw_dbg("flags: %d, mcs: %d, bw: %d, gi: %d, nss: %d, he_ru: %d\n",
+		rate->flags, rate->mcs_idx, rate->bw,
+		rate->gi, rate->nss, rate->he_ru);
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
+	switch (rate->bw) {
+	case SKW_RATE_INFO_BW_40:
+		rinfo->bw = RATE_INFO_BW_40;
+		break;
+
+	case SKW_RATE_INFO_BW_80:
+		rinfo->bw = RATE_INFO_BW_80;
+		break;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)
+	case SKW_RATE_INFO_BW_HE_RU:
+		rinfo->bw = RATE_INFO_BW_HE_RU;
+		rinfo->he_ru_alloc = rate->he_ru;
+		break;
+#endif
+	default:
+		rinfo->bw = RATE_INFO_BW_20;
+		break;
+	}
+#endif
+
+	rinfo->flags = 0;
+	switch (rate->flags) {
+	case SKW_RATE_INFO_FLAGS_HT:
+		rinfo->mcs = rate->mcs_idx;
+
+		rinfo->flags |= RATE_INFO_FLAGS_MCS;
+		if (rate->gi)
+			rinfo->flags |= RATE_INFO_FLAGS_SHORT_GI;
+
+		break;
+
+	case SKW_RATE_INFO_FLAGS_VHT:
+		rinfo->mcs = rate->mcs_idx;
+		rinfo->nss = rate->nss;
+
+		rinfo->flags |= RATE_INFO_FLAGS_VHT_MCS;
+		if (rate->gi)
+			rinfo->flags |= RATE_INFO_FLAGS_SHORT_GI;
+
+		break;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)
+	case SKW_RATE_INFO_FLAGS_HE:
+		rate->gi = skw_gi_to_nl80211_info_gi(rate->gi);
+		rinfo->mcs = rate->mcs_idx;
+		rinfo->nss = rate->nss;
+		rinfo->he_gi = rate->gi;
+		rinfo->he_dcm = rate->he_dcm;
+		rinfo->flags |= RATE_INFO_FLAGS_HE_MCS;
+		break;
+#endif
+	default:
+		rinfo->legacy = rate->legacy_rate;
+		break;
+	}
+}
+
+static int skw_get_station(struct wiphy *wiphy, struct net_device *dev,
+			   const u8 *mac, struct station_info *sinfo)
+{
+	u64 ts;
+	int ret = -1;
+	struct skw_peer_ctx *ctx;
+	struct skw_station_params params = {0};
+	struct skw_get_sta_resp get_sta_resp;
+	struct skw_iface *iface = netdev_priv(dev);
+
+	// skw_dbg("dev: %s, mac: %pM\n", netdev_name(dev), mac);
+
+	if (!mac)
+		return 0;
+
+	ctx = skw_peer_ctx(iface, mac);
+	if (!ctx)
+		return -ENOENT;
+
+	memset(&get_sta_resp, 0, sizeof(get_sta_resp));
+
+	ts = local_clock();
+	do_div(ts, 1000000);
+	params.timestamp = ts;
+	skw_ether_copy(params.mac, mac);
+
+	ret = skw_send_msg(wiphy, dev, SKW_CMD_GET_STA, &params,
+			   sizeof(params), &get_sta_resp,
+			   sizeof(struct skw_get_sta_resp));
+	if (ret) {
+		skw_warn("failed, ret: %d\n", ret);
+		return ret;
+	}
+
+	sinfo->tx_failed = get_sta_resp.tx_failed;
+	sinfo->filled |= SKW_COMPAT_TX_FAILED;
+
+	sinfo->signal = get_sta_resp.signal;
+	sinfo->filled |= SKW_COMPAT_SIGNAL;
+
+	if (is_skw_sta_mode(iface)) {
+		sinfo->tx_packets = dev->stats.tx_packets;
+		sinfo->filled |= SKW_COMPAT_TX_PACKETS;
+
+		sinfo->tx_bytes = dev->stats.tx_bytes;
+		sinfo->filled |= SKW_COMPAT_TX_BYTES;
+
+		sinfo->rx_packets = dev->stats.rx_packets;
+		sinfo->filled |= SKW_COMPAT_RX_PACKETS;
+
+		sinfo->rx_bytes = dev->stats.rx_bytes;
+		sinfo->filled |= SKW_COMPAT_RX_BYTES;
+	}
+
+	skw_set_rate_info(&get_sta_resp.tx_rate, &sinfo->txrate);
+	sinfo->filled |= SKW_COMPAT_TX_BITRATE;
+
+	skw_peer_ctx_lock(ctx);
+
+	if (ctx->peer) {
+		ctx->peer->tx.rssi = sinfo->signal;
+
+		skw_hex_dump("get_sta_rx_rate:", &get_sta_resp.rx_rate,
+			sizeof(get_sta_resp.rx_rate), false);
+
+		skw_desc_get_rx_rate(&ctx->peer->rx.rate, get_sta_resp.rx_rate.bw,
+			get_sta_resp.rx_rate.ppdu_mode,
+			skw_desc_gi_to_skw_gi(get_sta_resp.rx_rate.gi_type,
+				get_sta_resp.rx_rate.ppdu_mode),
+			skw_desc_nss_to_nss_num(get_sta_resp.rx_rate.nss),
+			get_sta_resp.rx_rate.dcm,
+			get_sta_resp.rx_rate.data_rate);
+		skw_set_rate_info(&ctx->peer->rx.rate, &sinfo->rxrate);
+
+		sinfo->filled |= SKW_COMPAT_RX_BITRATE;
+
+		memcpy(&ctx->peer->tx.rate, &get_sta_resp.tx_rate,
+			 sizeof(struct skw_rate));
+
+		ctx->peer->tx.tx_psr = get_sta_resp.tx_psr;
+		ctx->peer->tx.tx_failed = get_sta_resp.tx_failed;
+
+		memcpy(ctx->peer->rx.filter_cnt,
+			get_sta_resp.filter_cnt, sizeof(get_sta_resp.filter_cnt));
+		memcpy(ctx->peer->rx.filter_drop_offload_cnt,
+			get_sta_resp.filter_drop_offload_cnt,
+			sizeof(get_sta_resp.filter_drop_offload_cnt));
+
+		ctx->peer->tx.percent = get_sta_resp.tx_percent;
+		ctx->peer->rx.percent = get_sta_resp.rx_percent;
+
+		if (is_skw_ap_mode(iface)) {
+			sinfo->tx_packets = ctx->peer->tx.pkts;
+			sinfo->tx_bytes = ctx->peer->tx.bytes;
+			sinfo->rx_packets = ctx->peer->rx.pkts;
+			sinfo->rx_bytes = ctx->peer->rx.bytes;
+			sinfo->filled |= SKW_COMPAT_TX_PACKETS | SKW_COMPAT_TX_BYTES |
+				         SKW_COMPAT_RX_PACKETS | SKW_COMPAT_RX_BYTES;
+		}
+	}
+
+	skw_peer_ctx_unlock(ctx);
+
+//	skw_dbg("tx packets:%u tx_bytes:%llu rx_packets:%u rx_bytes:%llu\n",
+//		sinfo->tx_packets, sinfo->tx_bytes,
+//		sinfo->rx_packets, sinfo->rx_bytes);
+
+	return ret;
+}
+
+static int skw_cfg80211_get_station(struct wiphy *wiphy, struct net_device *dev,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
+			   const u8 *mac,
+#else
+			   u8 *mac,
+#endif
+			   struct station_info *sinfo)
+{
+	return skw_get_station(wiphy, dev, (const u8 *)mac, sinfo);
+}
+
+static void skw_scan_timeout(void *data)
+{
+	struct skw_iface *iface = data;
+
+	if (unlikely(!iface)) {
+		skw_warn("iface is NULL\n");
+		return;
+	}
+
+	skw_queue_work(priv_to_wiphy(iface->skw), iface,
+			SKW_WORK_SCAN_TIMEOUT, NULL, 0);
+}
+
+static bool skw_cqm_bg_scan(struct skw_iface *iface,
+		struct cfg80211_scan_request *req, u16 *target_chn)
+{
+	bool ret;
+
+	if (iface->wdev.iftype != NL80211_IFTYPE_STATION)
+		return false;
+
+	spin_lock_bh(&iface->sta.roam_data.lock);
+	if (iface->sta.roam_data.flags & SKW_IFACE_STA_ROAM_FLAG_CQM_LOW &&
+		iface->sta.core.sm.state == SKW_STATE_COMPLETED &&
+		req->n_channels > 10 && req->n_ssids == 1 &&
+		req->ssids != NULL && req->ssids->ssid_len != 0 &&
+		req->ssids->ssid_len == iface->sta.core.bss.ssid_len &&
+		memcmp(req->ssids->ssid, iface->sta.core.bss.ssid,
+			req->ssids->ssid_len) == 0) {
+		skw_dbg("only %d", iface->sta.roam_data.target_chn);
+		*target_chn = iface->sta.roam_data.target_chn;
+		iface->sta.roam_data.flags &= ~SKW_IFACE_STA_ROAM_FLAG_CQM_LOW;
+		skw_del_timer_work(iface->skw, skw_cqm_scan_timeout);
+		ret = true;
+	} else {
+		*target_chn = 0;
+		ret = false;
+	}
+
+	spin_unlock_bh(&iface->sta.roam_data.lock);
+
+	return ret;
+}
+
+static bool is_skw_6ghz_non_psc_chan(struct ieee80211_channel *channel)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)
+	if (channel->band != NL80211_BAND_6GHZ)
+		return false;
+
+	if (channel->hw_value % 16 != 5)
+		return true;
+#endif
+
+	return false;
+}
+
+static int skw_scan(struct wiphy *wiphy, struct cfg80211_scan_request *req)
+{
+	int i, ret;
+	struct skw_scan_chan_info *chan;
+	int size, nssids_size, offset;
+	u16 roam_chn = 0;
+	u16 scan_chn_num = 0;
+	char *buff = NULL;
+	struct skw_scan_param *param = NULL;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct skw_iface *iface = SKW_WDEV_TO_IFACE(req->wdev);
+
+	skw_dbg("%s: chip: %d, nr_chan: %d, n_ssids: %d, ie_len: %zd\n",
+		skw_iftype_name(req->wdev->iftype), skw->idx,
+		req->n_channels, req->n_ssids, req->ie_len);
+
+	size = sizeof(struct skw_scan_param) +
+	       req->n_channels * sizeof(*chan) +
+	       req->n_ssids * sizeof(struct cfg80211_ssid) +
+	       req->ie_len;
+
+	buff = SKW_ZALLOC(size, GFP_KERNEL);
+	if (!buff) {
+		skw_err("malloc failed, size: %d\n", size);
+		return -ENOMEM;
+	}
+
+	offset = 0;
+
+	param = (struct skw_scan_param *)buff;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+	if (req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR &&
+	    iface->wdev.iftype == NL80211_IFTYPE_STATION) {
+		param->flags |= SKW_SCAN_FLAG_RND_MAC;
+
+		get_random_mask_addr(param->rand_mac,
+				     req->mac_addr,
+				     req->mac_addr_mask);
+	}
+#endif
+
+	if (iface->wdev.iftype == NL80211_IFTYPE_AP) {
+		param->flags |= SKW_SCAN_FLAG_ACS;
+		skw_purge_survey_data(iface);
+	}
+
+	offset += sizeof(struct skw_scan_param);
+	param->chan_offset = offset;
+
+	chan = (struct skw_scan_chan_info *)(buff + offset);
+
+	skw_cqm_bg_scan(iface, req, &roam_chn);
+
+	for (i = 0; i < req->n_channels; i++) {
+		if (unlikely(iface->extend.scan_band_filter)) {
+			if (!(iface->extend.scan_band_filter & BIT(req->channels[i]->band)))
+				continue;
+		}
+
+		if (unlikely(roam_chn && roam_chn != req->channels[i]->hw_value))
+			continue;
+
+		if (is_skw_6ghz_non_psc_chan(req->channels[i]))
+			continue;
+
+		chan->band = to_skw_band(req->channels[i]->band);
+		chan->chan_num = req->channels[i]->hw_value;
+
+		if ((req->channels[i]->flags & SKW_PASSIVE_SCAN) ||
+		    (!req->n_ssids && is_skw_sta_mode(iface)))
+			chan->scan_flags |= SKW_SCAN_FLAG_PASSIVE;
+
+		scan_chn_num++;
+		chan++;
+	}
+
+	param->nr_chan = scan_chn_num;
+	offset += scan_chn_num * sizeof(*chan);
+
+	param->n_ssid = req->n_ssids;
+	if (req->n_ssids) {
+		nssids_size = req->n_ssids * sizeof(struct cfg80211_ssid);
+		memcpy(buff + offset, req->ssids, nssids_size);
+		param->ssid_offset = offset;
+		offset += nssids_size;
+	}
+
+	if (req->ie_len) {
+		memcpy(buff + offset, req->ie, req->ie_len);
+		param->ie_offset = offset;
+		param->ie_len = req->ie_len;
+	}
+
+	skw->scan_req = req;
+	skw->nr_scan_results = 0;
+
+	skw_add_timer_work(skw, "scan_timeout", skw_scan_timeout, iface,
+			SKW_SCAN_TIMEOUT, req, GFP_KERNEL);
+
+	ret = skw_msg_xmit(wiphy, iface->id, SKW_CMD_START_SCAN,
+			   buff, size, NULL, 0);
+	if (ret) {
+		skw->scan_req = NULL;
+		skw_del_timer_work(skw, req);
+		skw_dbg("failed, ret: %d\n", ret);
+	}
+
+	SKW_KFREE(buff);
+
+	return ret;
+}
+
+void skw_scan_done(struct skw_core *skw, struct skw_iface *iface, bool aborted)
+{
+	struct cfg80211_scan_request *scan_req;
+	int ret_val;
+
+	mutex_lock(&skw->lock);
+
+	if (!skw->scan_req)
+		goto ret;
+
+	if (&iface->wdev != skw->scan_req->wdev)
+		goto ret;
+
+	skw_dbg("inst: %d, aborted: %d, scan result: %d\n",
+		iface->id, aborted, skw->nr_scan_results);
+
+	scan_req = skw->scan_req;
+	skw->scan_req = NULL;
+
+	skw_del_timer_work(skw, scan_req);
+
+	if (aborted) {
+		ret_val = skw_msg_xmit(priv_to_wiphy(skw), iface->id,
+				SKW_CMD_STOP_SCAN, NULL, 0, NULL, 0);
+		if (ret_val)
+			skw_warn("failed, return: %d\n", ret_val);
+	}
+
+	skw_compat_scan_done(scan_req, aborted);
+
+ret:
+	mutex_unlock(&skw->lock);
+}
+
+static void skw_abort_scan(struct wiphy *wiphy, struct wireless_dev *wdev)
+{
+	struct skw_iface *iface = SKW_WDEV_TO_IFACE(wdev);
+	struct skw_core *skw = wiphy_priv(wiphy);
+	int ret;
+
+	skw_dbg("inst: %d, scaning: %d\n", iface->id, !!skw->scan_req);
+
+	if (!skw->scan_req)
+		return;
+
+	ret = skw_msg_xmit(wiphy, iface->id, SKW_CMD_STOP_SCAN, NULL, 0, NULL, 0);
+	if (ret)
+		skw_err("failed, ret: %d\n", ret);
+
+	skw_scan_done(skw, iface, false);
+}
+
+static int skw_mbssid_index(struct skw_core *skw, struct cfg80211_bss *bss)
+{
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 1, 0))
+	return skw_bss_priv(bss)->bssid_index;
+#else
+	return bss->bssid_index;
+#endif
+}
+
+static int skw_mbssid_max_indicator(struct skw_core *skw,
+				struct cfg80211_bss *bss)
+{
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 1, 0))
+	return skw_bss_priv(bss)->max_bssid_indicator;
+#else
+	return bss->max_bssid_indicator;
+#endif
+}
+
+const u8 *skw_bss_get_ext_ie(struct cfg80211_bss *bss, u8 ext_eid)
+{
+	const struct cfg80211_bss_ies *ies;
+
+	ies = rcu_dereference(bss->ies);
+	if (!ies)
+		return NULL;
+
+	return skw_find_ie_match(SKW_WLAN_EID_EXTENSION, ies->data,
+				 ies->len, &ext_eid, 1, 2);
+}
+
+static int skw_set_he_mib(struct wiphy *wiphy, struct net_device *ndev, int he_enable)
+{
+	int ret;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	skw_dbg("he_enable: %d\n", he_enable);
+
+	ret = skw_tlv_alloc(&conf, 128, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_HE, &he_enable, 4)) {
+		skw_err("set HE mode [%d] failed\n", he_enable);
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	*plen = conf.total_len;
+	ret = skw_send_msg(wiphy, ndev, SKW_CMD_SET_MIB, conf.buff,
+			   conf.total_len, NULL, 0);
+	if (ret)
+		skw_warn("failed, ret: %d\n", ret);
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+static int skw_set_vht_mib(struct wiphy *wiphy, struct net_device *ndev, int vht_enable)
+{
+	int ret;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	skw_dbg("vht_enable: %d\n", vht_enable);
+
+	ret = skw_tlv_alloc(&conf, 128, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_DOT11_MODE_VHT, &vht_enable, 4)) {
+		skw_err("set vht mode [%d] failed\n", vht_enable);
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	*plen = conf.total_len;
+	ret = skw_send_msg(wiphy, ndev, SKW_CMD_SET_MIB, conf.buff,
+			   conf.total_len, NULL, 0);
+	if (ret)
+		skw_warn("failed, ret: %d\n", ret);
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+static void skw_parse_center_chn(struct cfg80211_bss *bss, int *he_enable,
+				 struct skw_center_chn *cc)
+{
+	unsigned int diff;
+	const u8 *ht_ie, *vht_ie;
+	u8 vht_seg0_idx, vht_seg1_idx;
+	struct ieee80211_ht_operation *ht_oper;
+	struct ieee80211_vht_operation *vht_oper;
+	const u8 *he_ie;
+	struct skw_he_cap_elem *he_cap;
+
+	cc->bw = SKW_CHAN_WIDTH_20;
+	cc->center_chn1 = bss->channel->hw_value;
+	cc->center_chn2 = 0;
+
+	*he_enable = 1;
+
+	if (WARN_ON(!bss))
+		return;
+
+	rcu_read_lock();
+
+	ht_ie = ieee80211_bss_get_ie(bss, WLAN_EID_HT_OPERATION);
+	if (ht_ie && ht_ie[1]) {
+		ht_oper = (struct ieee80211_ht_operation *)(ht_ie + 2);
+
+		cc->center_chn2 = 0;
+
+		switch (ht_oper->ht_param & 0x3) {
+		case IEEE80211_HT_PARAM_CHA_SEC_NONE:
+			cc->bw = SKW_CHAN_WIDTH_20;
+			cc->center_chn1 = ht_oper->primary_chan;
+
+			break;
+
+		case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
+			cc->bw = SKW_CHAN_WIDTH_40;
+			cc->center_chn1 = ht_oper->primary_chan + 2;
+			break;
+
+		case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
+			cc->bw = SKW_CHAN_WIDTH_40;
+			cc->center_chn1 = ht_oper->primary_chan - 2;
+			break;
+
+		default:
+			break;
+		}
+	}
+
+	vht_ie = ieee80211_bss_get_ie(bss, WLAN_EID_VHT_OPERATION);
+	if (vht_ie && vht_ie[1]) {
+		vht_oper = (struct ieee80211_vht_operation *)(vht_ie + 2);
+		cc->center_chn2 = 0;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
+		vht_seg0_idx = vht_oper->center_freq_seg0_idx;
+		vht_seg1_idx = vht_oper->center_freq_seg1_idx;
+#else
+		vht_seg0_idx = vht_oper->center_freq_seg1_idx;
+		vht_seg1_idx = vht_oper->center_freq_seg2_idx;
+#endif
+		switch (vht_oper->chan_width) {
+		case IEEE80211_VHT_CHANWIDTH_80MHZ:
+			cc->bw = SKW_CHAN_WIDTH_80;
+			cc->center_chn1 = vht_seg0_idx;
+
+			if (vht_seg1_idx) {
+				diff = abs(vht_seg1_idx - vht_seg0_idx);
+				if (diff == 8) {
+					cc->bw = SKW_CHAN_WIDTH_160;
+					cc->center_chn1 = vht_seg1_idx;
+				} else if (diff > 8) {
+					cc->bw = SKW_CHAN_WIDTH_80P80;
+					cc->center_chn2 = vht_seg1_idx;
+				}
+			}
+
+			break;
+
+		case IEEE80211_VHT_CHANWIDTH_160MHZ:
+			cc->bw = SKW_CHAN_WIDTH_160;
+			cc->center_chn1 = vht_seg0_idx;
+			break;
+
+		case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
+			cc->bw = SKW_CHAN_WIDTH_80P80;
+			cc->center_chn1 = vht_seg0_idx;
+			cc->center_chn2 = vht_seg1_idx;
+			break;
+
+		default:
+			break;
+		}
+	}
+
+	he_ie = skw_bss_get_ext_ie(bss, SKW_WLAN_EID_EXT_HE_CAPABILITY);
+	if (he_ie && he_ie[1]) {
+		skw_hex_dump("he capa", he_ie, he_ie[1] + 2, false);
+
+		/* 802.11ax D3.0 */
+		he_cap = (struct skw_he_cap_elem *)(he_ie + 3); // ID: 1 + len: 1 + Num: 1
+
+		skw_dbg("band: %d, ppe: 0x%x, phy_cap_info[0]: 0x%x\n",
+			bss->channel->band, he_cap->ppe, he_cap->phy_cap_info[0]);
+
+		if ((he_cap->phy_cap_info[6] & 0x80) == 0x80 &&
+		    (he_cap->ppe & 0x78) == 0x60) { // check BIT[3:6]
+			switch (bss->channel->band) {
+			case NL80211_BAND_2GHZ:
+				*he_enable = 0;
+				break;
+
+			case NL80211_BAND_5GHZ:
+				if (!(he_cap->phy_cap_info[0] &
+				    SKW_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G))
+					*he_enable = 0;
+				break;
+
+			default:
+				break;
+			}
+		}
+	}
+
+	cc->band = to_skw_band(bss->channel->band);
+	skw_dbg("cc->bw:%d cc->band:%d\n", cc->bw, cc->band);
+
+	rcu_read_unlock();
+}
+
+static int skw_cmd_join(struct wiphy *wiphy, struct net_device *ndev,
+			struct cfg80211_bss *bss, u32 bw, u8 band,
+			u16 center_chn1, u16 center_chn2,
+			bool roaming, struct skw_join_resp *resp)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct skw_iface *iface = netdev_priv(ndev);
+	struct skw_join_param *params;
+	int ret = 0, size = 0;
+
+	skw_dbg("bssid: %pM(idx: %d, ind: %d), chn: %d(%d, %d), bw: %d band: %d\n",
+		bss->bssid, skw_mbssid_index(skw, bss),
+		skw_mbssid_max_indicator(skw, bss),
+		bss->channel->hw_value,
+		center_chn1, center_chn2, bw, band);
+
+	size = sizeof(struct skw_join_param) + bss->ies->len;
+	params = SKW_ZALLOC(size, GFP_KERNEL);
+	if (!params)
+		return -ENOMEM;
+
+	params->bandwidth = bw;
+	params->band = band;
+	params->center_chn1 = center_chn1;
+	params->center_chn2 = center_chn2;
+	params->chan_num = bss->channel->hw_value;
+
+	params->reserved = 0;
+	params->roaming = !!roaming;
+	params->capability = bss->capability;
+	params->beacon_interval = bss->beacon_interval;
+	params->bssid_index = skw_mbssid_index(skw, bss);
+	params->max_bssid_indicator = skw_mbssid_max_indicator(skw, bss);
+	memcpy(params->bssid, bss->bssid, ETH_ALEN);
+
+	if (bss->ies->len) {
+		memcpy(params->bss_ie, bss->ies->data, bss->ies->len);
+		params->bss_ie_offset = sizeof(struct skw_join_param);
+		params->bss_ie_len = bss->ies->len;
+	}
+
+	skw_edma_mask_irq(skw, iface->lmac_id);
+	ret = skw_send_msg(wiphy, ndev, SKW_CMD_JOIN, params,
+			   size, resp, sizeof(*resp));
+	if (ret)
+		skw_err("failed, ret: %d\n", ret);
+
+	SKW_KFREE(params);
+
+	return ret;
+}
+
+int skw_cmd_unjoin(struct wiphy *wiphy, struct net_device *ndev,
+		   const u8 *addr, u16 reason, bool tx_frame)
+{
+	int ret;
+	struct skw_disconnect_param params;
+
+	skw_dbg("%s, bssid: %pM, reason: %d\n",
+		netdev_name(ndev), addr, reason);
+
+	memset(&params, 0x0, sizeof(params));
+
+	params.type = SKW_DISCONNECT_ONLY;
+	params.reason_code = reason;
+	params.local_state_change = !tx_frame;
+
+	if (tx_frame)
+		params.type = SKW_DISCONNECT_SEND_DEAUTH;
+
+	ret = skw_send_msg(wiphy, ndev, SKW_CMD_DISCONNECT, &params,
+			   sizeof(params), NULL, 0);
+	if (ret)
+		skw_err("failed, ret: %d\n", ret);
+
+	return ret;
+}
+
+int skw_cmd_monitor(struct wiphy *wiphy, struct cfg80211_chan_def *chandef, u8 mode)
+{
+	int ret = 0;
+	struct skw_set_monitor_param param = {0};
+
+	param.mode = mode;
+	switch (param.mode) {
+	case SKW_MONITOR_CLOSE:
+		break;
+	case SKW_MONITOR_COMMON:
+	case SKW_MONITOR_MAC_CAP:
+	case SKW_MONITOR_PHY_CAP:
+		if (chandef == NULL || chandef->chan == NULL)
+			return -EINVAL;
+		param.chan_num = chandef->chan->hw_value;
+		param.center_chn1 = skw_freq_to_chn(chandef->center_freq1);
+		param.center_chn2 = skw_freq_to_chn(chandef->center_freq2);
+
+		param.bandwidth = to_skw_bw(chandef->width);
+
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	ret = skw_msg_xmit(wiphy, 0, SKW_CMD_SET_MONITOR_PARAM,
+		&param, sizeof(struct skw_set_monitor_param), NULL, 0);
+
+	return ret;
+}
+
+static int skw_cmd_auth(struct wiphy *wiphy, struct net_device *dev,
+			struct cfg80211_auth_request *req)
+{
+	int ret = 0;
+	u16 auth_alg;
+	int size, offset;
+	struct skw_auth_param *params = NULL;
+	struct skw_iface *iface = netdev_priv(dev);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
+	const u8 *auth_data = req->auth_data;
+	size_t auth_data_len = req->auth_data_len;
+#else
+	const u8 *auth_data = req->sae_data;
+	size_t auth_data_len = req->sae_data_len;
+#endif
+
+	switch (req->auth_type) {
+	case NL80211_AUTHTYPE_OPEN_SYSTEM:
+		auth_alg = WLAN_AUTH_OPEN;
+		break;
+	case NL80211_AUTHTYPE_SHARED_KEY:
+		auth_alg = WLAN_AUTH_SHARED_KEY;
+		break;
+	case NL80211_AUTHTYPE_FT:
+		auth_alg = WLAN_AUTH_FT;
+		break;
+	case NL80211_AUTHTYPE_NETWORK_EAP:
+		auth_alg = WLAN_AUTH_LEAP;
+		break;
+	case NL80211_AUTHTYPE_SAE:
+		auth_alg = WLAN_AUTH_SAE;
+		break;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
+	case NL80211_AUTHTYPE_FILS_SK:
+		auth_alg = WLAN_AUTH_FILS_SK;
+		break;
+	case NL80211_AUTHTYPE_FILS_SK_PFS:
+		auth_alg = WLAN_AUTH_FILS_SK_PFS;
+		break;
+	case NL80211_AUTHTYPE_FILS_PK:
+		auth_alg = WLAN_AUTH_FILS_PK;
+		break;
+#endif
+	case NL80211_AUTHTYPE_AUTOMATIC:
+		/*
+		 * Fixme: try open wep first, then set share key after using
+		 * open wep failed.
+		 */
+		auth_alg = WLAN_AUTH_OPEN;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	size = sizeof(struct skw_auth_param) +
+	       req->ie_len +
+	       auth_data_len;
+
+	params = SKW_ZALLOC(size, GFP_KERNEL);
+	if (!params) {
+		skw_err("malloc failed, size: %d\n", size);
+		return -ENOMEM;
+	}
+
+	offset = sizeof(struct skw_auth_param);
+	params->auth_algorithm = auth_alg;
+
+	if (auth_data_len) {
+		params->auth_data_offset = offset;
+		params->auth_data_len = auth_data_len;
+
+		memcpy((u8 *)params + offset, auth_data,
+		       auth_data_len);
+
+		offset += auth_data_len;
+	}
+
+	if (req->ie && req->ie_len) {
+		params->auth_ie_offset = offset;
+		params->auth_ie_len = req->ie_len;
+		memcpy((u8 *)params + offset, req->ie, req->ie_len);
+
+		offset += req->ie_len;
+	}
+
+	memcpy(iface->sta.core.pending.auth_cmd, params, size);
+	iface->sta.core.pending.auth_cmd_len = size;
+
+	ret = skw_msg_xmit_timeout(wiphy, SKW_NDEV_ID(dev), SKW_CMD_AUTH,
+				params, size, NULL, 0, "SKW_CMD_AUTH",
+				msecs_to_jiffies(SKW_CMD_TIMEOUT), 0);
+
+	SKW_KFREE(params);
+
+	return ret;
+}
+
+static inline void skw_oper_and_ht_capa(struct ieee80211_ht_cap *ht_capa,
+		const struct ieee80211_ht_cap *ht_capa_mask)
+{
+	int i;
+	u8 *p1, *p2;
+
+	if (!ht_capa_mask) {
+		memset(ht_capa, 0, sizeof(*ht_capa));
+		return;
+	}
+
+	p1 = (u8 *)(ht_capa);
+	p2 = (u8 *)(ht_capa_mask);
+	for (i = 0; i < sizeof(*ht_capa); i++)
+		p1[i] &= p2[i];
+}
+
+ /*  Do a logical ht_capa &= ht_capa_mask.  */
+static inline void skw_oper_and_vht_capa(struct ieee80211_vht_cap *vht_capa,
+				const struct ieee80211_vht_cap *vht_capa_mask)
+{
+	int i;
+	u8 *p1, *p2;
+
+	if (!vht_capa_mask) {
+		memset(vht_capa, 0, sizeof(*vht_capa));
+		return;
+	}
+
+	p1 = (u8 *)(vht_capa);
+	p2 = (u8 *)(vht_capa_mask);
+	for (i = 0; i < sizeof(*vht_capa); i++)
+		p1[i] &= p2[i];
+}
+
+static int skw_cmd_assoc(struct wiphy *wiphy, struct net_device *dev,
+			 struct cfg80211_assoc_request *req)
+{
+	int ret = 0;
+	int size, offset;
+	char *buff = NULL;
+	struct skw_assoc_req_param *param = NULL;
+	struct skw_iface *iface = netdev_priv(dev);
+
+	size = sizeof(struct skw_assoc_req_param) + req->ie_len;
+
+	buff = SKW_ZALLOC(size, GFP_KERNEL);
+	if (!buff) {
+		skw_err("malloc failed, size: %d\n", size);
+		return -ENOMEM;
+	}
+
+	offset = 0;
+	param = (struct skw_assoc_req_param *)buff;
+	memcpy(&param->ht_capa, &req->ht_capa, sizeof(req->ht_capa));
+
+	skw_oper_and_ht_capa(&param->ht_capa, &req->ht_capa_mask);
+	memcpy(&param->vht_capa, &req->vht_capa, sizeof(req->vht_capa));
+
+	skw_oper_and_vht_capa(&param->vht_capa, &req->vht_capa_mask);
+	memcpy(param->bssid, req->bss->bssid, ETH_ALEN);
+
+	if (req->prev_bssid)
+		memcpy(param->pre_bssid, req->prev_bssid, ETH_ALEN);
+
+	param->req_ie_len = req->ie_len;
+
+	offset += sizeof(struct skw_assoc_req_param);
+	param->req_ie_offset = offset;
+
+	if (req->ie_len)
+		memcpy(param->req_ie, req->ie, req->ie_len);
+
+	memcpy(iface->sta.core.pending.assoc_cmd, buff, size);
+	iface->sta.core.pending.assoc_cmd_len = size;
+
+	ret = skw_msg_xmit_timeout(wiphy, SKW_NDEV_ID(dev), SKW_CMD_ASSOC,
+				buff, size, NULL, 0, "SKW_CMD_ASSOC",
+				msecs_to_jiffies(SKW_CMD_TIMEOUT), 0);
+
+	SKW_KFREE(buff);
+
+	return ret;
+}
+
+static void skw_fix_compatibility_issues(struct wiphy *wiphy,
+		struct skw_iface *iface, struct cfg80211_bss *bss,
+		int he_enable, struct skw_center_chn *cc)
+{
+	struct net_device *ndev = iface->ndev;
+
+	if (ndev->ieee80211_ptr->iftype != NL80211_IFTYPE_STATION &&
+	    ndev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_CLIENT)
+		return;
+
+	skw_set_he_mib(wiphy, ndev, he_enable);
+
+#if 0 // disabled for lite
+	rcu_read_lock();
+
+	he_oper_ie = skw_bss_get_ext_ie(bss, SKW_WLAN_EID_EXT_HE_OPERATION);
+	if (he_oper_ie) {
+		he_oper = (struct skw_he_oper_elem *)(he_oper_ie + 3);
+
+		if (he_oper->bss_color == 0 &&
+		    (int)bss->channel->band == (int)NL80211_BAND_5GHZ &&
+		    !(skw->fw.fw_bw_capa & (SKW_BW_5GHZ_80M | SKW_BW_5GHZ_160M | SKW_BW_5GHZ_8080M))) {
+			if (he_oper->bss_color_disabled == 0) {
+				skw_set_vht_mib(wiphy, ndev, 0);
+				skw_info("Disable VHT");
+			} else {
+				if (cc->bw >= SKW_CHAN_WIDTH_80 &&
+				    skw_bss_check_vendor_name(bss, oui)) {
+					skw_set_he_mib(wiphy, ndev, 0);
+					skw_info("Disable HE");
+
+				}
+			}
+		}
+
+	}
+
+	rcu_read_unlock();
+#endif
+}
+
+static int skw_join(struct wiphy *wiphy, struct net_device *ndev,
+		    struct cfg80211_bss *bss, bool roaming)
+{
+	int ret = 0, he_enable;
+	struct skw_peer *peer;
+	struct skw_peer_ctx *ctx;
+	struct skw_center_chn cc = {};
+	struct skw_join_resp resp = {};
+	struct skw_iface *iface = netdev_priv(ndev);
+	struct skw_sta_core *core = &iface->sta.core;
+
+	skw_wdev_assert_lock(iface);
+
+	peer = skw_peer_alloc();
+	if (!peer) {
+		skw_err("alloc peer failed\n");
+		return -ENOMEM;
+	}
+
+	skw_parse_center_chn(bss, &he_enable, &cc);
+	skw_fix_compatibility_issues(wiphy, iface, bss, he_enable, &cc);
+
+	SKW_CLEAR(iface->flags, SKW_IFACE_FLAG_DEAUTH);
+	ret = skw_cmd_join(wiphy, ndev, bss, cc.bw, cc.band, cc.center_chn1,
+			   cc.center_chn2, roaming, &resp);
+	if (ret < 0) {
+		skw_err("command join failed, ret: %d\n", ret);
+		SKW_KFREE(peer);
+
+		return ret;
+	}
+
+	skw_peer_init(peer, bss->bssid, resp.peer_idx);
+	ctx = skw_get_ctx(iface->skw, resp.lmac_id, resp.peer_idx);
+	ret = skw_peer_ctx_bind(iface, ctx, peer);
+	if (ret) {
+		skw_cmd_unjoin(wiphy, ndev, bss->bssid, SKW_LEAVE, false);
+		SKW_KFREE(peer);
+		return -EFAULT;
+	}
+
+	skw_join_resp_handler(wiphy_priv(wiphy), iface, &resp);
+
+	skw_ether_copy(core->bss.bssid, bss->bssid);
+	core->bss.channel = bss->channel;
+	core->bss.ctx_idx = resp.peer_idx;
+	core->bss.width = cc.bw;
+
+	skw_dpd_set_coeff_params(wiphy, ndev, bss->channel->hw_value,
+				 cc.center_chn1, cc.center_chn2, cc.bw);
+
+	if (!iface->sta.sme_external) {
+		if (!is_valid_ether_addr(iface->sta.conn->prev_bssid))
+			core->bss.auth_type = iface->sta.conn->auth_type;
+	}
+
+	return 0;
+}
+
+static int skw_unjoin(struct wiphy *wiphy, struct net_device *ndev,
+		      const u8 *bssid, u16 reason, bool tx_frame)
+{
+	int ret = 0;
+	struct skw_peer_ctx *ctx;
+	struct skw_iface *iface = netdev_priv(ndev);
+
+	skw_dbg("bssid: %pM, reason: %d\n", bssid, reason);
+
+	if (ndev->ieee80211_ptr->iftype == NL80211_IFTYPE_STATION) {
+		skw_set_he_mib(wiphy, ndev, 1);
+		skw_set_vht_mib(wiphy, ndev, 1);
+	}
+
+	ctx = skw_peer_ctx(iface, bssid);
+	if (!ctx) {
+		skw_warn("bssid: %pM not exist\n", bssid);
+		return 0;
+	}
+
+	skw_peer_ctx_transmit(ctx, false);
+
+	iface->sta.is_wep = false;
+	SKW_SET(iface->flags, SKW_IFACE_FLAG_DEAUTH);
+
+	ret = skw_cmd_unjoin(wiphy, ndev, bssid, reason, tx_frame);
+	if (!ret) {
+		memset(&iface->sta.core.bss, 0x0, sizeof(iface->sta.core.bss));
+		iface->sta.core.bss.ctx_idx = SKW_INVALID_ID;
+
+		skw_lmac_unbind_iface(wiphy_priv(wiphy), iface->lmac_id, iface->id);
+
+		skw_peer_ctx_bind(iface, ctx, NULL);
+	} else {
+		skw_warn("command unjoin failed, ret: %d\n", ret);
+		SKW_CLEAR(iface->flags, SKW_IFACE_FLAG_DEAUTH);
+	}
+
+	return ret;
+}
+
+int skw_sta_leave(struct wiphy *wiphy, struct net_device *dev,
+		const u8 *bssid, u16 reason, bool tx_frame)
+{
+	int i;
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_dbg("bssid: %pM, reason: %d\n", bssid, reason);
+
+	skw_wdev_assert_lock(iface);
+
+	netif_carrier_off(dev);
+
+	if (iface->skw->hw.bus == SKW_BUS_PCIE &&
+		iface->sta.core.sm.state >= SKW_STATE_ASSOCED)
+		skw_edma_dec_refill((void *)iface->skw, iface->lmac_id);
+
+	memset(&iface->wmm, 0x0, sizeof(iface->wmm));
+
+	del_timer_sync(&iface->sta.core.timer);
+
+	skw_set_state(&iface->sta.core.sm, SKW_STATE_NONE);
+	iface->sta.core.sm.flags = 0;
+
+	skw_unjoin(wiphy, dev, bssid, reason, tx_frame);
+	skw_purge_key_conf(&iface->key_conf);
+
+	memset(iface->sta.core.bss.ssid, 0x0, IEEE80211_MAX_SSID_LEN);
+	iface->sta.core.bss.ssid_len = 0;
+
+	for (i = 0; i < SKW_MAX_DEFRAG_ENTRY; i++) {
+		skb_queue_purge(&iface->frag[i].skb_list);
+		iface->frag[i].tid = SKW_INVALID_ID;
+	}
+
+	return 0;
+}
+
+void skw_tx_mlme_mgmt(struct net_device *dev, u16 stype,
+		      const u8 *bssid, const u8 *da, u16 reason)
+{
+	struct ieee80211_mgmt mgmt;
+	struct skw_iface *iface = netdev_priv(dev);
+
+	mgmt.duration = 0;
+	mgmt.seq_ctrl = 0;
+	memcpy(mgmt.da, da, ETH_ALEN);
+	memcpy(mgmt.sa, iface->addr, ETH_ALEN);
+	memcpy(mgmt.bssid, bssid, ETH_ALEN);
+	mgmt.u.deauth.reason_code = cpu_to_le16(reason);
+	mgmt.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | stype);
+
+	skw_cfg80211_tx_mlme_mgmt(dev, (void *)&mgmt, SKW_DEAUTH_FRAME_LEN);
+}
+
+static int skw_auth(struct wiphy *wiphy, struct net_device *ndev,
+		    struct cfg80211_auth_request *req)
+{
+	int ret;
+	struct key_params key;
+	bool roaming = false;
+	struct skw_iface *iface = netdev_priv(ndev);
+	struct skw_bss_cfg *bss = &iface->sta.core.bss;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
+	const u8 *auth_data = req->auth_data;
+#else
+	const u8 *auth_data = req->sae_data;
+#endif
+
+	skw_info("%s, bssid: %pM, auth type: %d, state: %s\n",
+		 netdev_name(ndev), req->bss->bssid,
+		 req->auth_type, skw_state_name(iface->sta.core.sm.state));
+
+	skw_wdev_assert_lock(iface);
+
+#ifdef CONFIG_SWT6621S_USB3_WORKAROUND
+	ret = skw_switch_usb3_to_usb2_using_2G(iface, req->bss->channel->band);
+	if (ret)
+		return ret;
+#endif
+
+	skw_abort_scan(wiphy, ndev->ieee80211_ptr);
+
+	// skw_scan_done(iface->skw, iface, true);
+	// skw_sched_scan_stop(wiphy, ndev, iface->skw->sched_scan_req->reqid);
+
+	switch (iface->sta.core.sm.state) {
+	case SKW_STATE_AUTHING:
+	case SKW_STATE_ASSOCING:
+		return -EBUSY;
+
+	case SKW_STATE_ASSOCED:
+	case SKW_STATE_COMPLETED:
+		if (ether_addr_equal(bss->bssid, req->bss->bssid))
+			return 0;
+
+		roaming = true;
+
+		if (iface->sta.sme_external)
+			skw_tx_mlme_mgmt(iface->ndev, IEEE80211_STYPE_DEAUTH,
+				iface->sta.core.bss.bssid,
+				iface->sta.core.bss.bssid, SKW_LEAVE);
+
+		skw_set_state(&iface->sta.core.sm, SKW_STATE_NONE);
+
+		ret = skw_unjoin(wiphy, ndev, bss->bssid, SKW_LEAVE, false);
+		if (ret)
+			return ret;
+
+		/* fall through */
+		skw_fallthrough;
+	case SKW_STATE_NONE:
+		if (is_valid_ether_addr(bss->bssid)) {
+			skw_warn("unexpected bssid: %pM\n", bss->bssid);
+			ret = skw_unjoin(wiphy, ndev, bss->bssid, 3, false);
+			if (ret)
+				return ret;
+		}
+
+		if (!skw_channel_allowed(wiphy, req->bss->channel->hw_value))
+			return -EBUSY;
+
+		ret = skw_join(wiphy, ndev, req->bss, roaming);
+		if (ret < 0)
+			return ret;
+
+		break;
+
+	default:
+		break;
+	}
+
+	if (req->key && req->key_len) {
+		key.seq = NULL;
+		key.seq_len = 0;
+		key.key = (u8 *)req->key;
+		key.key_len = req->key_len;
+		key.cipher = SKW_CIPHER_SUITE_WEP40;
+
+		if (req->key_len != 5)
+			key.cipher = SKW_CIPHER_SUITE_WEP104;
+
+		ret = skw_add_key(wiphy, ndev, 0, req->key_idx, false, NULL, &key);
+		if (ret < 0) {
+			skw_err("add share key failed, ret: %d\n", ret);
+			goto unjoin;
+		}
+
+		iface->sta.is_wep = true;
+		skw_set_default_key(wiphy, ndev, 0, req->key_idx, true, true);
+	}
+
+	iface->sta.core.auth_start = jiffies;
+	iface->sta.core.pending.retry = 0;
+	iface->sta.core.pending.redo = 0;
+	iface->sta.core.pending.step_start = jiffies;
+	iface->sta.core.pending.ctx_start = jiffies;
+	iface->sta.core.pending.auth_type = req->auth_type;
+	iface->sta.core.sm.rty_state = SKW_RETRY_NONE;
+	iface->sta.core.pending.ctx_to = SKW_AUTH_TIMEOUT;
+	skw_set_state(&iface->sta.core.sm, SKW_STATE_AUTHING);
+
+	ret = skw_cmd_auth(wiphy, ndev, req);
+	if (ret) {
+		skw_dbg("command auth failed, ret: %d\n", ret);
+
+		goto unjoin;
+	}
+
+	skw_set_sta_timer(&iface->sta.core, SKW_STEP_TIMEOUT);
+
+	/* SAE confirm */
+	if (auth_data && le16_to_cpu(*((u16 *)auth_data) == 2) &&
+	    iface->sta.core.sm.flags & SKW_SM_FLAG_SAE_RX_CONFIRM)
+		skw_set_state(&iface->sta.core.sm, SKW_STATE_AUTHED);
+
+	iface->sta.report_deauth = true;
+	return 0;
+
+unjoin:
+	skw_unjoin(wiphy, ndev, req->bss->bssid, SKW_LEAVE, false);
+
+	skw_set_state(&iface->sta.core.sm, SKW_STATE_NONE);
+
+	return ret;
+}
+
+static int skw_cfg80211_auth(struct wiphy *wiphy, struct net_device *dev,
+			     struct cfg80211_auth_request *req)
+{
+	struct skw_iface *iface = netdev_priv(dev);
+
+	iface->sta.report_deauth = false;
+
+	return skw_auth(wiphy, dev, req);
+}
+
+static int skw_assoc(struct wiphy *wiphy, struct net_device *dev,
+		struct cfg80211_assoc_request *req)
+{
+	int ret;
+	const u8 *ssid_ie;
+	struct skw_iface *iface = netdev_priv(dev);
+	struct skw_sta_core *core = &iface->sta.core;
+
+	skw_dbg("%s, bssid: %pM\n", netdev_name(dev), req->bss->bssid);
+
+	skw_wdev_assert_lock(iface);
+
+	switch (core->sm.state) {
+	case SKW_STATE_AUTHING:
+	case SKW_STATE_ASSOCING:
+		return -EBUSY;
+
+	case SKW_STATE_ASSOCED:
+	case SKW_STATE_COMPLETED:
+		if (ether_addr_equal(core->bss.bssid, req->bss->bssid))
+			return 0;
+
+		skw_set_state(&core->sm, SKW_STATE_NONE);
+
+		ret = skw_unjoin(wiphy, dev, core->bss.bssid, SKW_LEAVE, false);
+		if (ret)
+			return ret;
+
+		ret = skw_join(wiphy, dev, req->bss, true);
+		if (ret)
+			return ret;
+
+		skw_set_state(&core->sm, SKW_STATE_AUTHED);
+
+		break;
+
+		/* continue */
+	case SKW_STATE_AUTHED:
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	rcu_read_lock();
+
+	ssid_ie = ieee80211_bss_get_ie(req->bss, WLAN_EID_SSID);
+	if (ssid_ie) {
+		memcpy(core->bss.ssid, ssid_ie + 2, ssid_ie[1]);
+		core->bss.ssid_len = ssid_ie[1];
+	}
+
+	rcu_read_unlock();
+
+	core->cbss = req->bss;
+	core->pending.retry = 0;
+	core->pending.ctx_start = jiffies;
+	iface->sta.core.pending.ctx_to = SKW_ASSOC_TIMEOUT;
+	core->assoc_req_ie_len = 0;
+	memset(core->assoc_req_ie, 0x0, SKW_2K_SIZE);
+
+	skw_set_state(&core->sm, SKW_STATE_ASSOCING);
+
+	ret = skw_cmd_assoc(wiphy, dev, req);
+	if (!ret) {
+		skw_set_sta_timer(core, SKW_STEP_TIMEOUT);
+	} else {
+		skw_err("command assoc failed, ret: %d\n", ret);
+
+		core->cbss = NULL;
+
+		del_timer_sync(&core->timer);
+
+		skw_unjoin(wiphy, dev, req->bss->bssid, SKW_LEAVE, false);
+		skw_set_state(&core->sm, SKW_STATE_NONE);
+
+		memset(core->bss.ssid, 0x0, IEEE80211_MAX_SSID_LEN);
+		core->bss.ssid_len = 0;
+	}
+
+	return ret;
+}
+
+static int skw_cfg80211_assoc(struct wiphy *wiphy, struct net_device *dev,
+			      struct cfg80211_assoc_request *req)
+{
+	return skw_assoc(wiphy, dev, req);
+}
+
+static int skw_cfg80211_deauth(struct wiphy *wiphy, struct net_device *dev,
+			struct cfg80211_deauth_request *req)
+{
+	int ret;
+	struct skw_iface *iface = netdev_priv(dev);
+	bool tx_frame = !req->local_state_change && iface->sta.report_deauth;
+
+	skw_info("%s: bssid: %pM, reason: %d, tx frame: %d\n",
+		 netdev_name(dev), req->bssid, req->reason_code, tx_frame);
+
+	ret = skw_sta_leave(wiphy, dev, req->bssid, req->reason_code, tx_frame);
+	if (!ret && iface->sta.report_deauth) {
+		skw_tx_mlme_mgmt(dev, IEEE80211_STYPE_DEAUTH,
+				 req->bssid, req->bssid,
+				 req->reason_code);
+	} else {
+		skw_err("failed, ret: %d\n", ret);
+	}
+
+	return ret;
+}
+
+static int skw_disassoc(struct wiphy *wiphy, struct net_device *dev,
+			const u8 *bssid, u16 reason, bool tx_frame)
+{
+	int ret = 0;
+
+	skw_info("%s, bssid: %pM, reason: %d, tx frame: %d\n",
+		 netdev_name(dev), bssid, reason, tx_frame);
+
+	ret = skw_sta_leave(wiphy, dev, bssid, reason, tx_frame);
+	if (ret) {
+		skw_err("failed, ret: %d\n", ret);
+		return ret;
+	}
+
+	skw_tx_mlme_mgmt(dev, IEEE80211_STYPE_DISASSOC, bssid, bssid, reason);
+
+	return 0;
+}
+
+void skw_connected(struct net_device *dev, struct skw_connect_param *conn,
+		   const u8 *req_ie, int req_ie_len, const u8 *resp_ie,
+		   int resp_ie_len, u16 status, gfp_t gfp)
+{
+	if (conn->flags & SKW_CONN_FLAG_ASSOCED) {
+		skw_compat_cfg80211_roamed(dev, conn->bssid, req_ie,
+				req_ie_len, resp_ie, resp_ie_len, gfp);
+	} else {
+		cfg80211_connect_result(dev, conn->bssid, req_ie, req_ie_len,
+				resp_ie, resp_ie_len, status, gfp);
+	}
+
+	SKW_SET(conn->flags, SKW_CONN_FLAG_ASSOCED);
+}
+
+void skw_disconnected(struct net_device *dev, u16 reason, const u8 *resp_ie,
+	int resp_ie_len, bool local_gen, gfp_t gfp)
+{
+	struct skw_iface *iface = netdev_priv(dev);
+	struct skw_connect_param *conn = iface->sta.conn;
+
+	mutex_lock(&conn->lock);
+	if (conn->flags & SKW_CONN_FLAG_ASSOCED) {
+		skw_compat_disconnected(dev, reason, NULL, 0, local_gen, gfp);
+	} else {
+		cfg80211_connect_result(dev, conn->bssid, NULL, 0,
+			resp_ie, resp_ie_len, reason, gfp);
+	}
+
+	SKW_CLEAR(iface->sta.conn->flags, SKW_CONN_FLAG_ASSOCED);
+	mutex_unlock(&conn->lock);
+}
+
+int skw_connect_sae_auth(struct wiphy *wiphy, struct net_device *dev,
+			 struct cfg80211_bss *bss)
+{
+	int ret = 0;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0)
+	bool roaming = false;
+	struct skw_iface *iface = netdev_priv(dev);
+	struct skw_connect_param *conn = iface->sta.conn;
+	struct cfg80211_external_auth_params params;
+
+	if (!bss) {
+		cfg80211_connect_result(dev, conn->bssid, NULL, 0, NULL, 0,
+				WLAN_STATUS_UNSPECIFIED_FAILURE,
+				GFP_KERNEL);
+
+		return -EINVAL;
+	}
+
+	// TODO:
+	// unjoin prev bssid for roaming connection
+
+	roaming = is_valid_ether_addr(conn->prev_bssid);
+	ret = skw_join(wiphy, dev, bss, roaming);
+	if (ret < 0) {
+		skw_err("join %pM failed\n", conn->bssid);
+		return ret;
+	}
+
+	skw_set_state(&iface->sta.core.sm, SKW_STATE_AUTHING);
+
+	params.action = NL80211_EXTERNAL_AUTH_START;
+	memcpy(params.bssid, conn->bssid, ETH_ALEN);
+
+	params.ssid.ssid_len = conn->ssid_len;
+	memcpy(params.ssid.ssid, conn->ssid, conn->ssid_len);
+
+	params.key_mgmt_suite = cpu_to_be32(WLAN_AKM_SUITE_SAE);
+	params.status = WLAN_STATUS_SUCCESS;
+
+	ret = cfg80211_external_auth_request(dev, &params, GFP_KERNEL);
+	if (ret) {
+		skw_err("failed, ret: %d\n", ret);
+
+		skw_unjoin(wiphy, dev, conn->bssid, SKW_LEAVE, false);
+		skw_set_state(&iface->sta.core.sm, SKW_STATE_NONE);
+
+		cfg80211_connect_result(dev, conn->bssid, NULL, 0, NULL, 0,
+				WLAN_STATUS_UNSPECIFIED_FAILURE,
+				GFP_KERNEL);
+	}
+#endif
+
+	return ret;
+}
+
+int skw_connect_auth(struct wiphy *wiphy, struct net_device *dev,
+		struct skw_connect_param *conn, struct cfg80211_bss *bss)
+{
+	struct cfg80211_auth_request req;
+
+	if (!bss) {
+		skw_warn("Invalid bss\n");
+		return -EINVAL;
+	}
+
+	memset(&req, 0x0, sizeof(req));
+
+	req.bss = bss;
+	req.key = conn->key_len ? conn->key : NULL;
+	req.key_len = conn->key_len;
+	req.key_idx = conn->key_idx;
+	req.auth_type = conn->auth_type;
+
+	return skw_auth(wiphy, dev, &req);
+}
+
+int skw_connect_assoc(struct wiphy *wiphy, struct net_device *ndev,
+		struct skw_connect_param *conn)
+{
+	int ret = 0;
+	struct cfg80211_assoc_request req = {};
+
+	req.bss = cfg80211_get_bss(wiphy, conn->channel, conn->bssid,
+				   conn->ssid, conn->ssid_len,
+				   SKW_BSS_TYPE_ESS, SKW_PRIVACY_ESS_ANY);
+	if (!req.bss) {
+		skw_info("cfg80211_get_bss null\n");
+		return -ENOENT;
+	}
+
+	req.ie = conn->assoc_ie;
+	req.ie_len = conn->assoc_ie_len;
+	req.prev_bssid = conn->prev_bssid;
+	req.use_mfp = conn->flags & SKW_CONN_FLAG_USE_MFP;
+	req.flags = conn->flags;
+	req.ht_capa = conn->ht_capa;
+	req.ht_capa_mask = conn->ht_capa_mask;
+	req.vht_capa = conn->vht_capa;
+	req.vht_capa_mask = conn->vht_capa_mask;
+
+	ret = skw_assoc(wiphy, ndev, &req);
+
+	cfg80211_put_bss(wiphy, req.bss);
+
+	return ret;
+}
+
+int skw_roam_connect(struct skw_iface *iface, const u8 *bssid, u8 chn,
+		     enum nl80211_band band)
+{
+	struct ieee80211_channel *req_channel = NULL;
+	struct wiphy *wiphy = iface->wdev.wiphy;
+	struct skw_connect_param *conn = iface->sta.conn;
+	u32 freq = 0;
+
+	if (!is_valid_ether_addr(bssid))
+		return -EINVAL;
+
+	skw_dbg("roam from %pM to %pM auth_type: %d, chn: %d, band: %d\n",
+		conn->bssid, bssid, conn->auth_type, chn, band);
+	freq = ieee80211_channel_to_frequency(chn, band);
+	req_channel = ieee80211_get_channel(wiphy, freq);
+
+	if (!req_channel) {
+		skw_err("invalid channel: %d\n", chn);
+		return -EINVAL;
+	}
+#if 0
+	iface->sta.backup = SKW_KMEMDUP(&iface->sta.core.bss,
+					sizeof(iface->sta.core.bss),
+					GFP_KERNEL);
+	if (!iface->sta.backup)
+		return -EINVAL;
+
+	// skw_peer_transmit();
+	memset(&iface->sta.core.bss, 0x0, sizeof(iface->sta.core.bss));
+#endif
+	conn->channel = req_channel;
+	skw_ether_copy(conn->bssid, bssid);
+	skw_ether_copy(conn->prev_bssid, iface->sta.core.bss.bssid);
+
+	conn->auth_type = iface->sta.conn->auth_type;
+
+	skw_queue_local_event(priv_to_wiphy(iface->skw), iface,
+			      SKW_EVENT_LOCAL_STA_CONNECT, NULL, 0);
+
+	return 0;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0))
+static int skw_set_cqm_rssi_config(struct wiphy *wiphy, struct net_device *dev,
+				s32 rssi_thold, u32 rssi_hyst)
+{
+	struct skw_set_cqm_rssi_param cqm_param;
+
+	skw_dbg("dev: %s, thold: %d, hyst: %d\n",
+		netdev_name(dev), rssi_thold, rssi_hyst);
+
+	//TBD: whether to store the config at host driver
+
+	cqm_param.rssi_thold = rssi_thold;
+	cqm_param.rssi_hyst = (u8)rssi_hyst;
+
+	return skw_send_msg(wiphy, dev, SKW_CMD_SET_CQM_RSSI, &cqm_param,
+			    sizeof(cqm_param), NULL, 0);
+}
+
+static int skw_set_cqm_rssi_range_config(struct wiphy *wiphy,
+							struct net_device *dev,
+							s32 rssi_low, s32 rssi_high)
+{
+	struct skw_set_cqm_rssi_param cqm_param = {0};
+	s32 val = rssi_high - rssi_low;
+
+	skw_dbg("dev: %s, rssi_low: %d, rssi_high: %d\n",
+		netdev_name(dev), rssi_low, rssi_high);
+
+	cqm_param.rssi_thold = rssi_low;
+	if (val < 0) {
+		skw_warn("rssi err\n");
+		return -EINVAL;
+	} else {
+		cqm_param.rssi_hyst = (u8)val;
+	}
+
+	return skw_send_msg(wiphy, dev, SKW_CMD_SET_CQM_RSSI, &cqm_param,
+				sizeof(cqm_param), NULL, 0);
+}
+#endif
+
+static int skw_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev,
+			struct cfg80211_connect_params *req)
+{
+	struct skw_iface *iface = netdev_priv(ndev);
+	struct skw_connect_param *conn = iface->sta.conn;
+	const u8 *bssid = skw_compat_bssid(req);
+	struct ieee80211_channel *channel = skw_compat_channel(req);
+
+	skw_dbg("%s, ssid: %s, bssid: %pM, auth: %d, chn: %d key_len: %d\n",
+		netdev_name(ndev), req->ssid, bssid, req->auth_type,
+		channel->hw_value, req->key_len);
+
+	if (!conn) {
+		skw_dbg("conn is NULL\n");
+		return -ENOMEM;
+	}
+
+	if (unlikely(req->ssid_len > IEEE80211_MAX_SSID_LEN)) {
+		skw_err("Invalid SSID: %s, len: %zd\n",
+			req->ssid, req->ssid_len);
+
+		return -EINVAL;
+	}
+
+	mutex_lock(&conn->lock);
+
+	skw_ether_copy(conn->bssid, bssid);
+	eth_zero_addr(conn->prev_bssid);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
+	if (req->prev_bssid)
+		skw_ether_copy(conn->prev_bssid, req->prev_bssid);
+#endif
+
+	if (req->ie && req->ie_len)
+		memcpy(conn->assoc_ie, req->ie, req->ie_len);
+
+	conn->assoc_ie_len = req->ie_len;
+
+	if (req->auth_type == NL80211_AUTHTYPE_AUTOMATIC) {
+		conn->auth_type = NL80211_AUTHTYPE_OPEN_SYSTEM;
+		SKW_SET(conn->flags, SKW_CONN_FLAG_AUTH_AUTO);
+	}
+
+	if (req->key && req->key_len) {
+		memcpy(conn->key, req->key, req->key_len);
+		conn->key_len = req->key_len;
+		conn->key_idx = req->key_idx;
+		SKW_SET(conn->flags, SKW_CONN_FLAG_KEY_VALID);
+	} else
+		conn->key_len = 0;
+
+	conn->ssid_len = req->ssid_len;
+	memcpy(conn->ssid, req->ssid, req->ssid_len);
+
+	conn->auth_type = req->auth_type;
+	conn->ht_capa = req->ht_capa;
+	conn->vht_capa = req->vht_capa;
+
+	conn->ht_capa_mask = req->ht_capa_mask;
+	conn->vht_capa_mask = req->vht_capa_mask;
+
+	mutex_unlock(&conn->lock);
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0))
+	if (iface->wdev.iftype == NL80211_IFTYPE_STATION) {
+		skw_set_cqm_rssi_config(wiphy, ndev, SKW_CQM_DEFAUT_RSSI_THOLD,
+					SKW_CQM_DEFAUT_RSSI_HYST);
+	}
+#endif
+
+	return skw_queue_local_event(wiphy, iface,
+			SKW_EVENT_LOCAL_STA_CONNECT, NULL, 0);
+}
+
+
+static int skw_cfg80211_disconnect(struct wiphy *wiphy,
+			struct net_device *dev, u16 reason)
+{
+	int ret;
+	struct skw_iface *iface = netdev_priv(dev);
+	struct skw_sta_core *core = &iface->sta.core;
+
+	skw_info("%s, reason: %d\n", netdev_name(dev), reason);
+
+	ret = skw_sta_leave(wiphy, dev, core->bss.bssid, reason, true);
+	if (!ret)
+		skw_disconnected(dev, reason, NULL, 0, true, GFP_KERNEL);
+
+	return ret;
+}
+
+static u64 skw_tx_cookie(void)
+{
+	static u64 skw_cookie;
+
+	if (WARN_ON(++skw_cookie == 0))
+		skw_cookie++;
+
+	return skw_cookie;
+}
+
+static int skw_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev,
+				 struct ieee80211_channel *chan,
+				 unsigned int duration, u64 *cookie)
+{
+	int ret;
+	struct skw_roc_param roc;
+	u64 tx_cookie = skw_tx_cookie();
+	struct skw_iface *iface = SKW_WDEV_TO_IFACE(wdev);
+
+	skw_dbg("iface: %u, chan: %u, band: %u duration: %d, cookie: %llu\n",
+		iface->id, chan->hw_value, chan->band, duration, tx_cookie);
+
+	roc.enable = 1;
+	roc.channel_num = chan->hw_value;
+	roc.band = to_skw_band(chan->band);
+	roc.duration = duration;
+	roc.cookie = *cookie = tx_cookie;
+	//TBD: define the referenced value
+	if (chan->flags & IEEE80211_CHAN_NO_HT40MINUS)
+		roc.channel_type = 2;
+	else if (chan->flags & IEEE80211_CHAN_NO_HT40PLUS)
+		roc.channel_type = 1;
+	else if (chan->flags & SKW_IEEE80211_CHAN_NO_20MHZ)
+		roc.channel_type = 0;
+	else
+		roc.channel_type = 3;
+
+	ret = skw_msg_xmit(wiphy, iface->id, SKW_CMD_REMAIN_ON_CHANNEL,
+			   &roc, sizeof(roc), NULL, 0);
+
+	return ret;
+}
+
+static int skw_cancel_roc(struct wiphy *wiphy, struct wireless_dev *wdev, u64 cookie)
+{
+	struct skw_iface *iface = SKW_WDEV_TO_IFACE(wdev);
+	struct skw_roc_param param;
+
+	skw_dbg("cookie: %lld\n", cookie);
+
+#if 0
+	// fixme:
+	if (cookie != skw->remain_on_channel_cookie)
+		return -ENOENT;
+#endif
+
+	memset(&param, 0x0, sizeof(param));
+
+	return skw_msg_xmit(wiphy, iface->id, SKW_CMD_REMAIN_ON_CHANNEL,
+			    &param, sizeof(param), NULL, 0);
+}
+static inline void __skw_set_peer_flags(struct skw_peer_ctx *ctx, u32 flags)
+{
+	if (ctx) {
+		skw_peer_ctx_lock(ctx);
+
+		if (ctx->peer)
+			ctx->peer->flags |= flags;
+
+		skw_peer_ctx_unlock(ctx);
+	}
+}
+
+static void skw_set_peer_flags(struct skw_iface *iface,
+			const u8 *addr, u32 flags)
+{
+	int idx;
+	struct skw_peer_ctx *ctx;
+	u32 peer_map = atomic_read(&iface->peer_map);
+
+	if (!addr)
+		return;
+
+	if (is_unicast_ether_addr(addr)) {
+		ctx = skw_peer_ctx(iface, addr);
+		__skw_set_peer_flags(ctx, flags);
+		return;
+	}
+
+	while (peer_map) {
+		idx = ffs(peer_map) - 1;
+		SKW_CLEAR(peer_map, BIT(idx));
+
+		ctx = &iface->skw->hw.lmac[iface->lmac_id].peer_ctx[idx];
+		__skw_set_peer_flags(ctx, flags);
+	}
+}
+
+int skw_mgmt_tx(struct wiphy *wiphy, struct skw_iface *iface,
+		struct ieee80211_channel *chan, u32 wait, u64 *cookie,
+		bool dont_wait_ack, const void *frame, int frame_len,
+		int total_frame_len, const struct ieee80211_mgmt *mgmt, bool switchover)
+{
+	int ret, total_len;
+	struct skw_mgmt_tx_param *param = NULL;
+	u64 tx_cookie = skw_tx_cookie();
+	u16 fc = SKW_MGMT_SFC(mgmt->frame_control);
+
+	if (!chan || !iface)
+		return -EINVAL;
+
+	if (frame_len > total_frame_len)
+		return -EFBIG;
+
+	skw_dbg("%s: chan: %d, wait: %d, cookie: 0x%llx, no_ack: %d, len: %d total: %d\n",
+		skw_mgmt_name(fc), chan->hw_value, wait, tx_cookie,
+		dont_wait_ack, frame_len, total_frame_len);
+
+	skw_hex_dump("mgmt tx", frame, frame_len, false);
+
+	total_len = sizeof(*param) + frame_len;
+	param = SKW_ZALLOC(total_len, GFP_KERNEL);
+	if (!param) {
+		skw_err("malloc failed, size: %d\n", total_len);
+		return -ENOMEM;
+	}
+
+	param->wait = wait;
+	param->channel = chan->hw_value;
+	param->band = to_skw_band(chan->band);
+	param->dont_wait_for_ack = dont_wait_ack;
+	param->cookie = *cookie = tx_cookie;
+
+	memcpy(param->mgmt, frame, frame_len);
+	param->mgmt_frame_len = total_frame_len;
+
+	ret = skw_msg_xmit(wiphy, iface->id, SKW_CMD_TX_MGMT,
+			   param, total_len, NULL, 0);
+	if (!ret) {
+		if (switchover)
+			if (is_unicast_ether_addr(mgmt->da) &&
+				(fc == IEEE80211_STYPE_DEAUTH ||
+				fc == IEEE80211_STYPE_DISASSOC)) {
+				skw_set_peer_flags(iface, mgmt->da,
+						SKW_PEER_FLAG_DEAUTHED);
+			}
+	} else {
+		skw_err("failed, ret: %d\n", ret);
+	}
+
+	SKW_KFREE(param);
+
+	return ret;
+}
+
+static inline bool is_skw_rrm_report(const void *buf, int buf_len)
+{
+	const struct ieee80211_mgmt *mgmt = buf;
+
+	if (!ieee80211_is_action(mgmt->frame_control))
+		return false;
+
+	if (buf_len < IEEE80211_MIN_ACTION_SIZE +
+		      sizeof(mgmt->u.action.u.measurement))
+		return false;
+
+	if (mgmt->u.action.category != SKW_WLAN_CATEGORY_RADIO_MEASUREMENT)
+		return false;
+
+	if (mgmt->u.action.u.measurement.action_code != WLAN_ACTION_SPCT_MSR_RPRT)
+		return false;
+
+	return true;
+}
+
+static int __skw_cfg80211_mgmt_tx(struct wiphy *wiphy, struct skw_iface *iface,
+				  struct ieee80211_channel *chan, u32 wait,
+				  u64 *cookie, bool dont_wait_for_ack,
+				  const void *frame, int frame_len)
+{
+	struct ieee80211_channel *tx_chan = chan;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	int ret = 0;
+
+#define SKW_MGMT_TX_LEN 1500
+
+	if (!tx_chan) {
+		if (is_skw_sta_mode(iface))
+			tx_chan = iface->sta.core.bss.channel;
+		else
+			tx_chan = iface->sap.cfg.channel;
+	}
+
+
+	skw_hex_dump("frame", frame, frame_len, false);
+
+	down(&skw->cmd.mgmt_cmd_lock);
+
+	if (!skw_cmd_data_len_in_limit(skw, frame_len + sizeof(struct skw_mgmt_tx_param))) {
+		if (is_skw_rrm_report(frame, frame_len)) {
+			int head_offset = offsetof(struct ieee80211_mgmt,
+					u.action.u.measurement.element_id);
+
+			int ret = -E2BIG;
+			int elem_len = 0, next_len = 0;
+			int left = frame_len - head_offset;
+			char *pos = (u8 *)frame + head_offset, *next = pos;
+			char *data = NULL;
+
+			data = SKW_ZALLOC(SKW_MGMT_TX_LEN, GFP_KERNEL);
+			if (!data) {
+				skw_err("alloc %d failed\n", SKW_MGMT_TX_LEN);
+				ret = -ENOMEM;
+				goto unlock;
+			}
+
+			while (left) {
+				int tx_len;
+
+				next_len = next[1] + 2;
+				tx_len = elem_len + head_offset + next_len;
+				if (tx_len < SKW_MGMT_TX_LEN) {
+					elem_len += next_len;
+					left -= next_len;
+
+					if (left) {
+						next += next_len;
+						continue;
+					}
+				}
+
+				memcpy(data, frame, head_offset);
+				memcpy(data + head_offset, pos, elem_len);
+
+				skw_hex_dump("rrm", data, elem_len + head_offset, false);
+
+				ret = skw_mgmt_tx(wiphy, iface, tx_chan, wait,
+						cookie, dont_wait_for_ack, data,
+						elem_len + head_offset, elem_len + head_offset, frame, true);
+
+				pos = next;
+				elem_len = 0;
+			}
+
+			SKW_KFREE(data);
+			goto unlock;
+
+		} else {
+			int send_len;
+			int remain = frame_len;
+			const u8 *data = frame;
+			int buf_size = SKW_MGMT_TX_LEN;
+
+			while (remain > 0) {
+				send_len = (remain < buf_size) ? remain : buf_size;
+				remain -= send_len;
+
+				skw_dbg("mgmt part : remain: %d, send_len: %d \n", remain, send_len);
+				skw_hex_dump("part", data, send_len, false);
+
+				ret = skw_mgmt_tx(wiphy, iface, tx_chan, wait,
+						cookie, true, data, send_len,
+						frame_len, frame, true);
+
+				if (ret) {
+					skw_err("failed, ret: %d\n", ret);
+					goto unlock;
+				}
+
+				data += send_len;
+			}
+
+			if (dont_wait_for_ack == false) {
+				struct skw_mgmt_status status;
+				status.mgmt_status_data = SKW_ZALLOC(frame_len, GFP_KERNEL);
+
+				if (!status.mgmt_status_data) {
+					skw_err("malloc mgmt_status_data failed, size: %d\n", frame_len);
+					ret = -ENOMEM;
+				} else {
+					status.mgmt_status_data_len = frame_len;
+					memcpy(status.mgmt_status_data, frame, frame_len);
+					status.mgmt_status_cookie = *cookie;
+
+					skw_dbg("split part coockie:0x%llx\n", status.mgmt_status_cookie);
+					skw_queue_work(wiphy, iface,
+						SKW_WORK_SPLIT_MGMT_TX_STATUS, &status, sizeof(status));
+				}
+			}
+
+			goto unlock;
+		}
+	}
+
+#undef SKW_MGMT_TX_LEN
+	skw_dump_frame((u8 *)frame, (u16)frame_len);
+	ret = skw_mgmt_tx(wiphy, iface, tx_chan, wait, cookie,
+			dont_wait_for_ack, frame, frame_len, frame_len, frame, true);
+
+unlock:
+	up(&skw->cmd.mgmt_cmd_lock);
+	return ret;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
+static int skw_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
+				struct cfg80211_mgmt_tx_params *params,
+				u64 *cookie)
+{
+	struct skw_iface *iface = SKW_WDEV_TO_IFACE(wdev);
+
+	return __skw_cfg80211_mgmt_tx(wiphy, iface, params->chan,
+				      params->wait, cookie,
+				      params->dont_wait_for_ack,
+				      params->buf, params->len);
+}
+#else
+static int skw_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
+			  struct ieee80211_channel *chan, bool offchan,
+			  unsigned int wait, const u8 *buf, size_t len,
+			  bool no_cck, bool dont_wait_for_ack, u64 *cookie)
+{
+	struct skw_iface *iface = SKW_WDEV_TO_IFACE(wdev);
+
+	return __skw_cfg80211_mgmt_tx(wiphy, iface, chan, wait, cookie,
+			dont_wait_for_ack, buf, len);
+}
+#endif
+
+static int skw_join_ibss(struct wiphy *wiphy, struct net_device *dev,
+			struct cfg80211_ibss_params *params)
+{
+	int i;
+	u8 *pos;
+	struct cfg80211_bss *bss;
+	struct ieee80211_mgmt *mgmt;
+	struct ieee80211_supported_band *sband;
+	struct skw_iface *iface = netdev_priv(dev);
+	struct cfg80211_chan_def *chandef = &params->chandef;
+
+	skw_dbg("%s, bssid: %pM, ssid: %s, channel: %d, band: %u, chan_fixed: %d\n",
+		netdev_name(dev), params->bssid, params->ssid,
+		chandef->chan->hw_value, chandef->chan->band, params->channel_fixed);
+
+	if (params->bssid)
+		memcpy(iface->ibss.bssid, params->bssid, ETH_ALEN);
+	else
+		eth_random_addr(iface->ibss.bssid);
+
+	iface->ibss.bw = to_skw_bw(params->chandef.width);
+	if (iface->ibss.bw == SKW_CHAN_WIDTH_MAX)
+		return -EINVAL;
+
+	iface->ibss.beacon_int = params->beacon_interval;
+	iface->ibss.channel = chandef->chan->hw_value;
+	iface->ibss.band = to_skw_band(chandef->chan->band);
+	iface->ibss.center_freq1 = chandef->center_freq1;
+	iface->ibss.center_freq2 = chandef->center_freq2;
+	iface->ibss.chandef = params->chandef;
+
+	// start build presp frame
+	mgmt = SKW_ZALLOC(SKW_2K_SIZE, GFP_KERNEL);
+	if (!mgmt) {
+		skw_err("malloc failed, size: %d\n", SKW_2K_SIZE);
+		return -ENOMEM;
+	}
+
+	mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+					IEEE80211_STYPE_PROBE_RESP);
+
+	eth_broadcast_addr(mgmt->da);
+	memcpy(mgmt->sa, iface->addr, ETH_ALEN);
+	memcpy(mgmt->bssid, iface->ibss.bssid, ETH_ALEN);
+
+	mgmt->u.beacon.beacon_int = cpu_to_le16(params->beacon_interval);
+	// mgmt->u.beacon.timestamp = cpu_to_le64(0);
+	mgmt->u.beacon.capab_info = cpu_to_le16(WLAN_CAPABILITY_IBSS);
+
+	pos = mgmt->u.beacon.variable;
+
+	*pos++ = WLAN_EID_SSID;
+	*pos++ = params->ssid_len;
+	memcpy(pos, params->ssid, params->ssid_len);
+	pos += params->ssid_len;
+
+	*pos++ = WLAN_EID_SUPP_RATES;
+	*pos++ = 8;
+	sband = wiphy->bands[chandef->chan->band];
+
+	for (i = 0; i < sband->n_bitrates; i++) {
+		int rate = DIV_ROUND_UP(sband->bitrates[i].bitrate, 5);
+		*pos++ = rate | 0x80;
+	}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0)
+	if (sband->band == IEEE80211_BAND_2GHZ) {
+#else
+	if (sband->band == NL80211_BAND_2GHZ) {
+#endif
+		*pos++ = WLAN_EID_DS_PARAMS;
+		*pos++ = 1;
+		*pos++ = chandef->chan->hw_value;
+	}
+
+	*pos++ = WLAN_EID_IBSS_PARAMS;
+	*pos++ = 2;
+	*pos++ = 0;
+	*pos++ = 0;
+#if 0
+	*pos++ = WLAN_EID_EXT_SUPP_RATES;
+	*pos++ = 0;
+#endif
+	if (params->ie) {
+		memcpy(pos, params->ie, params->ie_len);
+		pos += params->ie_len;
+	}
+	// end build frame
+
+//	skw_set_template_frame();
+	bss = cfg80211_get_bss(wiphy, chandef->chan, params->bssid,
+				params->ssid, params->ssid_len,
+				SKW_BSS_TYPE_IBSS,
+				SKW_PRIVACY_IBSS_ANY);
+	if (!bss) {
+		skw_info("creating new ibss: %pM\n", iface->ibss.bssid);
+
+		bss = cfg80211_inform_bss_frame(wiphy, chandef->chan,
+				mgmt, pos - (u8 *)mgmt, DBM_TO_MBM(-30), GFP_KERNEL);
+	}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0)
+	// fixme:
+	if (params->wep_keys) {
+		skw_add_key(wiphy, dev, 0, params->wep_tx_key, true,
+			    iface->ibss.bssid, params->wep_keys);
+
+		skw_set_default_key(wiphy, dev, 0, params->wep_tx_key, true, true);
+	}
+#endif
+
+	cfg80211_put_bss(wiphy, bss);
+
+	skw_queue_local_event(wiphy, iface, SKW_EVENT_LOCAL_IBSS_CONNECT,
+				NULL, 0);
+
+	SKW_KFREE(mgmt);
+	return 0;
+}
+
+static int skw_leave_ibss(struct wiphy *wiphy, struct net_device *dev)
+{
+	struct skw_disconnect_param params = {0};
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_dbg("%s\n", netdev_name(dev));
+
+	iface->ibss.joined = false;
+	iface->ibss.ssid_len = 0;
+
+	params.type = SKW_DISCONNECT_ONLY;
+	params.reason_code = 0;
+
+	return skw_send_msg(wiphy, dev, SKW_CMD_DISCONNECT,
+			&params, sizeof(params), NULL, 0);
+}
+
+static int skw_set_wiphy_params(struct wiphy *wiphy, u32 changed)
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	skw_dbg("changed: 0x%x\n", changed);
+
+	ret = skw_tlv_alloc(&conf, 128, GFP_KERNEL);
+	if (ret)
+		return ret;
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (changed & WIPHY_PARAM_RETRY_SHORT) {
+		if (skw_tlv_add(&conf, SKW_MIB_RETRY_SHORT,
+				&wiphy->retry_short,
+				sizeof(wiphy->retry_short)))
+			skw_err("add SKW_MIB_RETRY_SHORT failed.\n");
+	}
+
+	if (changed & WIPHY_PARAM_RETRY_LONG) {
+		if (skw_tlv_add(&conf, SKW_MIB_RETRY_LONG,
+				&wiphy->retry_long,
+				sizeof(wiphy->retry_long)))
+			skw_err("add SKW_MIB_RETRY_LONG failed.\n");
+	}
+
+
+	if (changed & WIPHY_PARAM_FRAG_THRESHOLD) {
+		if (skw_tlv_add(&conf, SKW_MIB_FRAG_THRESHOLD,
+				&wiphy->frag_threshold,
+				sizeof(wiphy->frag_threshold)))
+			skw_err("add SKW_MIB_FRAG_THRESHOLD failed.\n");
+	}
+
+	if (changed & WIPHY_PARAM_RTS_THRESHOLD) {
+		if (skw_tlv_add(&conf, SKW_MIB_RTS_THRESHOLD,
+				  &wiphy->rts_threshold,
+				  sizeof(wiphy->rts_threshold)))
+			skw_err("add SKW_MIB_RTS_THRESHOLD failed.\n");
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		ret = skw_msg_xmit(wiphy, 0, SKW_CMD_SET_MIB, conf.buff,
+				conf.total_len, NULL, 0);
+	}
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+static int skw_cfg80211_sched_scan_start(struct wiphy *wiphy,
+		struct net_device *dev, struct cfg80211_sched_scan_request *req)
+{
+	int i, ret;
+	struct skw_scan_chan_info *chan = NULL;
+
+	u32 delay = 0;
+	u64 reqid = 0;
+	s8 relative_rssi = 0;
+	bool relative_rssi_set = false;
+	s32 min_rssi_thold = 0;
+	int n_scan_plans = 0, n_plans_len = 0;
+	int n_ssids_len, n_match_len;
+	int size, fixed, offset = 0;
+	u16 scan_chn_num = 0;
+
+
+	struct skw_sched_match_sets *match_sets;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct skw_sched_scan_param *params;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
+	reqid = req->reqid;
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
+	relative_rssi_set = req->relative_rssi_set;
+	relative_rssi = req->relative_rssi;
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
+	n_scan_plans = req->n_scan_plans;
+	n_plans_len = n_scan_plans * sizeof(*req->scan_plans);
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
+	delay = req->delay;
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0)
+	min_rssi_thold = req->rssi_thold;
+#else
+	min_rssi_thold = req->min_rssi_thold;
+#endif
+
+	skw_dbg("%s, n_ssids: %d, n_channels: %d, n_match: %d, n_plans: %d, ie_len: %zd\n",
+		netdev_name(dev), req->n_ssids, req->n_channels,
+		req->n_match_sets, n_scan_plans, req->ie_len);
+
+	fixed = sizeof(struct skw_sched_scan_param);
+	n_ssids_len = req->n_ssids * sizeof(struct cfg80211_ssid);
+	n_match_len = req->n_match_sets * sizeof(struct skw_sched_match_sets);
+
+	size = fixed + req->ie_len + n_ssids_len + n_plans_len + n_match_len +
+	       req->n_channels * sizeof(*chan);
+
+	params = SKW_ZALLOC(size, GFP_KERNEL);
+	if (!params) {
+		skw_err("malloc failed, size: %d\n", size);
+
+		return -ENOMEM;
+	}
+
+	params->req_id = reqid;
+	params->flags = req->flags;
+	params->delay = delay;
+	params->min_rssi_thold = min_rssi_thold;
+	params->relative_rssi_set = relative_rssi_set;
+	params->relative_rssi = relative_rssi;
+	params->scan_width = NL80211_BSS_CHAN_WIDTH_20;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+	skw_ether_copy(params->mac_addr, req->mac_addr);
+	skw_ether_copy(params->mac_addr_mask, req->mac_addr_mask);
+#endif
+
+	params->n_ssids = req->n_ssids;
+	if (req->n_ssids) {
+		params->n_ssid_offset = fixed + offset;
+		params->n_ssids_len = n_ssids_len;
+		memcpy(params->data + offset, req->ssids, n_ssids_len);
+
+		offset += n_ssids_len;
+	}
+
+	match_sets = (void *)params->data + offset;
+	for (i = 0; i < req->n_match_sets; i++) {
+		memcpy(match_sets[i].ssid, req->match_sets[i].ssid.ssid, 32);
+		match_sets[i].ssid_len = req->match_sets[i].ssid.ssid_len;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0)
+		match_sets[i].rssi_thold = req->match_sets[i].rssi_thold;
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
+		skw_ether_copy(match_sets[i].bssid, req->match_sets[i].bssid);
+#endif
+	}
+
+	params->n_match_sets = req->n_match_sets;
+	params->match_sets_offset = fixed + offset;
+	params->match_sets_len = n_match_len;
+	offset += n_match_len;
+
+	params->n_scan_plans = n_scan_plans;
+	if (n_scan_plans) {
+		params->scan_plans_offset = fixed + offset;
+		params->scan_plans_len = n_plans_len;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
+		memcpy(params->data + offset, req->scan_plans, n_plans_len);
+#endif
+
+		offset += n_plans_len;
+	}
+
+	if (req->ie_len) {
+		params->ie_offset = fixed + offset;
+		params->ie_len = req->ie_len;
+		memcpy(params->data + offset, req->ie, req->ie_len);
+		offset += req->ie_len;
+	}
+
+	chan = (struct skw_scan_chan_info *)(params->data + offset);
+	for (i = 0; i < req->n_channels; i++) {
+		if (is_skw_6ghz_non_psc_chan(req->channels[i]))
+			continue;
+
+		chan->band = to_skw_band(req->channels[i]->band);
+		chan->chan_num = req->channels[i]->hw_value;
+		/* BIT[15]: set 1 means to run a passive scan on this channel */
+		if (req->channels[i]->flags & SKW_PASSIVE_SCAN)
+			chan->scan_flags |= SKW_SCAN_FLAG_PASSIVE;
+
+		chan++;
+		scan_chn_num++;
+	}
+
+	params->n_channels = scan_chn_num;
+	params->channels_len = scan_chn_num * sizeof(struct skw_scan_chan_info);
+	params->channels_offset = fixed + offset;
+
+	skw->sched_scan_req = req;
+	ret = skw_send_msg(wiphy, dev, SKW_CMD_START_SCHED_SCAN,
+			   params, size, NULL, 0);
+	if (ret) {
+		skw_err("failed, ret: %d\n", ret);
+		skw->sched_scan_req = NULL;
+	}
+
+	SKW_KFREE(params);
+
+	return ret;
+}
+
+static int skw_sched_scan_stop(struct wiphy *wiphy,
+			struct net_device *dev, u64 reqid)
+{
+	u64 scan_id = 0;
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	skw_dbg("dev: %s, id: %lld, actived: %d\n",
+		netdev_name(dev), scan_id, !!skw->sched_scan_req);
+
+	if (!skw->sched_scan_req)
+		return 0;
+
+	skw->sched_scan_req = NULL;
+	return skw_send_msg(wiphy, dev, SKW_CMD_STOP_SCHED_SCAN,
+			&scan_id, sizeof(scan_id), NULL, 0);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
+static int skw_cfg80211_sched_scan_stop(struct wiphy *wiphy,
+			struct net_device *dev, u64 reqid)
+{
+	return skw_sched_scan_stop(wiphy, dev, reqid);
+}
+#else
+static int skw_cfg80211_sched_scan_stop(struct wiphy *wiphy,
+			struct net_device *dev)
+{
+	return skw_sched_scan_stop(wiphy, dev, 0);
+}
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
+static void skw_mgmt_frame_register(struct wiphy *wiphy,
+				    struct wireless_dev *wdev,
+				    struct mgmt_frame_regs *upd)
+{
+	u64 ts;
+	int ret = 0, idx;
+	struct skw_mgmt_register_param param;
+	struct skw_iface *iface = SKW_WDEV_TO_IFACE(wdev);
+	u16 new_mask = upd->interface_stypes;
+	u16 temp_mask = new_mask ^ iface->mgmt_frame_bitmap;
+	u16 frame_mtype;
+	bool reg;
+
+	if (!temp_mask)
+		return;
+
+	while (temp_mask) {
+		idx = ffs(temp_mask) - 1;
+		SKW_CLEAR(temp_mask, BIT(idx));
+		frame_mtype = idx << 4;
+		reg = new_mask & BIT(idx);
+
+		skw_dbg("%s %s filter %s\n", skw_iftype_name(wdev->iftype),
+			reg ? "add" : "del", skw_mgmt_name(frame_mtype));
+
+		param.frame_type = frame_mtype;
+		param.reg = reg;
+		ts = local_clock();
+		do_div(ts, 1000000);
+
+		param.timestamp = ts;
+		ret = skw_msg_xmit(wiphy, iface->id, SKW_CMD_REGISTER_FRAME,
+				&param, sizeof(param), NULL, 0);
+		if (ret) {
+			skw_err("%s %s failed, ret: %d\n",
+				reg ? "add" : "del",
+				skw_mgmt_name(frame_mtype), ret);
+		}
+	}
+
+	iface->mgmt_frame_bitmap = new_mask;
+}
+#else
+static void skw_mgmt_frame_register(struct wiphy *wiphy,
+				    struct wireless_dev *wdev,
+				    u16 frame_type, bool reg)
+{
+	u64 ts = 0;
+	int ret = 0;
+	struct skw_mgmt_register_param param = {0};
+	int type = (frame_type >> 4) & 0xf;
+	struct skw_iface *iface = SKW_WDEV_TO_IFACE(wdev);
+	u16 bitmap = iface->mgmt_frame_bitmap;
+
+	if (reg)
+		iface->mgmt_frame_bitmap |= BIT(type);
+	else
+		iface->mgmt_frame_bitmap &= ~BIT(type);
+
+	if (bitmap == iface->mgmt_frame_bitmap)
+		return;
+
+	skw_dbg("%s %s filter %s\n", skw_iftype_name(wdev->iftype),
+		reg ? "add" : "del", skw_mgmt_name(frame_type));
+
+	param.frame_type = frame_type;
+	param.reg = reg;
+	ts = local_clock();
+	do_div(ts, 1000000);
+
+	param.timestamp = ts;
+	ret = skw_msg_xmit(wiphy, iface->id, SKW_CMD_REGISTER_FRAME,
+			   &param, sizeof(param), NULL, 0);
+	if (ret) {
+		skw_err("%s %s failed, ret: %d\n",
+			reg ? "add" : "del",
+			skw_mgmt_name(frame_type), ret);
+	}
+}
+#endif
+
+static int skw_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev,
+				bool enabled, int timeout)
+{
+	/* firmware trigger legacy ps automatically */
+	skw_dbg("%s, enabled: %d, timeout: %d\n",
+		netdev_name(dev), enabled, timeout);
+
+	return 0;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
+static int skw_set_qos_map(struct wiphy *wiphy, struct net_device *dev,
+			    struct cfg80211_qos_map *qos_map)
+{
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_dbg("ndev: %s, %s qos_map\n", netdev_name(dev),
+		qos_map ? "add" : "del");
+
+	if (!qos_map) {
+		SKW_KFREE(iface->qos_map);
+		return 0;
+	}
+
+	if (!iface->qos_map) {
+		iface->qos_map = SKW_ZALLOC(sizeof(struct cfg80211_qos_map), GFP_KERNEL);
+		if (!iface->qos_map)
+			return -ENOMEM;
+	}
+
+	memcpy(iface->qos_map, qos_map, sizeof(*qos_map));
+
+	return 0;
+}
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
+static int skw_add_tx_ts(struct wiphy *wiphy, struct net_device *ndev,
+			u8 tsid, const u8 *peer, u8 up, u16 admitted_time)
+{
+	struct skw_ts_info ts;
+
+	skw_dbg("dev: %s, ts id: %d, addr: %pM, up: %d, time: %d\n",
+		netdev_name(ndev), tsid, peer, up, admitted_time);
+	/* cfg80211 will make a sanity check */
+	ts.up = up;
+	ts.tsid = tsid;
+	skw_ether_copy(ts.peer, peer);
+	ts.admitted_time = admitted_time;
+
+	return skw_send_msg(wiphy, ndev, SKW_CMD_ADD_TX_TS,
+			    &ts, sizeof(ts), NULL, 0);
+}
+
+static int skw_del_tx_ts(struct wiphy *wiphy, struct net_device *ndev,
+				u8 tsid, const u8 *peer)
+{
+	struct skw_ts_info ts;
+
+	skw_dbg("dev: %s, ts id: %d, addr: %pM\n",
+		netdev_name(ndev), tsid, peer);
+
+	ts.tsid = tsid;
+	skw_ether_copy(ts.peer, peer);
+	ts.up = 0xFF;
+	ts.admitted_time = 0;
+
+	return skw_send_msg(wiphy, ndev, SKW_CMD_DEL_TX_TS,
+			    &ts, sizeof(ts), NULL, 0);
+}
+#endif
+
+static int skw_tdls_oper(struct wiphy *wiphy, struct net_device *ndev,
+			 const u8 *peer_addr, enum nl80211_tdls_operation oper)
+{
+	int ret = 0;
+	struct skw_iface *iface = netdev_priv(ndev);
+	struct skw_tdls_oper tdls;
+	struct skw_peer_ctx *ctx;
+
+	skw_dbg("dev: %s, oper: %d, addr: %pM\n",
+		netdev_name(ndev), oper, peer_addr);
+
+	ctx = skw_peer_ctx(iface, peer_addr);
+	if (!ctx)
+		return -ENOENT;
+
+	switch (oper) {
+	case NL80211_TDLS_ENABLE_LINK:
+		skw_peer_ctx_transmit(ctx, true);
+		break;
+
+	case NL80211_TDLS_DISABLE_LINK:
+		skw_peer_ctx_transmit(ctx, false);
+		skw_peer_ctx_bind(iface, ctx, NULL);
+
+		break;
+
+	default:
+		ret = -ENOTSUPP;
+		break;
+	}
+
+	if (ret)
+		return ret;
+
+	tdls.oper = oper;
+	skw_ether_copy(tdls.peer_addr, peer_addr);
+
+	return skw_send_msg(wiphy, ndev, SKW_CMD_TDLS_OPER, &tdls,
+			    sizeof(tdls), NULL, 0);
+
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
+static int skw_cfg80211_tdls_oper(struct wiphy *wiphy, struct net_device *ndev,
+			 const u8 *peer_addr, enum nl80211_tdls_operation oper)
+{
+	return skw_tdls_oper(wiphy, ndev, peer_addr, oper);
+}
+#else
+static int skw_cfg80211_tdls_oper(struct wiphy *wiphy, struct net_device *ndev,
+			 u8 *peer_addr, enum nl80211_tdls_operation oper)
+{
+	return skw_tdls_oper(wiphy, ndev, (const u8 *)peer_addr, oper);
+}
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+static int skw_tdls_chn_switch(struct wiphy *wiphy, struct net_device *ndev,
+		const u8 *addr, u8 oper_class, struct cfg80211_chan_def *def)
+{
+	int ret;
+	struct skw_tdls_chan_switch tdls;
+	struct skw_peer_ctx *ctx;
+	struct skw_iface *iface = netdev_priv(ndev);
+
+	skw_dbg("dev: %s, addr: %pM, def chan: %d\n",
+		netdev_name(ndev), addr, def->chan->hw_value);
+
+	ctx = skw_peer_ctx(iface, addr);
+	if (!ctx) {
+		skw_err("can't find tdls peer: %pM\n", addr);
+		return -EINVAL;
+	}
+
+	if (!skw_channel_allowed(wiphy, def->chan->hw_value))
+		return -EBUSY;
+
+	switch (def->width) {
+	case NL80211_CHAN_WIDTH_20:
+	case NL80211_CHAN_WIDTH_20_NOHT:
+		tdls.chan_width = SKW_CHAN_WIDTH_20;
+		break;
+	case NL80211_CHAN_WIDTH_40:
+		tdls.chan_width = SKW_CHAN_WIDTH_40;
+		break;
+	case NL80211_CHAN_WIDTH_80:
+		tdls.chan_width = SKW_CHAN_WIDTH_80;
+		break;
+	default:
+		skw_err("channel width: %d not support\n", def->width);
+		return -ENOTSUPP;
+	}
+
+	skw_ether_copy(tdls.addr, addr);
+	tdls.chn_switch_enable = 1;
+	tdls.oper_class = oper_class;
+	tdls.chn = def->chan->hw_value;
+	tdls.band = to_skw_band(def->chan->band);
+
+	ret = skw_send_msg(wiphy, ndev, SKW_CMD_TDLS_CHANNEL_SWITCH,
+			   &tdls, sizeof(tdls), NULL, 0);
+	if (!ret) {
+		skw_peer_ctx_lock(ctx);
+
+		if (ctx->peer)
+			ctx->peer->channel = def->chan->hw_value;
+
+		skw_peer_ctx_unlock(ctx);
+	}
+
+	return ret;
+}
+
+static void skw_tdls_cancel_chn_switch(struct wiphy *wiphy,
+		struct net_device *ndev, const u8 *addr)
+{
+	struct skw_tdls_chan_switch tdls;
+	struct skw_iface *iface = netdev_priv(ndev);
+
+	skw_dbg("dev: %s, addr: %pM\n", netdev_name(ndev), addr);
+
+	if (!skw_peer_ctx(iface, addr)) {
+		skw_dbg("can't find tdls peer:%pM\n", addr);
+		return;
+	}
+
+	memset(&tdls, 0x0, sizeof(tdls));
+
+	tdls.chn_switch_enable = 0;
+	skw_ether_copy(tdls.addr, addr);
+
+	if (skw_send_msg(wiphy, ndev, SKW_CMD_TDLS_CHANNEL_SWITCH,
+			 &tdls, sizeof(tdls), NULL, 0) < 0)
+		skw_err("set command SKW_CMD_TDLS_CANCEL_CHN_SWITCH failed\n");
+}
+#endif
+
+static int skw_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev,
+			const u8 *peer, u8 action, u8 token, u16 status,
+			u32 peer_capability, bool initiator,
+			const u8 *ies, size_t ies_len)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	return skw_tdls_build_send_mgmt(skw, dev, peer, action, token, status,
+				peer_capability, initiator, ies, ies_len);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0)
+static int skw_cfg80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev,
+			const u8 *peer, u8 action, u8 token, u16 status,
+			u32 peer_capability, bool initiator,
+			const u8 *ies, size_t ies_len)
+{
+	return skw_tdls_mgmt(wiphy, dev, peer, action, token, status,
+			peer_capability, initiator, ies, ies_len);
+}
+#else
+static int skw_cfg80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
+			const u8 *peer, u8 action, u8 token, u16 status,
+#else
+			u8 *peer, u8 action, u8 token, u16 status,
+#endif
+			u32 peer_capability, const u8 *ies, size_t ies_len)
+{
+	return skw_tdls_mgmt(wiphy, dev, (const u8 *)peer, action, token,
+			status, peer_capability, false, ies, ies_len);
+}
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
+//iw phy5 wowlan enable patterns 28+43:34:-:12 16+33:-:11:ee:12:34:-:88:99
+static int skw_wow_enable(struct wiphy *wiphy)
+{
+	int ret = 0;
+#ifdef CONFIG_PM
+	struct cfg80211_wowlan *wow = wiphy->wowlan_config;
+	struct cfg80211_pkt_pattern *patterns = wow->patterns;
+	u32 i, j;
+	int total;
+	struct skw_spd_action_param *spd = NULL;
+	struct skw_wow_input_param *wow_param = NULL;
+	struct skw_wow_rule *rule;
+	struct skw_pkt_pattern *ptn;
+	struct skw_pkt_pattern ptn_tmp;
+	int vi = 0;
+	int y, b, start = 0, gap = 0;
+	u8 *rdata;
+
+	total = sizeof(struct skw_spd_action_param) +
+			 sizeof(struct skw_wow_input_param);
+
+	if (wow->any) {
+		spd = SKW_ZALLOC(total, GFP_KERNEL);
+		if (!spd) {
+			skw_err("malloc failed, size: %d\n", total);
+			return -ENOMEM;
+		}
+
+		wow_param = (struct skw_wow_input_param *)((u8 *)spd
+			 + sizeof(*spd));
+		wow_param->wow_flags = SKW_WOW_ANY_PKT;
+		wow_param->rule_num = 0;
+		spd->sub_cmd = ACTION_EN_WOW;
+		spd->len = sizeof(struct skw_wow_input_param);
+		goto cmd_send;
+	}
+
+	total += sizeof(struct skw_wow_rule) * wow->n_patterns;
+
+	spd = SKW_ZALLOC(total, GFP_KERNEL);
+	if (!spd) {
+		skw_err("malloc failed, size: %d\n", total);
+		return -ENOMEM;
+	}
+
+	wow_param = (struct skw_wow_input_param *)((u8 *)spd
+			+ sizeof(*spd));
+	wow_param->rule_num = wow->n_patterns;
+	spd->sub_cmd = ACTION_EN_WOW;
+
+	if (wow->disconnect)
+		wow_param->wow_flags |= SKW_WOW_DISCONNECT;
+
+	if (wow->magic_pkt)
+		wow_param->wow_flags |= SKW_WOW_MAGIC_PKT;
+
+	if (wow->gtk_rekey_failure)
+		wow_param->wow_flags |= SKW_WOW_GTK_REKEY_FAIL;
+
+	if (wow->eap_identity_req)
+		wow_param->wow_flags |= SKW_WOW_EAP_IDENTITY_REQ;
+
+	if (wow->four_way_handshake)
+		wow_param->wow_flags |= SKW_WOW_FOUR_WAY_HANDSHAKE;
+
+	if (wow->rfkill_release)
+		wow_param->wow_flags |= SKW_WOW_RFKILL_RELEASE;
+
+	for (i = 0; i < wow_param->rule_num; i++) {
+		rule = &wow_param->rules[i];
+		rdata = rule->rule;
+		ptn_tmp.op = PAT_OP_TYPE_SAME;
+		ptn_tmp.type_offset = PAT_TYPE_ETH;
+		ptn_tmp.offset = patterns[i].pkt_offset;
+		ptn_tmp.len = 0;
+
+		vi = 0;
+		start = 0;
+		gap = 0;
+		for (j = 0; j < patterns[i].pattern_len; j++) {
+			y = round_up(j + 1, 8)/8 - 1;
+			b = j%8;
+			if (patterns[i].mask[y] & BIT(b)) {
+				if (!start) {
+					if (vi + sizeof(ptn_tmp)
+						>= sizeof(rule->rule)) {
+						skw_warn("pat:%d overage\n", i);
+						break;
+					}
+
+					ptn =
+					(struct skw_pkt_pattern *)&rdata[vi];
+					memcpy(ptn, &ptn_tmp, sizeof(ptn_tmp));
+					ptn->offset += gap;
+					vi += sizeof(ptn_tmp);
+				}
+
+				rdata[vi++] = patterns[i].pattern[j];
+				ptn->len++;
+				start = 1;
+				gap++;
+
+				if (vi >= sizeof(rule->rule)) {
+					skw_warn("pat:%d overage\n", i);
+					break;
+				}
+			} else {
+				gap++;
+				start = 0;
+			}
+		}
+		rule->len = vi;
+
+		skw_hex_dump("rule", rule, sizeof(*rule), false);
+	}
+
+	spd->len = sizeof(struct skw_wow_input_param) +
+		sizeof(struct skw_wow_rule) * wow_param->rule_num;
+
+cmd_send:
+	skw_dbg("len:%d %d\n", spd->len, total);
+	skw_hex_dump("wow", spd, total, false);
+
+	ret = skw_msg_xmit(wiphy, 0, SKW_CMD_SET_SPD_ACTION,
+			spd, total, NULL, 0);
+	if (ret)
+		skw_err("failed, ret: %d\n", ret);
+
+	SKW_KFREE(spd);
+#endif
+	return ret;
+}
+#endif
+
+int skw_wow_disable(struct wiphy *wiphy)
+{
+	struct skw_spd_action_param spd;
+	int ret = 0;
+
+	spd.sub_cmd = ACTION_DIS_WOW;
+	spd.len = 0;
+
+	ret = skw_msg_xmit(wiphy, 0, SKW_CMD_SET_SPD_ACTION,
+			&spd, sizeof(spd), NULL, 0);
+	if (ret)
+		skw_err("failed, ret: %d\n", ret);
+
+	return ret;
+}
+
+int skw_suspend(struct wiphy *wiphy, struct cfg80211_wowlan *wow)
+{
+	int ret;
+	unsigned long flags;
+	struct skw_suspend_t suspend;
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	skw_dbg("WoW: %s, skw flags: 0x%lx\n",
+		wow ? "enabled" : "disabled", skw->flags);
+
+	/*
+	 * If we have transmitted packets, but don't receive the txc on pcie
+	 * platform, just return busy, because hw can't access the host
+	 * memory while host is sleep.
+	 */
+	if (skw->hw.bus == SKW_BUS_PCIE &&
+		!skw_edma_is_txc_completed(skw)) {
+		skw_dbg("txc is not completed");
+		return -EBUSY;
+	}
+
+	set_bit(SKW_FLAG_BLOCK_TX, &skw->flags);
+
+	memset(&suspend, 0x0, sizeof(suspend));
+
+	/* WoW disabled */
+	if (!wow) {
+		suspend.wow_enable = 0;
+		goto send;
+	}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+	if (wow->nd_config)
+		skw_cfg80211_sched_scan_start(wiphy, wow->nd_config->dev, wow->nd_config);
+#endif
+
+	suspend.wow_enable = 1;
+
+	if (wow->disconnect)
+		suspend.wow_flags |= SKW_WOW_DISCONNECT;
+
+	if (wow->magic_pkt)
+		suspend.wow_flags |= SKW_WOW_MAGIC_PKT;
+
+	if (wow->gtk_rekey_failure)
+		suspend.wow_flags |= SKW_WOW_GTK_REKEY_FAIL;
+
+	if (wow->eap_identity_req)
+		suspend.wow_flags |= SKW_WOW_EAP_IDENTITY_REQ;
+
+	if (wow->four_way_handshake)
+		suspend.wow_flags |= SKW_WOW_FOUR_WAY_HANDSHAKE;
+
+	if (wow->rfkill_release)
+		suspend.wow_flags |= SKW_WOW_RFKILL_RELEASE;
+
+send:
+	flags = BIT(SKW_CMD_FLAG_IGNORE_BLOCK_TX) |
+		BIT(SKW_CMD_FLAG_NO_ACK) |
+		BIT(SKW_CMD_FLAG_NO_WAKELOCK);
+
+	if (skw->hw.bus == SKW_BUS_SDIO)
+		flags |= BIT(SKW_CMD_FLAG_DISABLE_IRQ);
+
+	ret = skw_msg_xmit_timeout(wiphy, 0, SKW_CMD_SUSPEND, &suspend,
+				   sizeof(suspend), NULL, 0, "SKW_CMD_SUSPEND",
+				   msecs_to_jiffies(SKW_CMD_TIMEOUT), flags);
+	if (ret) {
+		clear_bit(SKW_FLAG_BLOCK_TX, &skw->flags);
+
+		skw_err("ret: %d, fw flags: 0x%lx\n", ret, skw->flags);
+	}
+
+	return  ret;
+}
+
+static int skw_cfg80211_suspend(struct wiphy *wiphy, struct cfg80211_wowlan *wow)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	skw_dbg("bus: %s, hw flags: 0x%lx\n", skw_bus_name(skw->hw.bus), skw->hw.flags);
+
+	if (test_bit(SKW_HW_FLAG_CFG80211_PM, &skw->hw.flags))
+		return skw_suspend(wiphy, wow);
+
+	if (!wow) {
+		skw->hw.wow.flags = 0;
+		skw->hw.wow.enabled = false;
+
+		return 0;
+	}
+
+	if (wow->disconnect)
+		skw->hw.wow.flags |= SKW_WOW_DISCONNECT;
+
+	if (wow->magic_pkt)
+		skw->hw.wow.flags |= SKW_WOW_MAGIC_PKT;
+
+	if (wow->gtk_rekey_failure)
+		skw->hw.wow.flags |= SKW_WOW_GTK_REKEY_FAIL;
+
+	if (wow->eap_identity_req)
+		skw->hw.wow.flags |= SKW_WOW_EAP_IDENTITY_REQ;
+
+	if (wow->four_way_handshake)
+		skw->hw.wow.flags |= SKW_WOW_FOUR_WAY_HANDSHAKE;
+
+	if (wow->rfkill_release)
+		skw->hw.wow.flags |= SKW_WOW_RFKILL_RELEASE;
+
+	skw->hw.wow.enabled = true;
+
+	return 0;
+}
+
+int skw_resume(struct wiphy *wiphy)
+{
+	int ret = 0;
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	skw_dbg("skw flags: 0x%lx\n", skw->flags);
+
+	clear_bit(SKW_FLAG_BLOCK_TX, &skw->flags);
+
+	ret = skw_msg_xmit(wiphy, 0, SKW_CMD_RESUME, NULL, 0, NULL, 0);
+	if (ret)
+		skw_warn("ret: %d\n", ret);
+
+	return 0;
+}
+
+static int skw_cfg80211_resume(struct wiphy *wiphy)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	skw_dbg("bus: %s, hw flags: 0x%lx\n", skw_bus_name(skw->hw.bus), skw->hw.flags);
+
+	if (test_bit(SKW_HW_FLAG_CFG80211_PM, &skw->hw.flags))
+		return skw_resume(wiphy);
+
+	return 0;
+}
+
+static void skw_set_wakeup(struct wiphy *wiphy, bool enabled)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
+	if (enabled)
+		skw_wow_enable(wiphy);
+	else
+		skw_wow_disable(wiphy);
+#endif
+
+	device_set_wakeup_enable(wiphy_dev(wiphy), enabled);
+}
+
+static int skw_start_p2p_device(struct wiphy *wiphy, struct wireless_dev *wdev)
+{
+	skw_dbg("traced\n");
+
+	return 0;
+}
+
+static void skw_stop_p2p_device(struct wiphy *wiphy, struct wireless_dev *wdev)
+{
+	skw_dbg("traced\n");
+}
+
+static int skw_probe_client(struct wiphy *wiphy, struct net_device *dev,
+			    const u8 *peer, u64 *cookie)
+{
+	skw_dbg("traced\n");
+
+	return 0;
+}
+
+static int skw_change_bss(struct wiphy *wiphy, struct net_device *ndev,
+		struct bss_parameters *params)
+{
+	struct skw_iface *iface = netdev_priv(ndev);
+
+	skw_dbg("%s ap_isolate:%d\n", netdev_name(ndev), params->ap_isolate);
+
+	if (params->ap_isolate >= 0)
+		iface->sap.ap_isolate = params->ap_isolate;
+
+	return 0;
+}
+
+static int skw_set_monitor_channel(struct wiphy *wiphy,
+		struct cfg80211_chan_def *chandef)
+{
+	return skw_cmd_monitor(wiphy, chandef, SKW_MONITOR_COMMON);
+}
+
+static int skw_dump_survey(struct wiphy *wiphy, struct net_device *ndev,
+		int idx, struct survey_info *info)
+{
+	struct skw_iface *iface = netdev_priv(ndev);
+	struct skw_survey_info *sinfo = NULL;
+	int freq;
+
+	skw_detail("%s, idx: %d\n", netdev_name(ndev), idx);
+
+	sinfo = list_first_entry_or_null(&iface->survey_list,
+					 struct skw_survey_info, list);
+	if (!sinfo) {
+		skw_dbg("last idx: %d\n", idx);
+		return -EINVAL;
+	}
+
+	list_del(&sinfo->list);
+
+	freq = ieee80211_channel_to_frequency(sinfo->data.chan,
+			to_nl80211_band(sinfo->data.band));
+	info->noise = sinfo->data.noise;
+	info->channel = ieee80211_get_channel(wiphy, freq);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
+	info->time = sinfo->data.time;
+	info->time_busy = sinfo->data.time_busy;
+	info->time_ext_busy = sinfo->data.time_ext_busy;
+	info->filled = SURVEY_INFO_TIME |
+		       SURVEY_INFO_TIME_BUSY |
+		       SURVEY_INFO_TIME_EXT_BUSY |
+		       SURVEY_INFO_NOISE_DBM;
+#else
+	info->channel_time = sinfo->data.time;
+	info->channel_time_busy = sinfo->data.time_busy;
+	info->channel_time_ext_busy = sinfo->data.time_ext_busy;
+	info->filled = SURVEY_INFO_CHANNEL_TIME |
+		       SURVEY_INFO_CHANNEL_TIME_BUSY |
+		       SURVEY_INFO_CHANNEL_TIME_EXT_BUSY |
+		       SURVEY_INFO_NOISE_DBM;
+#endif
+
+	SKW_KFREE(sinfo);
+
+	return 0;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0)
+static int skw_external_auth(struct wiphy *wiphy, struct net_device *ndev,
+		struct cfg80211_external_auth_params *params)
+{
+	struct skw_iface *iface = netdev_priv(ndev);
+
+	skw_dbg("%s bssid: %pM, action: %u, status: %u\n",
+		 netdev_name(ndev), params->bssid,
+		 params->action, params->status);
+
+	if (iface->wdev.iftype == NL80211_IFTYPE_AP ||
+	    iface->wdev.iftype == NL80211_IFTYPE_P2P_GO) {
+		return 0;
+	}
+
+	/* Non-AP STA */
+	if (!iface->sta.conn) {
+		skw_set_state(&iface->sta.core.sm, SKW_STATE_NONE);
+		return -EINVAL;
+	}
+
+	if (params->status != WLAN_STATUS_SUCCESS) {
+		skw_set_state(&iface->sta.core.sm, SKW_STATE_NONE);
+		skw_unjoin(wiphy, ndev, params->bssid, SKW_LEAVE, false);
+		// release peer and report connect result
+
+		cfg80211_connect_result(iface->ndev, params->bssid,
+					NULL, 0, NULL, 0,
+					WLAN_STATUS_UNSPECIFIED_FAILURE,
+					GFP_KERNEL);
+		return 0;
+	}
+
+	skw_set_state(&iface->sta.core.sm, SKW_STATE_AUTHED);
+
+	return skw_connect_assoc(wiphy, ndev, iface->sta.conn);
+}
+#endif
+
+static int skw_update_ft_ies(struct wiphy *wiphy, struct net_device *dev,
+			     struct cfg80211_update_ft_ies_params *ftie)
+{
+#if 0
+	struct skw_iface *iface = NULL;
+	struct cfg80211_assoc_request req;
+	u8 *ie = NULL;
+	int ret = 0;
+
+	skw_dbg("md:%u\n", ftie->md);
+
+	if (ftie->ie && ftie->ie_len) {
+		iface->sta.ft_ie = SKW_ZALLOC(ftie->ie_len, GFP_KERNEL);
+		if (iface->sta.ft_ie)
+			memcpy(iface->sta.ft_ie, ftie->ie, ftie->ie_len);
+		iface->sta.ft_ie_len = ftie->ie_len;
+		skw_dbg("ft ie len:%u\n", iface->sta.ft_ie_len);
+	}
+
+	skw_dbg("state:%u\n", iface->sta.core.sm.state);
+	if (iface->sta.core.sm.state != SKW_STATE_AUTHING) {
+		skw_dbg("received update ft cmd during EAPOL process\n");
+		return 0;
+	}
+
+	// req.bss = iface->sta.associating_bss;
+	req.ie_len = iface->sta.assoc_ie_len + ftie->ie_len;
+	ie = SKW_ZALLOC(req.ie_len, GFP_KERNEL);
+	if (!ie) {
+		skw_err("Mem is not enough\n");
+		return -ENOMEM;
+	}
+	memcpy(ie, ftie->ie, ftie->ie_len);
+	memcpy(ie + ftie->ie_len, iface->sta.assoc_ie,
+		iface->sta.assoc_ie_len);
+
+	req.ie = ie;
+	req.prev_bssid = iface->sta.core.bssid;
+	req.use_mfp = iface->sta.use_mfp;
+	req.flags = iface->sta.flags;
+	req.ht_capa = iface->sta.ht_capa;
+	req.ht_capa_mask = iface->sta.ht_capa_mask;
+	req.vht_capa = iface->sta.vht_capa;
+	req.vht_capa_mask = iface->sta.vht_capa_mask;
+
+	ret = skw_assoc(iface->wdev.wiphy, iface->ndev, &req);
+
+	SKW_KFREE(ie);
+	return ret;
+#endif
+	return 0;
+}
+
+static int skw_start_radar_detection(struct wiphy *wiphy, struct net_device *dev,
+				struct cfg80211_chan_def *chandef, u32 cac_time_ms)
+{
+	int ret;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_dbg("dev: %s, channel: %d, cac time: %dms, dfs_region: %d, fw enabled: %d\n",
+		netdev_name(dev), chandef->chan->hw_value, cac_time_ms,
+		skw->dfs.region, skw->dfs.fw_enabled);
+
+	if (!skw->dfs.fw_enabled)
+		return -EINVAL;
+
+	ret = skw_dfs_chan_init(wiphy, dev, chandef, cac_time_ms);
+	if (ret)
+		return ret;
+
+	ret = skw_dfs_start_cac(wiphy, dev);
+	if (!ret) {
+		set_bit(SKW_DFS_FLAG_CAC_MODE, &iface->sap.dfs.flags);
+		queue_delayed_work(skw->event_wq, &iface->sap.dfs.cac_work,
+				msecs_to_jiffies(cac_time_ms));
+	}
+
+	return ret;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)
+static int skw_channel_switch(struct wiphy *wiphy, struct net_device *dev,
+				  struct cfg80211_csa_settings *csa)
+{
+	int ret;
+	char *buff;
+	const u8 *ie;
+	int ie_len, offset = 0;
+
+	skw_dbg("dev: %s, chan: %d, width: %d\n",
+		netdev_name(dev), csa->chandef.chan->hw_value,
+		csa->chandef.width);
+
+	buff = SKW_ZALLOC(csa->beacon_csa.tail_len, GFP_KERNEL);
+	if (!buff)
+		return -ENOMEM;
+
+	ie = cfg80211_find_ie(WLAN_EID_CHANNEL_SWITCH,
+				  csa->beacon_csa.tail,
+				  csa->beacon_csa.tail_len);
+	if (ie) {
+		ie_len = ie[1] + 2;
+
+		memcpy(buff, ie, ie_len);
+		offset = ie_len;
+	}
+
+	ie = cfg80211_find_ie(WLAN_EID_EXT_CHANSWITCH_ANN,
+				   csa->beacon_csa.tail,
+				   csa->beacon_csa.tail_len);
+	if (ie) {
+		ie_len = ie[1] + 2;
+
+		memcpy(buff + offset, ie, ie_len);
+		offset += ie_len;
+	}
+
+	ie = cfg80211_find_ie(WLAN_EID_WIDE_BW_CHANNEL_SWITCH,
+				   csa->beacon_csa.tail,
+				   csa->beacon_csa.tail_len);
+	if (ie) {
+		ie_len = ie[1] + 2;
+
+		memcpy(buff + offset, ie, ie_len);
+		offset += ie_len;
+	}
+
+	ie = cfg80211_find_ie(WLAN_EID_CHANNEL_SWITCH_WRAPPER,
+				   csa->beacon_csa.tail,
+				   csa->beacon_csa.tail_len);
+	if (ie) {
+		ie_len = ie[1] + 2;
+
+		memcpy(buff + offset, ie, ie_len);
+		offset += ie_len;
+	}
+
+	ret = skw_send_msg(wiphy, dev, SKW_CMD_REQ_CHAN_SWITCH,
+			buff, offset, NULL, 0);
+
+	SKW_KFREE(buff);
+
+	return ret;
+}
+#endif
+
+#ifdef __SKW_ANDROID__
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 74)
+static int skw_cfg80211_get_key(struct wiphy *wiphy, struct net_device *dev,
+		int link_id, u8 key_idx, bool pairwise,
+		const u8 *mac_addr, void *cookie,
+		void (*callback)(void *cookie, struct key_params *params))
+{
+	return skw_get_key(wiphy, dev, link_id, key_idx, pairwise,
+			mac_addr, cookie, callback);
+}
+
+static int skw_cfg80211_add_key(struct wiphy *wiphy, struct net_device *dev,
+			int link_id, u8 key_idx, bool pairwise,
+			const u8 *addr, struct key_params *params)
+{
+	return skw_add_key(wiphy, dev, link_id, key_idx, pairwise, addr, params);
+}
+
+static int skw_cfg80211_del_key(struct wiphy *wiphy, struct net_device *dev,
+			int link_id, u8 key_idx, bool pairwise, const u8 *addr)
+{
+	return skw_del_key(wiphy, dev, link_id, key_idx, pairwise, addr);
+}
+
+static int skw_cfg80211_set_default_key(struct wiphy *wiphy, struct net_device *dev,
+			       int link_id, u8 key_idx, bool unicast, bool multicast)
+{
+	return skw_set_default_key(wiphy, dev, link_id, key_idx, unicast, multicast);
+}
+
+static int skw_cfg80211_set_default_mgmt_key(struct wiphy *wiphy,
+		struct net_device *dev, int link_id, u8 key_idx)
+{
+	return skw_set_default_mgmt_key(wiphy, dev, link_id, key_idx);
+}
+
+static int skw_cfg80211_disassoc(struct wiphy *wiphy, struct net_device *dev,
+			struct cfg80211_disassoc_request *req)
+{
+	bool tx = !req->local_state_change;
+	const u8 *bssid = req->ap_addr;
+
+	return skw_disassoc(wiphy, dev, bssid, req->reason_code, tx);
+}
+
+#else
+static int skw_cfg80211_get_key(struct wiphy *wiphy, struct net_device *dev,
+		u8 key_idx, bool pairwise, const u8 *mac_addr, void *cookie,
+		void (*callback)(void *cookie, struct key_params *params))
+{
+	return skw_get_key(wiphy, dev, 0, key_idx, pairwise,
+			mac_addr, cookie, callback);
+}
+
+static int skw_cfg80211_add_key(struct wiphy *wiphy, struct net_device *dev,
+			u8 key_idx, bool pairwise,
+			const u8 *addr, struct key_params *params)
+{
+	return skw_add_key(wiphy, dev, 0, key_idx, pairwise, addr, params);
+}
+
+static int skw_cfg80211_del_key(struct wiphy *wiphy, struct net_device *dev,
+			u8 key_idx, bool pairwise, const u8 *addr)
+{
+	return skw_del_key(wiphy, dev, 0, key_idx, pairwise, addr);
+}
+
+static int skw_cfg80211_set_default_key(struct wiphy *wiphy, struct net_device *dev,
+			       u8 key_idx, bool unicast, bool multicast)
+{
+	return skw_set_default_key(wiphy, dev, 0, key_idx, unicast, multicast);
+}
+
+static int skw_cfg80211_set_default_mgmt_key(struct wiphy *wiphy,
+		struct net_device *dev, u8 key_idx)
+{
+	return skw_set_default_mgmt_key(wiphy, dev, 0, key_idx);
+}
+
+static int skw_cfg80211_disassoc(struct wiphy *wiphy, struct net_device *dev,
+			struct cfg80211_disassoc_request *req)
+{
+	bool tx = !req->local_state_change;
+	const u8 *bssid = (const u8 *)req->bss->bssid;
+
+	return skw_disassoc(wiphy, dev, bssid, req->reason_code, tx);
+}
+
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 41)
+static int skw_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev,
+			unsigned int link_id)
+{
+	return skw_stop_ap(wiphy, dev, link_id);
+}
+#else
+static int skw_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev)
+{
+	return skw_stop_ap(wiphy, dev, 0);
+}
+#endif
+
+#else /* Linux */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)
+static int skw_cfg80211_get_key(struct wiphy *wiphy, struct net_device *dev,
+		int link_id, u8 key_idx, bool pairwise,
+		const u8 *mac_addr, void *cookie,
+		void (*callback)(void *cookie, struct key_params *params))
+{
+	return skw_get_key(wiphy, dev, link_id, key_idx, pairwise,
+			mac_addr, cookie, callback);
+}
+
+static int skw_cfg80211_add_key(struct wiphy *wiphy, struct net_device *dev,
+			int link_id, u8 key_idx, bool pairwise,
+			const u8 *addr, struct key_params *params)
+{
+	return skw_add_key(wiphy, dev, link_id, key_idx, pairwise, addr, params);
+}
+
+static int skw_cfg80211_del_key(struct wiphy *wiphy, struct net_device *dev,
+			int link_id, u8 key_idx, bool pairwise, const u8 *addr)
+{
+	return skw_del_key(wiphy, dev, link_id, key_idx, pairwise, addr);
+}
+
+static int skw_cfg80211_set_default_key(struct wiphy *wiphy, struct net_device *dev,
+			       int link_id, u8 key_idx, bool unicast, bool multicast)
+{
+	return skw_set_default_key(wiphy, dev, link_id, key_idx, unicast, multicast);
+}
+
+static int skw_cfg80211_set_default_mgmt_key(struct wiphy *wiphy,
+		struct net_device *dev, int link_id, u8 key_idx)
+{
+	return skw_set_default_mgmt_key(wiphy, dev, link_id, key_idx);
+}
+
+static int skw_cfg80211_disassoc(struct wiphy *wiphy, struct net_device *dev,
+			struct cfg80211_disassoc_request *req)
+{
+	bool tx = !req->local_state_change;
+	const u8 *bssid = req->ap_addr;
+
+	return skw_disassoc(wiphy, dev, bssid, req->reason_code, tx);
+}
+
+static int skw_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev,
+		unsigned int link_id)
+{
+	return skw_stop_ap(wiphy, dev, link_id);
+}
+#else
+static int skw_cfg80211_get_key(struct wiphy *wiphy, struct net_device *dev,
+		u8 key_idx, bool pairwise, const u8 *mac_addr, void *cookie,
+		void (*callback)(void *cookie, struct key_params *params))
+{
+	return skw_get_key(wiphy, dev, 0, key_idx, pairwise,
+			mac_addr, cookie, callback);
+}
+
+static int skw_cfg80211_add_key(struct wiphy *wiphy, struct net_device *dev,
+			u8 key_idx, bool pairwise,
+			const u8 *addr, struct key_params *params)
+{
+	return skw_add_key(wiphy, dev, 0, key_idx, pairwise, addr, params);
+}
+
+static int skw_cfg80211_del_key(struct wiphy *wiphy, struct net_device *dev,
+			u8 key_idx, bool pairwise, const u8 *addr)
+{
+	return skw_del_key(wiphy, dev, 0, key_idx, pairwise, addr);
+}
+
+static int skw_cfg80211_set_default_key(struct wiphy *wiphy, struct net_device *dev,
+			       u8 key_idx, bool unicast, bool multicast)
+{
+	return skw_set_default_key(wiphy, dev, 0, key_idx, unicast, multicast);
+}
+
+static int skw_cfg80211_set_default_mgmt_key(struct wiphy *wiphy,
+		struct net_device *dev, u8 key_idx)
+{
+	return skw_set_default_mgmt_key(wiphy, dev, 0, key_idx);
+}
+
+static int skw_cfg80211_disassoc(struct wiphy *wiphy, struct net_device *dev,
+			struct cfg80211_disassoc_request *req)
+{
+	bool tx = !req->local_state_change;
+	const u8 *bssid = (const u8 *)req->bss->bssid;
+
+	return skw_disassoc(wiphy, dev, bssid, req->reason_code, tx);
+}
+
+static int skw_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev)
+{
+	return skw_stop_ap(wiphy, dev, 0);
+}
+#endif
+
+#endif /* Linux */
+
+static struct cfg80211_ops skw_cfg80211_ops  = {
+	.add_virtual_intf = skw_cfg80211_add_virtual_intf,
+	.del_virtual_intf = skw_cfg80211_del_virtual_intf,
+	.change_virtual_intf = skw_cfg80211_change_intf,
+	.scan = skw_scan,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0)
+	.abort_scan = skw_abort_scan,
+#endif
+	.get_key = skw_cfg80211_get_key,
+	.add_key = skw_cfg80211_add_key,
+	.del_key = skw_cfg80211_del_key,
+	.set_default_key = skw_cfg80211_set_default_key,
+	.set_default_mgmt_key = skw_cfg80211_set_default_mgmt_key,
+	.change_beacon = skw_change_beacon,
+	.start_ap = skw_cfg80211_start_ap,
+	.stop_ap = skw_cfg80211_stop_ap,
+	.add_station = skw_cfg80211_add_station,
+	.change_station = skw_cfg80211_change_station,
+	.del_station = skw_cfg80211_del_station,
+	.get_station = skw_cfg80211_get_station,
+	.auth = skw_cfg80211_auth,
+	.assoc = skw_cfg80211_assoc,
+	.deauth = skw_cfg80211_deauth,
+	.disassoc = skw_cfg80211_disassoc,
+	.connect = skw_cfg80211_connect,
+	.disconnect = skw_cfg80211_disconnect,
+	.join_ibss = skw_join_ibss,
+	.leave_ibss = skw_leave_ibss,
+	.set_wiphy_params = skw_set_wiphy_params,
+	.remain_on_channel = skw_remain_on_channel,
+	.cancel_remain_on_channel = skw_cancel_roc,
+	.mgmt_tx = skw_cfg80211_mgmt_tx,
+	.sched_scan_start = skw_cfg80211_sched_scan_start,
+	.sched_scan_stop = skw_cfg80211_sched_scan_stop,
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0))
+	.update_mgmt_frame_registrations = skw_mgmt_frame_register,
+#else
+	.mgmt_frame_register = skw_mgmt_frame_register,
+#endif
+	.set_power_mgmt = skw_set_power_mgmt,
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0))
+	.set_cqm_rssi_config = skw_set_cqm_rssi_config,
+	.set_cqm_rssi_range_config = skw_set_cqm_rssi_range_config,
+#endif
+	.start_p2p_device = skw_start_p2p_device,
+	.stop_p2p_device = skw_stop_p2p_device,
+	.set_mac_acl = skw_set_mac_acl,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
+	.set_qos_map = skw_set_qos_map,
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
+	.add_tx_ts = skw_add_tx_ts,
+	.del_tx_ts = skw_del_tx_ts,
+#endif
+	.tdls_mgmt = skw_cfg80211_tdls_mgmt,
+	.tdls_oper = skw_cfg80211_tdls_oper,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+	.tdls_channel_switch = skw_tdls_chn_switch,
+	.tdls_cancel_channel_switch = skw_tdls_cancel_chn_switch,
+#endif
+	.suspend = skw_cfg80211_suspend,
+	.resume = skw_cfg80211_resume,
+	.set_wakeup = skw_set_wakeup,
+	.probe_client = skw_probe_client,
+	.dump_survey = skw_dump_survey,
+	.set_monitor_channel = skw_set_monitor_channel,
+	.change_bss = skw_change_bss,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0)
+	.external_auth = skw_external_auth,
+#endif
+	.update_ft_ies = skw_update_ft_ies,
+	.start_radar_detection = skw_start_radar_detection,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)
+	.channel_switch = skw_channel_switch,
+#endif
+};
+
+static void skw_regd_notifier(struct wiphy *wiphy,
+			      struct regulatory_request *req)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	skw_info("regd: %s, initiator = %d, dfs_region: %d\n",
+		req->alpha2, req->initiator, req->dfs_region);
+
+	skw->dfs.region = req->dfs_region;
+
+	if (!skw_set_wiphy_regd(wiphy, req->alpha2))
+		skw_cmd_set_regdom(wiphy, req->alpha2);
+}
+
+struct wiphy *skw_alloc_wiphy(int priv_size)
+{
+#ifdef CONFIG_SWT6621S_STA_SME_EXT
+	skw_cfg80211_ops.connect = NULL;
+	skw_cfg80211_ops.disconnect = NULL;
+#else
+	skw_cfg80211_ops.auth = NULL;
+	skw_cfg80211_ops.assoc = NULL;
+	skw_cfg80211_ops.deauth = NULL;
+	skw_cfg80211_ops.disassoc = NULL;
+#endif
+
+	return wiphy_new(&skw_cfg80211_ops, priv_size);
+}
+
+#ifdef CONFIG_PM
+/* cfg80211 wowlan definitions */
+#define SKW_WOWLAN_MAX_PATTERNS              15
+#define SKW_WOWLAN_MIN_PATTERN_LEN           1
+#define SKW_WOWLAN_MAX_PATTERN_LEN           255
+#define SKW_WOWLAN_PKT_FILTER_ID_FIRST       201
+
+static const struct wiphy_wowlan_support skw_wowlan_support = {
+	.flags = WIPHY_WOWLAN_ANY |
+		 WIPHY_WOWLAN_DISCONNECT |
+		 WIPHY_WOWLAN_MAGIC_PKT,
+	.n_patterns = SKW_WOWLAN_MAX_PATTERNS,
+	.pattern_min_len = SKW_WOWLAN_MIN_PATTERN_LEN,
+	.pattern_max_len = SKW_WOWLAN_MAX_PATTERN_LEN,
+	.max_pkt_offset = SKW_WOWLAN_MAX_PATTERN_LEN,
+};
+#endif /* CONFIG_PM */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
+struct skw_iftype_ext_cap iftype_ext_cap[NUM_NL80211_IFTYPES] = {
+	{NL80211_IFTYPE_STATION,	{0}, 0},
+	{NL80211_IFTYPE_AP,		{0}, 0},
+	{NL80211_IFTYPE_P2P_GO,		{0}, 0},
+#ifndef CONFIG_SWT6621S_LEGACY_P2P
+	{NL80211_IFTYPE_P2P_DEVICE,	{0}, 0},
+#endif
+};
+
+static struct skw_iftype_ext_cap *skw_get_iftype_ext_cap(u8 iftype)
+{
+	int i;
+	struct skw_iftype_ext_cap *capab = NULL;
+
+	for (i = 0; i < NUM_NL80211_IFTYPES; i++) {
+		if (iftype_ext_cap[i].iftype == iftype)
+			capab = &iftype_ext_cap[iftype];
+	}
+
+	return capab;
+}
+
+static void skw_setup_wiphy_iftype_ext_cap(struct wiphy *wiphy)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct wiphy_iftype_ext_capab *capab = NULL;
+	struct skw_iftype_ext_cap *skw_ext_cap = NULL;
+
+	skw->num_iftype_ext_capab  = 0;
+
+	if (wiphy->interface_modes & (BIT(NL80211_IFTYPE_STATION))) {
+		capab = &skw->iftype_ext_cap[NL80211_IFTYPE_STATION];
+		capab->iftype = NL80211_IFTYPE_STATION;
+		skw_ext_cap = skw_get_iftype_ext_cap(capab->iftype);
+		capab->extended_capabilities = skw_ext_cap->ext_cap;
+		capab->extended_capabilities_mask = skw_ext_cap->ext_cap;
+		capab->extended_capabilities_len = skw_ext_cap->ext_cap_len;
+		skw->num_iftype_ext_capab++;
+	}
+
+	if (wiphy->interface_modes & (BIT(NL80211_IFTYPE_AP))) {
+		capab = &skw->iftype_ext_cap[NL80211_IFTYPE_AP];
+		capab->iftype = NL80211_IFTYPE_AP;
+		skw_ext_cap = skw_get_iftype_ext_cap(capab->iftype);
+		capab->extended_capabilities = skw_ext_cap->ext_cap;
+		capab->extended_capabilities_mask = skw_ext_cap->ext_cap;
+		capab->extended_capabilities_len = skw_ext_cap->ext_cap_len;
+		skw->num_iftype_ext_capab++;
+	}
+
+	skw->num_iftype_ext_capab  = 0; //Remove it after set the actual info
+	wiphy->num_iftype_ext_capab = skw->num_iftype_ext_capab;
+	wiphy->iftype_ext_capab = skw->iftype_ext_cap;
+}
+#endif
+
+static void skw_sync_band_capa(struct ieee80211_supported_band *band,
+				struct skw_chip_info *chip)
+{
+	u32 flags;
+	u16 bit_rate;
+	int i, mcs_map;
+	int tx_chain = 0, rx_chain = 0;
+
+	band->ht_cap.cap = chip->ht_capa;
+	band->ht_cap.ht_supported = true;
+	band->ht_cap.ampdu_factor = chip->ht_ampdu_param & 0x3;
+	band->ht_cap.ampdu_density = (chip->ht_ampdu_param >> 2) & 0x7;
+
+	for (i = 0; i < 4; i++) {
+		mcs_map = (chip->ht_rx_mcs_maps >> (i * 8)) & 0xff;
+		if (mcs_map) {
+			rx_chain++;
+			band->ht_cap.mcs.rx_mask[i] = mcs_map;
+		}
+
+		mcs_map = (chip->ht_tx_mcs_maps >> (i * 8)) & 0xff;
+		if (mcs_map)
+			tx_chain++;
+	}
+
+	if (chip->fw_bw_capa & SKW_BW_2GHZ_40M)
+		bit_rate = rx_chain * 150; /* Mbps */
+	else
+		bit_rate = rx_chain * 72;  /* Mbps */
+
+	band->ht_cap.mcs.rx_highest = cpu_to_le16(bit_rate);
+	band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED;
+	if (tx_chain != rx_chain) {
+		band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_RX_DIFF;
+		band->ht_cap.mcs.tx_params |= ((tx_chain - 1) << 2);
+	}
+
+	band->vht_cap.cap = chip->vht_capa;
+	band->vht_cap.vht_supported = true;
+	band->vht_cap.vht_mcs.tx_mcs_map = chip->vht_tx_mcs_maps;
+	band->vht_cap.vht_mcs.rx_mcs_map = chip->vht_rx_mcs_maps;
+
+	if (!chip->fw_bw_capa)
+		return;
+
+	/* set channel flags */
+	for (flags = 0, i = 0; i < 32; i++) {
+		if (!(chip->fw_bw_capa & BIT(i))) {
+			switch (BIT(i)) {
+			case SKW_BW_CAP_2G_20M:
+			case SKW_BW_CAP_5G_20M:
+				flags |= SKW_IEEE80211_CHAN_NO_20MHZ;
+				break;
+
+			case SKW_BW_CAP_2G_40M:
+			case SKW_BW_CAP_5G_40M:
+				flags |= IEEE80211_CHAN_NO_HT40;
+				break;
+
+			case SKW_BW_CAP_5G_80M:
+				flags |= IEEE80211_CHAN_NO_80MHZ;
+				break;
+
+			case SKW_BW_CAP_5G_160M:
+				flags |= IEEE80211_CHAN_NO_160MHZ;
+				break;
+
+			default:
+				break;
+			}
+		}
+	}
+
+	skw_dbg("BW capa: 0x%x, flags: 0x%x\n", chip->fw_bw_capa, flags);
+
+#ifdef SKW_SYNC_CHANNEL_FLAGS
+	for (i = 0; i < band->n_channels; i++)
+		band->channels[i].flags = flags;
+#endif
+}
+
+int skw_setup_wiphy(struct wiphy *wiphy, struct skw_chip_info *chip)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	wiphy->mgmt_stypes = skw_mgmt_stypes;
+#if 0
+	wiphy->probe_resp_offload = NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS |
+				NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 |
+				NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P;
+#endif
+
+	wiphy->flags = WIPHY_FLAG_NETNS_OK |
+			WIPHY_FLAG_4ADDR_AP |
+			WIPHY_FLAG_4ADDR_STATION |
+			WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD |
+			WIPHY_FLAG_REPORTS_OBSS;
+
+#ifdef CONFIG_SWT6621S_TDLS
+	wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS;
+	wiphy->flags |= WIPHY_FLAG_TDLS_EXTERNAL_SETUP;
+#endif
+
+#ifdef CONFIG_SWT6621S_OFFCHAN_TX
+	wiphy->flags |= WIPHY_FLAG_OFFCHAN_TX;
+#else
+	wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)
+	wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH;
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
+	wiphy->max_num_csa_counters = 2;
+#endif
+
+	/* STA SME EXTERNAL */
+	if (!test_bit(SKW_FLAG_STA_SME_EXTERNAL, &skw->flags))
+		wiphy->flags |= WIPHY_FLAG_SUPPORTS_FW_ROAM;
+
+	/* AP SME INTERNAL */
+	if (!test_bit(SKW_FLAG_SAP_SME_EXTERNAL, &skw->flags)) {
+		wiphy->max_acl_mac_addrs = SKW_MAX_ACL_ENTRIES;
+		wiphy->flags |= WIPHY_FLAG_HAVE_AP_SME;
+		wiphy->ap_sme_capa = 1;
+	}
+
+	wiphy->features = NL80211_FEATURE_SK_TX_STATUS |
+			  NL80211_FEATURE_SAE |
+			  NL80211_FEATURE_HT_IBSS |
+			  NL80211_FEATURE_VIF_TXPOWER |
+			  NL80211_FEATURE_USERSPACE_MPM |
+			  NL80211_FEATURE_FULL_AP_CLIENT_STATE |
+			  NL80211_FEATURE_INACTIVITY_TIMER;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+	//wiphy->features |= NL80211_FEATURE_TDLS_CHANNEL_SWITCH;
+	wiphy->features |= NL80211_FEATURE_MAC_ON_CREATE;
+#endif
+
+#ifdef CONFIG_SWT6621S_SCAN_RANDOM_MAC
+	wiphy->features |= SKW_WIPHY_FEATURE_SCAN_RANDOM_MAC;
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
+	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_RRM);
+	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_VHT_IBSS);
+
+	//TODO:Add an function to initialize iftype_ext_cap
+	skw_setup_wiphy_iftype_ext_cap(wiphy);
+#endif
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0))
+	wiphy->support_mbssid = true;
+#else
+	wiphy->bss_priv_size = sizeof(struct skw_bss_priv);
+	set_bit(SKW_FLAG_MBSSID_PRIV, &skw->flags);
+#endif
+
+	wiphy->interface_modes = BIT(NL80211_IFTYPE_ADHOC) |
+				 BIT(NL80211_IFTYPE_STATION) |
+				 BIT(NL80211_IFTYPE_AP) |
+				 BIT(NL80211_IFTYPE_P2P_GO) |
+				 BIT(NL80211_IFTYPE_P2P_CLIENT) |
+				 BIT(NL80211_IFTYPE_MONITOR);
+
+#ifndef CONFIG_SWT6621S_LEGACY_P2P
+	wiphy->interface_modes |= BIT(NL80211_IFTYPE_P2P_DEVICE);
+#endif
+
+	BUILD_BUG_ON_MSG(SKW_EXTENDED_CAPA_LEN > sizeof(skw->ext_capa),
+			 "SKW_EXTENDED_CAPA_LEN larger than buffer");
+	wiphy->extended_capabilities = skw->ext_capa;
+	wiphy->extended_capabilities_mask = skw->ext_capa;
+	wiphy->extended_capabilities_len = SKW_EXTENDED_CAPA_LEN;
+
+#if defined(CONFIG_PM)
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0))
+	wiphy->wowlan = &skw_wowlan_support;
+#else
+	wiphy->wowlan = skw_wowlan_support;
+#endif
+#endif
+
+	skw_sync_band_capa(&skw_band_2ghz, chip);
+	wiphy->bands[NL80211_BAND_2GHZ] = &skw_band_2ghz;
+
+	skw_info("2g_only:%d", chip->priv_2g_only);
+	if (!chip->priv_2g_only) {
+		skw_sync_band_capa(&skw_band_5ghz, chip);
+		wiphy->bands[NL80211_BAND_5GHZ] = &skw_band_5ghz;
+
+#ifdef CONFIG_SWT6621S_6GHZ
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)
+		wiphy->bands[NL80211_BAND_6GHZ] = &skw_band_6ghz;
+#endif
+#endif
+	}
+
+	wiphy->cipher_suites = skw_cipher_suites;
+	wiphy->n_cipher_suites = ARRAY_SIZE(skw_cipher_suites);
+
+	wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
+	wiphy->max_scan_ssids = chip->max_scan_ssids;
+	wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN; /*2304*/
+	wiphy->max_remain_on_channel_duration = 500;
+	wiphy->max_sched_scan_ie_len = IEEE80211_MAX_DATA_LEN;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
+	wiphy->max_sched_scan_reqs = 1;
+#endif
+	wiphy->max_sched_scan_ssids = 10;
+	wiphy->max_match_sets = 16;
+
+	/* MCC support */
+	wiphy->iface_combinations = skw_iface_combos;
+	wiphy->n_iface_combinations = ARRAY_SIZE(skw_iface_combos);
+
+	wiphy->addresses = skw->address;
+	wiphy->n_addresses = ARRAY_SIZE(skw->address);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0)
+	wiphy->max_ap_assoc_sta = skw->fw.max_num_sta;
+#endif
+
+	wiphy->reg_notifier = skw_regd_notifier;
+
+#ifdef CONFIG_SWT6621S_REGD_SELF_MANAGED
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
+	wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED;
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
+	wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG;
+#else
+	wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY;
+#endif
+	set_bit(SKW_FLAG_PRIV_REGD, &skw->flags);
+
+#endif
+
+	return wiphy_register(wiphy);
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_cfg80211.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_cfg80211.h
new file mode 100755
index 0000000..e64585d
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_cfg80211.h
@@ -0,0 +1,902 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_CFG80211_H__
+#define __SKW_CFG80211_H__
+
+#include <linux/ieee80211.h>
+#include "skw_msg.h"
+#include "skw_iface.h"
+
+#define SKW_AUTH_TIMEOUT               msecs_to_jiffies(4800)
+#define SKW_ASSOC_TIMEOUT              msecs_to_jiffies(4800)
+#define SKW_STEP_TIMEOUT               msecs_to_jiffies(300)
+#define SKW_MAX_AUTH_RETRY_NUM       3
+#define SKW_MAX_ASSOC_RETRY_NUM      5
+#define SKW_MAX_REAUTH_REDO_NUM      3
+
+#define SKW_MAX_SCAN_SSID                 4
+#define SKW_SCAN_TIMEOUT                  8000
+#define SKW_CQM_SCAN_TIMEOUT              4
+#define SKW_MAX_STA_AUTH_ASSOC_RETRY      3
+#define SKW_EXTENDED_CAPA_LEN             11
+
+/* hostap mac acl mode */
+#define SKW_MAX_ACL_ENTRIES               16
+
+#define SKW_WOW_DISCONNECT                BIT(0)
+#define SKW_WOW_MAGIC_PKT                 BIT(1)
+#define SKW_WOW_GTK_REKEY_FAIL            BIT(2)
+#define SKW_WOW_EAP_IDENTITY_REQ          BIT(3)
+#define SKW_WOW_FOUR_WAY_HANDSHAKE        BIT(4)
+#define SKW_WOW_RFKILL_RELEASE            BIT(5)
+#define SKW_WOW_BLACKLIST_FILTER          BIT(31)
+#define SKW_WOW_ANY_PKT                   0xff
+
+enum SKW_IP_VERSION {
+	SKW_IP_IPV4 = 0,
+	SKW_IP_IPV6,
+};
+
+struct skw_bss_priv {
+	u8 bssid_index;
+	u8 max_bssid_indicator;
+	u16 resv;
+};
+
+struct skw_suspend_t {
+	u8 wow_enable;
+	u8 reserved;
+	/* reference SKW_WOW_*,
+	 * set wow_flags to 0 if wakeup any
+	 */
+	u16 wow_flags;
+};
+
+#define SKW_SUITE(oui, id)                  (((oui) << 8) | (id))
+#define SKW_CIPHER_SUITE_WEP40              SKW_SUITE(0x000FAC, 1)
+#define SKW_CIPHER_SUITE_TKIP               SKW_SUITE(0x000FAC, 2)
+#define SKW_CIPHER_SUITE_CCMP               SKW_SUITE(0x000FAC, 4)
+#define SKW_CIPHER_SUITE_WEP104             SKW_SUITE(0x000FAC, 5)
+#define SKW_CIPHER_SUITE_AES_CMAC           SKW_SUITE(0x000FAC, 6)
+#define SKW_CIPHER_SUITE_GCMP               SKW_SUITE(0x000FAC, 8)
+#define SKW_CIPHER_SUITE_GCMP_256           SKW_SUITE(0x000FAC, 9)
+#define SKW_CIPHER_SUITE_CCMP_256           SKW_SUITE(0x000FAC, 10)
+#define SKW_CIPHER_SUITE_BIP_GMAC_128       SKW_SUITE(0x000FAC, 11)
+#define SKW_CIPHER_SUITE_BIP_GMAC_256       SKW_SUITE(0x000FAC, 12)
+#define SKW_CIPHER_SUITE_BIP_CMAC_256       SKW_SUITE(0x000FAC, 13)
+
+#define SKW_CIPHER_SUITE_SMS4               SKW_SUITE(0x001472, 1)
+
+enum SKW_CIPHER_TYPE {
+	SKW_CIPHER_TYPE_INVALID = 0,
+	SKW_CIPHER_TYPE_WEP40 = 1,
+	SKW_CIPHER_TYPE_WEP104 = 2,
+	SKW_CIPHER_TYPE_TKIP = 3,
+	SKW_CIPHER_TYPE_SMS4 = 4,
+	SKW_CIPHER_TYPE_CCMP = 8,
+	SKW_CIPHER_TYPE_CCMP_256 = 9,
+	SKW_CIPHER_TYPE_GCMP = 10,
+	SKW_CIPHER_TYPE_GCMP_256 = 11,
+	SKW_CIPHER_TYPE_AES_CMAC = 12, /* BIP_CMAC_128 */
+	SKW_CIPHER_TYPE_BIP_CMAC_256 = 13,
+	SKW_CIPHER_TYPE_BIP_GMAC_128 = 14,
+	SKW_CIPHER_TYPE_BIP_GMAC_256 = 15,
+};
+
+enum SKW_MIB_ID {
+	SKW_MIB_RTS_THRESHOLD = 1,
+	SKW_MIB_FRAG_THRESHOLD = 2,
+	SKW_MIB_COVERAGE_CLASS = 3,
+	SKW_MIB_RETRY_SHORT = 4,
+	SKW_MIB_RETRY_LONG = 5,
+	SKW_MIB_DYN_ACK = 6,
+	SKW_MIB_TXQ_LIMIT = 7,
+	SKW_MIB_TXQ_MEMORY_LIMIT = 8,
+	SKW_MIB_TXQ_QUANTUM = 9,
+	SKW_MIB_DOT11_OMI = 10,
+	SKW_MIB_DOT11_MODE_B = 11,
+	SKW_MIB_DOT11_MODE_G = 12,
+	SKW_MIB_DOT11_MODE_A = 13,
+	SKW_MIB_DOT11_MODE_HT = 14,
+	SKW_MIB_DOT11_MODE_VHT = 15,
+	SKW_MIB_DOT11_MODE_HE = 16,
+	SKW_MIB_DOT11_CBW_20M = 17,
+	SKW_MIB_DOT11_CBW_40M_ABOVE = 18,
+	SKW_MIB_DOT11_CBW_40M_BELOW = 19,
+	SKW_MIB_DOT11_CBW_80M = 20,
+	SKW_MIB_DOT11_CBW_160M = 21,
+	SKW_MIB_DOT11_CBW_80P80M = 22,
+	SKW_MIB_SET_BAND_2G = 23,
+	SKW_MIB_SET_BAND_5G = 24,
+
+	SKW_MIB_SET_11K_TLV_ID = 30,
+	SKW_MIB_SET_11V_TLV_ID = 31,
+	SKW_MIB_SET_11R_TLV_ID = 32,
+	SKW_MIB_SET_ONCE_NOA_ENABLE = 33,
+	SKW_MIB_SET_NOA_RATIO_TYPE = 34,
+	SKW_MIB_SET_LINK_LOSS_THOLD = 35,
+
+	SKW_MIB_SET_AGEOUT_THOLD = 39,
+	SKW_MIB_SET_TXOP_LIMIT = 40,
+	SKW_MIB_SET_MAX_PPDU_DUR = 41,
+	SKW_MIB_SET_REPORT_CQM_RSSI_LOW_INT = 42,
+	SKW_MIB_SET_EDCA_PARAM = 43,
+	SKW_MIB_SET_CCA_THRE_NOWIFI = 44,
+	SKW_MIB_SET_CCA_THRE_11B = 45,
+	SKW_MIB_SET_CCA_THRE_OFDM = 46,
+	SKW_MIB_SET_FORCE_RTS_RATE = 47,
+	SKW_MIB_SET_FORCE_RX_RSP_RATE = 48,
+	SKW_MIB_SET_SCAN_TIME = 49,
+	SKW_MIB_SET_TCP_DISCONN_WAKEUP_HOST = 50,
+	SKW_MIB_SET_TX_LIFETIME = 51,
+	SKW_MIB_SET_TX_RETRY_CNT = 52,
+	SKW_MIB_SET_TX_RTS_THRD = 53,
+	SKW_MIB_SET_AP_NEW_CHAN = 54,
+	SKW_MIB_SET_TX_RETRY_LIMIT_EN = 55,
+	SKW_MIB_SET_PARTIAL_TWT_SCHED = 56,
+	SKW_MIB_SET_THM_THRD = 57,
+
+	SKW_MIB_SET_NORMAL_SCAN_WITH_ACS = 59,
+	SKW_MIB_SET_FORCE_RX_SPECIAL_80211FRAME = 60,
+	SKW_MIB_SET_FORCE_RX_UPDATE_NAV = 61,
+	SKW_MIB_SET_SCAN_SEND_PROBE_REQ_CNT = 62,
+
+	SKW_MIB_SET_APGO_TIMMAP = 70,
+	SKW_MIB_SET_DBDC_DISABLE = 71,
+
+	SKW_MIB_SET_RATE_CTRL_MIN_RATE = 80,
+	SKW_MIB_SET_RATE_CTRL_RATE_CHANGE_PARAM = 81,
+	SKW_MIB_SET_RATE_CTRL_SPECIAL_FRM_RATE = 82,
+
+	SKW_MIB_SET_HDK_TEST = 131,
+
+	SKW_MIB_SET_ASSIGN_ADDRESS_VAL = 150,
+	SKW_MIB_SET_WAKEUP_HOST_ENABLE = 151,
+	SKW_MIB_SET_WDS_ENABLE = 152,
+	SKW_MIB_SET_SUSPEND_MODE = 153,
+
+	SKW_MIB_W_INFO = 200,
+	SKW_MIB_MAC_NSS_INFO = 201,
+	SKW_MIB_GET_HW_TX_INFO_E = 202,
+	SKW_MIB_GET_INST_INFO_E = 203,
+	SKW_MIB_GET_PEER_INFO_E = 204,
+	SKW_MIB_GET_HW_RX_INFO_E = 205,
+	SKW_MIB_GET_INST_TSF_E = 206,
+
+	SKW_MIB_GET_ASSIGN_ADDR_VAL_E = 250,
+
+	SKW_MIB_LAST
+};
+
+enum SKW_MONITOR_MODE {
+	SKW_MONITOR_CLOSE,
+	SKW_MONITOR_COMMON,
+	SKW_MONITOR_MAC_CAP,
+	SKW_MONITOR_PHY_CAP,
+	SKW_MONITOR_MAX,
+};
+
+#define SKW_BW_2GHZ_20M             BIT(0)
+#define SKW_BW_2GHZ_40M             BIT(1)
+#define SKW_BW_5GHZ_20M             BIT(2)
+#define SKW_BW_5GHZ_40M             BIT(3)
+#define SKW_BW_5GHZ_80M             BIT(4)
+#define SKW_BW_5GHZ_160M            BIT(5)
+#define SKW_BW_5GHZ_8080M           BIT(6)
+
+enum SKW_CMD_DISCONNECT_TYPE_E {
+	SKW_DISCONNECT_ONLY = 0,
+	SKW_DISCONNECT_SEND_DISASSOC = 1,
+	SKW_DISCONNECT_SEND_DEAUTH = 2,
+};
+
+struct skw_once_noa_enable_mib {
+	u8 en;
+	u8 go_pre_time;
+	u8 go_abs_time;
+} __packed;
+
+struct skw_noa_ratio_mib {
+	u8 valid;
+	u8 ratio_lv;
+} __packed;
+
+#define SKW_SCAN_FLAG_RND_MAC         BIT(0)
+#define SKW_SCAN_FLAG_ACS             BIT(1)
+#define SKW_SCAN_FLAG_PASSIVE         BIT(7)
+
+struct skw_scan_chan_info {
+	u8 chan_num;
+	u8 band;
+	u8 scan_flags;
+} __packed;
+
+struct skw_scan_param {
+	u16 flags;  /* reference SKW_SCAN_FLAG_ */
+	u8 rand_mac[6];
+	u32 nr_chan;
+	u32 chan_offset;
+	u32 n_ssid;
+	u32 ssid_offset;
+	u32 ie_len;
+	u32 ie_offset;
+	u8 ie[];
+} __packed;
+
+struct skw_sched_match_sets {
+	u8 ssid[IEEE80211_MAX_SSID_LEN];
+	u16 ssid_len;
+	u8 bssid[ETH_ALEN];
+	s32 rssi_thold;
+} __packed;
+
+struct skw_sched_scan_param {
+	u32 req_id;
+	u32 flags;
+	s32 min_rssi_thold;
+	u32 delay;
+	u8 mac_addr[ETH_ALEN];
+	u8 mac_addr_mask[ETH_ALEN];
+	u8 relative_rssi_set;
+	s8 relative_rssi;
+	u8 scan_width;
+
+	u8 n_ssids;
+	u32 n_ssids_len;
+	u32 n_ssid_offset;
+
+	u32 ie_len;
+	u32 ie_offset;
+
+	u8 n_channels;
+	u32 channels_len;
+	u32 channels_offset;
+
+	u8 n_match_sets;
+	u32 match_sets_len;
+	u32 match_sets_offset;
+
+	u8 n_scan_plans;
+	u32 scan_plans_len;
+	u32 scan_plans_offset;
+	u8 data[0];
+} __packed;
+
+struct skw_center_chn {
+	u8 bw;
+	u8 band;
+	u16 center_chn1;
+	u16 center_chn2;
+};
+
+struct skw_he_cap_elem {
+	u8 mac_cap_info[6];
+	u8 phy_cap_info[11];
+	u16 rx_mcs_map;
+	u16 tx_mcs_map;
+	u32 ppe;
+} __packed;
+
+struct skw_he_oper_param {
+	u16 default_pe_dur:3;
+	u16 twt_req:1;
+	u16 txop_dur_rts_thr:10;
+	u16 vht_opt_info_pre:1;
+	u16 co_hosted_bss:1;
+	u8 er_su_disable:1;
+	u8 opt_info_6g_pre:1;
+	u8 reserved:6;
+} __packed;
+
+struct skw_he_oper_elem {
+	struct skw_he_oper_param he_param;
+	u8 bss_color:6;
+	u8 partial_bss_color:1;
+	u8 bss_color_disabled:1;
+	u8 basic_mcs_nss[2];
+	//u8 vht_opt_info[3];
+	//u8 max_cohosted_bssid_ind[1];
+	//u8 opt_info_6g[5];
+} __packed;
+
+struct skw_join_param {
+	u8 chan_num;
+	u8 center_chn1;
+	u8 center_chn2;
+	u8 bandwidth;
+	u8 band;
+	u16 beacon_interval;
+	u16 capability;
+	u8 bssid_index;
+	u8 max_bssid_indicator;
+	u8 bssid[6];
+	u16 roaming:1;
+	u16 reserved:15;
+	u16 bss_ie_offset;
+	u32 bss_ie_len;
+	u8 bss_ie[];
+} __packed;
+
+struct skw_join_resp {
+	u8 peer_idx;
+	u8 lmac_id;
+	u8 inst;
+	u8 multicast_idx;
+} __packed;
+
+struct skw_auth_param {
+	u16 auth_algorithm;
+	u16 key_data_offset;
+	u16 key_data_len;
+	u16 auth_data_offset;
+	u16 auth_data_len;
+	u16 auth_ie_offset;
+	u16 auth_ie_len;
+	u8  data[];
+} __packed;
+
+struct skw_assoc_req_param {
+	struct ieee80211_ht_cap ht_capa;
+	struct ieee80211_vht_cap  vht_capa;
+	u8 bssid[6];
+	u8 pre_bssid[6];
+	u16 req_ie_offset;
+	u16 req_ie_len;
+	u8  req_ie[];
+} __packed;
+
+struct skw_disconnect_param {
+	u8 type;
+	u8 local_state_change;
+	u16 reason_code;
+	u16 ie_offset;
+	u16 ie_len;
+	u8 ie[];
+} __packed;
+
+struct skw_ibss_params {
+	/*
+	 * 0: join ibss
+	 * 1: create ibss
+	 */
+	u8 type;
+	u8 chan;
+	u8 bw;
+	u8 center_chan1;
+	u8 center_chan2;
+	u8 band;
+
+	u8 ssid_len;
+	u8 ssid[32];
+
+	u8 bssid[ETH_ALEN];
+	u16 atim_win;
+	u16 beacon_int;
+} __packed;
+
+enum SKW_KEY_TYPE {
+	SKW_KEY_TYPE_PTK = 0,
+	SKW_KEY_TYPE_GTK = 1,
+	SKW_KEY_TYPE_IGTK = 2,
+	SKW_KEY_TYPE_BIGTK = 3,
+};
+
+struct skw_startap_param {
+	int beacon_int;
+	u8 dtim_period;
+	u8 flags; /* reference SKW_AP_FLAGS_* */
+	u8 chan;
+	u8 chan_width;
+	u8 center_chn1;
+	u8 center_chn2;
+	u8 band;
+	u8 ssid_len;
+	u8 ssid[32];
+
+	u16 beacon_head_offset;
+	u16 beacon_head_len;
+	u16 beacon_tail_offset;
+	u16 beacon_tail_len;
+	u16 beacon_ies_offset;
+	u16 beacon_ies_len;
+
+	u16 probe_rsp_ies_offset;
+	u16 probe_rsp_ies_len;
+	u16 assoc_rsp_ies_offset;
+	u16 assoc_rsp_ies_len;
+	u8 ies[0];
+} __packed;
+
+struct skw_startap_resp {
+	u8 lmac_id;
+	u8 inst_id;
+	u8 multicast_idx;
+};
+
+//TBD: put skw_beacon_param into skw_startp_param
+struct skw_beacon_params {
+	u16 beacon_head_offset;
+	u16 beacon_head_len;
+	u16 beacon_tail_offset;
+	u16 beacon_tail_len;
+	u16 beacon_ies_offset;
+	u16 beacon_ies_len;
+
+	u16 probe_rsp_ies_offset;
+	u16 probe_rsp_ies_len;
+	u16 assoc_rsp_ies_offset;
+	u16 assoc_rsp_ies_len;
+	u16 probe_rsp_offset;
+	u16 probe_rsp_len;
+	u8 ies[0];
+} __packed;
+
+struct skw_del_sta_param {
+	u8 mac[6];
+	u16 reason_code;
+	u8 tx_frame;
+} __packed;
+
+enum skw_rate_info_bw {
+	SKW_RATE_INFO_BW_20,
+	SKW_RATE_INFO_BW_40,
+	SKW_RATE_INFO_BW_80,
+	SKW_RATE_INFO_BW_HE_RU = 15,
+};
+
+struct skw_rx_rate_desc {
+	u8 ppdu_mode;
+	u8 data_rate;
+	u8 nss;
+	u8 bw;
+	u8 gi_type;
+	u8 ru;
+	u8 dcm;
+	u8 msdu_filter;
+	u8 retry_frame;
+	u8 data_snr;
+	u16 data_rssi;
+	u16 resv1;
+} __packed;
+
+struct skw_get_sta_resp {
+	struct skw_rate tx_rate;
+	s8 signal;
+	u8 noise;
+	u8 tx_psr;
+	u32 tx_failed;
+	u16 filter_cnt[35];
+	u16 filter_drop_offload_cnt[35];
+	struct skw_rx_rate_desc rx_rate;
+	u8 tx_percent;
+	u8 rx_percent;
+} __packed;
+
+struct skw_roc_param {
+	u8 enable;
+	u8 channel_num;
+	u8 band;
+	u8 channel_type;
+	u32 duration;
+	u64 cookie;
+} __packed;
+
+struct skw_mgmt_tx_param {
+	u32 wait;
+	u64 cookie;
+	u8 channel;
+	u8 band;
+	u8 dont_wait_for_ack;
+	u16 mgmt_frame_len;
+	struct ieee80211_mgmt mgmt[0];
+} __packed;
+
+struct skw_mgmt_register_param {
+	u16 frame_type;
+	u8 reg;
+	u8 resv[5];
+	u64 timestamp;
+} __packed;
+
+struct skw_station_params {
+	u8 mac[ETH_ALEN];
+	u16 resv;
+
+	u64 timestamp;
+};
+
+#define SKW_CQM_DEFAUT_RSSI_THOLD	(-70)
+#define SKW_CQM_DEFAUT_RSSI_HYST	(40)
+
+struct skw_set_cqm_rssi_param {
+	s32 rssi_thold;
+	u8 rssi_hyst;
+} __packed;
+
+enum SKW_SCAN_TYPE {
+	SKW_SCAN_IDLE,
+	SKW_SCAN_NORMAL,
+	SKW_SCAN_SCHED,
+	SKW_SCAN_BG,
+	SKW_SCAN_AUTO,
+	SKW_SCAN_ROAM,
+#ifdef RRM_SCAN_SUPPORT
+	SKW_SCAN_RRM,
+#endif
+};
+
+enum SKW_CQM_STATUS {
+	CQM_STATUS_RSSI_LOW = 1,
+	CQM_STATUS_RSSI_HIGH = 2,
+	CQM_STATUS_BEACON_LOSS = 3,
+	CQM_STATUS_TDLS_LOSS = 4,
+};
+
+enum SKW_MPDU_DESC_GI {
+	DESC_GI_04 = 0,
+	DESC_GI_08 = 1,
+	DESC_GI_16 = 2,
+	DESC_GI_32 = 3,
+};
+
+/* define same as cp get station rate bw */
+#define SKW_DESC_BW_USED_RU     15
+
+/* define same as cp get station tx gi*/
+enum SKW_HE_GI {
+	SKW_HE_GI_3_2 = 0,
+	SKW_HE_GI_1_6 = 1,
+	SKW_HE_GI_0_8 = 2,
+};
+
+/* define same as cp get station tx gi*/
+enum SKW_HTVHT_GI {
+	SKW_HTVHT_GI_0_8 = 0,
+	SKW_HTVHT_GI_0_4 = 1,
+};
+
+struct skw_cqm_info {
+	u8 cqm_status;
+	s16 cqm_rssi;
+	u8 bssid[ETH_ALEN];
+	u8 chan;
+	u8 band;
+} __packed;
+
+struct skw_del_sta {
+	u8 reason_code;
+	u8 mac[ETH_ALEN];
+} __packed;
+
+struct skw_mic_failure {
+	u8 is_mcbc;
+	u8 key_id;
+	u8 lmac_id;
+	u8 mac[ETH_ALEN];
+} __packed;
+
+struct skw_tdls_oper {
+	u16 oper; /* reference enum nl80211_tdls_operation */
+	u8 peer_addr[ETH_ALEN];
+};
+
+struct skw_ts_info {
+	u8 tsid;
+	u8 up;
+	u8 peer[ETH_ALEN];
+	__le16 admitted_time;
+} __packed;
+
+struct skw_tdls_chan_switch {
+	u8 addr[6];
+	u8 chn_switch_enable;  /* 0: disable, 1: enable */
+	u8 oper_class;
+	u8 chn;
+	u8 band;               /* enum nl80211_band */
+	u8 chan_width;         /* enum skw_chan_width */
+};
+
+struct skw_setip_param {
+	u8 ip_type;
+	union {
+		__be32 ipv4;
+		u8 ipv6[16];
+	};
+} __packed;
+
+#define SKW_CONN_FLAG_ASSOCED            BIT(0)
+#define SKW_CONN_FLAG_KEY_VALID          BIT(1)
+#define SKW_CONN_FLAG_USE_MFP            BIT(2)
+#define SKW_CONN_FLAG_AUTH_AUTO          BIT(3)
+#define SKW_CONN_FLAG_SAE_AUTH           BIT(4)
+
+struct skw_connect_param {
+	struct mutex lock;
+
+	u8 ssid[IEEE80211_MAX_SSID_LEN];
+	u16 ssid_len;
+	u8 bssid[ETH_ALEN];
+
+	u8 key[32];
+	u8 key_len, key_idx;
+
+	u8 prev_bssid[ETH_ALEN];
+
+	enum SKW_STATES state;
+	enum nl80211_auth_type auth_type;
+
+	u32 flags; /* reference SKW_CONN_FLAG_ */
+
+	u8 *assoc_ie;
+	size_t assoc_ie_len;
+
+	struct ieee80211_ht_cap ht_capa, ht_capa_mask;
+	struct ieee80211_vht_cap vht_capa, vht_capa_mask;
+
+	struct ieee80211_channel *channel;
+
+	struct cfg80211_crypto_settings crypto;
+};
+
+enum SKW_BAND {
+	SKW_BAND_2GHZ,
+	SKW_BAND_5GHZ,
+	SKW_BAND_6GHZ,
+	SKW_BAND_60GHZ,
+
+	SKW_BAND_INVALD,
+};
+
+static inline enum SKW_BAND to_skw_band(enum nl80211_band band)
+{
+	enum SKW_BAND new_band;
+
+	switch (band) {
+	case NL80211_BAND_2GHZ:
+		new_band = SKW_BAND_2GHZ;
+		break;
+
+	case NL80211_BAND_5GHZ:
+		new_band = SKW_BAND_5GHZ;
+		break;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)
+	case NL80211_BAND_6GHZ:
+		new_band = SKW_BAND_6GHZ;
+		break;
+#endif
+
+	default:
+		new_band = SKW_BAND_INVALD;
+		break;
+	}
+
+	return new_band;
+}
+
+static inline enum nl80211_band to_nl80211_band(enum SKW_BAND skw_band)
+{
+	enum nl80211_band band;
+
+	switch (skw_band) {
+	case SKW_BAND_2GHZ:
+		band = NL80211_BAND_2GHZ;
+		break;
+
+	case SKW_BAND_5GHZ:
+		band = NL80211_BAND_5GHZ;
+		break;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)
+	case SKW_BAND_6GHZ:
+		band = NL80211_BAND_6GHZ;
+		break;
+#endif
+
+	default:
+		band = NUM_NL80211_BANDS;
+	}
+
+	return band;
+}
+
+static inline struct skw_bss_priv *skw_bss_priv(struct cfg80211_bss *bss)
+{
+	return (struct skw_bss_priv *)bss->priv;
+}
+
+static inline void skw_join_resp_handler(struct skw_core *skw,
+					 struct skw_iface *iface,
+					 struct skw_join_resp *resp)
+{
+	SKW_BUG_ON(skw_lmac_bind_iface(iface->skw, iface, resp->lmac_id));
+	iface->default_multicast = resp->multicast_idx;
+}
+
+static inline void skw_startap_resp_handler(struct skw_core *skw,
+					    struct skw_iface *iface,
+					    struct skw_startap_resp *resp)
+{
+	SKW_BUG_ON(skw_lmac_bind_iface(iface->skw, iface, resp->lmac_id));
+	iface->default_multicast = resp->multicast_idx;
+}
+
+static inline u8 skw_desc_nss_to_nss_num(u8 cp_nss_idx)
+{
+	/* convert cp nss index to nss number */
+	return cp_nss_idx + 1;
+}
+
+static inline enum SKW_HE_GI skw_desc_he_gi_to_skw_gi(enum SKW_MPDU_DESC_GI desc_gi)
+{
+	enum SKW_HE_GI gi = 0;
+
+	/* convert cp desc gi to SKW_HE_GI */
+	switch (desc_gi) {
+	case DESC_GI_04:
+	case DESC_GI_08:
+		gi = SKW_HE_GI_0_8;
+		break;
+	case DESC_GI_16:
+		gi = SKW_HE_GI_1_6;
+		break;
+	case DESC_GI_32:
+		gi = SKW_HE_GI_3_2;
+		break;
+	default:
+		SKW_BUG_ON(1);
+	}
+
+	return gi;
+}
+
+static inline enum SKW_HTVHT_GI skw_desc_htvht_gi_to_skw_gi(enum SKW_MPDU_DESC_GI desc_gi)
+{
+	enum SKW_HTVHT_GI gi = 0;
+
+	/* convert cp desc gi to SKW_HTVHT_GI */
+	switch (desc_gi) {
+	case DESC_GI_04:
+		gi = SKW_HTVHT_GI_0_4;
+		break;
+	case DESC_GI_08:
+		gi = SKW_HTVHT_GI_0_8;
+		break;
+	default:
+		SKW_BUG_ON(1);
+	}
+
+	return gi;
+}
+
+static inline u8 skw_desc_gi_to_skw_gi(enum SKW_MPDU_DESC_GI desc_gi,
+		enum SKW_RX_MPDU_DESC_PPDUMODE ppdu_mode)
+{
+	u8 gi = 0;
+
+	switch (ppdu_mode) {
+	case SKW_PPDUMODE_HT_MIXED:
+	case SKW_PPDUMODE_VHT_SU:
+	case SKW_PPDUMODE_VHT_MU:
+		gi = skw_desc_htvht_gi_to_skw_gi(gi);
+		break;
+
+	case SKW_PPDUMODE_HE_SU:
+	case SKW_PPDUMODE_HE_TB:
+	case SKW_PPDUMODE_HE_ER_SU:
+	case SKW_PPDUMODE_HE_MU:
+		gi = skw_desc_he_gi_to_skw_gi(gi);
+		break;
+
+	default:
+		gi = desc_gi;
+		break;
+	};
+
+	return gi;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)
+static inline enum nl80211_he_gi skw_gi_to_nl80211_info_gi(enum SKW_HE_GI skw_gi)
+{
+	enum nl80211_he_gi gi = 0;
+
+	/* cp tx get station gi to nl80211_he_gi */
+	switch (skw_gi) {
+	case SKW_HE_GI_3_2:
+		gi = NL80211_RATE_INFO_HE_GI_3_2;
+		break;
+	case SKW_HE_GI_1_6:
+		gi = NL80211_RATE_INFO_HE_GI_1_6;
+		break;
+	case SKW_HE_GI_0_8:
+		gi = NL80211_RATE_INFO_HE_GI_0_8;
+		break;
+	default:
+		SKW_BUG_ON(1);
+	}
+
+	return gi;
+}
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,0,0)
+	#define __STA_INFO_FIELD(field)				SKW_BIT_ULL(NL80211_STA_INFO_##field)
+#else
+	#define __STA_INFO_FIELD(field)				STATION_INFO_##field
+#endif
+
+#define SKW_COMPAT_SIGNAL                                       __STA_INFO_FIELD(SIGNAL)
+#define SKW_COMPAT_TX_PACKETS                                   __STA_INFO_FIELD(TX_PACKETS)
+#define SKW_COMPAT_TX_BYTES                                     __STA_INFO_FIELD(TX_BYTES)
+#define SKW_COMPAT_TX_BITRATE                                   __STA_INFO_FIELD(TX_BITRATE)
+#define SKW_COMPAT_TX_FAILED                                    __STA_INFO_FIELD(TX_FAILED)
+#define SKW_COMPAT_RX_PACKETS                                   __STA_INFO_FIELD(RX_PACKETS)
+#define SKW_COMPAT_RX_BYTES                                     __STA_INFO_FIELD(RX_BYTES)
+#define SKW_COMPAT_RX_BITRATE                                   __STA_INFO_FIELD(RX_BITRATE)
+
+int to_skw_bw(enum nl80211_chan_width bw);
+struct wiphy *skw_alloc_wiphy(int priv_size);
+int skw_setup_wiphy(struct wiphy *wiphy, struct skw_chip_info *chip);
+
+int skw_mgmt_tx(struct wiphy *wiphy, struct skw_iface *iface,
+		struct ieee80211_channel *chan, u32 wait, u64 *cookie,
+		bool dont_wait_ack, const void *frame, int frame_len,
+		int total_frame_len, const struct ieee80211_mgmt *mgmt, bool switchover);
+
+int skw_cmd_del_sta(struct wiphy *wiphy, struct net_device *dev,
+		const u8 *mac, u8 type, u16 reason, bool tx_frame);
+
+int skw_del_station(struct wiphy *wiphy, struct net_device *dev,
+			const u8 *mac, u8 subtype, u16 reason);
+int skw_change_station(struct wiphy *wiphy, struct net_device *dev,
+		const u8 *mac, struct station_parameters *params);
+int skw_add_station(struct wiphy *wiphy, struct net_device *dev,
+		    const u8 *mac, struct station_parameters *params);
+void skw_scan_done(struct skw_core *skw, struct skw_iface *iface, bool abort);
+
+void skw_set_state(struct skw_sm *sm, enum SKW_STATES state);
+int skw_roam_connect(struct skw_iface *iface, const u8 *bssid, u8 chn,
+		     enum nl80211_band band);
+
+int skw_sta_leave(struct wiphy *wiphy, struct net_device *dev,
+		const u8 *bssid, u16 reason, bool tx_frame);
+
+void skw_tx_mlme_mgmt(struct net_device *dev, u16 stype,
+		      const u8 *bssid, const u8 *da, u16 reason);
+
+int skw_connect_sae_auth(struct wiphy *wiphy, struct net_device *dev,
+			 struct cfg80211_bss *bss);
+int skw_connect_auth(struct wiphy *wiphy, struct net_device *dev,
+		struct skw_connect_param *conn, struct cfg80211_bss *bss);
+int skw_connect_assoc(struct wiphy *wiphy, struct net_device *ndev,
+		struct skw_connect_param *conn);
+void skw_connected(struct net_device *dev, struct skw_connect_param *conn,
+		   const u8 *req_ie, int req_ie_len, const u8 *resp_ie,
+		   int resp_ie_len, u16 status, gfp_t gfp);
+void skw_disconnected(struct net_device *dev, u16 reason, const u8 *resp_ie,
+		int resp_ie_len, bool local_gen, gfp_t gfp);
+int skw_cmd_unjoin(struct wiphy *wiphy, struct net_device *ndev,
+		   const u8 *addr, u16 reason, bool tx_frame);
+int skw_sap_set_mib(struct wiphy *wiphy, struct net_device *dev,
+		const u8 *ies, int ies_len);
+int skw_wow_disable(struct wiphy *wiphy);
+int skw_cmd_monitor(struct wiphy *wiphy, struct cfg80211_chan_def *chandef, u8 mode);
+int skw_suspend(struct wiphy *wiphy, struct cfg80211_wowlan *wow);
+int skw_resume(struct wiphy *wiphy);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_compat.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_compat.h
new file mode 100755
index 0000000..dbd43dd
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_compat.h
@@ -0,0 +1,659 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_COMPAT_H__
+#define __SKW_COMPAT_H__
+
+#include <linux/version.h>
+#include <linux/skbuff.h>
+#include <net/cfg80211.h>
+#include <linux/proc_fs.h>
+#include <linux/rtc.h>
+#include <linux/etherdevice.h>
+
+/* EID block */
+#define SKW_WLAN_EID_EXT_HE_CAPABILITY                            35
+#define SKW_WLAN_EID_EXT_HE_OPERATION                             36
+#define SKW_WLAN_EID_MULTI_BSSID_IDX                              85
+#define SKW_WLAN_EID_EXTENSION                                    255
+#define SKW_WLAN_EID_FRAGMENT                                     242
+
+/* compat for ieee80211 */
+#define SKW_HE_MAC_CAP0_HTC_HE                                    0x01
+#define SKW_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US                       0x08
+#define SKW_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_8                    0x70
+
+#define SKW_HE_MAC_CAP2_BSR                                       0x08
+#define SKW_HE_MAC_CAP2_MU_CASCADING                              0x40
+#define SKW_HE_MAC_CAP2_ACK_EN                                    0x80
+
+#define SKW_HE_MAC_CAP3_GRP_ADDR_MULTI_STA_BA_DL_MU               0x01
+#define SKW_HE_MAC_CAP3_OMI_CONTROL                               0x02
+#define SKW_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_VHT_2                   0x10
+
+#define SKW_HE_MAC_CAP4_AMDSU_IN_AMPDU                            0x40
+
+#define SKW_HE_PHY_CAP0_DUAL_BAND                                 0x01
+#define SKW_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G             0x02
+#define SKW_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G       0x04
+#define SKW_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G            0x08
+#define SKW_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G      0x10
+
+#define SKW_HE_PHY_CAP1_PREAMBLE_PUNC_RX_MASK                     0x0f
+#define SKW_HE_PHY_CAP1_DEVICE_CLASS_A                            0x10
+#define SKW_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD                    0x20
+#define SKW_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS                   0X80
+
+#define SKW_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US                      0x02
+#define SKW_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ                       0x04
+#define SKW_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ                       0x08
+#define SKW_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO                        0x40
+#define SKW_HE_PHY_CAP2_UL_MU_PARTIAL_MU_MIMO                     0x80
+
+#define SKW_WLAN_REASON_TDLS_TEARDOWN_UNREACHABLE                 25
+#define SKW_WLAN_CATEGORY_RADIO_MEASUREMENT                       5
+
+#define SKW_IEEE80211_CHAN_NO_20MHZ                               BIT(11)
+/* end of compat for ieee80211 */
+
+#define SKW_WIPHY_FEATURE_SCAN_RANDOM_MAC                         BIT(29)
+#define SKW_EXT_CAPA_BSS_TRANSITION                               19
+#define SKW_EXT_CAPA_MBSSID                                       22
+#define SKW_EXT_CAPA_TDLS_SUPPORT                                 37
+#define SKW_EXT_CAPA_TWT_REQ_SUPPORT                              77
+
+#define SKW_BSS_MEMBERSHIP_SELECTOR_HT_PHY                        127
+#define SKW_BSS_MEMBERSHIP_SELECTOR_VHT_PHY                       126
+
+#ifndef MIN_NICE
+#define MIN_NICE                                                  -20
+#endif
+
+#ifndef TASK_IDLE
+#define TASK_IDLE                                                 TASK_INTERRUPTIBLE
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
+#define SKW_BSS_TYPE_ESS                              IEEE80211_BSS_TYPE_ESS
+#define SKW_BSS_TYPE_IBSS                             IEEE80211_BSS_TYPE_IBSS
+#define SKW_PRIVACY_ESS_ANY                           IEEE80211_PRIVACY_ANY
+#define SKW_PRIVACY_IBSS_ANY                          IEEE80211_PRIVACY_ANY
+#else
+#define SKW_BSS_TYPE_ESS                              WLAN_CAPABILITY_ESS
+#define SKW_BSS_TYPE_IBSS                             WLAN_CAPABILITY_IBSS
+#define SKW_PRIVACY_ESS_ANY                           WLAN_CAPABILITY_ESS
+#define SKW_PRIVACY_IBSS_ANY                          WLAN_CAPABILITY_ESS
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
+#define SKW_PASSIVE_SCAN (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_RADAR)
+#else
+#define SKW_PASSIVE_SCAN IEEE80211_CHAN_PASSIVE_SCAN
+#endif
+
+#define skw_from_timer(var, callback_timer, timer_fieldname) \
+	container_of(callback_timer, typeof(*var), timer_fieldname)
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0)
+#define skw_compat_setup_timer(timer, fn) \
+	timer_setup(timer, fn, 0)
+#else
+typedef void (*tfunc)(unsigned long);
+#define skw_compat_setup_timer(timer, fn) \
+	setup_timer(timer, (tfunc)fn, (unsigned long)timer)
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0)
+enum nl80211_bss_scan_width {
+	NL80211_BSS_CHAN_WIDTH_20,
+	NL80211_BSS_CHAN_WIDTH_10,
+	NL80211_BSS_CHAN_WIDTH_5,
+};
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0)
+#define NUM_NL80211_BANDS    3
+#endif
+
+#if (KERNEL_VERSION(4, 6, 0) <= LINUX_VERSION_CODE)
+#define SKW_IS_COMPAT_TASK in_compat_syscall
+#else
+#define SKW_IS_COMPAT_TASK is_compat_task
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
+static inline struct sk_buff *
+skw_compat_vendor_event_alloc(struct wiphy *wiphy, struct wireless_dev *wdev,
+			int roxlen, int event_idx, gfp_t gfp)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
+	return cfg80211_vendor_event_alloc(wiphy, wdev, roxlen, event_idx, gfp);
+#else
+	return cfg80211_vendor_event_alloc(wiphy, roxlen, event_idx, gfp);
+#endif
+}
+#endif
+
+static inline void skw_compat_cqm_rssi_notify(struct net_device *dev,
+			enum nl80211_cqm_rssi_threshold_event rssi_event,
+			s32 rssi_level, gfp_t gfp)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
+	cfg80211_cqm_rssi_notify(dev, rssi_event, rssi_level, gfp);
+#else
+	cfg80211_cqm_rssi_notify(dev, rssi_event, gfp);
+#endif
+}
+
+static inline void skw_compat_page_frag_free(void *addr)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
+	page_frag_free(addr);
+#else
+	put_page(virt_to_head_page(addr));
+#endif
+}
+
+static inline void
+skw_compat_scan_done(struct cfg80211_scan_request *req, bool aborted)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
+	struct cfg80211_scan_info info = {
+		.aborted = aborted,
+	};
+
+	cfg80211_scan_done(req, &info);
+#else
+	cfg80211_scan_done(req, aborted);
+#endif
+}
+
+static inline void skw_compat_disconnected(struct net_device *ndev, u16 reason,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0)
+				   u8 *ie,
+#else
+				   const u8 *ie,
+#endif
+				   size_t ie_len,
+				   bool locally_generated, gfp_t gfp)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
+	cfg80211_disconnected(ndev, reason, ie, ie_len, locally_generated, gfp);
+#else
+	cfg80211_disconnected(ndev, reason, ie, ie_len, gfp);
+#endif
+}
+
+static inline bool skw_compat_reg_can_beacon(struct wiphy *wiphy,
+			     struct cfg80211_chan_def *chandef,
+			     enum nl80211_iftype iftype)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
+	return cfg80211_reg_can_beacon_relax(wiphy, chandef, iftype);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
+	return cfg80211_reg_can_beacon(wiphy, chandef, iftype);
+#else
+	return cfg80211_reg_can_beacon(wiphy, chandef);
+#endif
+}
+
+static inline unsigned int
+skw_compat_classify8021d(struct sk_buff *skb, void *qos_map)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0)
+	return cfg80211_classify8021d(skb);
+#else
+	return cfg80211_classify8021d(skb, qos_map);
+#endif
+}
+
+static inline void
+skw_compat_rx_mlme_mgmt(struct net_device *dev, void *buf, size_t len)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
+	cfg80211_rx_mlme_mgmt(dev, buf, len);
+#else
+	//FIXME: Dead lock wdev_lock->sta_core
+	struct ieee80211_mgmt *mgmt = buf;
+
+	mutex_unlock(&dev->ieee80211_ptr->mtx);
+
+	if (ieee80211_is_auth(mgmt->frame_control))
+		cfg80211_send_rx_auth(dev, buf, len);
+	else if (ieee80211_is_deauth(mgmt->frame_control))
+		cfg80211_send_deauth(dev, buf, len);
+	else if (ieee80211_is_disassoc(mgmt->frame_control))
+		cfg80211_send_disassoc(dev, buf, len);
+
+	mutex_lock(&dev->ieee80211_ptr->mtx);
+#endif
+}
+
+static inline const u8 *skw_compat_bssid(struct cfg80211_connect_params *req)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0)
+	return req->bssid ? req->bssid : req->bssid_hint;
+#else
+	return req->bssid;
+#endif
+}
+
+static inline struct ieee80211_channel *
+skw_compat_channel(struct cfg80211_connect_params *req)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0)
+	return req->channel ? req->channel : req->channel_hint;
+#else
+	return req->channel;
+#endif
+}
+
+// fixme:
+// disable interface combination check for debug
+#if 0
+static inline int skw_compat_check_combs(struct wiphy *wiphy, int nr_channels,
+				u8 radar, int type_num[NUM_NL80211_IFTYPES])
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
+	struct iface_combination_params params = {
+		.num_different_channels = nr_channels,
+		.radar_detect = radar,
+	};
+
+	memcpy(params.iftype_num, type_num, NUM_NL80211_IFTYPES * sizeof(int));
+
+	return cfg80211_check_combinations(wiphy, &params);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
+	return cfg80211_check_combinations(wiphy, nr_channels, radar, type_num);
+#else
+	// TODO:
+	// implement function to check combinations
+	return 0;
+#endif
+}
+#else
+static inline int skw_compat_check_combs(struct wiphy *wiphy, int nr_channels,
+				u8 radar, int type_num[NUM_NL80211_IFTYPES])
+{
+	return 0;
+}
+#endif
+
+static inline void skw_compat_auth_timeout(struct net_device *dev, const u8 *addr)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
+	cfg80211_auth_timeout(dev, addr);
+#else
+	mutex_unlock(&dev->ieee80211_ptr->mtx);
+	cfg80211_send_auth_timeout(dev, addr);
+	mutex_lock(&dev->ieee80211_ptr->mtx);
+#endif
+}
+
+static inline void skw_cfg80211_tx_mlme_mgmt(struct net_device *dev,
+				const u8 *buf, size_t len)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
+	cfg80211_tx_mlme_mgmt(dev, buf, len, true);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
+	cfg80211_tx_mlme_mgmt(dev, buf, len);
+#else
+	struct ieee80211_mgmt *mgmt = (void *)buf;
+
+	if (ieee80211_is_deauth(mgmt->frame_control))
+		__cfg80211_send_deauth(dev, buf, len);
+	else
+		__cfg80211_send_disassoc(dev, buf, len);
+#endif
+}
+
+static inline void skw_compat_rtc_time_to_tm(unsigned long time, struct rtc_time *tm)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0)
+	rtc_time_to_tm(time, tm);
+#else
+	rtc_time64_to_tm(time, tm);
+#endif
+}
+
+static inline bool skw_compat_cfg80211_rx_mgmt(struct wireless_dev *wdev,
+				int freq, int sig_dbm, const u8 *buf,
+				size_t len, u32 flags, gfp_t gfp)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0)
+	return cfg80211_rx_mgmt(wdev, freq, sig_dbm, buf, len, gfp);
+#elif LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0)
+	return cfg80211_rx_mgmt(wdev, freq, sig_dbm, buf, len, flags, gfp);
+#else
+	return cfg80211_rx_mgmt(wdev, freq, sig_dbm, buf, len, flags);
+#endif
+}
+
+static inline void *skw_pde_data(const struct inode *inode)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)
+	return pde_data(inode);
+#else
+	return PDE_DATA(inode);
+#endif
+}
+
+static inline int skw_set_wiphy_regd_sync(struct wiphy *wiphy,
+				struct ieee80211_regdomain *rd)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)
+	return regulatory_set_wiphy_regd_sync(wiphy, rd);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
+	return regulatory_set_wiphy_regd_sync_rtnl(wiphy, rd);
+#else
+	return -EINVAL;
+#endif
+}
+
+#ifdef __SKW_ANDROID__
+static inline void skw_compat_rx_assoc_resp(struct net_device *dev,
+			struct cfg80211_bss *bss, const u8 *buf, size_t len,
+			int uapsd, const u8 *req_ies, size_t req_ies_len)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 74)
+	struct cfg80211_rx_assoc_resp assoc_resp = {
+		.buf = buf,
+		.len = len,
+		.req_ies = req_ies,
+		.req_ies_len = req_ies_len,
+		.ap_mld_addr = NULL,
+		.links[0] = {
+			.bss = bss,
+		},
+	};
+
+	cfg80211_rx_assoc_resp(dev, &assoc_resp);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0)
+	cfg80211_rx_assoc_resp(dev, bss, buf, len, uapsd, req_ies, req_ies_len);
+#elif defined SKW_RX_ASSOC_RESP_EXT
+	cfg80211_rx_assoc_resp_ext(dev, bss, buf, len, uapsd, req_ies, req_ies_len);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
+	cfg80211_rx_assoc_resp(dev, bss, buf, len, uapsd);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
+	cfg80211_rx_assoc_resp(dev, bss, buf, len);
+#else
+	mutex_unlock(&dev->ieee80211_ptr->mtx);
+	cfg80211_send_rx_assoc(dev, bss, buf, len);
+	mutex_lock(&dev->ieee80211_ptr->mtx);
+#endif
+}
+
+static inline void skw_compat_assoc_failure(struct net_device *dev,
+				struct cfg80211_bss *bss, bool timeout)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 74)
+	struct cfg80211_assoc_failure info = {
+		.ap_mld_addr = NULL,
+		.bss[0] = bss,
+		.timeout = timeout,
+	};
+
+	cfg80211_assoc_failure(dev, &info);
+#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 2) || LINUX_VERSION_CODE == KERNEL_VERSION(4, 8, 17))
+	if (timeout)
+		cfg80211_assoc_timeout(dev, bss);
+	else
+		cfg80211_abandon_assoc(dev, bss);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
+	cfg80211_assoc_timeout(dev, bss);
+#else
+	mutex_unlock(&dev->ieee80211_ptr->mtx);
+	cfg80211_send_assoc_timeout(dev, bss->bssid);
+	mutex_lock(&dev->ieee80211_ptr->mtx);
+#endif
+}
+
+static inline void skw_compat_cfg80211_roamed(struct net_device *dev,
+			const u8 *bssid, const u8 *req_ie,
+			size_t req_ie_len, const u8 *resp_ie,
+			size_t resp_ie_len, gfp_t gfp)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 41)
+	struct cfg80211_roam_info roam_info = {
+		.req_ie = req_ie,
+		.req_ie_len = req_ie_len,
+		.resp_ie = resp_ie,
+		.resp_ie_len = resp_ie_len,
+		.valid_links = 0,
+		.links[0] = {
+			.bss = NULL,
+			.bssid = bssid,
+		},
+	};
+
+	cfg80211_roamed(dev, &roam_info, gfp);
+
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
+	struct cfg80211_roam_info roam_info = {
+		.bss = NULL,
+		.bssid = bssid,
+		.req_ie = req_ie,
+		.req_ie_len = req_ie_len,
+		.resp_ie = resp_ie,
+		.resp_ie_len = resp_ie_len,
+	};
+
+	cfg80211_roamed(dev, &roam_info, gfp);
+#else
+	// fixme:
+	// fix channel
+	cfg80211_roamed(dev, NULL, bssid, req_ie, req_ie_len,
+			resp_ie, resp_ie_len, gfp);
+#endif
+}
+
+static inline void skw_ch_switch_started_notify(struct net_device *dev,
+		struct cfg80211_chan_def *chandef, u8 count,  bool quiet)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 74)
+	cfg80211_ch_switch_started_notify(dev, chandef, 0, count, quiet, 0);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 35)
+	cfg80211_ch_switch_started_notify(dev, chandef, count, quiet);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+	cfg80211_ch_switch_started_notify(dev, chandef, count);
+#else
+#endif
+}
+
+static inline void skw_ch_switch_notify(struct net_device *dev,
+		struct cfg80211_chan_def *chandef)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 74)
+	cfg80211_ch_switch_notify(dev, chandef, 0, 0);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 41)
+	cfg80211_ch_switch_notify(dev, chandef, 0);
+#else
+	cfg80211_ch_switch_notify(dev, chandef);
+#endif
+}
+
+#else /* Linux */
+static inline void skw_compat_rx_assoc_resp(struct net_device *dev,
+			struct cfg80211_bss *bss, const u8 *buf, size_t len,
+			int uapsd, const u8 *req_ies, size_t req_ies_len)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)
+	struct cfg80211_rx_assoc_resp assoc_resp = {
+		.buf = buf,
+		.len = len,
+		.req_ies = req_ies,
+		.req_ies_len = req_ies_len,
+		.ap_mld_addr = NULL,
+		.links[0] = {
+			.bss = bss,
+		},
+	};
+
+	cfg80211_rx_assoc_resp(dev, &assoc_resp);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0)
+	cfg80211_rx_assoc_resp(dev, bss, buf, len, uapsd, req_ies, req_ies_len);
+#elif defined SKW_RX_ASSOC_RESP_EXT
+	cfg80211_rx_assoc_resp_ext(dev, bss, buf, len, uapsd, req_ies, req_ies_len);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
+	cfg80211_rx_assoc_resp(dev, bss, buf, len, uapsd);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
+	cfg80211_rx_assoc_resp(dev, bss, buf, len);
+#else
+	mutex_unlock(&dev->ieee80211_ptr->mtx);
+	cfg80211_send_rx_assoc(dev, bss, buf, len);
+	mutex_lock(&dev->ieee80211_ptr->mtx);
+#endif
+}
+
+static inline void skw_compat_assoc_failure(struct net_device *dev,
+				struct cfg80211_bss *bss, bool timeout)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)
+	struct cfg80211_assoc_failure info = {
+		.ap_mld_addr = NULL,
+		.bss[0] = bss,
+		.timeout = timeout,
+	};
+
+	cfg80211_assoc_failure(dev, &info);
+#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 2) || LINUX_VERSION_CODE == KERNEL_VERSION(4, 8, 17))
+	if (timeout)
+		cfg80211_assoc_timeout(dev, bss);
+	else
+		cfg80211_abandon_assoc(dev, bss);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
+	cfg80211_assoc_timeout(dev, bss);
+#else
+	mutex_unlock(&dev->ieee80211_ptr->mtx);
+	cfg80211_send_assoc_timeout(dev, bss->bssid);
+	mutex_lock(&dev->ieee80211_ptr->mtx);
+#endif
+}
+
+static inline void skw_compat_cfg80211_roamed(struct net_device *dev,
+			const u8 *bssid, const u8 *req_ie,
+			size_t req_ie_len, const u8 *resp_ie,
+			size_t resp_ie_len, gfp_t gfp)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)
+	struct cfg80211_roam_info roam_info = {
+		.req_ie = req_ie,
+		.req_ie_len = req_ie_len,
+		.resp_ie = resp_ie,
+		.resp_ie_len = resp_ie_len,
+		.valid_links = 0,
+		.links[0] = {
+			.bss = NULL,
+			.bssid = bssid,
+		},
+	};
+
+	cfg80211_roamed(dev, &roam_info, gfp);
+
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
+	struct cfg80211_roam_info roam_info = {
+		.bss = NULL,
+		.bssid = bssid,
+		.req_ie = req_ie,
+		.req_ie_len = req_ie_len,
+		.resp_ie = resp_ie,
+		.resp_ie_len = resp_ie_len,
+	};
+
+	cfg80211_roamed(dev, &roam_info, gfp);
+#else
+	// fixme:
+	// fix channel
+	cfg80211_roamed(dev, NULL, bssid, req_ie, req_ie_len,
+			resp_ie, resp_ie_len, gfp);
+#endif
+}
+
+static inline void skw_ch_switch_started_notify(struct net_device *dev,
+		struct cfg80211_chan_def *chandef, u8 count,  bool quiet)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)
+	cfg80211_ch_switch_started_notify(dev, chandef, 0, count, quiet);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
+	cfg80211_ch_switch_started_notify(dev, chandef, count, quiet);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+	cfg80211_ch_switch_started_notify(dev, chandef, count);
+#else
+#endif
+}
+
+static inline void skw_ch_switch_notify(struct net_device *dev,
+		struct cfg80211_chan_def *chandef)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 2)
+	cfg80211_ch_switch_notify(dev, chandef, 0);
+#else
+	cfg80211_ch_switch_notify(dev, chandef);
+#endif
+}
+
+#endif /* Linux */
+
+static inline unsigned long skw_get_seconds(void)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
+	return ktime_get_real_seconds();
+#else
+	return get_seconds();
+#endif
+}
+
+static inline int skw_register_netdevice(struct net_device *dev)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)
+	return cfg80211_register_netdevice(dev);
+#else
+	return register_netdevice(dev);
+#endif
+}
+
+static inline void skw_unregister_netdevice(struct net_device *dev)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)
+	return cfg80211_unregister_netdevice(dev);
+#else
+	return unregister_netdevice(dev);
+#endif
+}
+
+static inline int skw_compat_cfg80211_chandef_dfs_required(struct wiphy *wiphy,
+				  const struct cfg80211_chan_def *chandef,
+				  enum nl80211_iftype iftype)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
+	return cfg80211_chandef_dfs_required(wiphy, chandef, iftype);
+#else
+	return cfg80211_chandef_dfs_required(wiphy, chandef);
+#endif
+}
+
+static inline void skw_compat_netif_napi_add_weight(struct net_device *dev,
+		 struct napi_struct *napi, int (*poll)(struct napi_struct *, int), int weight)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
+	netif_napi_add_weight(dev, napi, poll, weight);
+#else
+	netif_napi_add(dev, napi, poll, weight);
+#endif
+}
+
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_config.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_config.c
new file mode 100755
index 0000000..87cd184
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_config.c
@@ -0,0 +1,481 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/ctype.h>
+#include <linux/firmware.h>
+
+#include "skw_util.h"
+#include "skw_config.h"
+#include "skw_log.h"
+#include "skw_regd.h"
+
+#define SKW_LINE_BUFF_LEN   128
+
+struct skwifi_cfg {
+	char *name;
+	int (*parser)(struct skwifi_cfg *cfg, char *key, char *data);
+	void *priv;
+};
+
+static struct skwifi_cfg *skw_cfg_match(struct skwifi_cfg *table, char *name)
+{
+	int i;
+	struct skwifi_cfg *cfg = NULL;
+
+	for (i = 0; table[i].name != NULL; i++) {
+		if (!strcmp(name, table[i].name)) {
+			cfg = &table[i];
+			break;
+		}
+	}
+
+	return cfg;
+}
+
+static void skw_parser(struct skwifi_cfg *table, const u8 *data, int size)
+{
+	char chr, token;
+	char word[64] = {0};
+	char skw_key[64] = {0};
+	char skw_dat[256] = {0};
+	int flags = 0;
+	char comma[] = ",";
+	int i, nr = 0, params = 0;
+	struct skwifi_cfg *cfg = NULL;
+	bool do_save, do_append, do_parse;
+
+	for (token = 0, i = 0; i < size; i++) {
+		chr = data[i];
+		do_save = do_append = do_parse = false;
+
+		switch (token) {
+		case '%':
+			if (chr == '%') {
+				token = 0;
+				word[nr] = '\0';
+
+				cfg = skw_cfg_match(table, word);
+			} else {
+				do_save = true;
+			}
+
+			break;
+
+		case '[':
+			if (chr == ']') {
+				token = 0;
+				word[nr] = '\0';
+
+				flags |= BIT(0);
+				strlcpy(skw_key, word, sizeof(skw_key));
+			} else {
+				do_save = true;
+			}
+
+			break;
+
+		case '"':
+			if (chr == '"') {
+				token = 0;
+				do_append = true;
+			} else {
+				do_save = true;
+			}
+
+			break;
+
+		case '<':
+			if (chr == '<') {
+				do_append = true;
+
+			} else if (chr == '>') {
+				token = 0;
+
+				if (!nr)
+					word[nr++] = '*';
+
+				do_append = true;
+			} else {
+				if (isxdigit(chr) || chr == ',' ||
+				    (tolower(chr) == 'x' && (nr && word[nr - 1] == '0')))
+					do_save = true;
+			}
+
+			break;
+
+		default:
+			switch (chr) {
+			case '%':
+			case '[':
+				nr = 0;
+				memset(word, 0x0, sizeof(word));
+
+				token = chr;
+				do_parse = true;
+
+				break;
+
+			case '"':
+			case '<':
+				if (flags & BIT(1)) {
+					nr = 0;
+					memset(word, 0x0, sizeof(word));
+
+					token = chr;
+				}
+
+				break;
+
+			case '\n':
+			case '\0':
+				token = 0;
+				do_parse = true;
+
+				break;
+
+			case '=':
+				if (flags & BIT(0))
+					flags |= BIT(1);
+
+				break;
+
+			default:
+				// drop
+				break;
+			}
+
+			break;
+		}
+
+		if (do_save) {
+			if (nr < sizeof(word) - 1)
+				word[nr++] = chr;
+		} else if (do_append) {
+
+			if (params++)
+				strlcat(skw_dat, comma, sizeof(skw_dat));
+
+			word[nr] = '\0';
+			strlcat(skw_dat, word, sizeof(skw_dat));
+
+			nr = 0;
+			memset(word, 0x0, sizeof(word));
+
+		} else if (do_parse) {
+			if (cfg && flags & BIT(1) && params) {
+				skw_detail("key: %s, data: %s\n", skw_key, skw_dat);
+				cfg->parser(cfg, skw_key, skw_dat);
+			}
+
+			flags = 0;
+			params = 0;
+			do_parse = false;
+
+			memset(skw_key, 0x0, sizeof(skw_key));
+			memset(skw_dat, 0x0, sizeof(skw_dat));
+		}
+	}
+}
+
+static int skw_global_parser(struct skwifi_cfg *config, char *key, char *data)
+{
+	int ret;
+	char *endp;
+	struct skw_cfg_global *cfg = config->priv;
+
+	if (cfg == NULL)
+		return -EINVAL;
+
+	if (!strcmp(key, "mac")) {
+		u8 addr[ETH_ALEN] = {0};
+
+		ret = sscanf(data, "0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x",
+			     (int *)&addr[0], (int *)&addr[1], (int *)&addr[2],
+			     (int *)&addr[3], (int *)&addr[4], (int *)&addr[5]);
+
+		skw_dbg("ret: %d, addr: %pM\n", ret, addr);
+		if (ret == ETH_ALEN && is_valid_ether_addr(addr))
+			skw_ether_copy(cfg->mac, addr);
+
+	} else if (!strcmp(key, "dma_addr_align")) {
+		cfg->dma_addr_align = simple_strtol(data, &endp, 0);
+
+	} else if (!strcmp(key, "reorder_timeout")) {
+		cfg->reorder_timeout = simple_strtol(data, &endp, 0);
+
+	} else if (!strcmp(key, "offchan_tx")) {
+		switch (simple_strtol(data, &endp, 0)) {
+		case 0:
+			clear_bit(SKW_CFG_FLAG_OFFCHAN_TX, &cfg->flags);
+			break;
+		case 1:
+			set_bit(SKW_CFG_FLAG_OFFCHAN_TX, &cfg->flags);
+			break;
+		default:
+			break;
+		}
+
+	} else if (!strcmp(key, "overlay_mode")) {
+		switch (simple_strtol(data, &endp, 0)) {
+		case 0:
+			clear_bit(SKW_CFG_FLAG_OVERLAY_MODE, &cfg->flags);
+			break;
+		case 1:
+			set_bit(SKW_CFG_FLAG_OVERLAY_MODE, &cfg->flags);
+			break;
+		default:
+			break;
+		}
+
+	} else {
+		skw_dbg("unsupport key: %s\n", key);
+	}
+
+	return 0;
+}
+
+static int skw_firmware_parser(struct skwifi_cfg *config, char *key, char *data)
+{
+	int ret;
+	char *endp;
+	struct skw_cfg_firmware *cfg = config->priv;
+
+	if (cfg == NULL)
+		return -EINVAL;
+
+	if (!strcmp(key, "link_loss_thrd")) {
+		cfg->link_loss_thrd = simple_strtol(data, &endp, 0);
+
+		skw_dbg("link_loss_thrd: %d\n", cfg->link_loss_thrd);
+	} else if (!strcmp(key, "go_noa_ratio_idx")) {
+		cfg->noa_ratio_en = simple_strtol(data, &endp, 0);
+
+		if (cfg->noa_ratio_en)
+			ret = sscanf(data, "%d,%d",
+				&cfg->noa_ratio_en, &cfg->noa_ratio_idx);
+
+		skw_dbg("ret:%d noa_ratio_en: %d, idx:%d\n", ret,
+			 cfg->noa_ratio_en, cfg->noa_ratio_idx);
+	} else if (!strcmp(key, "force_go_once_noa")) {
+		cfg->once_noa_en = simple_strtol(data, &endp, 0);
+
+		if (cfg->once_noa_en)
+			ret = sscanf(data, "%d,%d,%d",
+				&cfg->once_noa_en, &cfg->once_noa_pre, &cfg->once_noa_abs);
+
+		skw_dbg("ret:%d once_noa_en: %d, pre:%d, abs:%d\n", ret, cfg->once_noa_en,
+			 cfg->once_noa_pre, cfg->once_noa_abs);
+	} else if (!strcmp(key, "offload_roaming_disable")) {
+		cfg->offload_roaming_disable = simple_strtol(data, &endp, 0);
+
+		skw_dbg("offload_roaming_disable: %d\n", cfg->offload_roaming_disable);
+	} else if (!strcmp(key, "24ghz_bandwidth")) {
+		cfg->band_24ghz = simple_strtol(data, &endp, 0);
+
+		skw_dbg("24ghz_bandwidth: %d\n", cfg->band_24ghz);
+	} else if (!strcmp(key, "5ghz_bandwidth")) {
+		cfg->band_5ghz = simple_strtol(data, &endp, 0);
+
+		skw_dbg("5ghz_bandwidth: %d\n", cfg->band_5ghz);
+	} else if (!strcmp(key, "hdk_test")) {
+		cfg->hdk_tst = simple_strtol(data, &endp, 0);
+
+		skw_dbg("hdk_test: %d\n", cfg->hdk_tst);
+	} else {
+		skw_dbg("unsupport key: %s\n", key);
+	}
+
+	return 0;
+}
+
+static int skw_intf_parser(struct skwifi_cfg *config, char *key, char *data)
+{
+	int ret;
+	char *endp;
+	int i, nr_params;
+	char mode[64] = {0};
+	char inst[64] = {0};
+	char flags[64] = {0};
+	char mac[64] = {0};
+	char ifname[32] = {0};
+	u8 addr[ETH_ALEN] = {0};
+	// struct skw_cfg_intf *intf = config->priv;
+
+	/* "ifname",<mode>,<inst>,<flags>,<mac> */
+	nr_params = sscanf(data, "%16[^,],%32[^,],%32[^,],%32[^,],%s",
+			   ifname, mode, inst, flags, mac);
+
+	skw_detail("key: %s, data: %s, nr_params: %d\n", key, data, nr_params);
+
+	for (i = 0; i < nr_params; i++)
+		switch (i) {
+		/* interface name */
+		case 0:
+			skw_dbg("ifname: %s\n", ifname);
+			break;
+
+		/* interface mode */
+		case 1:
+			skw_dbg("mode: %ld\n", simple_strtol(mode, &endp, 0));
+			break;
+
+		/* interface instance */
+		case 2:
+			skw_dbg("inst: %ld\n", simple_strtol(inst, &endp, 0));
+			break;
+
+		/* interface flags */
+		case 3:
+			skw_dbg("flags: 0x%lx\n", simple_strtol(flags, &endp, 0));
+			break;
+
+		/* interface mac */
+		case 4:
+			ret = sscanf(mac, "0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x",
+				     (int *)&addr[0], (int *)&addr[1],
+				     (int *)&addr[2], (int *)&addr[3],
+				     (int *)&addr[4], (int *)&addr[5]);
+
+			skw_dbg("ret: %d, addr: %pM\n", ret, addr);
+			break;
+		}
+
+	return 0;
+}
+
+static int skw_calib_parser(struct skwifi_cfg *config, char *key, char *data)
+{
+	char *endp;
+	struct skw_cfg_calib *calib = config->priv;
+
+	if (calib == NULL)
+		return -EINVAL;
+
+	if (!strcmp(key, "strict_mode")) {
+		switch (simple_strtol(data, &endp, 0)) {
+		case 0:
+			clear_bit(SKW_CFG_CALIB_STRICT_MODE, &calib->flags);
+			break;
+		case 1:
+			set_bit(SKW_CFG_CALIB_STRICT_MODE, &calib->flags);
+			break;
+		default:
+			break;
+		}
+
+	} else if (!strcmp(key, "chip")) {
+		strlcpy(calib->chip, data, sizeof(calib->chip) - 1);
+
+	} else if (!strcmp(key, "project")) {
+		strlcpy(calib->project, data, sizeof(calib->project) - 1);
+
+	} else {
+		skw_dbg("unsupport key: %s\n", key);
+	}
+
+	return 0;
+}
+
+static int skw_regdom_parser(struct skwifi_cfg *config, char *key, char *data)
+{
+	struct skw_cfg_regd *regd = config->priv;
+
+	if (regd == NULL)
+		return -EINVAL;
+
+	if (!strcmp(key, "country")) {
+		if (strlen(data) == 2)
+			memcpy(regd->country, data, 2);
+
+	} else {
+		skw_dbg("unsupport key: %s\n", key);
+	}
+
+	return 0;
+}
+
+static int skw_roam_parser(struct skwifi_cfg *config, char *key, char *data)
+{
+	return 0;
+}
+
+static struct skwifi_cfg  g_cfg_table[] = {
+	{
+		.name = "global",
+		.parser = skw_global_parser,
+	},
+	{
+		.name = "interface",
+		.parser = skw_intf_parser,
+	},
+	{
+		.name = "calib",
+		.parser = skw_calib_parser,
+	},
+	{
+		.name = "regdom",
+		.parser = skw_regdom_parser,
+	},
+	{
+		.name = "roaming",
+		.parser = skw_roam_parser,
+	},
+	{
+		.name = "firmware",
+		.parser = skw_firmware_parser,
+	},
+	{
+		.name = NULL,
+		.parser = NULL,
+		.priv = NULL,
+	}
+};
+
+void skw_update_config(struct device *dev, const char *name, struct skw_config *config)
+{
+	int i;
+	const struct firmware *fw;
+
+	if (request_firmware_direct(&fw, name, dev))
+		return;
+
+	skw_dbg("load %s successful\n", name);
+
+	for (i = 0; g_cfg_table[i].name != NULL; i++) {
+		if (!strcmp(g_cfg_table[i].name, "global"))
+			g_cfg_table[i].priv = &config->global;
+		else if (!strcmp(g_cfg_table[i].name, "interface"))
+			g_cfg_table[i].priv = &config->intf;
+		else if (!strcmp(g_cfg_table[i].name, "calib"))
+			g_cfg_table[i].priv = &config->calib;
+		else if (!strcmp(g_cfg_table[i].name, "regdom"))
+			g_cfg_table[i].priv = &config->regd;
+		else if (!strcmp(g_cfg_table[i].name, "roaming"))
+			g_cfg_table[i].priv = NULL;
+		else if (!strcmp(g_cfg_table[i].name, "firmware"))
+			g_cfg_table[i].priv = &config->fw;
+		else {
+			g_cfg_table[i].priv = NULL;
+
+			skw_warn("section: %s not support\n", g_cfg_table[i].name);
+		}
+	}
+
+	skw_parser(g_cfg_table, fw->data, fw->size);
+
+	release_firmware(fw);
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_config.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_config.h
new file mode 100755
index 0000000..badaac5
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_config.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_CONFIG_H__
+#define __SKW_CONFIG_H__
+
+#include <linux/kernel.h>
+#include <linux/etherdevice.h>
+
+#define SKW_CFG_FLAG_OVERLAY_MODE           0
+#define SKW_CFG_FLAG_STA_EXT                1
+#define SKW_CFG_FLAG_SAP_EXT                2
+#define SKW_CFG_FLAG_OFFCHAN_TX             4
+#define SKW_CFG_FLAG_REPEATER               5
+#define SKW_CFG_FLAG_P2P_DEV                6
+
+struct skw_cfg_global {
+	unsigned long flags;
+
+	u8 mac[ETH_ALEN];
+	u8 dma_addr_align;
+	u8 reorder_timeout;
+};
+
+#define SKW_CFG_INTF_FLAG_INVALID           0
+#define SKW_CFG_INTF_FLAG_LEGACY            1
+struct skw_cfg_interface {
+	char name[IFNAMSIZ];
+	u8 mac[ETH_ALEN];
+	u8 iftype;
+	u8 inst;
+	unsigned long flags;
+};
+
+struct skw_cfg_intf {
+	struct skw_cfg_interface interface[4];
+};
+
+#define SKW_CFG_REGD_COUNTRY_CODE           0
+#define SKW_CFG_REGD_SELF_MANAGED           1
+#define SKW_CFG_REGD_IGNORE_USER            2
+#define SKW_CFG_REGD_IGNORE_COUNTRY_IE      3
+
+struct skw_cfg_regd {
+	unsigned long flags;
+	char country[2];
+};
+
+#define SKW_CFG_CALIB_STRICT_MODE           0
+#define SKW_CFG_CALIB_CHIP_NAME             1
+#define SKW_CFG_CALIB_PROJECT_NAME          2
+#define SKW_CFG_CALIB_EXTRA_ID              3
+
+struct skw_cfg_calib {
+	unsigned long flags;
+
+	char chip[16];
+	char project[16];
+};
+
+struct skw_cfg_firmware {
+	u32 link_loss_thrd;
+	u32 noa_ratio_idx;
+	u32 noa_ratio_en;
+	u32 once_noa_en;
+	u32 once_noa_pre;
+	u32 once_noa_abs;
+	u32 dot11k_disable;
+	u32 dot11v_disable;
+	u32 dot11r_disable;
+	u32 offload_roaming_disable;
+	u32 band_24ghz;
+	u32 band_5ghz;
+	u32 hdk_tst;
+};
+
+struct skw_config {
+	struct skw_cfg_global global;
+	struct skw_cfg_intf intf;
+	struct skw_cfg_calib calib;
+	struct skw_cfg_regd regd;
+	struct skw_cfg_firmware fw;
+};
+
+void skw_update_config(struct device *dev, const char *name, struct skw_config *config);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_core.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_core.c
new file mode 100755
index 0000000..5a319cf
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_core.c
@@ -0,0 +1,3530 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/ip.h>
+#include <linux/if_ether.h>
+#include <linux/skbuff.h>
+#include <linux/inetdevice.h>
+#include <net/addrconf.h>
+#include <linux/ctype.h>
+#include <net/tcp.h>
+#include <net/arp.h>
+#include <linux/platform_device.h>
+#include <linux/if_tunnel.h>
+#include <linux/firmware.h>
+#include <generated/utsrelease.h>
+#include <linux/suspend.h>
+
+#ifdef CONFIG_PLATFORM_ROCKCHIP
+#include <linux/rfkill-wlan.h>
+#endif
+
+#include "skw_core.h"
+#include "skw_cfg80211.h"
+#include "skw_tx.h"
+#include "skw_rx.h"
+#include "skw_iw.h"
+#include "skw_timer.h"
+#include "skw_work.h"
+#include "version.h"
+#include "skw_calib.h"
+#include "skw_vendor.h"
+#include "skw_regd.h"
+#include "skw_recovery.h"
+#include "skw_config.h"
+#include "trace.h"
+
+struct skw_global_config {
+	atomic_t index;
+	struct skw_config conf;
+};
+
+static u8 skw_mac[ETH_ALEN];
+static struct skw_global_config g_skw_config;
+
+static atomic_t skw_chip_idx = ATOMIC_INIT(0);
+
+static const int g_skw_up_to_ac[8] = {
+	SKW_WMM_AC_BE,
+	SKW_WMM_AC_BK,
+	SKW_WMM_AC_BK,
+	SKW_WMM_AC_BE,
+	SKW_WMM_AC_VI,
+	SKW_WMM_AC_VI,
+	SKW_WMM_AC_VO,
+	SKW_WMM_AC_VO
+};
+
+static int skw_repeater_show(struct seq_file *seq, void *data)
+{
+	struct skw_core *skw = seq->private;
+
+	if (test_bit(SKW_FLAG_REPEATER, &skw->flags))
+		seq_puts(seq, "enable\n");
+	else
+		seq_puts(seq, "disable\n");
+
+	return 0;
+}
+
+static int skw_repeater_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, skw_repeater_show, inode->i_private);
+}
+
+static ssize_t skw_repeater_write(struct file *fp, const char __user *buf,
+				size_t len, loff_t *offset)
+{
+	int i;
+	char cmd[32] = {0};
+	struct skw_core *skw = fp->f_inode->i_private;
+
+	for (i = 0; i < len; i++) {
+		char c;
+
+		if (get_user(c, buf))
+			return -EFAULT;
+
+		if (c == '\n' || c == '\0')
+			break;
+
+		cmd[i] = tolower(c);
+		buf++;
+	}
+
+	if (strcmp(cmd, "enable") == 0)
+		set_bit(SKW_FLAG_REPEATER, &skw->flags);
+	else if (strcmp(cmd, "disable") == 0)
+		clear_bit(SKW_FLAG_REPEATER, &skw->flags);
+	else
+		skw_warn("rx_reorder support setting values of \"enable\" or \"disable\"\n");
+
+	return len;
+}
+
+static const struct file_operations skw_repeater_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_repeater_open,
+	.read = seq_read,
+	.release = single_release,
+	.write = skw_repeater_write,
+};
+
+static void skw_dbg_print(struct skw_core *skw, struct seq_file *seq)
+{
+	int i;
+	u64 nsecs;
+	unsigned long rem_nsec;
+
+	for (i = 0; i < skw->dbg.nr_cmd; i++) {
+		struct skw_dbg_cmd *cmd = &skw->dbg.cmd[i];
+
+		seq_printf(seq, "cmd[%d].id: %d, seq: %d, flags: 0x%lx, loop: %d\n",
+			i, cmd->id, cmd->seq, cmd->flags, cmd->loop);
+
+		nsecs = cmd->trigger;
+		rem_nsec = do_div(nsecs, 1000000000);
+		seq_printf(seq, "    cmd.%d.%d.trigger: %5lu.%06lu\n", cmd->id, cmd->seq,
+			(unsigned long)nsecs, rem_nsec / 1000);
+
+		nsecs = cmd->build;
+		rem_nsec = do_div(nsecs, 1000000000);
+		seq_printf(seq, "    cmd.%d.%d.build: %5lu.%06lu\n", cmd->id, cmd->seq,
+			(unsigned long)nsecs, rem_nsec / 1000);
+
+		nsecs = cmd->xmit;
+		rem_nsec = do_div(nsecs, 1000000000);
+		seq_printf(seq, "    cmd.%d.%d.xmit: %5lu.%06lu\n", cmd->id, cmd->seq,
+			(unsigned long)nsecs, rem_nsec / 1000);
+
+		nsecs = cmd->done;
+		rem_nsec = do_div(nsecs, 1000000000);
+		seq_printf(seq, "    cmd.%d.%d.done: %5lu.%06lu\n", cmd->id, cmd->seq,
+			(unsigned long)nsecs, rem_nsec / 1000);
+
+		nsecs = cmd->ack;
+		rem_nsec = do_div(nsecs, 1000000000);
+		seq_printf(seq, "    cmd.%d.%d.ack: %5lu.%06lu\n", cmd->id, cmd->seq,
+			(unsigned long)nsecs, rem_nsec / 1000);
+
+		nsecs = cmd->assert;
+		rem_nsec = do_div(nsecs, 1000000000);
+		seq_printf(seq, "    cmd.%d.%d.assert: %5lu.%06lu\n", cmd->id, cmd->seq,
+			(unsigned long)nsecs, rem_nsec / 1000);
+
+		seq_puts(seq, "\n");
+	}
+
+	for (i = 0; i < skw->dbg.nr_dat; i++) {
+		struct skw_dbg_dat *dat = &skw->dbg.dat[i];
+
+		seq_printf(seq, "dat[%d], tx_qlen: %d\n", i, dat->qlen);
+
+		nsecs = dat->trigger;
+		rem_nsec = do_div(nsecs, 1000000000);
+		seq_printf(seq, "    dat.%d.%d.trigger: %5lu.%06lu\n",
+			i, dat->qlen, (unsigned long)nsecs, rem_nsec / 1000);
+
+		nsecs = dat->done;
+		rem_nsec = do_div(nsecs, 1000000000);
+		seq_printf(seq, "    dat.%d.%d.done: %5lu.%06lu\n",
+			i, dat->qlen, (unsigned long)nsecs, rem_nsec / 1000);
+
+		seq_puts(seq, "\n");
+	}
+}
+
+static void skw_timestap_print(struct skw_core *skw, struct seq_file *seq)
+{
+	u64 ts;
+	struct rtc_time tm;
+	unsigned long rem_nsec;
+
+	ts = skw->fw.host_timestamp;
+	rem_nsec = do_div(ts, 1000000000);
+	skw_compat_rtc_time_to_tm(skw->fw.host_seconds, &tm);
+
+	seq_printf(seq,
+		   "Timestamp: %u - %lu.%06lu (%d-%02d-%02d %02d:%02d:%02d UTC)\n",
+		   skw->fw.timestamp, (unsigned long)ts,
+		   rem_nsec / 1000, tm.tm_year + 1900,
+		   tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
+		   tm.tm_min, tm.tm_sec);
+}
+
+static void skw_drv_info_print(struct skw_core *skw, struct seq_file *seq)
+{
+	int i;
+
+#define FLAG_TEST(n) test_bit(SKW_FLAG_##n, &skw->flags)
+	seq_printf(seq,
+		   "SKW Flags:\t 0x%lx %s\n"
+		   "TX Packets:\t %ld\n"
+		   "RX Packets:\t %ld\n"
+		   "txqlen_pending:\t %d\n"
+		   "nr lmac:\t %d\n"
+		   "skb_recycle_qlist:%d\n",
+		   skw->flags, FLAG_TEST(FW_ASSERT) ? "(Assert)" : "",
+		   skw->tx_packets,
+		   skw->rx_packets,
+		   atomic_read(&skw->txqlen_pending),
+		   skw->hw.nr_lmac,
+		   READ_ONCE(skw->skb_recycle_qlist.qlen));
+
+	for (i = 0; i < skw->hw.nr_lmac; i++)
+		seq_printf(seq, "    credit[%d]:\t %d (%s)\n",
+			   i, skw_get_hw_credit(skw, i),
+			   skw_lmac_is_actived(skw, i) ? "active" : "inactive");
+#undef FLAG_TEST
+}
+
+static void skw_fw_info_print(struct skw_core *skw, struct seq_file *seq)
+{
+	if (!skw->hw_pdata)
+		return;
+
+	/* Firmware & BSP Info */
+	seq_printf(seq, "Calib File:\t %s\n"
+			"FW Build:\t %s\n"
+			"FW Version:\t %s-%s (BSP-MAC)\n"
+			"BUS Type:\t 0x%x (%s)\n"
+			"Align Size:\t %d\n"
+			"TX Limit:\t %d\n",
+			skw->fw.calib_file,
+			skw->fw.build_time,
+			skw->fw.plat_ver,
+			skw->fw.wifi_ver,
+			skw->hw_pdata->bus_type,
+			skw_bus_name(skw->hw.bus),
+			skw->hw_pdata->align_value,
+			skw->hw.pkt_limit);
+}
+
+static int skw_core_show(struct seq_file *seq, void *data)
+{
+	struct skw_core *skw = seq->private;
+
+	seq_puts(seq, "\n");
+	skw_timestap_print(skw, seq);
+
+	seq_puts(seq, "\n");
+	skw_drv_info_print(skw, seq);
+
+	seq_puts(seq, "\n");
+	skw_fw_info_print(skw, seq);
+
+	seq_puts(seq, "\n");
+	skw_dbg_print(skw, seq);
+
+	seq_puts(seq, "\n");
+
+	return 0;
+}
+
+static int skw_core_open(struct inode *inode, struct file *file)
+{
+	// return single_open(file, skw_core_show, inode->i_private);
+	return single_open(file, skw_core_show, skw_pde_data(inode));
+}
+
+void skw_debug_print_hw_tx_info(struct seq_file *seq, struct skw_get_hw_tx_info_s *info)
+{
+	int i = 0;
+
+	if (!info) {
+		seq_puts(seq, "HW Tx Info structure is NULL\n");
+		return;
+	}
+
+	seq_puts(seq, "Hardware Tx info:\n\n");
+	seq_printf(seq, "\tHIF TX All Count: %u\n", info->hif_tx_all_cnt);
+	seq_printf(seq, "\tHIF Report All Credit Count: %u\n", info->hif_rpt_all_credit_cnt);
+	seq_printf(seq, "\tCurrent Free Buffer Count: %u\n", info->cur_free_buf_cnt);
+	seq_printf(seq, "\tPending TX Packet Count: %u\n", info->pendding_tx_pkt_cnt);
+	seq_printf(seq, "\tLast Command Sequence: %u\n", info->lst_cmd_seq);
+	seq_printf(seq, "\tLast Event Sequence: %u\n", info->lst_evt_seq);
+	seq_printf(seq, "\tLMAC TX Broadcast Count: %u\n", info->lmac_tx_bc_cnt);
+	seq_printf(seq, "\tLMAC TX Unicast Count: %u\n", info->lmac_tx_uc_cnt[0]);
+
+	// Print array lmac_tx_uc_cnt
+	seq_puts(seq, "\tLMAC TX Unicast Count Array: ");
+
+	for (i = 0; i < 5; i++)
+		seq_printf(seq, "%u ", info->lmac_tx_uc_cnt[i]);
+
+	seq_puts(seq, "\n");
+
+	// Print hmac_tx_stat
+	seq_puts(seq, "\tHMAC TX Stat: ");
+
+	for (i = 0; i < 7; i++)
+		seq_printf(seq, "%u ", info->hmac_tx_stat.hmac_tx_stat[i]);
+
+	seq_puts(seq, "\n");
+
+	// Print tx_mib_acc_info
+	seq_printf(seq, "\tAccumulated TX PSDU Count: %u\n", info->tx_mib_acc_info.acc_tx_psdu_cnt);
+	seq_printf(seq, "\tAccumulated TX With ACK Timeout Count: %u\n", info->tx_mib_acc_info.acc_tx_wi_ack_to_cnt);
+	seq_printf(seq, "\tAccumulated TX With ACK Fail Count: %u\n", info->tx_mib_acc_info.acc_tx_wi_ack_fail_cnt);
+	seq_printf(seq, "\tAccumulated RTS Without CTS Count: %u\n", info->tx_mib_acc_info.acc_rts_wo_cts_cnt);
+	seq_printf(seq, "\tAccumulated TX Post Busy Count: %u\n", info->tx_mib_acc_info.acc_tx_post_busy_cnt);
+	seq_printf(seq, "\tTX Enable Percentage: %u%%\n", info->tx_mib_acc_info.tx_en_perct);
+
+	// Print tx_mib_tb_acc_info
+	seq_printf(seq, "\tTB Basic Trigger Count: %u\n", info->tx_mib_tb_acc_info.rx_basic_trig_cnt);
+	seq_printf(seq, "\tTB Basic Trigger Success Count: %u\n", info->tx_mib_tb_acc_info.rx_basic_trig_succ_cnt);
+	seq_printf(seq, "\tTB Generate Invoke Count: %u\n", info->tx_mib_tb_acc_info.tb_gen_invoke_cnt);
+	seq_printf(seq, "\tTB Generate Abort Count: %u\n", info->tx_mib_tb_acc_info.tb_gen_abort_cnt);
+	seq_printf(seq, "\tTB CS Fail Accumulated Count: %u\n", info->tx_mib_tb_acc_info.tb_cs_fail_acc_cnt);
+	seq_printf(seq, "\tAll TB Pre TX Count: %u\n", info->tx_mib_tb_acc_info.all_tb_pre_tx_cnt);
+	seq_printf(seq, "\tTB TX Accumulated Count: %u\n", info->tx_mib_tb_acc_info.tb_tx_acc_cnt);
+	seq_printf(seq, "\tTB TX Success Accumulated Count: %u\n", info->tx_mib_tb_acc_info.tb_tx_acc_scucc_cnt);
+	seq_printf(seq, "\tTB TX Success Ratio: %u\n", info->tx_mib_tb_acc_info.tb_tx_suc_ratio);
+	seq_printf(seq, "\tRX Trigger Register Count: %u\n", info->tx_mib_tb_acc_info.rx_trig_reg_cnt);
+	seq_printf(seq, "\tRX Trigger Start TX Count: %u\n", info->tx_mib_tb_acc_info.rx_trig_start_tx_cnt);
+	seq_printf(seq, "\tRX Trigger Success Ratio: %u\n", info->tx_mib_tb_acc_info.rx_trig_suc_ratio);
+}
+
+void skw_debug_print_inst_info(struct seq_file *seq, struct skw_get_inst_info_s *info)
+{
+	int i = 0;
+
+	if (!info) {
+		seq_puts(seq, "\tInst Info structure is NULL\n");
+		return;
+	}
+
+	seq_puts(seq, "Inst info:\n\n");
+	seq_printf(seq, "\tInst Mode: %u\n", info->inst_mode);
+	seq_printf(seq, "\tHW ID: %u\n", info->hw_id);
+	seq_printf(seq, "\tEnable: %u\n", info->enable);
+	seq_printf(seq, "\tMAC ID: %u\n", info->mac_id);
+	seq_printf(seq, "\tASOC Peer Map: %u\n", info->asoc_peer_map);
+	seq_printf(seq, "\tPeer PS State: %u\n", info->peer_ps_state);
+
+	seq_puts(seq, "\tCS Info:\n");
+	seq_printf(seq, "\t\tP20 Last Busy Percentage: %u\n", info->cs_info.p20_lst_busy_perctage);
+	seq_printf(seq, "\t\tP40 Last Busy Percentage: %u\n", info->cs_info.p40_lst_busy_perctage);
+	seq_printf(seq, "\t\tP80 Last Busy Percentage: %u\n", info->cs_info.p80_lst_busy_perctage);
+	seq_printf(seq, "\t\tP20 Last NAV Percentage: %u\n", info->cs_info.p20_lst_nav_perctage);
+	seq_printf(seq, "\t\tP40 Last NAV Percentage: %u\n", info->cs_info.p40_lst_nav_perctage);
+	seq_printf(seq, "\t\tP80 Last NAV Percentage: %u\n", info->cs_info.p80_lst_nav_perctage);
+
+	seq_puts(seq, "\tRPI Info:\n");
+	seq_printf(seq, "\t\tNo UC Direct RPI Level: %d\n", info->rpi_info.no_uc_direct_rpi_level);
+	seq_printf(seq, "\t\tUC Direct RPI Level: %d\n", info->rpi_info.uc_direct_rpi_level);
+	seq_printf(seq, "\t\tRX BA RSSI: %d\n", info->rpi_info.rx_ba_rssi);
+	seq_printf(seq, "\t\tRX Idle Percent in RPI: %u\n", info->rpi_info.rx_idle_percent_in_rpi);
+	seq_printf(seq, "\t\tRX Percent in RPI: %u\n", info->rpi_info.rx_percent_in_rpi);
+
+	seq_puts(seq, "\t\tRPI Class Duration: ");
+
+	for (i = 0; i < RPI_MAX_LEVEL; i++)
+		seq_printf(seq, "%u ", info->rpi_info.rpi_class_dur[i]);
+
+	seq_puts(seq, "\n");
+
+	seq_puts(seq, "\tNoise Info:\n");
+	seq_puts(seq, "\t\tNoise Class Duration: ");
+
+	for (i = 0; i < NOISE_MAX_LEVEL; i++)
+		seq_printf(seq, "%u ", info->noise_info.noise_class_dur[i]);
+
+	seq_puts(seq, "\n");
+}
+
+void skw_debug_print_peer_info(struct seq_file *seq, struct skw_get_peer_info *peer_info)
+{
+	int i = 0;
+
+	if (!peer_info) {
+		seq_puts(seq, "\tPeer Info structure is NULL\n");
+		return;
+	}
+
+	seq_puts(seq, "Peer info:\n\n");
+	seq_printf(seq, "\tIs Valid: %u\n", peer_info->is_valid);
+	seq_printf(seq, "\tInstance ID: %u\n", peer_info->inst_id);
+	seq_printf(seq, "\tHW ID: %u\n", peer_info->hw_id);
+	seq_printf(seq, "\tRX RSSI: %d\n", peer_info->rx_rsp_rssi);
+	seq_printf(seq, "\tAverage ADD Delay (ms): %u\n", peer_info->avg_add_delay_in_ms);
+	seq_printf(seq, "\tTX Max Delay Time: %u\n", peer_info->tx_max_delay_time);
+
+	// Print TX Hmac Per Peer Report
+	seq_puts(seq, "\tHMAC TX Stat:\n");
+	seq_puts(seq, "\t\t");
+
+	for (i = 0; i < 5; i++)
+		seq_printf(seq, "%u ", peer_info->hmac_tx_stat.hmac_tx_stat[i]);
+
+	seq_puts(seq, "\n");
+
+	seq_printf(seq, "\tTXC ISR Read DSCR FIFO Count: %u\n",
+		   peer_info->hmac_tx_stat.txc_isr_read_dscr_fifo_cnt);
+
+	// Print TX Rate Info
+	seq_puts(seq, "\tTX Rate Info:\n");
+	seq_printf(seq, "\t\tFlags: %u\n", peer_info->tx_rate_info.flags);
+	seq_printf(seq, "\t\tMCS Index: %u\n", peer_info->tx_rate_info.mcs_idx);
+	seq_printf(seq, "\t\tLegacy Rate: %u\n", peer_info->tx_rate_info.legacy_rate);
+	seq_printf(seq, "\t\tNSS: %u\n", peer_info->tx_rate_info.nss);
+	seq_printf(seq, "\t\tBW: %u\n", peer_info->tx_rate_info.bw);
+	seq_printf(seq, "\t\tGI: %u\n", peer_info->tx_rate_info.gi);
+	seq_printf(seq, "\t\tRU: %u\n", peer_info->tx_rate_info.ru);
+	seq_printf(seq, "\t\tDCM: %u\n", peer_info->tx_rate_info.dcm);
+
+	// Print RX MSDU Info Report
+	seq_puts(seq, "\tRX MSDU Info:\n");
+	seq_printf(seq, "\t\tPPDU Mode: %u\n", peer_info->rx_msdu_info_rpt.ppdu_mode);
+	seq_printf(seq, "\t\tData Rate: %u\n", peer_info->rx_msdu_info_rpt.data_rate);
+	seq_printf(seq, "\t\tGI Type: %u\n", peer_info->rx_msdu_info_rpt.gi_type);
+	seq_printf(seq, "\t\tSBW: %u\n", peer_info->rx_msdu_info_rpt.sbw);
+	seq_printf(seq, "\t\tDCM: %u\n", peer_info->rx_msdu_info_rpt.dcm);
+	seq_printf(seq, "\t\tNSS: %u\n", peer_info->rx_msdu_info_rpt.nss);
+
+	// Print TX Debug Stats Per Peer
+	seq_puts(seq, "\tTX Stats Info:\n");
+	seq_printf(seq, "\t\tRTS Fail Count: %u\n", peer_info->tx_stats_info.rts_fail_cnt);
+	seq_printf(seq, "\t\tPSDU Ack Timeout Count: %u\n", peer_info->tx_stats_info.psdu_ack_timeout_cnt);
+	seq_printf(seq, "\t\tTX MPDU Count: %u\n", peer_info->tx_stats_info.tx_mpdu_cnt);
+	seq_printf(seq, "\t\tTX Wait Time: %u\n", peer_info->tx_stats_info.tx_wait_time);
+
+	// Print TX Pending Packet Count
+	seq_puts(seq, "\tTX Pending Packet Count: ");
+
+	for (i = 0; i < 4; i++)
+		seq_printf(seq, "%u ", peer_info->tx_pending_pkt_cnt[i]);
+
+	seq_puts(seq, "\n");
+}
+
+void skw_debug_print_hw_rx_info(struct seq_file *seq, struct skw_get_hw_rx_info *rx_info)
+{
+	if (!rx_info) {
+		seq_puts(seq, "\tInfo structure is NULL\n");
+		return;
+	}
+
+	seq_puts(seq, "rx info:\n\n");
+	seq_printf(seq, "\tPHY RX All Count: %u\n", rx_info->phy_rx_all_cnt);
+	seq_printf(seq, "\tPHY RX 11B Count: %u\n", rx_info->phy_rx_11b_cnt);
+	seq_printf(seq, "\tPHY RX 11G Count: %u\n", rx_info->phy_rx_11g_cnt);
+	seq_printf(seq, "\tPHY RX 11N Count: %u\n", rx_info->phy_rx_11n_cnt);
+	seq_printf(seq, "\tPHY RX 11AC Count: %u\n", rx_info->phy_rx_11ac_cnt);
+	seq_printf(seq, "\tPHY RX 11AX Count: %u\n", rx_info->phy_rx_11ax_cnt);
+	seq_printf(seq, "\tMAC RX MPDU Count: %u\n", rx_info->mac_rx_mpdu_cnt);
+	seq_printf(seq, "\tMAC RX MPDU FCS Pass Count: %u\n", rx_info->mac_rx_mpdu_fcs_pass_cnt);
+	seq_printf(seq, "\tMAC RX MPDU FCS Pass Direct Count: %u\n", rx_info->mac_rx_mpdu_fcs_pass_dir_cnt);
+}
+
+void skw_debug_print_inst_tsf(struct seq_file *seq, struct skw_inst_tsf *inst_tsf)
+{
+	if (!inst_tsf) {
+		seq_puts(seq, "\tInst tsf structure is NULL\n");
+		return;
+	}
+
+	seq_puts(seq, "inst tsf:\n\n");
+	seq_printf(seq, "\tInst tsf low: %u\n", inst_tsf->tsf_l);
+	seq_printf(seq, "\tInst tsf high: %u\n", inst_tsf->tsf_h);
+}
+
+void skw_debug_print_winfo(struct seq_file *seq, struct skw_debug_info_w  *w_info)
+{
+	int i, j;
+	int percent;
+
+	seq_puts(seq, "debug_w info:\n");
+
+	for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
+		seq_printf(seq, "    hw[%d]:\t total[%d]\t other_count:%d\n",
+			i, w_info->hw_w[i].w_total_cnt,
+			w_info->hw_w[i].w_cnt[SKW_MAX_W_LEVEL - 1]);
+		seq_puts(seq, "\n");
+		for (j = 0; j < SKW_MAX_W_LEVEL - 1; j++) {
+			percent = w_info->hw_w[i].w_cnt[j] * 1000
+					/ w_info->hw_w[i].w_total_cnt;
+			seq_printf(seq, "\t  %d:thresh[%d]\t"
+			"count:%d\t     percent:%d.%d%%\n", j,
+				w_info->hw_w[i].w_ctrl_thresh[j],
+				w_info->hw_w[i].w_cnt[j],
+				percent/10, percent%10);
+		}
+		seq_puts(seq, "\n");
+	}
+
+	seq_puts(seq, "\n");
+
+}
+
+void skw_debug_print_nss_info(struct seq_file *seq, struct skw_mac_nss_info  *nss_info)
+{
+	int i;
+
+	seq_puts(seq, "nss info:\n");
+	seq_printf(seq,
+		"\t mac0 nss: %d,\tmac1 nss: %d.\t"
+		" dbdc mode:%d\n\n"
+		"\t instence num:%d\n",
+		nss_info->mac_nss[0],
+		nss_info->mac_nss[1],
+		nss_info->is_dbdc_mode,
+		nss_info->max_inst_num);
+
+	for (i = 0; i < nss_info->max_inst_num; i++)
+		if (nss_info->inst_nss[i].valid_id)
+			seq_printf(seq, "\t  inst[%d]:\t valid:%d\t"
+				"rx_nss:%d\t tx_nss:%d\n",
+				i, nss_info->inst_nss[i].valid_id,
+				nss_info->inst_nss[i].inst_rx_nss,
+				nss_info->inst_nss[i].inst_tx_nss);
+
+	seq_puts(seq, "\n");
+
+}
+
+int skw_debug_get_hw_tx_info(struct skw_core *skw, u8 *buf, int len)
+{
+	int ret = -1;
+	struct skw_tlv *tlv;
+	struct skw_debug_info_s debug_info_param = {0};
+
+	debug_info_param.tlv = SKW_MIB_GET_HW_TX_INFO_E;
+	ret = skw_msg_xmit(priv_to_wiphy(skw), 0, SKW_CMD_GET_DEBUG_INFO,
+				&debug_info_param, sizeof(struct skw_debug_info_s),
+				buf, len);
+
+	if (ret) {
+		skw_warn("failed, ret: %d\n", ret);
+		return ret;
+	}
+
+	tlv = (struct skw_tlv *)buf;
+	if (tlv->type != SKW_MIB_GET_HW_TX_INFO_E ||
+		tlv->len != sizeof(struct skw_get_hw_tx_info_s))
+		return -1;
+
+	return 0;
+}
+
+int skw_debug_get_inst_info(struct skw_core *skw, u8 inst_id, u8 *buf, int len)
+{
+	int ret = -1;
+	struct skw_tlv *tlv;
+	struct skw_debug_info_s debug_info_param = {0};
+
+	debug_info_param.tlv = SKW_MIB_GET_INST_INFO_E;
+	ret = skw_msg_xmit(priv_to_wiphy(skw), inst_id, SKW_CMD_GET_DEBUG_INFO,
+				&debug_info_param, sizeof(struct skw_debug_info_s),
+				buf, len);
+
+	if (ret) {
+		skw_warn("failed, ret: %d\n", ret);
+		return ret;
+	}
+
+	tlv = (struct skw_tlv *)buf;
+	if (tlv->type != SKW_MIB_GET_INST_INFO_E ||
+		tlv->len != sizeof(struct skw_get_inst_info_s))
+		return -1;
+
+	return 0;
+}
+
+int skw_debug_get_peer_info(struct skw_core *skw, u8 peer_idx, u8 *buf, int len)
+{
+	int ret = -1;
+	struct skw_tlv *tlv;
+	struct skw_debug_info_s debug_info_param = {0};
+
+	debug_info_param.tlv = SKW_MIB_GET_PEER_INFO_E;
+	debug_info_param.val[0] = peer_idx;
+	ret = skw_msg_xmit(priv_to_wiphy(skw), 0, SKW_CMD_GET_DEBUG_INFO,
+				&debug_info_param, sizeof(struct skw_debug_info_s),
+				buf, len);
+
+	if (ret) {
+		skw_warn("failed, ret: %d\n", ret);
+		return ret;
+	}
+
+	tlv = (struct skw_tlv *)buf;
+	if (tlv->type != SKW_MIB_GET_PEER_INFO_E ||
+		tlv->len != sizeof(struct skw_get_peer_info))
+		return -1;
+
+	return 0;
+}
+
+
+int skw_debug_get_hw_rx_info(struct skw_core *skw,  u8 *buf, int len)
+{
+	int ret = -1;
+	struct skw_tlv *tlv;
+	struct skw_debug_info_s debug_info_param = {0};
+
+	debug_info_param.tlv = SKW_MIB_GET_HW_RX_INFO_E;
+	ret = skw_msg_xmit(priv_to_wiphy(skw), 0, SKW_CMD_GET_DEBUG_INFO,
+				&debug_info_param, sizeof(struct skw_debug_info_s),
+				buf, len);
+
+	if (ret) {
+		skw_warn("failed, ret: %d\n", ret);
+		return ret;
+	}
+
+	tlv = (struct skw_tlv *)buf;
+	if (tlv->type != SKW_MIB_GET_HW_RX_INFO_E ||
+		tlv->len != sizeof(struct skw_get_hw_rx_info))
+		return -1;
+
+	return 0;
+}
+
+int skw_debug_get_inst_tsf(struct skw_core *skw, u8 *buf, int len)
+{
+	int ret = -1;
+	struct skw_tlv *tlv;
+	struct skw_debug_info_s debug_info_param = {0};
+
+	debug_info_param.tlv = SKW_MIB_GET_INST_TSF_E;
+	ret = skw_msg_xmit(priv_to_wiphy(skw), 0, SKW_CMD_GET_DEBUG_INFO,
+				&debug_info_param, sizeof(struct skw_debug_info_s),
+				buf, len);
+
+	if (ret) {
+		skw_warn("failed, ret: %d\n", ret);
+		return ret;
+	}
+
+	tlv = (struct skw_tlv *)buf;
+	if (tlv->type != SKW_MIB_GET_INST_TSF_E ||
+		tlv->len != sizeof(struct skw_inst_tsf))
+		return -1;
+
+	return 0;
+}
+
+int skw_debug_get_winfo(struct skw_core *skw, u8 *buf, int len)
+{
+	int ret = -1;
+	struct skw_tlv *tlv;
+
+	struct skw_debug_info_s debug_info_param = {0};
+	debug_info_param.tlv = SKW_MIB_W_INFO;
+	ret = skw_msg_xmit(priv_to_wiphy(skw), 0, SKW_CMD_GET_DEBUG_INFO,
+						&debug_info_param, sizeof(struct skw_debug_info_s),
+						buf, len);
+
+	if (ret) {
+		skw_warn("failed, ret: %d\n", ret);
+		return ret;
+	}
+
+	tlv = (struct skw_tlv *)buf;
+	if (tlv->type != SKW_MIB_W_INFO ||
+		tlv->len != sizeof(struct skw_debug_info_w))
+		return -1;
+
+	return 0;
+}
+
+int skw_debug_get_nss_info(struct skw_core *skw, u8 *buf, int len)
+{
+	int ret = -1;
+	struct skw_tlv *tlv;
+
+	struct skw_debug_info_s debug_info_param = {0};
+	debug_info_param.tlv = SKW_MIB_MAC_NSS_INFO;
+	ret = skw_msg_xmit(priv_to_wiphy(skw), 0, SKW_CMD_GET_DEBUG_INFO,
+						&debug_info_param, sizeof(struct skw_debug_info_s),
+						buf, len);
+
+	if (ret) {
+		skw_warn("failed, ret: %d\n", ret);
+		return ret;
+	}
+
+	tlv = (struct skw_tlv *)buf;
+	if (tlv->type != SKW_MIB_MAC_NSS_INFO ||
+		tlv->len != sizeof(struct skw_mac_nss_info))
+		return -1;
+
+	return 0;
+}
+
+static int skw_debug_info_show(struct seq_file *seq, void *data)
+{
+	struct skw_core *skw = seq->private;
+	u8 *tx_info = NULL;
+	u8 *inst_info = NULL;
+	u8 *peer_info = NULL;
+	u8 *rx_info = NULL;
+	u8 *inst_tsf = NULL;
+	u8 *nss_buf = NULL;
+	u8 *w_info = NULL;
+	int len;
+
+	len = sizeof(struct skw_get_hw_tx_info_s) + sizeof(struct skw_tlv);
+	tx_info = SKW_ZALLOC(len, GFP_KERNEL);
+	if (!tx_info) {
+		skw_err("tx_info failed\n");
+	} else {
+		if (skw_debug_get_hw_tx_info(skw, tx_info, len) == 0) {
+			seq_puts(seq, "\n");
+			skw_debug_print_hw_tx_info(seq, (void *)(tx_info + sizeof(struct skw_tlv)));
+			seq_puts(seq, "\n");
+		}
+
+		SKW_KFREE(tx_info);
+	}
+
+	len = sizeof(struct skw_get_inst_info_s) + sizeof(struct skw_tlv);
+	inst_info = SKW_ZALLOC(len, GFP_KERNEL);
+	if (!inst_info) {
+		skw_err("inst_info failed\n");
+	} else {
+		if (skw_debug_get_inst_info(skw, 0, inst_info, len) == 0) {
+			seq_puts(seq, "\n");
+			skw_debug_print_inst_info(seq, (void *)(inst_info + sizeof(struct skw_tlv)));
+			seq_puts(seq, "\n");
+		}
+
+		SKW_KFREE(inst_info);
+	}
+
+	len = sizeof(struct skw_get_peer_info) + sizeof(struct skw_tlv);
+	peer_info = SKW_ZALLOC(len, GFP_KERNEL);
+	if (!peer_info) {
+		skw_err("peer_info failed\n");
+	} else {
+		if (skw_debug_get_peer_info(skw, 0, peer_info, len) == 0) {
+			seq_puts(seq, "\n");
+			skw_debug_print_peer_info(seq, (void *)(peer_info + sizeof(struct skw_tlv)));
+			seq_puts(seq, "\n");
+		}
+
+		SKW_KFREE(peer_info);
+	}
+
+	len = sizeof(struct skw_get_hw_rx_info) + sizeof(struct skw_tlv);
+	rx_info = SKW_ZALLOC(len, GFP_KERNEL);
+	if (!rx_info) {
+		skw_err("rx_info failed\n");
+	} else {
+		if (skw_debug_get_hw_rx_info(skw, rx_info, len) == 0) {
+			seq_puts(seq, "\n");
+			skw_debug_print_hw_rx_info(seq, (void *)(rx_info + sizeof(struct skw_tlv)));
+			seq_puts(seq, "\n");
+		}
+
+		SKW_KFREE(rx_info);
+	}
+
+	len = sizeof(struct skw_inst_tsf) + sizeof(struct skw_tlv);
+	inst_tsf = SKW_ZALLOC(len, GFP_KERNEL);
+	if (!inst_tsf) {
+		skw_err("inst_tsf failed\n");
+	} else {
+		if (skw_debug_get_inst_tsf(skw, inst_tsf, len) == 0) {
+			seq_puts(seq, "\n");
+			skw_debug_print_inst_tsf(seq, (void *)(inst_tsf + sizeof(struct skw_tlv)));
+			seq_puts(seq, "\n");
+		}
+
+		SKW_KFREE(inst_tsf);
+	}
+
+	len = sizeof(struct skw_debug_info_w) + sizeof(struct skw_tlv);
+	w_info = SKW_ZALLOC(len, GFP_KERNEL);
+	if (!w_info) {
+		skw_err("w_info failed\n");
+	} else {
+		if (skw_debug_get_winfo(skw, w_info, len) == 0) {
+			seq_puts(seq, "\n");
+			skw_debug_print_winfo(seq, (void *)(w_info + sizeof(struct skw_tlv)));
+			seq_puts(seq, "\n");
+		}
+
+		SKW_KFREE(w_info);
+	}
+
+	len = sizeof(struct skw_mac_nss_info) + sizeof(struct skw_tlv);
+	nss_buf = SKW_ZALLOC(len, GFP_KERNEL);
+	if (!nss_buf) {
+		skw_err("nss_buf failed\n");
+	} else {
+		if (skw_debug_get_nss_info(skw, nss_buf, len) == 0) {
+			seq_puts(seq, "\n");
+			skw_debug_print_nss_info(seq, (void *)(nss_buf + sizeof(struct skw_tlv)));
+			seq_puts(seq, "\n");
+		}
+
+		SKW_KFREE(nss_buf);
+	}
+
+	seq_puts(seq, "\n");
+
+	return 0;
+}
+
+static int skw_debug_info_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, skw_debug_info_show, skw_pde_data(inode));
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_core_fops = {
+	.proc_open = skw_core_open,
+	.proc_read = seq_read,
+	.proc_lseek = seq_lseek,
+	.proc_release = single_release,
+};
+static const struct proc_ops skw_debug_info_fops = {
+	.proc_open = skw_debug_info_open,
+	.proc_read = seq_read,
+	.proc_lseek = seq_lseek,
+	.proc_release = single_release,
+};
+#else
+static const struct file_operations skw_core_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_core_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+static const struct file_operations skw_debug_info_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_debug_info_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+#endif
+
+static int skw_assert_show(struct seq_file *seq, void *data)
+{
+	return 0;
+}
+
+static int skw_assert_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, skw_assert_show, inode->i_private);
+}
+
+static ssize_t skw_assert_write(struct file *fp, const char __user *buf,
+				size_t len, loff_t *offset)
+{
+	struct skw_core *skw = fp->f_inode->i_private;
+
+	skw_hw_assert(skw, false);
+
+	return len;
+}
+
+static const struct file_operations skw_assert_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_assert_open,
+	.read = seq_read,
+	.write = skw_assert_write,
+	.release = single_release,
+};
+
+static int skw_ndo_open(struct net_device *dev)
+{
+	struct skw_iface *iface = netdev_priv(dev);
+	struct skw_core *skw = iface->skw;
+
+	skw_dbg("dev: %s, type: %s\n", netdev_name(dev),
+		skw_iftype_name(dev->ieee80211_ptr->iftype));
+
+	if (test_bit(SKW_FLAG_FW_THERMAL, &skw->flags)) {
+		skw_warn("disable TX for thermal warnning");
+		netif_tx_stop_all_queues(dev);
+	}
+
+	netif_carrier_off(dev);
+
+	if (dev->ieee80211_ptr->iftype == NL80211_IFTYPE_MONITOR) {
+		dev->type = ARPHRD_IEEE80211_RADIOTAP;
+		netif_tx_stop_all_queues(dev);
+	} else
+		dev->type = ARPHRD_ETHER;
+
+	return 0;
+}
+
+static int skw_ndo_stop(struct net_device *dev)
+{
+	struct skw_iface *iface = netdev_priv(dev);
+	struct skw_core *skw = iface->skw;
+
+	skw_dbg("dev: %s, type: %s\n", netdev_name(dev),
+		skw_iftype_name(dev->ieee80211_ptr->iftype));
+
+	//netif_tx_stop_all_queues(dev);
+
+	// fixme:
+	// check if sched scan is going on current netdev
+	skw_scan_done(skw, iface, true);
+
+	switch (dev->ieee80211_ptr->iftype) {
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_P2P_CLIENT:
+		// if (iface->sta.sm.state !=  SKW_STATE_NONE)
+			//skw_disconnect(iface->wdev.wiphy, iface->ndev,
+			//		WLAN_REASON_DEAUTH_LEAVING);
+		break;
+
+	case NL80211_IFTYPE_AP:
+	case NL80211_IFTYPE_P2P_GO:
+		// skw_flush_sta_info(iface);
+		break;
+
+	case NL80211_IFTYPE_MONITOR:
+		skw_cmd_monitor(priv_to_wiphy(skw), NULL, SKW_MONITOR_CLOSE);
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static bool skw_udp_filter(struct net_device *ndev, struct sk_buff *skb)
+{
+	bool ret = false;
+	struct udphdr *udp = udp_hdr(skb);
+
+#define DHCP_SERVER_PORT         67
+#define DHCP_CLIENT_PORT         68
+#define DHCPV6_SERVER_PORT       546
+#define DHCPV6_CLIENT_PORT       547
+
+	switch (ntohs(udp->dest)) {
+	case DHCP_CLIENT_PORT:
+	case DHCP_SERVER_PORT:
+		if (ndev->priv_flags & IFF_BRIDGE_PORT) {
+			/* set BOOTP flag to broadcast */
+			*((u8 *)udp + 18) = 0x80;
+			udp->check = 0;
+		}
+
+		skw_fallthrough;
+
+	case DHCPV6_CLIENT_PORT:
+	case DHCPV6_SERVER_PORT:
+		ret = true;
+		skw_dbg("DHCP, port: %d\n", ntohs(udp->dest));
+		break;
+
+	default:
+		ret = false;
+		break;
+	}
+
+	return ret;
+}
+
+static void skw_setup_txba(struct skw_core *skw, struct skw_iface *iface,
+			   struct skw_peer *peer, int tid)
+{
+	int ret = 0;
+	struct skw_ba_action tx_ba = {0};
+
+	if (tid >= SKW_NR_TID) {
+		skw_warn("tid: %d invalid\n", tid);
+		return;
+	}
+
+	if ((peer->txba.bitmap | peer->txba.blacklist) & BIT(tid))
+		return;
+
+	tx_ba.tid = tid;
+	tx_ba.win_size = 64;
+	tx_ba.lmac_id = iface->lmac_id;
+	tx_ba.peer_idx = peer->idx;
+	tx_ba.action = SKW_ADD_TX_BA;
+
+	ret = skw_queue_work(priv_to_wiphy(skw), iface, SKW_WORK_SETUP_TXBA,
+				&tx_ba, sizeof(tx_ba));
+	if (!ret)
+		peer->txba.bitmap |= BIT(tid);
+}
+
+struct skw_ctx_entry *skw_get_ctx_entry(struct skw_core *skw, const u8 *addr)
+{
+	int i, j;
+	struct skw_ctx_entry *entry;
+
+	for (i = 0; i < skw->hw.nr_lmac; i++) {
+		for (j = 0; j < SKW_MAX_PEER_SUPPORT; j++) {
+			entry = rcu_dereference(skw->hw.lmac[i].peer_ctx[j].entry);
+			if (entry && ether_addr_equal(entry->addr, addr))
+				return entry;
+		}
+	}
+
+	return NULL;
+}
+
+struct skw_peer_ctx *skw_get_ctx(struct skw_core *skw, u8 lmac_id, u8 idx)
+{
+	if (idx >= SKW_MAX_PEER_SUPPORT)
+		return NULL;
+
+	return &skw->hw.lmac[lmac_id].peer_ctx[idx];
+}
+
+static int skw_downgrade_ac(struct skw_iface *iface, int aci)
+{
+#if 1
+	while (iface->wmm.acm & BIT(aci)) {
+		if (aci == SKW_WMM_AC_BK)
+			break;
+		aci++;
+	}
+#else
+	if (iface->wmm.acm & BIT(aci)) {
+		int i;
+		int acm = SKW_WMM_AC_BK;
+
+		for (i = SKW_WMM_AC_BK; i >= 0; i--) {
+			if (!(iface->wmm.acm & BIT(i))) {
+				acm = i;
+				break;
+			}
+		}
+
+		aci = acm;
+	}
+#endif
+
+	return aci;
+}
+
+static inline bool is_skw_tcp_pure_ack(struct sk_buff *skb)
+{
+	return tcp_hdr(skb)->ack &&
+	       ntohs(ip_hdr(skb)->tot_len) == ip_hdrlen(skb) + tcp_hdrlen(skb);
+}
+
+static netdev_tx_t skw_ndo_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+	int ret = -1;
+	u8 tid;
+	u8 fixed_rate = 0;
+	u8 fixed_tid = SKW_INVALID_ID;
+	u8 peer_index = SKW_INVALID_ID;
+	u8 ac_idx = 0, padding = 0;
+	u8 prot = 0, tcp_pkt = 0, do_csum = 0;
+	u32 filter_map = 0, data_prot_map = 0;
+	bool is_prot_filter = false;
+	bool is_udp_filter = false;
+	bool is_802_3_frame = false;
+	bool is_tx_filter = false;
+	bool pure_tcp_ack = false;
+	bool need_desc_hdr = false;
+	const u8 tid_map[] = {6, 4, 0, 1};
+	int l4_hdr_offset = 0, reset_l4_offset = 0;
+	int msdu_len, txq_len;
+	int nhead, ntail, hdr_size;
+	bool is_completed = true;
+
+	struct netdev_queue *txq;
+	struct skw_peer_ctx *ctx;
+	struct skw_iface *iface = netdev_priv(ndev);
+	struct ethhdr *eth = eth_hdr(skb);
+	struct skw_ctx_entry *entry = NULL;
+	struct sk_buff_head *qlist;
+	struct skw_tx_desc_hdr *desc_hdr;
+	struct skw_tx_desc_conf *desc_conf;
+	struct skw_core *skw = iface->skw;
+#ifdef CONFIG_SWT6621S_SKB_RECYCLE
+	struct sk_buff *skb_recycle;
+#endif
+	s16 pkt_limit;
+
+	__net_timestamp(skb);
+
+	/* Mini frame size that HW support */
+	if (unlikely(skb->len <= 16)) {
+		skw_dbg("current: %s\n", current->comm);
+		skw_hex_dump("short skb", skb->data, skb->len, true);
+
+		if (skb->len != ETH_HLEN || eth->h_proto != htons(ETH_P_IP))
+			SKW_BUG_ON(1);
+
+		goto free;
+	}
+
+	if (skb_linearize(skb))
+		goto free;
+
+	msdu_len = skb->len;
+	SKW_SKB_TXCB(skb)->skb_native_len = skb->len;
+	SKW_SKB_TXCB(skb)->ret = 0;
+	SKW_SKB_TXCB(skb)->recycle = 0;
+	if (skb->ip_summed == CHECKSUM_PARTIAL) {
+		l4_hdr_offset = skb_checksum_start_offset(skb);
+		do_csum = 1;
+	}
+
+	switch (eth->h_proto) {
+	case htons(ETH_P_IP):
+		prot = ip_hdr(skb)->protocol;
+		reset_l4_offset = ETH_HLEN + ip_hdrlen(skb);
+
+		if (prot == IPPROTO_TCP)
+			pure_tcp_ack = is_skw_tcp_pure_ack(skb);
+
+		break;
+
+	case htons(ETH_P_IPV6):
+		prot = ipv6_hdr(skb)->nexthdr;
+		// fixme:
+		// get tcp/udp head offset
+		reset_l4_offset = ETH_HLEN + sizeof(struct ipv6hdr);
+		break;
+
+	case htons(ETH_P_ARP):
+		if (test_bit(SKW_FLAG_FW_FILTER_ARP, &skw->flags))
+			is_prot_filter = true;
+
+		if (unlikely(test_bit(SKW_FLAG_REPEATER, &skw->flags)) &&
+		    ndev->priv_flags & IFF_BRIDGE_PORT) {
+			if (iface->wdev.iftype == NL80211_IFTYPE_STATION) {
+				struct sk_buff *arp_skb;
+				struct skw_ctx_entry *e;
+				struct skw_arphdr *arp = skw_arp_hdr(skb);
+
+				rcu_read_lock();
+				e = skw_get_ctx_entry(iface->skw, arp->ar_sha);
+				if (e)
+					e->peer->ip_addr = arp->ar_sip;
+
+				rcu_read_unlock();
+
+				arp_skb = arp_create(ntohs(arp->ar_op),
+						ETH_P_ARP, arp->ar_tip,
+						iface->ndev,
+						arp->ar_sip, eth->h_dest,
+						iface->addr, arp->ar_tha);
+
+				kfree_skb(skb);
+
+				skb = arp_skb;
+				if (!skb)
+					return NETDEV_TX_OK;
+
+				eth = skw_eth_hdr(skb);
+			}
+		}
+
+		if (is_skw_sta_mode(iface))
+			fixed_rate = 1;
+		//fixed_tid = 4;
+		break;
+
+	case htons(ETH_P_PAE):
+		data_prot_map |= (1 << SKW_MSDU_FILTER_EAP);
+		is_prot_filter = true;
+		break;
+
+	case htons(SKW_ETH_P_WAPI):
+		is_prot_filter = true;
+		data_prot_map |= (1 << SKW_MSDU_FILTER_WAPI);
+		break;
+
+	case htons(ETH_P_TDLS):
+		is_prot_filter = true;
+		break;
+
+	default:
+		if (eth->h_proto < ETH_P_802_3_MIN)
+			is_802_3_frame = true;
+
+		break;
+	}
+
+	if (!do_csum && (prot == IPPROTO_UDP || prot == IPPROTO_TCP))
+		skb_set_transport_header(skb, reset_l4_offset);
+
+	// fixme:
+	/* enable checksum for TCP & UDP frame, except framgment frame */
+	switch (prot) {
+	case IPPROTO_UDP:
+		is_udp_filter = skw_udp_filter(ndev, skb);
+		if (udp_hdr(skb)->check == 0)
+			do_csum = 0;
+
+		break;
+
+	case IPPROTO_TCP:
+		tcp_pkt = 1;
+
+		break;
+
+	default:
+		break;
+	}
+
+	ac_idx = skw_downgrade_ac(iface, g_skw_up_to_ac[skb->priority]);
+	tid = (fixed_tid != SKW_INVALID_ID) ? fixed_tid : tid_map[ac_idx];
+
+	rcu_read_lock();
+
+	switch (iface->wdev.iftype) {
+	case NL80211_IFTYPE_AP:
+	case NL80211_IFTYPE_P2P_GO:
+		if (is_unicast_ether_addr(eth->h_dest)) {
+			entry = skw_get_ctx_entry(skw, eth->h_dest);
+			peer_index = entry ? entry->idx : SKW_INVALID_ID;
+
+			if (entry && entry->peer->sm.state != SKW_STATE_COMPLETED)
+				is_completed = false;
+		} else {
+			fixed_rate = 1;
+			peer_index = iface->default_multicast;
+		}
+
+		break;
+
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_P2P_CLIENT:
+	case NL80211_IFTYPE_ADHOC:
+
+		if (atomic_read(&iface->actived_ctx) > 1)
+			entry = skw_get_ctx_entry(skw, eth->h_dest);
+
+		if (!entry) {
+			ctx = skw_get_ctx(skw, iface->lmac_id, iface->sta.core.bss.ctx_idx);
+			if (ctx)
+				entry = rcu_dereference(ctx->entry);
+		}
+
+		peer_index = entry ? entry->idx : SKW_INVALID_ID;
+
+		if (peer_index != SKW_INVALID_ID &&
+			is_multicast_ether_addr(eth->h_dest)) {
+			peer_index = iface->default_multicast;
+
+			if (iface->sta.core.sm.state != SKW_STATE_COMPLETED) {
+
+				rcu_read_unlock();
+
+				skw_dbg("%s drop dst: %pm, proto: 0x%x, state: %s\n",
+					netdev_name(ndev), eth->h_dest, htons(eth->h_proto),
+					skw_state_name(iface->sta.core.sm.state));
+
+				goto free;
+			}
+		}
+
+		if (iface->sta.core.sm.state != SKW_STATE_COMPLETED)
+			is_completed = false;
+
+		break;
+
+	default:
+		peer_index = SKW_INVALID_ID;
+		break;
+	}
+
+	if (entry) {
+		if(is_completed)
+			skw_setup_txba(skw, iface, entry->peer, tid);
+
+		if (entry->peer) {
+			filter_map = atomic_read(&entry->peer->rx_filter);
+
+			if (filter_map && !(filter_map & data_prot_map)) {
+
+				rcu_read_unlock();
+
+				skw_dbg("%s drop dst: %pm, proto: 0x%x, filter: 0x%x\n",
+					netdev_name(ndev), eth->h_dest,
+					htons(eth->h_proto), filter_map);
+
+				goto free;
+			}
+		}
+	}
+
+	rcu_read_unlock();
+
+	if (peer_index == SKW_INVALID_ID) {
+		skw_dbg("%s drop dst: %pM, proto: 0x%x\n",
+			netdev_name(ndev), eth->h_dest, htons(eth->h_proto));
+
+		goto free;
+	}
+
+	is_tx_filter = (is_prot_filter || is_udp_filter || is_802_3_frame);
+
+#ifdef CONFIG_SWT6621S_SKB_RECYCLE
+	if (!is_prot_filter && !is_udp_filter && !is_802_3_frame && !is_multicast_ether_addr(eth->h_dest)) {
+		skb_recycle = skw_recycle_skb_get(skw);
+		if (skb_recycle) {
+			if (!skw_recycle_skb_copy(skw, skb_recycle, skb)) {
+				dev_kfree_skb_any(skb);
+				skb = skb_recycle;
+				eth = skw_eth_hdr(skb);
+				SKW_SKB_TXCB(skb)->recycle = 1;
+			} else {
+				skw_err("skw_recycle_skb_copy failed\n");
+				skw_recycle_skb_free(skw, skb_recycle);
+			}
+		} else {
+			skb_recycle = skb_copy(skb, GFP_ATOMIC);
+			if (skb_recycle) {
+				dev_kfree_skb_any(skb);
+				skb = skb_recycle;
+				eth = skw_eth_hdr(skb);
+			}
+		}
+	}
+#endif
+
+#define SKW_EXPAND_SIZE(x, y)  ((x) > (y) ? (x) - (y) : 0)
+	hdr_size = sizeof(struct skw_tx_desc_conf);
+
+	if (skw->hw.bus == SKW_BUS_PCIE) {
+		SKW_SKB_TXCB(skb)->lmac_id = iface->lmac_id;
+		SKW_SKB_TXCB(skb)->e.eth_type = eth->h_proto;
+		SKW_SKB_TXCB(skb)->e.mac_id = iface->id;
+		SKW_SKB_TXCB(skb)->e.tid = tid;
+		SKW_SKB_TXCB(skb)->e.peer_idx = peer_index;
+		SKW_SKB_TXCB(skb)->e.prot = SKW_ETHER_FRAME;
+		SKW_SKB_TXCB(skb)->e.encry_dis = 0;
+		SKW_SKB_TXCB(skb)->e.rate = fixed_rate;
+		SKW_SKB_TXCB(skb)->e.msdu_len = msdu_len;
+
+		padding = 0;
+		ntail = 0;
+
+		if (is_tx_filter) {
+			need_desc_hdr = true;
+			hdr_size += sizeof(struct skw_tx_desc_hdr);
+		}
+
+		nhead = SKW_EXPAND_SIZE(hdr_size, skb_headroom(skb));
+	} else {
+		int total_len;
+
+		need_desc_hdr = true;
+
+		hdr_size += sizeof(struct skw_tx_desc_hdr) + SKW_EXTER_HDR_SIZE;
+
+		if (SKW_DATA_ALIGN_SIZE > 4) {
+			unsigned char *ptr = NULL;
+			struct sk_buff *pskb = NULL;
+
+			total_len = skb->len + hdr_size + SKW_DATA_ALIGN_SIZE;
+			pskb = netdev_alloc_skb(skb->dev, ALIGN(total_len, skw->hw.align));
+			if (!pskb)
+				goto free;
+
+			ptr = PTR_ALIGN(pskb->data, SKW_DATA_ALIGN_SIZE);
+			skb_reserve(pskb, ptr - pskb->data + hdr_size);
+			skw_put_skb_data(pskb, skb->data, skb->len);
+			eth = skw_eth_hdr(pskb);
+
+			dev_kfree_skb_any(skb);
+			skb = pskb;
+
+			nhead = ntail = 0;
+		} else {
+			padding = ((long)(skb->data + hdr_size)) & SKW_DATA_ALIGN_MASK;
+			nhead = SKW_EXPAND_SIZE(hdr_size + padding, skb_headroom(skb));
+
+			total_len = hdr_size + skb->len + padding;
+			ntail = ALIGN(total_len, skw->hw.align) - total_len;
+			ntail = SKW_EXPAND_SIZE(ntail, skb_tailroom(skb));
+		}
+	}
+#undef SKW_EXPAND_SIZE
+
+	if (nhead || ntail || skb_cloned(skb)) {
+		if (unlikely(pskb_expand_head(skb, nhead, ntail, GFP_ATOMIC))) {
+			skw_dbg("failed, nhead: %d, ntail: %d\n", nhead, ntail);
+			goto free;
+		}
+
+		eth = skw_eth_hdr(skb);
+	}
+
+	desc_conf = (void *)skb_push(skb, sizeof(*desc_conf));
+	desc_conf->csum = do_csum;
+	desc_conf->ip_prot = tcp_pkt;
+	desc_conf->l4_hdr_offset = l4_hdr_offset;
+
+	if (padding)
+		skb_push(skb, padding);
+
+	if (need_desc_hdr) {
+		desc_hdr = (void *)skb_push(skb, sizeof(*desc_hdr));
+
+		desc_hdr->padding_gap = padding;
+		desc_hdr->inst = iface->id & 0x3;
+		desc_hdr->lmac_id = iface->lmac_id;
+		desc_hdr->tid = tid;
+		desc_hdr->peer_lut = peer_index;
+		desc_hdr->frame_type = SKW_ETHER_FRAME;
+		desc_hdr->encry_dis = 0;
+		desc_hdr->msdu_len = msdu_len;
+		desc_hdr->rate = fixed_rate;
+
+		skw_set_tx_desc_eth_type(desc_hdr, eth->h_proto);
+	}
+
+	if (unlikely(is_tx_filter)) {
+		skw_dbg("proto: 0x%x, udp filter: %d, 802.3 frame: %d\n",
+			htons(eth->h_proto), is_udp_filter, is_802_3_frame);
+
+		ret = skw_msg_try_send(skw, iface->id, SKW_CMD_TX_DATA_FRAME,
+				       skb->data, skb->len, NULL, 0,
+				       "SKW_CMD_TX_DATA_FRAME");
+		if (ret < 0) {
+			if (SKW_SKB_TXCB(skb)->tx_retry++ > 3) {
+				skw_queue_work(priv_to_wiphy(skw), iface,
+					       SKW_WORK_TX_ETHER_DATA,
+					       skb->data, skb->len);
+			} else {
+				skb_pull(skb, skb->len - msdu_len);
+				return NETDEV_TX_BUSY;
+			}
+		}
+
+		goto free;
+	}
+
+	SKW_SKB_TXCB(skb)->ret = 0;
+	SKW_SKB_TXCB(skb)->peer_idx = peer_index;
+
+	if (pure_tcp_ack)
+		ac_idx = SKW_ACK_TXQ;
+
+	qlist = &iface->txq[ac_idx];
+
+	skb_queue_tail(qlist, skb);
+
+	if (skw->hw.bus == SKW_BUS_PCIE)
+		pkt_limit = TX_BUF_ADDR_CNT / 2;
+	else
+		pkt_limit = 15;
+
+	if (skw_get_hw_credit(skw, iface->lmac_id) == 0)
+		goto _ok;
+
+	if (prot == IPPROTO_UDP || prot == IPPROTO_TCP) {
+		if (skb_queue_len(qlist) < pkt_limit) {
+			if (!timer_pending(&skw->tx_worker.timer))
+				skw_wakeup_tx(skw, msecs_to_jiffies(1));
+		} else
+			skw_wakeup_tx(skw, 0);
+	} else
+		skw_wakeup_tx(skw, 0);
+
+_ok:
+	trace_skw_tx_xmit(eth->h_dest, peer_index, prot, fixed_rate,
+			  do_csum, ac_idx, skb_queue_len(qlist));
+
+	txq_len = skb_queue_len(qlist) + skb_queue_len(&iface->tx_cache[ac_idx]);
+	if (txq_len >= SKW_TXQ_HIGH_THRESHOLD) {
+		txq = netdev_get_tx_queue(ndev, ac_idx);
+		if (!netif_tx_queue_stopped(txq))
+			netif_tx_stop_queue(txq);
+	}
+
+	return NETDEV_TX_OK;
+
+free:
+	if (ret != 0)
+		ndev->stats.tx_dropped++;
+
+	dev_kfree_skb_any(skb);
+
+	return NETDEV_TX_OK;
+}
+
+static u16 skw_ndo_select(struct net_device *dev, struct sk_buff *skb
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0)
+		, struct net_device *sb_dev
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)
+		, struct net_device *sb_dev,
+		select_queue_fallback_t fallback
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
+		, void *accel_priv,
+		select_queue_fallback_t fallback
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)
+		, void *accel_priv
+#endif
+		SKW_NULL)
+{
+	struct skw_iface *iface = netdev_priv(dev);
+
+	if (!iface->wmm.qos_enabled) {
+		skb->priority = 0;
+		return SKW_WMM_AC_BE;
+	}
+
+	if(!skb->protocol && skb->len > ETH_HLEN) {
+		skb->protocol = eth_type_trans(skb, iface->ndev);
+		skb_push(skb, ETH_HLEN);
+	}
+
+	skb->priority = skw_compat_classify8021d(skb, iface->qos_map);
+
+	return g_skw_up_to_ac[skb->priority];
+}
+
+#ifdef CONFIG_SWT6621S_PRESUSPEND_SUPPORT
+static int skw_set_suspendmode(struct wiphy *wiphy, struct net_device *dev, char *presuspend)
+{
+	int ret;
+	u8 suspendmode;
+
+	if (presuspend[0] == '1') {
+		suspendmode = true;
+	} else if(presuspend[0] == '0') {
+		suspendmode = false;
+	} else {
+		skw_err("failed, invalid param %c\n", presuspend[0]);
+		return -1;
+	}
+
+	skw_dbg("set_suspendmode: %d\n", suspendmode);
+
+	return skw_util_set_mib_enable(wiphy, 0,
+			SKW_MIB_SET_SUSPEND_MODE, suspendmode);
+
+	return ret;
+}
+#endif
+
+#define SKW_ANDROID_CMD_LEN    128
+static int skw_android_cmd(struct net_device *dev, void __user *priv_data)
+{
+	char command[SKW_ANDROID_CMD_LEN], *data = NULL;
+	struct android_wifi_priv_cmd priv_cmd;
+	bool compat_task = false;
+	int ret = 0;
+
+	if (!priv_data)
+		return  -EINVAL;
+
+#ifdef CONFIG_COMPAT
+	if (SKW_IS_COMPAT_TASK()) {
+		struct compat_android_wifi_priv_cmd compat;
+
+		if (copy_from_user(&compat, priv_data, sizeof(compat)))
+			return -EFAULT;
+
+		priv_cmd.buf = compat_ptr(compat.buf);
+		priv_cmd.used_len = compat.used_len;
+		priv_cmd.total_len = compat.total_len;
+
+		compat_task = true;
+	}
+#endif
+
+	if (!compat_task &&
+	    copy_from_user(&priv_cmd, priv_data, sizeof(priv_cmd)))
+		return -EFAULT;
+
+	if (copy_from_user(command, priv_cmd.buf, sizeof(command)))
+		return -EFAULT;
+
+	command[SKW_ANDROID_CMD_LEN - 1] = 0;
+
+	skw_dbg("%s: %s\n", netdev_name(dev), command);
+
+#define IS_SKW_CMD(c, k)        \
+	!strncasecmp(c, SKW_ANDROID_PRIV_##k, strlen(SKW_ANDROID_PRIV_##k))
+
+#define SKW_CMD_DATA(c, k)     \
+	(c + strlen(SKW_ANDROID_PRIV_##k) + 1)
+
+#define SKW_CMD_DATA_LEN(c, k) \
+	(sizeof(command) - strlen(SKW_ANDROID_PRIV_##k) - 1)
+
+	if (IS_SKW_CMD(command, COUNTRY)) {
+		data = SKW_CMD_DATA(command, COUNTRY);
+		ret = skw_set_regdom(dev->ieee80211_ptr->wiphy, data);
+		if (ret)
+			skw_err("skw_set_regdom failed, ret: %d\n", ret);
+	} else if (IS_SKW_CMD(command, BTCOEXSCAN_STOP)) {
+	} else if (IS_SKW_CMD(command, RXFILTER_START)) {
+	} else if (IS_SKW_CMD(command, RXFILTER_STOP)) {
+	} else if (IS_SKW_CMD(command, RXFILTER_ADD)) {
+	} else if (IS_SKW_CMD(command, RXFILTER_REMOVE)) {
+	} else if (IS_SKW_CMD(command, SETSUSPENDMODE)) {
+#ifdef CONFIG_SWT6621S_PRESUSPEND_SUPPORT
+		data = SKW_CMD_DATA(command, SETSUSPENDMODE);
+		if (skw_set_suspendmode(dev->ieee80211_ptr->wiphy, dev, data))
+			skw_err("skw_set_suspend mode failed\n");
+#endif
+	} else if (IS_SKW_CMD(command, BTCOEXMODE)) {
+	} else if (IS_SKW_CMD(command, SET_AP_WPS_P2P_IE)) {
+	} else {
+		skw_info("Unsupport cmd: %s - ignored\n", command);
+	}
+
+#undef IS_SKW_CMD
+
+	return 0;
+}
+
+static int skw_ioctl(struct net_device *dev, void __user *user_data)
+{
+	int ret = -ENOTSUPP;
+	char country[4] = {0};
+	struct skw_ioctl_cmd cmd;
+
+	if (copy_from_user(&cmd, user_data, sizeof(cmd)))
+		return -ENOMEM;
+
+	switch (cmd.id) {
+	case SKW_IOCTL_SUBCMD_COUNTRY:
+
+		if (cmd.len > sizeof(country))
+			return -EINVAL;
+
+		if (copy_from_user(country, user_data + sizeof(cmd), cmd.len))
+			return -EINVAL;
+
+		if (strlen(country) != 2)
+			return -EINVAL;
+
+		ret = skw_set_regdom(dev->ieee80211_ptr->wiphy, country);
+		if (ret)
+			skw_err("set regdom failed, ret: %d\n", ret);
+
+		break;
+
+	default:
+		skw_warn("unsupport cmd: 0x%x, len: %d\n", cmd.id, cmd.len);
+		break;
+	}
+
+	return ret;
+}
+
+static int skw_ndo_ioctl(struct net_device *dev, struct ifreq *ifr,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
+			void __user *data,
+#endif
+			int cmd)
+{
+	int ret;
+	void __user *priv = ifr->ifr_data;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
+	priv = data;
+#endif
+
+	switch (cmd) {
+	case SKW_IOCTL_ANDROID_CMD:
+		ret = skw_android_cmd(dev, priv);
+		break;
+
+	case SKW_IOCTL_CMD:
+		ret = skw_ioctl(dev, priv);
+		break;
+
+	default:
+		ret = -ENOTSUPP;
+		skw_warn("%s, unsupport cmd: 0x%x\n", netdev_name(dev), cmd);
+
+		break;
+	}
+
+	return ret;
+}
+
+static void skw_ndo_tx_timeout(struct net_device *dev
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+				, unsigned int txqueue
+#endif
+				SKW_NULL)
+{
+	struct skw_iface *iface = (struct skw_iface *)netdev_priv(dev);
+	struct skw_core *skw;
+
+	if (!iface) {
+		skw_err("iface NULL\n");
+		return;
+	}
+
+	skw = iface->skw;
+
+	skw_warn("ndev:%s,flag:0x%lx\n", netdev_name(dev), skw->flags);
+}
+
+static void skw_ndo_set_rx_mode(struct net_device *dev)
+{
+	int count, total_len;
+	struct skw_mc_list *mc;
+	struct netdev_hw_addr *ha;
+
+	skw_dbg("%s, mc: %d, uc: %d\n", netdev_name(dev),
+		netdev_mc_count(dev), netdev_uc_count(dev));
+
+	count = netdev_mc_count(dev);
+	if (!count)
+		return;
+
+	total_len = sizeof(*mc) + sizeof(struct mac_address) * count;
+	mc = SKW_ZALLOC(total_len, GFP_ATOMIC);
+	if (!mc) {
+		skw_err("alloc failed, mc count: %d, total_len: %d\n",
+			count, total_len);
+		return;
+	}
+
+	mc->count = count;
+
+	count = 0;
+	netdev_for_each_mc_addr(ha, dev)
+		skw_ether_copy(mc->mac[count++].addr, ha->addr);
+
+	skw_queue_work(dev->ieee80211_ptr->wiphy, netdev_priv(dev),
+		SKW_WORK_SET_MC_ADDR, mc, total_len);
+
+	SKW_KFREE(mc);
+}
+#if 0
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0)
+static struct rtnl_link_stats64 *
+#else
+static void
+#endif
+skw_ndo_get_stats64(struct net_device *dev,
+		    struct rtnl_link_stats64 *stats)
+{
+	int i;
+
+	for_each_possible_cpu(i) {
+		const struct pcpu_sw_netstats *tstats;
+		u64 rx_packets, rx_bytes, tx_packets, tx_bytes;
+		unsigned int start;
+
+		tstats = per_cpu_ptr(dev->tstats, i);
+
+		do {
+			start = u64_stats_fetch_begin_irq(&tstats->syncp);
+			rx_packets = tstats->rx_packets;
+			tx_packets = tstats->tx_packets;
+			rx_bytes = tstats->rx_bytes;
+			tx_bytes = tstats->tx_bytes;
+		} while (u64_stats_fetch_retry_irq(&tstats->syncp, start));
+
+		stats->rx_packets += rx_packets;
+		stats->tx_packets += tx_packets;
+		stats->rx_bytes   += rx_bytes;
+		stats->tx_bytes   += tx_bytes;
+	}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0)
+	return stats;
+#endif
+}
+#endif
+
+static int skw_ndo_set_mac_address(struct net_device *dev, void *addr)
+{
+	struct skw_iface *iface = (struct skw_iface *)netdev_priv(dev);
+	struct sockaddr *sa = addr;
+	int ret = 0, err = 0;
+
+	skw_dbg("mac: %pM\n", sa->sa_data);
+
+	ret = eth_mac_addr(dev, sa);
+	if (ret) {
+		skw_err("failed, addr: %pM, ret: %d\n", sa->sa_data, ret);
+		return ret;
+	}
+
+	ret = skw_send_msg(iface->wdev.wiphy, dev, SKW_CMD_RANDOM_MAC,
+			sa->sa_data, ETH_ALEN, NULL, 0);
+	if (ret) {
+		skw_err("set mac: %pM failed, ret: %d\n",
+			sa->sa_data, ret);
+
+		err = eth_mac_addr(dev, iface->addr);
+		if (err)
+			skw_warn("set eth macaddr failed\n");
+
+		return ret;
+	}
+
+	skw_ether_copy(iface->addr, sa->sa_data);
+
+	return 0;
+}
+
+#if 0
+static void skw_ndo_uninit(struct net_device *ndev)
+{
+	struct skw_iface *iface = netdev_priv(ndev);
+	struct wiphy *wiphy = ndev->ieee80211_ptr->wiphy;
+
+	skw_dbg("%s\n", netdev_name(ndev));
+
+	free_percpu(ndev->tstats);
+
+	skw_del_vif(wiphy, iface);
+	skw_iface_teardown(wiphy, iface);
+	skw_release_inst(wiphy, iface->id);
+}
+#endif
+
+static const struct net_device_ops skw_netdev_ops = {
+	// .ndo_uninit = skw_ndo_uninit,
+	.ndo_open = skw_ndo_open,
+	.ndo_stop = skw_ndo_stop,
+	.ndo_start_xmit = skw_ndo_xmit,
+	.ndo_select_queue = skw_ndo_select,
+	.ndo_tx_timeout = skw_ndo_tx_timeout,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
+	.ndo_siocdevprivate = skw_ndo_ioctl,
+#else
+	.ndo_do_ioctl = skw_ndo_ioctl,
+#endif
+	//.ndo_get_stats64 = skw_ndo_get_stats64,
+	.ndo_set_rx_mode = skw_ndo_set_rx_mode,
+	.ndo_set_mac_address = skw_ndo_set_mac_address,
+};
+
+int skw_netdev_init(struct wiphy *wiphy, struct net_device *ndev, u8 *addr)
+{
+	struct skw_core *skw;
+	struct skw_iface *iface;
+
+	if (!ndev)
+		return -EINVAL;
+
+	skw = wiphy_priv(wiphy);
+	iface = netdev_priv(ndev);
+	SET_NETDEV_DEV(ndev, wiphy_dev(wiphy));
+
+	ndev->features = NETIF_F_GRO |
+			 NETIF_F_IP_CSUM |
+			 NETIF_F_IPV6_CSUM;
+
+	ndev->ieee80211_ptr = &iface->wdev;
+	ndev->netdev_ops = &skw_netdev_ops;
+	ndev->watchdog_timeo = 3 * HZ;
+	ndev->needed_headroom =	skw->skb_headroom;
+	ndev->needed_tailroom = skw->hw_pdata->align_value;
+	// ndev->priv_destructor = skw_netdev_deinit;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0)
+	ndev->destructor = free_netdev;
+#else
+	ndev->needs_free_netdev = true;
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)
+	eth_hw_addr_set(ndev, addr);
+#else
+	skw_ether_copy(ndev->dev_addr, addr);
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0)
+	ndev->tstats = netdev_alloc_pcpu_stats(struct pcpu_tstats);
+#else
+	ndev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
+#endif
+	if (!ndev->tstats)
+		return -ENOMEM;
+
+#ifdef CONFIG_WIRELESS_EXT
+	ndev->wireless_handlers = skw_iw_handlers();
+#endif
+
+#ifdef CONFIG_RPS
+	iface->cpu_id = skw->isr_cpu_id;
+	skw_init_rps_map(iface);
+#endif
+
+	return 0;
+}
+
+void skw_netdev_deinit(struct net_device *ndev)
+{
+	free_percpu(ndev->tstats);
+}
+
+#if 0
+void skw_netdev_deinit(struct net_device *dev)
+{
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_dbg("%s\n", netdev_name(dev));
+
+#if 0 //move these actions to ndo_uninit
+	free_percpu(dev->tstats);
+	skw_cmd_close_dev(iface->wdev.wiphy, iface->id);
+	skw_del_vif(iface->skw, iface);
+	skw_iface_teardown(iface);
+#endif
+}
+#endif
+
+int skw_sync_cmd_event_version(struct wiphy *wiphy)
+{
+	int i, ret = 0;
+	struct skw_version_info *skw_fw_ver;
+
+#define SKW_CMD_VER(id, ver)   .cmd[id] = ver
+#define SKW_EVENT_VER(id, ver) .event[id] = ver
+	const struct skw_version_info skw_drv_ver = {
+		SKW_CMD_VER(SKW_CMD_DOWNLOAD_INI, V1),
+		SKW_CMD_VER(SKW_CMD_GET_INFO, V1),
+		SKW_CMD_VER(SKW_CMD_SYN_VERSION, V1),
+		SKW_CMD_VER(SKW_CMD_OPEN_DEV, V1),
+		SKW_CMD_VER(SKW_CMD_CLOSE_DEV, V1),
+		SKW_CMD_VER(SKW_CMD_START_SCAN, V1),
+		SKW_CMD_VER(SKW_CMD_STOP_SCAN, V1),
+		SKW_CMD_VER(SKW_CMD_START_SCHED_SCAN, V1),
+		SKW_CMD_VER(SKW_CMD_STOP_SCHED_SCAN, V1),
+		SKW_CMD_VER(SKW_CMD_JOIN, V1),
+		SKW_CMD_VER(SKW_CMD_AUTH, V1),
+		SKW_CMD_VER(SKW_CMD_ASSOC, V1),
+		SKW_CMD_VER(SKW_CMD_ADD_KEY, V1),
+		SKW_CMD_VER(SKW_CMD_DEL_KEY, V1),
+		SKW_CMD_VER(SKW_CMD_TX_MGMT, V1),
+		SKW_CMD_VER(SKW_CMD_TX_DATA_FRAME, V1),
+		SKW_CMD_VER(SKW_CMD_SET_IP, V1),
+		SKW_CMD_VER(SKW_CMD_DISCONNECT, V1),
+		SKW_CMD_VER(SKW_CMD_RPM_REQ, V1),
+		SKW_CMD_VER(SKW_CMD_START_AP, V1),
+		SKW_CMD_VER(SKW_CMD_STOP_AP, V1),
+		SKW_CMD_VER(SKW_CMD_ADD_STA, V1),
+		SKW_CMD_VER(SKW_CMD_DEL_STA, V1),
+		SKW_CMD_VER(SKW_CMD_GET_STA, V1),
+		SKW_CMD_VER(SKW_CMD_RANDOM_MAC, V1),
+		SKW_CMD_VER(SKW_CMD_GET_LLSTAT, V1),
+		SKW_CMD_VER(SKW_CMD_SET_MC_ADDR, V1),
+		SKW_CMD_VER(SKW_CMD_RESUME, V1),
+		SKW_CMD_VER(SKW_CMD_SUSPEND, V1),
+		SKW_CMD_VER(SKW_CMD_REMAIN_ON_CHANNEL, V1),
+		SKW_CMD_VER(SKW_CMD_BA_ACTION, V1),
+		SKW_CMD_VER(SKW_CMD_TDLS_MGMT, V1),
+		SKW_CMD_VER(SKW_CMD_TDLS_OPER, V1),
+		SKW_CMD_VER(SKW_CMD_TDLS_CHANNEL_SWITCH, V1),
+		SKW_CMD_VER(SKW_CMD_SET_CQM_RSSI, V1),
+		SKW_CMD_VER(SKW_CMD_NPI_MSG, V1),
+		SKW_CMD_VER(SKW_CMD_IBSS_JOIN, V1),
+		SKW_CMD_VER(SKW_CMD_SET_IBSS_ATTR, V1),
+		SKW_CMD_VER(SKW_CMD_RSSI_MONITOR, V1),
+		SKW_CMD_VER(SKW_CMD_SET_IE, V1),
+		SKW_CMD_VER(SKW_CMD_SET_MIB, V1),
+		SKW_CMD_VER(SKW_CMD_REGISTER_FRAME, V1),
+		SKW_CMD_VER(SKW_CMD_ADD_TX_TS, V1),
+		SKW_CMD_VER(SKW_CMD_DEL_TX_TS, V1),
+		SKW_CMD_VER(SKW_CMD_REQ_CHAN_SWITCH, V1),
+		SKW_CMD_VER(SKW_CMD_CHANGE_BEACON, V1),
+		SKW_CMD_VER(SKW_CMD_DPD_ILC_GEAR_PARAM, V1),
+		SKW_CMD_VER(SKW_CMD_DPD_ILC_MARTIX_PARAM, V1),
+		SKW_CMD_VER(SKW_CMD_DPD_ILC_COEFF_PARAM, V1),
+		SKW_CMD_VER(SKW_CMD_WIFI_RECOVER, V1),
+		SKW_CMD_VER(SKW_CMD_PHY_BB_CFG, V1),
+		SKW_CMD_VER(SKW_CMD_SET_REGD, V1),
+		SKW_CMD_VER(SKW_CMD_SET_EFUSE, V1),
+		SKW_CMD_VER(SKW_CMD_SET_PROBEREQ_FILTER, V1),
+		SKW_CMD_VER(SKW_CMD_CFG_ANT, V1),
+		SKW_CMD_VER(SKW_CMD_RTT, V1),
+		SKW_CMD_VER(SKW_CMD_GSCAN, V1),
+		SKW_CMD_VER(SKW_CMD_DFS, V1),
+		SKW_CMD_VER(SKW_CMD_SET_SPD_ACTION, V1),
+		SKW_CMD_VER(SKW_CMD_SET_DPD_RESULT, V1),
+		SKW_CMD_VER(SKW_CMD_SET_MONITOR_PARAM, V1),
+
+		/* event */
+	};
+
+	skw_fw_ver = SKW_ZALLOC(sizeof(struct skw_version_info), GFP_KERNEL);
+	if (!skw_fw_ver)
+		return -ENOMEM;
+
+	ret = skw_msg_xmit(wiphy, 0, SKW_CMD_SYN_VERSION,
+			   NULL, 0, skw_fw_ver,
+			   sizeof(struct skw_version_info));
+	if (ret) {
+		skw_err("ret: %d\n", ret);
+		SKW_KFREE(skw_fw_ver);
+
+		return ret;
+	}
+
+	for (i = 0; i < SKW_MAX_MSG_ID; i++) {
+		if (skw_fw_ver->cmd[i] == 0 || skw_drv_ver.cmd[i] == 0)
+			continue;
+
+		if (skw_drv_ver.cmd[i] != skw_fw_ver->cmd[i]) {
+			skw_warn("cmd: %d, drv ver: %d, fw ver: %d\n",
+				 i, skw_drv_ver.cmd[i], skw_fw_ver->cmd[i]);
+
+			ret = -EINVAL;
+		}
+	}
+
+	SKW_KFREE(skw_fw_ver);
+
+	return ret;
+
+}
+
+static int skw_set_capa(struct skw_core *skw, int capa)
+{
+	int idx, bit;
+	int size = sizeof(skw->ext_capa);
+
+	idx = capa / BITS_PER_BYTE;
+	bit = capa % BITS_PER_BYTE;
+
+	BUG_ON(idx >= size);
+
+	skw->ext_capa[idx] |= BIT(bit);
+
+	return 0;
+}
+
+static int skw_set_ext_capa(struct skw_core *skw, struct skw_chip_info *chip)
+{
+	skw_set_capa(skw, SKW_EXT_CAPA_BSS_TRANSITION);
+	skw_set_capa(skw, SKW_EXT_CAPA_MBSSID);
+	skw_set_capa(skw, SKW_EXT_CAPA_TDLS_SUPPORT);
+	skw_set_capa(skw, SKW_EXT_CAPA_TWT_REQ_SUPPORT);
+
+	return 0;
+}
+
+static const char *skw_get_chip_id(u32 chip_type)
+{
+	switch (chip_type) {
+	case 0x100:
+		return "EA6621Q";
+
+	case 0x101:
+		return "EA6521QF";
+
+	case 0x102:
+		return "EA6621QT";
+
+	case 0x103:
+		return "EA6521QT";
+
+	case 0x200:
+		return "EA6316";
+
+	case 0x300:
+		return "SWT6621S";
+
+	default:
+		skw_err("Unsupport chip type: 0x%x\n", chip_type);
+		break;
+	}
+
+	return NULL;
+}
+
+static int skw_set_link_loss_mib(struct wiphy *wiphy, u8 to)
+{
+	int ret;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	skw_dbg("beacon to: %d\n", to);
+
+	ret = skw_tlv_alloc(&conf, 128, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_LINK_LOSS_THOLD, &to, 1)) {
+		skw_err("set link loss failed\n");
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	*plen = conf.total_len;
+
+	ret = skw_msg_xmit(wiphy, 0, SKW_CMD_SET_MIB, conf.buff,
+			   conf.total_len, NULL, 0);
+	if (ret)
+		skw_warn("failed, ret: %d\n", ret);
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+static int skw_set_24ghz_band_mib(struct wiphy *wiphy, u32 band)
+{
+	int ret;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	skw_dbg("24ghz_band: %d\n", band);
+
+	ret = skw_tlv_alloc(&conf, 128, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_BAND_2G, &band, sizeof(band))) {
+		skw_err("set 24ghz band failed\n");
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	*plen = conf.total_len;
+
+	ret = skw_msg_xmit(wiphy, 0, SKW_CMD_SET_MIB, conf.buff,
+			   conf.total_len, NULL, 0);
+	if (ret)
+		skw_warn("failed, ret: %d\n", ret);
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+static int skw_set_hdk_test_mib(struct wiphy *wiphy, bool enable)
+{
+	return skw_util_set_mib_enable(wiphy, 0,
+			SKW_MIB_SET_HDK_TEST, enable);
+}
+
+#ifdef CONFIG_SWT6621S_NOT_WAKEUP_HOST
+static int skw_set_wakeup_host_mib(struct wiphy *wiphy, bool enable)
+{
+	return skw_util_set_mib_enable(wiphy, 0,
+			SKW_MIB_SET_WAKEUP_HOST_ENABLE, enable);
+}
+#endif
+
+static void skw_common_mib_set(struct wiphy *wiphy, struct skw_core *skw)
+{
+	skw_set_link_loss_mib(wiphy, skw->config.fw.link_loss_thrd);
+	skw_set_24ghz_band_mib(wiphy, skw->config.fw.band_24ghz);
+	skw_set_hdk_test_mib(wiphy, skw->config.fw.hdk_tst);
+
+#ifdef CONFIG_SWT6621S_NOT_WAKEUP_HOST
+	skw_set_wakeup_host_mib(wiphy, false);
+#endif
+}
+
+int skw_sync_chip_info(struct wiphy *wiphy, struct skw_chip_info *chip)
+{
+	int ret;
+	const char *chipid;
+	int vendor, revision;
+	u64 ts = local_clock();
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	skw->fw.host_timestamp = ts;
+	skw->fw.host_seconds = skw_get_seconds();
+
+	do_div(ts, 1000000);
+	ret = skw_msg_xmit(wiphy, 0, SKW_CMD_GET_INFO, &ts, sizeof(ts),
+			   chip, sizeof(*chip));
+	if (ret) {
+		skw_err("ret: %d\n", ret);
+		return ret;
+	}
+
+	if (chip->priv_filter_arp)
+		set_bit(SKW_FLAG_FW_FILTER_ARP, &skw->flags);
+
+	if (chip->priv_ignore_cred)
+		set_bit(SKW_FLAG_FW_IGNORE_CRED, &skw->flags);
+
+	if (chip->priv_p2p_common_port)
+		set_bit(SKW_FLAG_LEGACY_P2P_COMMON_PORT, &skw->flags);
+
+	if (chip->priv_dfs_master_enabled)
+		skw->dfs.fw_enabled = true;
+
+	if (chip->nr_hw_mac)
+		skw->hw.nr_lmac = chip->nr_hw_mac;
+	else
+		skw->hw.nr_lmac = 1;
+
+	BUG_ON(skw->hw.nr_lmac > SKW_MAX_LMAC_SUPPORT);
+
+	memcpy(skw->fw.build_time, chip->fw_build_time,
+	       sizeof(skw->fw.build_time));
+	memcpy(skw->fw.plat_ver, chip->fw_plat_ver, sizeof(skw->fw.plat_ver));
+	memcpy(skw->fw.wifi_ver, chip->fw_wifi_ver, sizeof(skw->fw.wifi_ver));
+
+	skw->fw.timestamp = chip->fw_timestamp;
+	skw->fw.max_num_sta = chip->max_sta_allowed;
+	skw->fw.fw_bw_capa = chip->fw_bw_capa;
+
+	chipid = skw_get_chip_id(chip->fw_chip_type);
+	if (!chipid) {
+		skw_err("chip id 0x%x not support\n", chip->fw_chip_type);
+		return -ENOTSUPP;
+	}
+
+	if (test_bit(SKW_CFG_FLAG_OVERLAY_MODE, &g_skw_config.conf.global.flags)) {
+		int j;
+		char *pos, cfg[64] = {0};
+
+		if (strlen(g_skw_config.conf.calib.chip))
+			pos = g_skw_config.conf.calib.chip;
+		else
+			pos = (char *)chipid;
+
+		snprintf(cfg, sizeof(cfg) - 1, "%s.cfg", pos);
+
+		for (j = 0; j < strlen(cfg); j++)
+			cfg[j] = tolower(cfg[j]);
+
+		skw_update_config(NULL, cfg, &skw->config);
+	}
+
+	/* BIT[0:1] Reserved
+	 * BIT[2:3] BUS Type
+	 * BIT[4:7] Vendor ID
+	 */
+	vendor = 0;
+
+	if (test_bit(SKW_CFG_CALIB_STRICT_MODE, &skw->config.calib.flags))
+		vendor |= ((skw->hw.bus & 0x3) << 2);
+
+#ifdef CONFIG_SWT6621S_CALIB_APPEND_MODULE_ID
+	if (chip->calib_module_id) {
+		int i;
+		int vdata = chip->calib_module_id;
+
+		for (i = 0; i < 4; i++) {
+			if ((vdata & 0xf) == 0xf)
+				vendor |= BIT(4 + i);
+
+			vdata >>= 4;
+		}
+	}
+#endif
+
+	revision = skw->hw_pdata->chipid[15];
+
+	if (strlen(skw->config.calib.chip))
+		chipid = skw->config.calib.chip;
+
+	snprintf(skw->fw.calib_file, sizeof(skw->fw.calib_file),
+		"%s_%s_R%02X%03X.bin", chipid, skw->config.calib.project,
+		vendor & 0xff, revision);
+
+	if (chip->priv_pn_reuse)
+		set_bit(SKW_FLAG_FW_PN_REUSE, &skw->flags);
+
+	skw_set_ext_capa(skw, chip);
+	/* HT capa */
+
+	skw_common_mib_set(wiphy, skw);
+
+	skw_dbg("efuse mac: %pM, %s\n", chip->mac,
+		is_valid_ether_addr(chip->mac) ? "valid" : "invalid");
+
+	return 0;
+}
+
+static void skw_setup_mac_address(struct wiphy *wiphy, u8 *user_mac, u8 *hw_mac)
+{
+	int i;
+	u8 addr[ETH_ALEN] = {0};
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	if (user_mac && is_valid_ether_addr(user_mac)) {
+		skw_ether_copy(addr, user_mac);
+	} else if (hw_mac && is_valid_ether_addr(hw_mac)) {
+		skw_ether_copy(addr, hw_mac);
+	} else {
+		eth_random_addr(addr);
+		addr[0] = 0xFE;
+		addr[1] = 0xFD;
+		addr[2] = 0xFC;
+	}
+
+	for (i = 0; i < SKW_NR_IFACE; i++) {
+		skw_ether_copy(skw->address[i].addr, addr);
+
+		if (i != 0) {
+			skw->address[i].addr[0] |= BIT(1);
+			skw->address[i].addr[3] ^= BIT(i);
+		}
+
+		skw_dbg("addr[%d]: %pM\n", i, skw->address[i].addr);
+	}
+}
+
+static int skw_buffer_init(struct skw_core *skw)
+{
+	int ret, size;
+
+	if (skw->hw.bus == SKW_BUS_PCIE) {
+		ret = skw_edma_init(priv_to_wiphy(skw));
+		if (ret < 0) {
+			skw_err("edma init failed, ret: %d\n", ret);
+			return ret;
+		}
+
+		skw->cmd.data = skw->edma.cmd_chn.current_node->buffer;
+	} else {
+		size = sizeof(struct scatterlist);
+
+		skw->sgl_dat = kcalloc(SKW_NR_SGL_DAT, size, GFP_KERNEL);
+		if (!skw->sgl_dat) {
+			skw_err("sg list malloc failed, sg length: %d\n",
+				SKW_NR_SGL_DAT);
+
+			return -ENOMEM;
+		}
+
+		skw->sgl_cmd = kcalloc(SKW_NR_SGL_CMD, size, GFP_KERNEL);
+		if (!skw->sgl_cmd) {
+			skw_err("sg list malloc failed, sg length: %d\n",
+				SKW_NR_SGL_CMD);
+
+			SKW_KFREE(skw->sgl_dat);
+			return -ENOMEM;
+		}
+
+		skw->cmd.data = SKW_ZALLOC(SKW_MSG_BUFFER_LEN, GFP_KERNEL);
+		if (!skw->cmd.data) {
+			skw_err("mallc data buffer failed\n");
+			SKW_KFREE(skw->sgl_dat);
+			SKW_KFREE(skw->sgl_cmd);
+			return -ENOMEM;
+		}
+	}
+
+	return 0;
+}
+
+static void skw_buffer_deinit(struct skw_core *skw)
+{
+	if (skw->hw.bus == SKW_BUS_PCIE) {
+		skw_edma_deinit(priv_to_wiphy(skw));
+	} else {
+		SKW_KFREE(skw->sgl_dat);
+		SKW_KFREE(skw->sgl_cmd);
+		SKW_KFREE(skw->cmd.data);
+
+		//TODO : recycle txqlen_pending if BSP support API
+		if (unlikely(atomic_read(&skw->txqlen_pending)))
+			skw_err("txqlen_pending:%d remind\n", atomic_read(&skw->txqlen_pending));
+	}
+}
+
+static struct wakeup_source *skw_wakeup_source_init(const char *name)
+{
+	struct wakeup_source *ws;
+
+	ws = wakeup_source_create(name);
+	if (ws)
+		wakeup_source_add(ws);
+
+	return ws;
+}
+
+static void skw_wakeup_source_deinit(struct wakeup_source *ws)
+{
+	if (ws) {
+		wakeup_source_remove(ws);
+		wakeup_source_destroy(ws);
+	}
+}
+
+static void skw_hw_hal_init(struct skw_core *skw, struct sv6160_platform_data *pdata)
+{
+	int i, j;
+	struct {
+		u8 dat_port;
+		u8 logic_port;
+		u8 flags;
+		u8 resv;
+	} skw_port[SKW_MAX_LMAC_SUPPORT] = {0};
+
+	atomic_set(&skw->hw.credit, 0);
+	skw->hw.align = pdata->align_value;
+	skw->hw.cmd_port = pdata->cmd_port;
+	skw->hw.pkt_limit = pdata->max_buffer_size / SKW_TX_PACK_SIZE;
+	skw->hw.dma = (pdata->bus_type >> 3) & 0x3;
+
+	if (skw->hw.pkt_limit >= SKW_NR_SGL_DAT) {
+		WARN_ONCE(1, "pkt limit(%d) larger than buffer", skw->hw.pkt_limit);
+		skw->hw.pkt_limit = SKW_NR_SGL_DAT - 1;
+	}
+
+	skw->hw.wow.enabled = false;
+	set_bit(SKW_HW_FLAG_CFG80211_PM, &skw->hw.flags);
+
+	switch (pdata->bus_type & SKW_BUS_TYPE_MASK) {
+	case SKW_BUS_SDIO2:
+		set_bit(SKW_HW_FLAG_SDIO_V2, &skw->hw.flags);
+
+		/* fall through */
+		skw_fallthrough;
+
+	case  SKW_BUS_SDIO:
+		skw->hw.bus = SKW_BUS_SDIO;
+
+		skw->hw.bus_dat_xmit = skw_sdio_xmit;
+		skw->hw.bus_cmd_xmit = skw_sdio_cmd_xmit;
+
+		set_bit(SKW_HW_FLAG_EXTRA_HDR, &skw->hw.flags);
+		skw->hw.extra.hdr_len = SKW_EXTER_HDR_SIZE;
+		skw->hw.extra.eof_offset = 23;
+		skw->hw.extra.chn_offset = 24;
+		skw->hw.extra.len_offset = 7;
+
+		if(test_bit(SKW_HW_FLAG_SDIO_V2, &skw->hw.flags))
+			skw->hw.extra.len_offset = 0;
+
+		skw->hw.rx_desc.hdr_offset = SKW_SDIO_RX_DESC_HDR_OFFSET;
+		skw->hw.rx_desc.msdu_offset = SKW_SDIO_RX_DESC_MSDU_OFFSET;
+
+		skw_port[0].dat_port = pdata->data_port & 0xf;
+		skw_port[0].logic_port = skw_port[0].dat_port;
+		skw_port[0].flags = SKW_LMAC_FLAG_INIT |
+				    SKW_LMAC_FLAG_RXCB;
+
+		skw_port[1].dat_port = (pdata->data_port >> 4) & 0xf;
+		skw_port[1].logic_port = skw_port[1].dat_port;
+		if (skw_port[1].logic_port) {
+			skw_port[1].flags = SKW_LMAC_FLAG_INIT |
+					    SKW_LMAC_FLAG_RXCB;
+
+		}
+
+		break;
+
+	case SKW_BUS_USB2:
+		set_bit(SKW_HW_FLAG_USB_V2, &skw->hw.flags);
+
+		/* fall through */
+		skw_fallthrough;
+
+	case SKW_BUS_USB:
+		skw->hw.bus = SKW_BUS_USB;
+
+		/* ignore PM callback from cfg80211 */
+		clear_bit(SKW_HW_FLAG_CFG80211_PM, &skw->hw.flags);
+
+		skw->hw.bus_dat_xmit = skw_usb_xmit;
+		skw->hw.bus_cmd_xmit = skw_usb_cmd_xmit;
+
+		set_bit(SKW_HW_FLAG_EXTRA_HDR, &skw->hw.flags);
+		skw->hw.extra.hdr_len = SKW_EXTER_HDR_SIZE;
+		skw->hw.extra.eof_offset = 23;
+		skw->hw.extra.chn_offset = 24;
+		skw->hw.extra.len_offset = 7;
+
+		if(test_bit(SKW_HW_FLAG_USB_V2, &skw->hw.flags))
+			skw->hw.extra.len_offset = 0;
+
+		skw->hw.rx_desc.hdr_offset = SKW_USB_RX_DESC_HDR_OFFSET;
+		skw->hw.rx_desc.msdu_offset = SKW_USB_RX_DESC_MSDU_OFFSET;
+
+		skw_port[0].dat_port = pdata->data_port;
+		skw_port[0].logic_port = 0;
+		skw_port[0].flags = SKW_LMAC_FLAG_INIT |
+				    SKW_LMAC_FLAG_RXCB |
+				    SKW_LMAC_FLAG_TXCB;
+
+		skw_port[1].dat_port = pdata->data_port;
+		skw_port[1].logic_port = 1;
+		skw_port[1].flags = SKW_LMAC_FLAG_INIT;
+
+		break;
+
+	case SKW_BUS_PCIE:
+		skw->hw.bus = SKW_BUS_PCIE;
+
+		/* ignore PM callback from cfg80211 */
+		clear_bit(SKW_HW_FLAG_CFG80211_PM, &skw->hw.flags);
+
+		skw->hw.bus_dat_xmit = skw_pcie_xmit;
+		skw->hw.bus_cmd_xmit = skw_pcie_cmd_xmit;
+		skw->hw.dma = SKW_ASYNC_EDMA_TX;
+
+		skw->hw.rx_desc.hdr_offset = SKW_PCIE_RX_DESC_HDR_OFFSET;
+		skw->hw.rx_desc.msdu_offset = SKW_PCIE_RX_DESC_MSDU_OFFSET;
+
+		skw->hw.cmd_port = -1;
+
+		skw->hw.pkt_limit = SKW_EDMA_TX_CHN_NODE_NUM * TX_BUF_ADDR_CNT;
+
+		skw_port[0].dat_port = 0;
+		skw_port[0].logic_port = 0;
+		skw_port[0].flags = SKW_LMAC_FLAG_INIT;
+
+		skw_port[1].logic_port = 0;
+		skw_port[1].dat_port = 0;
+		skw_port[1].flags = SKW_LMAC_FLAG_INIT;
+
+		break;
+
+	default:
+		break;
+	}
+
+	for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
+		skw->hw.lmac[i].id = i;
+		skw->hw.lmac[i].lport = skw_port[i].logic_port;
+		skw->hw.lmac[i].dport = skw_port[i].dat_port;
+		skw->hw.lmac[i].flags = skw_port[i].flags;
+		skw->hw.lmac[i].iface_bitmap = 0;
+
+		skw->hw.lmac[i].skw = skw;
+		skb_queue_head_init(&skw->hw.lmac[i].rx_dat_q);
+		skb_queue_head_init(&skw->hw.lmac[i].avail_skb);
+		skb_queue_head_init(&skw->hw.lmac[i].edma_free_list);
+
+		atomic_set(&skw->hw.lmac[i].fw_credit, 0);
+		atomic_set(&skw->hw.lmac[i].avail_skb_num, 0);
+
+		for (j = 0; j < SKW_MAX_PEER_SUPPORT; j++) {
+			skw->hw.lmac[i].peer_ctx[j].idx = j;
+			mutex_init(&skw->hw.lmac[i].peer_ctx[j].lock);
+		}
+	}
+}
+
+static int skw_core_init(struct skw_core *skw, struct platform_device *pdev, int idx)
+{
+	int ret;
+	char name[32] = {0};
+
+	skw->hw_pdata = dev_get_platdata(&pdev->dev);
+	if (!skw->hw_pdata) {
+		skw_err("get drv data failed\n");
+		return -ENODEV;
+	}
+
+	memcpy(&skw->config, &g_skw_config.conf, sizeof(struct skw_config));
+
+	skw->idx = idx;
+	skw_hw_hal_init(skw, skw->hw_pdata);
+
+	snprintf(name, sizeof(name), "chip%d.%s", idx, skw_bus_name(skw->hw.bus));
+	skw->dentry = skw_debugfs_subdir(name, NULL);
+	skw->pentry = skw_procfs_subdir(name, NULL);
+
+	mutex_init(&skw->lock);
+
+#ifdef CONFIG_SWT6621S_SAP_SME_EXT
+	set_bit(SKW_FLAG_SAP_SME_EXTERNAL, &skw->flags);
+#endif
+
+#ifdef CONFIG_SWT6621S_STA_SME_EXT
+	set_bit(SKW_FLAG_STA_SME_EXTERNAL, &skw->flags);
+#endif
+
+#ifdef CONFIG_SWT6621S_REPEATER_MODE
+	set_bit(SKW_FLAG_REPEATER, &skw->flags);
+#endif
+	skw->regd = NULL;
+	skw->country[0] = '0';
+	skw->country[1] = '0';
+
+	atomic_set(&skw->exit, 0);
+	atomic_set(&skw->tx_wake, 0);
+	atomic_set(&skw->rx_wake, 0);
+
+	init_waitqueue_head(&skw->tx_wait_q);
+	init_waitqueue_head(&skw->rx_wait_q);
+
+	skb_queue_head_init(&skw->rx_dat_q);
+	atomic_set(&skw->txqlen_pending, 0);
+
+	spin_lock_init(&skw->dfs.skw_pool_lock);
+	INIT_LIST_HEAD(&skw->dfs.skw_pulse_pool);
+	INIT_LIST_HEAD(&skw->dfs.skw_pseq_pool);
+
+	sema_init(&skw->cmd.lock, 1);
+	init_waitqueue_head(&skw->cmd.wq);
+	skw->cmd.ws = skw_wakeup_source_init("skwifi_cmd");
+	sema_init(&skw->cmd.mgmt_cmd_lock, 1);
+
+	spin_lock_init(&skw->vif.lock);
+
+	skw_event_work_init(&skw->event_work, skw_default_event_work);
+	skw->event_wq = alloc_workqueue("skw_evt_wq.%d", WQ_UNBOUND | WQ_MEM_RECLAIM, 1, idx);
+	if (!skw->event_wq) {
+		ret = -ENOMEM;
+		skw_err("alloc event wq failed, ret: %d\n", ret);
+
+		goto deinit_ws;
+	}
+
+	ret = skw_dpd_init(&skw->dpd);
+	if (ret < 0)
+		goto deinit_wq;
+
+	ret = skw_buffer_init(skw);
+	if (ret < 0)
+		goto deinit_dpd;
+
+	ret = skw_rx_init(skw);
+	if (ret < 0) {
+		skw_err("rx init failed, ret: %d\n", ret);
+		goto deinit_buff;
+	}
+
+	ret = skw_tx_init(skw);
+	if (ret < 0) {
+		skw_err("tx init failed, ret: %d\n", ret);
+		goto deinit_rx;
+	}
+
+	skw_debugfs_file(skw->dentry, "repeater", 0666, &skw_repeater_fops, skw);
+
+	skw->dbg.nr_cmd = SKW_DBG_NR_CMD;
+	skw->dbg.nr_dat = SKW_DBG_NR_DAT;
+	atomic_set(&skw->dbg.loop, 0);
+	skw->isr_cpu_id = -1;
+
+	return 0;
+
+deinit_rx:
+	skw_rx_deinit(skw);
+
+deinit_buff:
+	skw_buffer_deinit(skw);
+
+deinit_dpd:
+	skw_dpd_deinit(&skw->dpd);
+
+deinit_wq:
+	destroy_workqueue(skw->event_wq);
+	skw_event_work_deinit(&skw->event_work);
+
+deinit_ws:
+	skw_wakeup_source_deinit(skw->cmd.ws);
+
+	debugfs_remove_recursive(skw->dentry);
+	proc_remove(skw->pentry);
+
+	return ret;
+}
+
+static void skw_core_deinit(struct skw_core *skw)
+{
+	skw_tx_deinit(skw);
+
+	skw_rx_deinit(skw);
+
+	skw_buffer_deinit(skw);
+
+	skw_dpd_deinit(&skw->dpd);
+
+	destroy_workqueue(skw->event_wq);
+
+	skw_event_work_deinit(&skw->event_work);
+
+	skw_wakeup_source_deinit(skw->cmd.ws);
+
+	debugfs_remove_recursive(skw->dentry);
+	proc_remove(skw->pentry);
+}
+
+int skw_set_ip(struct wiphy *wiphy, struct net_device *ndev,
+		struct skw_setip_param *setip_param, int size)
+{
+	int ret = 0;
+	//int size = 0;
+
+	//size = sizeof(struct skw_setip_param);
+	ret = skw_queue_work(wiphy, netdev_priv(ndev),
+			SKW_WORK_SET_IP, setip_param, size);
+	if (ret)
+		skw_err("Set IP failed\n");
+
+	return ret;
+}
+
+/*
+ * Get the IP address for both IPV4 and IPV6, set it
+ * to firmware while they are valid.
+ */
+void skw_set_ip_to_fw(struct wiphy *wiphy, struct net_device *ndev)
+{
+	struct in_device *in_dev;
+	struct in_ifaddr *ifa;
+
+	struct inet6_dev *idev;
+	struct inet6_ifaddr *ifp;
+
+	struct skw_setip_param setip_param[SKW_FW_IPV6_COUNT_LIMIT+1];
+	int ip_count = 0, ipv6_count = 0;
+
+	in_dev = __in_dev_get_rtnl(ndev);
+	if (!in_dev)
+		goto ipv6;
+
+	ifa = in_dev->ifa_list;
+	if (!ifa || !ifa->ifa_local)
+		goto ipv6;
+
+	skw_info("ip addr: %pI4\n", &ifa->ifa_local);
+	setip_param[ip_count].ip_type = SKW_IP_IPV4;
+	setip_param[ip_count].ipv4 = ifa->ifa_local;
+	ip_count++;
+
+ipv6:
+	idev = __in6_dev_get(ndev);
+	if (!idev)
+		goto ip_apply;
+
+	read_lock_bh(&idev->lock);
+	list_for_each_entry_reverse(ifp, &idev->addr_list, if_list) {
+		skw_info("ip addr: %pI6\n", &ifp->addr);
+		if (++ipv6_count > SKW_FW_IPV6_COUNT_LIMIT) {
+			skw_warn("%s set first %d of %d ipv6 address\n",
+				ndev->name, SKW_FW_IPV6_COUNT_LIMIT, ipv6_count);
+
+			read_unlock_bh(&idev->lock);
+			goto ip_apply;
+		}
+
+		setip_param[ip_count].ip_type = SKW_IP_IPV6;
+		memcpy(&setip_param[ip_count].ipv6, &ifp->addr, 16);
+		ip_count++;
+	}
+	read_unlock_bh(&idev->lock);
+
+ip_apply:
+	if (ip_count)
+		skw_set_ip(wiphy, ndev, setip_param, ip_count*sizeof(struct skw_setip_param));
+
+}
+
+#ifdef CONFIG_INET
+static int skw_ifa4_notifier(struct notifier_block *nb,
+			unsigned long action, void *data)
+{
+	struct in_ifaddr *ifa = data;
+	struct net_device *ndev;
+	struct wireless_dev *wdev;
+	struct skw_core *skw = container_of(nb, struct skw_core, ifa4_nf);
+
+	skw_dbg("action: %ld\n", action);
+
+	if (!ifa->ifa_dev)
+		return NOTIFY_DONE;
+
+	ndev = ifa->ifa_dev->dev;
+	wdev = ndev->ieee80211_ptr;
+
+	if (!wdev || wdev->wiphy != priv_to_wiphy(skw))
+		return NOTIFY_DONE;
+
+	if (ndev->ieee80211_ptr->iftype != NL80211_IFTYPE_STATION &&
+	    ndev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_CLIENT)
+		return NOTIFY_DONE;
+
+	switch (action) {
+	case NETDEV_UP:
+//	case NETDEV_DOWN:
+		skw_set_ip_to_fw(wdev->wiphy, ndev);
+
+		break;
+
+	default:
+		break;
+	}
+
+	return NOTIFY_DONE;
+}
+
+#endif
+
+#ifdef CONFIG_IPV6
+static int skw_ifa6_notifier(struct notifier_block *nb,
+			unsigned long action, void *data)
+{
+	struct inet6_ifaddr *ifa6 = data;
+	struct skw_core *skw = container_of(nb, struct skw_core, ifa6_nf);
+
+	struct net_device *ndev;
+	struct wireless_dev *wdev;
+
+	skw_dbg("action: %ld\n", action);
+
+	if (!ifa6->idev || !ifa6->idev->dev)
+		return NOTIFY_DONE;
+
+	ndev = ifa6->idev->dev;
+	wdev = ndev->ieee80211_ptr;
+
+	if (!wdev || wdev->wiphy != priv_to_wiphy(skw))
+		return NOTIFY_DONE;
+
+	switch (action) {
+	case NETDEV_UP:
+	// case NETDEV_DOWN:
+
+		skw_set_ip_to_fw(wdev->wiphy, ndev);
+
+		break;
+
+	default:
+		break;
+	}
+
+	return NOTIFY_DONE;
+}
+
+#endif
+
+static int skw_bsp_notifier(struct notifier_block *nb,
+			unsigned long action, void *data)
+{
+	struct skw_core *skw = container_of(nb, struct skw_core, bsp_nf);
+	struct wiphy *wiphy = priv_to_wiphy(skw);
+	struct cfg80211_wowlan wow, *pwow = NULL;
+
+	skw_info("action: %ld, skw flags: 0x%lx, nf_flags: 0x%lx\n",
+		action, skw->flags, skw->nf_flags);
+
+	switch (action) {
+	case SKW_BSP_NF_ASSERT:
+	case SKW_BSP_NF_BLOCKED:
+	case SKW_BSP_NF_FW_REBOOT:
+		set_bit(SKW_FLAG_FW_ASSERT, &skw->flags);
+
+		WRITE_ONCE(skw->nf_flags, 0);
+		set_bit(SKW_BSP_NF_ASSERT, &skw->nf_flags);
+
+		skw_abort_cmd(skw);
+		cancel_work_sync(&skw->recovery_work);
+		skw_wifi_disable(skw->hw_pdata);
+
+		break;
+
+	case SKW_BSP_NF_READY:
+		if (test_and_clear_bit(SKW_BSP_NF_ASSERT, &skw->nf_flags))
+			schedule_work(&skw->recovery_work);
+
+		break;
+
+	case SKW_BSP_NF_SUSPEND:
+		if (!test_bit(SKW_HW_FLAG_CFG80211_PM, &skw->hw.flags)) {
+			if (skw->hw.wow.enabled)  {
+				memset(&wow, 0x0, sizeof(struct cfg80211_wowlan));
+
+				if (skw->hw.wow.flags & SKW_WOW_DISCONNECT)
+					wow.disconnect = true;
+
+				if (skw->hw.wow.flags & SKW_WOW_MAGIC_PKT)
+					wow.magic_pkt = true;
+
+				if (skw->hw.wow.flags & SKW_WOW_GTK_REKEY_FAIL)
+					wow.gtk_rekey_failure = true;
+
+				if (skw->hw.wow.flags & SKW_WOW_EAP_IDENTITY_REQ)
+					wow.eap_identity_req = true;
+
+				if (skw->hw.wow.flags & SKW_WOW_FOUR_WAY_HANDSHAKE)
+					wow.four_way_handshake = true;
+
+				if (skw->hw.wow.flags & SKW_WOW_RFKILL_RELEASE)
+					wow.rfkill_release = true;
+
+				pwow = &wow;
+			}
+
+			set_bit(SKW_BSP_NF_SUSPEND, &skw->nf_flags);
+
+			skw_suspend(wiphy, pwow);
+		}
+
+		break;
+
+	case SKW_BSP_NF_RESUME:
+		if (test_and_clear_bit(SKW_BSP_NF_SUSPEND, &skw->nf_flags))
+			skw_work_resume(wiphy);
+
+		break;
+
+	default:
+		break;
+	}
+
+	return NOTIFY_DONE;
+}
+
+static int skw_pm_notifier(struct notifier_block *nb,
+			unsigned long action, void *data)
+{
+	struct skw_core *skw = container_of(nb, struct skw_core, pm_nf);
+
+	skw_dbg("action: %ld\n", action);
+
+	switch (action) {
+	case PM_SUSPEND_PREPARE:
+		set_bit(SKW_FLAG_SUSPEND_PREPARE, &skw->flags);
+		break;
+
+	case PM_POST_SUSPEND:
+		clear_bit(SKW_FLAG_SUSPEND_PREPARE, &skw->flags);
+		break;
+	}
+
+	return 0;
+}
+
+static void skw_ifa_notifier_register(struct skw_core *skw)
+{
+#ifdef CONFIG_INET
+	skw->ifa4_nf.notifier_call = skw_ifa4_notifier;
+	register_inetaddr_notifier(&skw->ifa4_nf);
+#endif
+
+#ifdef CONFIG_IPV6
+	skw->ifa6_nf.notifier_call = skw_ifa6_notifier;
+	register_inet6addr_notifier(&skw->ifa6_nf);
+#endif
+
+	skw->bsp_nf.notifier_call = skw_bsp_notifier;
+	skw_register_bsp_notifier(skw, &skw->bsp_nf);
+
+	skw->pm_nf.notifier_call = skw_pm_notifier;
+	register_pm_notifier(&skw->pm_nf);
+}
+
+static void skw_ifa_notifier_unregister(struct skw_core *skw)
+{
+#ifdef CONFIG_INET
+	unregister_inetaddr_notifier(&skw->ifa4_nf);
+#endif
+
+#ifdef CONFIG_IPV6
+	unregister_inet6addr_notifier(&skw->ifa6_nf);
+#endif
+
+	skw_unregister_bsp_notifier(skw, &skw->bsp_nf);
+
+	unregister_pm_notifier(&skw->pm_nf);
+}
+
+int skw_lmac_bind_iface(struct skw_core *skw, struct skw_iface *iface, int lmac_id)
+{
+	struct skw_lmac *lmac;
+
+	if (lmac_id >= skw->hw.nr_lmac) {
+		skw_err("invalid lmac id: %d\n", skw->hw.nr_lmac);
+		return -ENOTSUPP;
+	}
+
+	iface->lmac_id = lmac_id;
+	lmac = &skw->hw.lmac[lmac_id];
+	BUG_ON(!(lmac->flags & SKW_LMAC_FLAG_INIT));
+
+	if (skw->hw.bus == SKW_BUS_PCIE) {
+		// TODO:
+		// register edma channel
+		// skw_edma_enable_channel(&lmac->edma_tx_chn, isr);
+	}
+
+	SKW_SET(lmac->iface_bitmap, BIT(iface->id));
+	SKW_SET(lmac->flags, SKW_LMAC_FLAG_ACTIVED);
+
+	return 0;
+}
+
+int skw_lmac_unbind_iface(struct skw_core *skw, int lmac_id, int iface_id)
+{
+	struct skw_lmac *lmac;
+
+	if (lmac_id >= skw->hw.nr_lmac) {
+		skw_err("invalid lmac id: %d\n", skw->hw.nr_lmac);
+		return 0;
+	}
+
+	lmac = &skw->hw.lmac[lmac_id];
+
+	SKW_CLEAR(lmac->iface_bitmap, BIT(iface_id));
+
+	if (lmac->iface_bitmap)
+		return 0;
+
+	// TODO:
+	// unregister edma channel
+
+	SKW_CLEAR(lmac->flags, SKW_LMAC_FLAG_ACTIVED);
+
+	skw_dbg("reset fw credit for mac:%d", lmac_id);
+	atomic_set(&skw->hw.lmac[lmac_id].fw_credit, 0);
+
+	return 0;
+}
+
+void skw_add_credit(struct skw_core *skw, int lmac_id, int cred)
+{
+	trace_skw_tx_add_credit(lmac_id, cred);
+
+	atomic_add(cred, &skw->hw.lmac[lmac_id].fw_credit);
+	smp_wmb();
+
+	skw_wakeup_tx(skw, 0);
+	skw_detail("lmac_id:%d cred:%d", lmac_id, cred);
+
+	if (skw->hw.bus == SKW_BUS_SDIO || skw->hw.bus == SKW_BUS_SDIO2)
+		schedule_work(&skw->hw.lmac[lmac_id].dy_work);
+
+}
+
+static int skw_add_default_iface(struct wiphy *wiphy)
+{
+	int ret = 0;
+	struct skw_iface *iface = NULL;
+#ifdef CONFIG_SWT6621S_LEGACY_P2P
+	struct skw_iface *p2p = NULL;
+	struct skw_core *skw = wiphy_priv(wiphy);
+#endif
+
+	rtnl_lock();
+
+	iface = skw_add_iface(wiphy, "wlan%d", NL80211_IFTYPE_STATION,
+				skw_mac, SKW_INVALID_ID, true);
+	if (IS_ERR(iface)) {
+		ret = PTR_ERR(iface);
+		skw_err("add iface failed, ret: %d\n", ret);
+
+		goto unlock;
+	}
+
+#ifdef CONFIG_SWT6621S_LEGACY_P2P
+	if (test_bit(SKW_FLAG_LEGACY_P2P_COMMON_PORT, &skw->flags))
+		p2p = skw_add_iface(wiphy, "p2p%d", NL80211_IFTYPE_STATION,
+				NULL, SKW_LAST_IFACE_ID, true);
+	else
+		p2p = skw_add_iface(wiphy, "p2p%d", NL80211_IFTYPE_P2P_DEVICE,
+				NULL, SKW_LAST_IFACE_ID, true);
+
+	if (IS_ERR(p2p)) {
+		ret = PTR_ERR(p2p);
+		skw_err("add p2p legacy interface failed, ret: %d\n", ret);
+
+		skw_del_iface(wiphy, iface);
+
+		goto unlock;
+	}
+
+	if (!test_bit(SKW_FLAG_LEGACY_P2P_COMMON_PORT, &skw->flags))
+		p2p->flags |= SKW_IFACE_FLAG_LEGACY_P2P_DEV;
+
+#endif
+
+unlock:
+	rtnl_unlock();
+
+	return ret;
+}
+
+static int skw_calib_bin_download(struct wiphy *wiphy, const u8 *data, u32 size)
+{
+	int i = 0, ret = 0;
+	int buf_size, remain = size;
+	struct skw_calib_param calib;
+
+	skw_dbg("file size: %d\n", size);
+
+	buf_size = sizeof(calib.data);
+
+	while (remain > 0) {
+		calib.len = (remain < buf_size) ? remain : buf_size;
+		calib.seq = i;
+		remain -= calib.len;
+
+		memcpy(calib.data, data, calib.len);
+
+		if (!remain)
+			calib.end = 1;
+		else
+			calib.end = 0;
+
+		skw_dbg("bb_file remain: %d, seq: %d len: %d end: %d\n",
+			remain, calib.seq, calib.len, calib.end);
+
+		ret = skw_msg_xmit_timeout(wiphy, 0, SKW_CMD_PHY_BB_CFG,
+			&calib, sizeof(calib), NULL, 0,
+			"SKW_CMD_PHY_BB_CFG", msecs_to_jiffies(5000), 0);
+		if (ret) {
+			skw_err("failed, ret: %d,  seq: %d\n", ret, i);
+			break;
+		}
+
+		i++;
+		data += calib.len;
+	}
+
+	return ret;
+}
+
+int skw_calib_download(struct wiphy *wiphy, const char *fname)
+{
+	int ret = 0;
+	const struct firmware *fw;
+
+	skw_dbg("fw file: %s\n", fname);
+
+	ret = request_firmware(&fw, fname, &wiphy->dev);
+	if (ret) {
+		skw_err("load %s failed, ret: %d\n", fname, ret);
+		return ret;
+	}
+
+	ret = skw_calib_bin_download(wiphy, fw->data, fw->size);
+	if (ret != 0)
+		skw_err("bb_file cali msg fail\n");
+
+	release_firmware(fw);
+
+	return ret;
+}
+
+void skw_dbg_dump(struct skw_core *skw)
+{
+	int i;
+	u64 nsecs;
+	unsigned long rem_nsec;
+
+	for (i = 0; i < skw->dbg.nr_cmd; i++) {
+		struct skw_dbg_cmd *cmd = &skw->dbg.cmd[i];
+
+		skw_info("cmd[%d].id: %d, seq: %d, flags: 0x%lx, loop: %d\n",
+			i, cmd->id, cmd->seq, cmd->flags, cmd->loop);
+
+		nsecs = cmd->trigger;
+		rem_nsec = do_div(nsecs, 1000000000);
+		skw_info("cmd.%d.%d.trigger: %5lu.%06lu\n", cmd->id, cmd->seq,
+			(unsigned long)nsecs, rem_nsec / 1000);
+
+		nsecs = cmd->build;
+		rem_nsec = do_div(nsecs, 1000000000);
+		skw_info("cmd.%d.%d.build: %5lu.%06lu\n", cmd->id, cmd->seq,
+			(unsigned long)nsecs, rem_nsec / 1000);
+
+		nsecs = cmd->xmit;
+		rem_nsec = do_div(nsecs, 1000000000);
+		skw_info("cmd.%d.%d.xmit: %5lu.%06lu\n", cmd->id, cmd->seq,
+			(unsigned long)nsecs, rem_nsec / 1000);
+
+		nsecs = cmd->done;
+		rem_nsec = do_div(nsecs, 1000000000);
+		skw_info("cmd.%d.%d.done: %5lu.%06lu\n", cmd->id, cmd->seq,
+			(unsigned long)nsecs, rem_nsec / 1000);
+
+		nsecs = cmd->ack;
+		rem_nsec = do_div(nsecs, 1000000000);
+		skw_info("cmd.%d.%d.ack: %5lu.%06lu\n", cmd->id, cmd->seq,
+			(unsigned long)nsecs, rem_nsec / 1000);
+
+		nsecs = cmd->assert;
+		rem_nsec = do_div(nsecs, 1000000000);
+		skw_info("cmd.%d.%d.assert: %5lu.%06lu\n", cmd->id, cmd->seq,
+			(unsigned long)nsecs, rem_nsec / 1000);
+	}
+
+	for (i = 0; i < skw->dbg.nr_dat; i++) {
+		struct skw_dbg_dat *dat = &skw->dbg.dat[i];
+
+		nsecs = dat->trigger;
+		rem_nsec = do_div(nsecs, 1000000000);
+		skw_info("dat.%d.%d.trigger: %5lu.%06lu\n",
+			i, dat->qlen, (unsigned long)nsecs, rem_nsec / 1000);
+
+		nsecs = dat->done;
+		rem_nsec = do_div(nsecs, 1000000000);
+		skw_info("dat.%d.%d.done: %5lu.%06lu\n",
+			i, dat->qlen, (unsigned long)nsecs, rem_nsec / 1000);
+	}
+}
+
+static int skw_drv_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct skw_chip_info chip;
+	struct wiphy *wiphy = NULL;
+	struct skw_core *skw = NULL;
+	int idx = atomic_inc_return(&skw_chip_idx);
+
+	skw_info("chip: %d, MAC: %pM\n", idx, skw_mac);
+
+	wiphy = skw_alloc_wiphy(sizeof(struct skw_core));
+	if (!wiphy) {
+		ret = -ENOMEM;
+		skw_err("malloc wiphy failed\n");
+
+		goto failed;
+	}
+
+	set_wiphy_dev(wiphy, &pdev->dev);
+
+	skw = wiphy_priv(wiphy);
+
+	ret = skw_core_init(skw, pdev, idx);
+	if (ret) {
+		skw_err("core init failed, ret: %d\n", ret);
+		goto free_wiphy;
+	}
+
+	skw_regd_init(wiphy);
+	skw_vendor_init(wiphy);
+	skw_work_init(wiphy);
+	skw_timer_init(skw);
+	skw_recovery_init(skw);
+
+	skw_wifi_enable(dev_get_platdata(&pdev->dev));
+
+	if (skw_sync_cmd_event_version(wiphy) ||
+	    skw_sync_chip_info(wiphy, &chip))
+		goto core_deinit;
+
+#ifdef CONFIG_PLATFORM_ROCKCHIP
+	if (!is_valid_ether_addr(skw_mac))
+		rockchip_wifi_mac_addr(skw_mac);
+#endif
+
+	skw_setup_mac_address(wiphy, skw_mac, chip.mac);
+
+	if (skw_calib_download(wiphy, skw->fw.calib_file) < 0)
+		goto core_deinit;
+
+	ret = skw_setup_wiphy(wiphy, &chip);
+	if (ret) {
+		skw_err("setup wiphy failed, ret: %d\n", ret);
+		goto core_deinit;
+	}
+
+	rtnl_lock();
+
+	skw_set_wiphy_regd(wiphy, skw->country);
+
+	if (skw->config.regd.country[0] != '0' &&
+	    skw->config.regd.country[1] != '0'){
+
+		ret = skw_set_regdom(wiphy, skw->config.regd.country);
+		if (ret)
+			skw_err("skw_set_regdom failed, ret: %d\n", ret);
+	}
+
+	rtnl_unlock();
+
+	ret = skw_add_default_iface(wiphy);
+	if (ret)
+		goto unregister_wiphy;
+
+	skw_ifa_notifier_register(skw);
+
+	platform_set_drvdata(pdev, skw);
+
+	skw_procfs_file(skw->pentry, "core", 0444, &skw_core_fops, skw);
+
+	skw_debugfs_file(SKW_WIPHY_DENTRY(wiphy), "assert", 0444,
+			 &skw_assert_fops, skw);
+
+	skw_procfs_file(skw->pentry, "debug_info", 0444, &skw_debug_info_fops, skw);
+
+	return 0;
+
+unregister_wiphy:
+	wiphy_unregister(wiphy);
+
+core_deinit:
+	skw_wifi_disable(dev_get_platdata(&pdev->dev));
+	skw_recovery_deinit(skw);
+	skw_timer_deinit(skw);
+	skw_work_deinit(wiphy);
+	skw_vendor_deinit(wiphy);
+	skw_core_deinit(skw);
+
+free_wiphy:
+	wiphy_free(wiphy);
+
+failed:
+	atomic_dec(&skw_chip_idx);
+
+	return ret;
+}
+
+static int skw_drv_remove(struct platform_device *pdev)
+{
+	int i;
+	struct wiphy *wiphy;
+	struct skw_core *skw = platform_get_drvdata(pdev);
+
+	skw_info("%s\n", pdev->name);
+
+	if (!skw)
+		return 0;
+
+	wiphy = priv_to_wiphy(skw);
+
+	skw_ifa_notifier_unregister(skw);
+
+	rtnl_lock();
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
+	cfg80211_shutdown_all_interfaces(wiphy);
+#endif
+
+	for (i = 0; i < SKW_NR_IFACE; i++)
+		skw_del_iface(wiphy, skw->vif.iface[i]);
+
+	rtnl_unlock();
+
+	wiphy_unregister(wiphy);
+
+	skw_wifi_disable(dev_get_platdata(&pdev->dev));
+
+	skw_recovery_deinit(skw);
+
+	skw_timer_deinit(skw);
+
+	skw_vendor_deinit(wiphy);
+
+	skw_core_deinit(skw);
+
+	skw_work_deinit(wiphy);
+
+	wiphy_free(wiphy);
+
+	atomic_dec(&skw_chip_idx);
+
+	return 0;
+}
+
+static struct platform_driver skw_drv = {
+	.probe = skw_drv_probe,
+	.remove = skw_drv_remove,
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "sv6621s_wireless1",
+	},
+};
+
+static void skw_global_config_init(struct skw_global_config *gconf)
+{
+	memset(gconf, 0x0, sizeof(struct skw_global_config));
+
+	atomic_set(&gconf->index, 0);
+
+	/* global */
+	gconf->conf.global.dma_addr_align = 4;
+
+	gconf->conf.global.reorder_timeout = 100;
+
+	gconf->conf.fw.link_loss_thrd = 6;
+
+	gconf->conf.fw.band_24ghz = SKW_CHAN_WIDTH_40;
+
+	gconf->conf.fw.hdk_tst = 0;
+
+#ifdef CONFIG_SWT6621S_RX_REORDER_TIMEOUT
+	gconf->conf.global.reorder_timeout = CONFIG_SWT6621S_RX_REORDER_TIMEOUT;
+#endif
+
+#ifdef CONFIG_SWT6621S_STA_SME_EXT
+	set_bit(SKW_CFG_FLAG_STA_EXT, &gconf->conf.global.flags);
+#endif
+
+#ifdef CONFIG_SWT6621S_SAP_SME_EXT
+	set_bit(SKW_CFG_FLAG_SAP_EXT, &gconf->conf.global.flags);
+#endif
+
+#ifdef CONFIG_SWT6621S_REPEATER_MODE
+	set_bit(SKW_CFG_FLAG_REPEATER, &gconf->conf.global.flags);
+#endif
+
+#ifdef CONFIG_SWT6621S_OFFCHAN_TX
+	set_bit(SKW_CFG_FLAG_OFFCHAN_TX, &gconf->conf.global.flags);
+#endif
+
+	/* interface */
+	strcpy(gconf->conf.intf.interface[0].name, "wlan%d");
+	gconf->conf.intf.interface[0].inst = SKW_INVALID_ID;
+	gconf->conf.intf.interface[0].iftype = NL80211_IFTYPE_STATION;
+
+#ifdef CONFIG_SWT6621S_LEGACY_P2P
+	set_bit(SKW_CFG_FLAG_P2P_DEV, &gconf->conf.global.flags);
+	strcpy(gconf->conf.intf.interface[3].name, "p2p%d");
+	gconf->conf.intf.interface[3].inst = 3;
+	gconf->conf.intf.interface[3].iftype = NL80211_IFTYPE_P2P_DEVICE;
+#endif
+
+	/* regdom */
+	gconf->conf.regd.country[0] = '0';
+	gconf->conf.regd.country[1] = '0';
+
+#ifdef CONFIG_SWT6621S_REGD_SELF_MANAGED
+	set_bit(SKW_CFG_REGD_SELF_MANAGED, &gconf->conf.regd.flags);
+#endif
+
+#ifdef CONFIG_SWT6621S_DEFAULT_COUNTRY
+	if (strlen(CONFIG_SWT6621S_DEFAULT_COUNTRY) == 2 &&
+	    is_skw_valid_reg_code(CONFIG_SWT6621S_DEFAULT_COUNTRY))
+		memcpy(gconf->conf.regd.country,
+		       CONFIG_SWT6621S_DEFAULT_COUNTRY, 2);
+
+#endif
+
+	/* calib */
+#ifdef CONFIG_SWT6621S_CALIB_APPEND_BUS_ID
+	set_bit(SKW_CFG_CALIB_STRICT_MODE, &gconf->conf.calib.flags);
+#endif
+
+#ifdef CONFIG_SWT6621S_CHIP_ID
+	if (strlen(CONFIG_SWT6621S_CHIP_ID))
+		strncpy(gconf->conf.calib.chip, CONFIG_SWT6621S_CHIP_ID,
+			sizeof(gconf->conf.calib.chip) - 1);
+#endif
+
+	strcpy(gconf->conf.calib.project, "SEEKWAVE");
+
+#ifdef CONFIG_SWT6621S_PROJECT_NAME
+	if (strlen(CONFIG_SWT6621S_PROJECT_NAME))
+		strncpy(gconf->conf.calib.project, CONFIG_SWT6621S_PROJECT_NAME,
+			sizeof(gconf->conf.calib.project) - 1);
+#endif
+
+	skw_update_config(NULL, "skwifid.cfg", &g_skw_config.conf);
+}
+
+static int __init skw_module_init(void)
+{
+	int ret = 0;
+
+	pr_info("[%s] VERSION: %s (%s)\n",
+		SKW_TAG_INFO, SKW_VERSION, UTS_RELEASE);
+
+	skw_dentry_init();
+	skw_log_level_init();
+
+	skw_power_on_chip();
+
+	skw_global_config_init(&g_skw_config);
+
+	ret = platform_driver_register(&skw_drv);
+	if (ret) {
+		skw_err("register %s failed, ret: %d\n",
+			skw_drv.driver.name, ret);
+
+		skw_power_off_chip();
+
+		skw_log_level_deinit();
+		skw_dentry_deinit();
+	}
+
+	return ret;
+}
+
+static void __exit skw_module_exit(void)
+{
+	skw_info("unload\n");
+
+	skw_del_dbg_iface();
+	platform_driver_unregister(&skw_drv);
+
+	skw_power_off_chip();
+
+	skw_log_level_deinit();
+	skw_dentry_deinit();
+}
+
+module_init(skw_module_init);
+module_exit(skw_module_exit);
+
+module_param_array_named(mac, skw_mac, byte, NULL, 0444);
+MODULE_PARM_DESC(mac, "config mac address");
+
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("1.0.0");
+MODULE_AUTHOR("seekwavetech");
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_core.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_core.h
new file mode 100755
index 0000000..6f51dec
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_core.h
@@ -0,0 +1,899 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_CORE_H__
+#define __SKW_CORE_H__
+
+#include <net/ipv6.h>
+#include <linux/version.h>
+#include <linux/pm_wakeup.h>
+
+#ifdef CONFIG_HAS_WAKELOCK
+#include <linux/wakelock.h>
+#endif
+
+#include "skw_util.h"
+#include "skw_compat.h"
+#include "skw_dentry.h"
+#include "skw_log.h"
+#include "skw_platform_data.h"
+#include "skw_work.h"
+#include "skw_edma.h"
+#include "skw_iface.h"
+#include "skw_calib.h"
+#include "skw_recovery.h"
+#include "skw_config.h"
+
+extern unsigned int tx_wait_time;
+
+#define SKW_BUS_TYPE_MASK                   TYPE_MASK
+#define SKW_BUS_SDIO                        SDIO_LINK
+#define SKW_BUS_USB                         USB_LINK
+#define SKW_BUS_PCIE                        PCIE_LINK
+#define SKW_BUS_SDIO2                       SDIO2_LINK
+#define SKW_BUS_USB2                        USB2_LINK
+
+#define SKW_BSP_NF_ASSERT                   DEVICE_ASSERT_EVENT
+#define SKW_BSP_NF_BLOCKED                  DEVICE_BLOCKED_EVENT
+#define SKW_BSP_NF_READY                    DEVICE_BSPREADY_EVENT
+#define SKW_BSP_NF_DISCONNECT               4
+#define SKW_BSP_NF_SUSPEND                  6
+#define SKW_BSP_NF_RESUME                   7
+#define SKW_BSP_NF_FW_REBOOT                8
+
+#define SKW_FW_IPV6_COUNT_LIMIT             3
+
+/*sap capa flag */
+#define SKW_CAPA_HT                         BIT(0)
+#define SKW_CAPA_VHT                        BIT(1)
+#define SKW_CAPA_HE                         BIT(2)
+
+/* capability */
+#define SKW_MAX_LMAC_SUPPORT                2
+#define SKW_NR_IFACE                        4
+#define SKW_LAST_IFACE_ID                   (SKW_NR_IFACE - 1)
+#define SKW_MAX_PEER_SUPPORT                32
+#define SKW_MAX_STA_ALLOWED                 10
+#define SKW_NR_SGL_DAT                      256
+#define SKW_NR_SGL_CMD                      4
+#define SKW_MAX_IE_LEN                      1400
+#define SKW_MSG_BUFFER_LEN                  2048
+#define SKW_TX_PACK_SIZE                    1536
+
+#define SKW_DATA_ALIGN_SIZE                 4
+#define SKW_DATA_ALIGN_MASK                 3
+
+#define SKW_EXTER_HDR_SIZE                  4
+#define SKW_TX_HDR_SIZE                     6
+
+#define SKW_DBG_NR_CMD                      2
+#define SKW_DBG_NR_DAT                      2
+
+/* protocal */
+#define SKW_ETH_P_WAPI                      0x88B4
+
+/* ioctl */
+#define PRIVATE_COMMAND_MAX_LEN             8192
+#define PRIVATE_COMMAND_DEF_LEN             4096
+
+#define SKW_ANDROID_PRIV_START              "START"
+#define SKW_ANDROID_PRIV_STOP               "STOP"
+#define SKW_ANDROID_PRIV_SETFWPATH          "SETFWPATH"
+#define SKW_ANDROID_PRIV_COUNTRY            "COUNTRY"
+#define SKW_ANDROID_PRIV_BTCOEXSCAN_STOP    "BTCOEXSCAN-STOP"
+#define SKW_ANDROID_PRIV_RXFILTER_START     "RXFILTER-START"
+#define SKW_ANDROID_PRIV_RXFILTER_STOP      "RXFILTER-STOP"
+#define SKW_ANDROID_PRIV_RXFILTER_ADD       "RXFILTER-ADD"
+#define SKW_ANDROID_PRIV_RXFILTER_REMOVE    "RXFILTER-REMOVE"
+#define SKW_ANDROID_PRIV_SETSUSPENDMODE     "SETSUSPENDMODE"
+#define SKW_ANDROID_PRIV_BTCOEXMODE         "BTCOEXMODE"
+#define SKW_ANDROID_PRIV_MAX_NUM_STA        "MAX_NUM_STA"
+#define SKW_ANDROID_PRIV_SET_AP_WPS_P2P_IE  "SET_AP_WPS_P2P_IE"
+
+/* SKW_FLAG_* */
+#define SKW_FLAG_FW_ASSERT                  (0)
+#define SKW_FLAG_BLOCK_TX                   (1)
+#define SKW_FLAG_FW_MAC_RECOVERY            (2)
+#define SKW_FLAG_FW_THERMAL                 (3)
+
+#define SKW_FLAG_FW_UART_OPEND              (4)
+#define SKW_FLAG_FW_FILTER_ARP              (5)
+#define SKW_FLAG_FW_IGNORE_CRED             (6)
+/* data not permit */
+#define SKW_FLAG_FW_CHIP_RECOVERY           (7)
+#define SKW_FLAG_SAP_SME_EXTERNAL           (8)
+#define SKW_FLAG_STA_SME_EXTERNAL           (9)
+#define SKW_FLAG_MBSSID_PRIV                (10)
+#define SKW_FLAG_MP_MODE                    (11)
+#define SKW_FLAG_LEGACY_P2P_COMMON_PORT     (12)
+#define SKW_FLAG_SWITCHING_USB_MODE         (13)
+#define SKW_FLAG_PRIV_REGD                  (15)
+#define SKW_FLAG_REPEATER                   (16)
+#define SKW_FLAG_FW_PN_REUSE                (17)
+#define SKW_FLAG_SUSPEND_PREPARE            (19)
+
+/* SKW_LMAC_FLAG_* */
+#define SKW_LMAC_FLAG_INIT                  BIT(0)
+#define SKW_LMAC_FLAG_ACTIVED               BIT(1)
+#define SKW_LMAC_FLAG_RXCB                  BIT(2)
+#define SKW_LMAC_FLAG_TXCB                  BIT(3)
+
+#define SKW_SYNC_ADMA_TX                    0
+#define SKW_SYNC_SDMA_TX                    1
+#define SKW_ASYNC_ADMA_TX                   2
+#define SKW_ASYNC_SDMA_TX                   3
+#define SKW_ASYNC_EDMA_TX                   4
+
+#define SKW_TXQ_STOPED(n, q) \
+	netif_tx_queue_stopped(netdev_get_tx_queue(n, q))
+
+#define SKW_IOCTL_ANDROID_CMD               (SIOCDEVPRIVATE + 1)
+#define SKW_IOCTL_CMD                       (SIOCDEVPRIVATE + 15)
+#define SKW_IOCTL_SUBCMD_COUNTRY            1
+
+struct skw_ioctl_cmd {
+	int len;
+	int id;
+};
+
+struct skw_lmac {
+	u8 id;
+	u8 flags; /* reference SKW_LMAC_FLAG_ */
+	s8 lport; /* logic port */
+	s8 dport; /* data port */
+
+	//u8 tx_done_chn;
+	//u8 rx_chn;
+	//u8 rx_buff_chn;
+	int iface_bitmap;
+	struct skw_peer_ctx peer_ctx[SKW_MAX_PEER_SUPPORT];
+	atomic_t fw_credit;
+
+	// struct skw_wmm_tx cached;
+
+	struct net_device dummy_dev;
+	struct napi_struct napi_tx;
+	struct napi_struct napi_rx;
+
+	struct work_struct dy_work;
+	struct skw_tx_vring *tx_vring;
+
+	atomic_t avail_skb_num;
+
+	struct sk_buff_head rx_dat_q;
+	struct sk_buff_head avail_skb;
+	struct sk_buff_head edma_free_list;
+
+	struct skw_list rx_todo_list;
+	struct skw_core *skw;
+};
+
+struct skw_firmware_info {
+	u8 build_time[32];
+	u8 plat_ver[16];
+	u8 wifi_ver[16];
+	u8 calib_file[64];
+	u16 max_num_sta;
+	u16 resv;
+	u32 timestamp;
+	u64 host_timestamp;
+	unsigned long host_seconds;
+	u32 fw_bw_capa;
+};
+
+#define SKW_BW_CAP_2G_20M       BIT(0)
+#define SKW_BW_CAP_2G_40M       BIT(1)
+#define SKW_BW_CAP_5G_20M       BIT(2)
+#define SKW_BW_CAP_5G_40M       BIT(3)
+#define SKW_BW_CAP_5G_80M       BIT(4)
+#define SKW_BW_CAP_5G_160M      BIT(5)
+#define SKW_BW_CAP_5G_80P80M    BIT(6)
+
+struct skw_chip_info {
+	u16 enc_capa;
+
+	u32 chip_model;
+	u32 chip_version;
+	u32 fw_version;
+	u32 fw_capa;
+
+	u8 max_sta_allowed;
+	u8 max_mc_addr_allowed;
+
+	/* HT */
+	u16 ht_capa;
+	u16 ht_ext_capa;
+	u16 ht_ampdu_param;
+	u32 ht_tx_mcs_maps;
+	u32 ht_rx_mcs_maps;
+
+	/* VHT */
+	u32 vht_capa;
+	u16 vht_tx_mcs_maps;
+	u16 vht_rx_mcs_maps;
+
+	/* HE */
+	u8 max_scan_ssids;
+	u8 he_capa[6];
+	u8 he_phy_capa[11];
+	u16 he_tx_mcs_maps;
+	u16 he_rx_mcs_maps;
+	u8 mac[ETH_ALEN];
+
+	u8 abg_rate_num;
+	u8 abg_rate[15];
+
+	u32 fw_bw_capa; /* reference SKW_BW_CAP_* */
+
+	u32 priv_filter_arp:1;
+	u32 priv_ignore_cred:1;
+	u32 priv_pn_reuse:1;
+	u32 priv_p2p_common_port:1;
+	u32 priv_dfs_master_enabled:1;
+	u32 priv_2g_only:1;
+	u32 priv_resv:18;
+	u32 nr_hw_mac:8;
+
+	u8 fw_build_time[32];
+	u8 fw_plat_ver[16];
+	u8 fw_wifi_ver[16];
+	u8 fw_bt_ver[16];
+
+	u32 fw_timestamp;
+	u32 fw_chip_type;
+	u16 calib_module_id;
+	u16 resv;
+	u32 fw_ext_capa[12];
+} __packed;
+
+enum SKW_MSG_VERSION {V0, V1, V2, V3};
+
+#define SKW_MAX_MSG_ID        256
+
+struct skw_version_info {
+	u8 cmd[SKW_MAX_MSG_ID];
+	u8 event[SKW_MAX_MSG_ID];
+} __packed;
+
+struct skw_hw_extra {
+	u8 hdr_len;
+	u8 chn_offset;
+	u8 len_offset;
+	u8 eof_offset;
+};
+
+struct skw_fixed_offset {
+	s16 hdr_offset;
+	s16 msdu_offset;
+};
+
+#define SKW_HW_FLAG_EXTRA_HDR            (0)
+#define SKW_HW_FLAG_SDIO_V2              (1)
+#define SKW_HW_FLAG_USB_V2               (2)
+#define SKW_HW_FLAG_CFG80211_PM          (3)
+
+typedef int (*hw_xmit_func)(struct skw_core *skw, struct sk_buff_head *list,
+			    int lmac_id, int port, struct scatterlist *sgl,
+			    int nents, int tx_bytes);
+
+typedef int (*bus_dat_xmit_func)(struct skw_core *skw, int lmac_id,
+				 struct sk_buff_head *txq_list);
+
+typedef int (*bus_cmd_xmit_func)(struct skw_core *skw, void *cmd, int cmd_len);
+
+struct skw_hw_info {
+	u8 bus;
+	u8 dma;
+	u8 nr_lmac;
+	u8 cmd_port;
+
+	u16 align;
+	s16 pkt_limit;
+	unsigned long flags;
+	atomic_t credit; /* total credit of all LMAC */
+
+	hw_xmit_func cmd_xmit;
+	hw_xmit_func cmd_disable_irq_xmit;
+	hw_xmit_func dat_xmit;
+
+	bus_dat_xmit_func bus_dat_xmit;
+	bus_cmd_xmit_func bus_cmd_xmit;
+
+	struct skw_hw_extra extra;
+	struct skw_fixed_offset rx_desc;
+	struct skw_lmac lmac[SKW_MAX_LMAC_SUPPORT];
+
+	struct {
+		bool enabled;
+		u16 flags;
+	} wow;
+};
+
+struct skw_vif {
+	u16 bitmap;
+	u16 opened_dev;
+	spinlock_t lock;
+	struct skw_iface *iface[SKW_NR_IFACE];
+};
+
+struct skw_work_data {
+	spinlock_t rcu_lock;
+	struct rcu_head *rcu_hdr;
+	struct rcu_head **rcu_tail;
+
+	unsigned long flags;
+	struct sk_buff_head work_list;
+};
+
+struct skw_timer_data {
+	int count;
+	spinlock_t lock;
+	struct list_head list;
+	struct timer_list timer;
+};
+
+struct skw_recovery_data {
+	struct mutex lock;
+	struct skw_recovery_ifdata iface[SKW_NR_IFACE];
+	struct skw_peer *peer[SKW_MAX_LMAC_SUPPORT][SKW_MAX_PEER_SUPPORT];
+};
+
+struct skw_dbg_cmd {
+	u16 id, seq;
+	u32 loop;
+	unsigned long flags;
+	u64 trigger, build, xmit, done, ack, assert;
+};
+
+struct skw_dbg_dat {
+	u32 qlen;
+	u32 resv;
+	u64 trigger, done;
+};
+
+struct skw_core {
+	struct sv6160_platform_data *hw_pdata;
+
+	struct sk_buff_head rx_dat_q;
+	atomic_t txqlen_pending;
+
+	atomic_t tx_wake, rx_wake, exit;
+	wait_queue_head_t tx_wait_q, rx_wait_q;
+
+	struct net_device dummy_dev;
+	struct napi_struct napi_rx;
+
+	struct task_struct *rx_thread;
+
+#ifdef CONFIG_SWT6621S_TX_WORKQUEUE
+	struct workqueue_struct *tx_wq;
+	struct delayed_work tx_worker;
+
+	//struct workqueue_struct *rx_wq;
+	//struct work_struct rx_worker;
+#else
+	struct task_struct *tx_thread, *rx_thread;
+#endif
+
+	/* workqueu for mlme worker and etc. */
+	struct workqueue_struct *event_wq;
+	struct skw_event_work event_work;
+
+	struct work_struct work;
+	struct skw_work_data work_data;
+	struct work_struct work_unlock;
+
+	struct sk_buff_head kfree_skb_qlist;
+	struct work_struct kfree_skb_task;
+
+	struct sk_buff_head skb_recycle_qlist;
+
+	struct work_struct recovery_work;
+	struct skw_recovery_data recovery_data;
+
+	struct mutex lock;
+	struct skw_firmware_info fw;
+	struct skw_hw_info hw;
+	struct skw_vif vif;
+	struct mac_address address[SKW_NR_IFACE];
+
+	unsigned long flags; /* reference SKW_FLAG_FW_ */
+	unsigned long nf_flags;
+
+	u8 country[2];
+	u16 idx;
+	u16 skw_event_sn;
+	int isr_cpu_id;
+
+	u16 nr_scan_results;
+	struct cfg80211_scan_request *scan_req;
+	struct cfg80211_sched_scan_request *sched_scan_req;
+
+	struct notifier_block ifa4_nf;
+	struct notifier_block ifa6_nf;
+	struct notifier_block bsp_nf;
+	struct notifier_block pm_nf;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
+	unsigned int num_iftype_ext_capab;
+	struct wiphy_iftype_ext_capab iftype_ext_cap[NUM_NL80211_IFTYPES];
+#endif
+
+	struct skw_dpd dpd;
+#ifdef CONFIG_SWT6621S_USB3_WORKAROUND
+	struct completion usb_switch_done;
+#endif
+
+	void *sdma_buff, *eof_blk;
+	struct scatterlist *sgl_cmd, *sgl_dat;
+
+	u16 skb_headroom;
+	u16 skb_share_len;
+
+	u8 ext_capa[12];
+	unsigned long trans_start;
+	unsigned long rx_packets;
+	unsigned long tx_packets;
+
+#ifdef CONFIG_HAS_WAKELOCK
+	struct wake_lock rx_wlock;
+#endif
+	spinlock_t rx_lock;
+	struct skw_list rx_todo_list;
+
+	const struct ieee80211_regdomain *regd;
+	struct dentry *dentry;
+	struct proc_dir_entry *pentry;
+
+	struct skw_timer_data timer_data;
+	struct skw_config config;
+
+	struct {
+		struct semaphore lock;
+		struct semaphore mgmt_cmd_lock;
+		wait_queue_head_t wq;
+		struct wakeup_source *ws;
+
+		unsigned long start_time;
+		void (*callback)(struct skw_core *skw);
+
+		unsigned long flags; /* reference SKW_CMD_FLAG_ */
+
+		const char *name;
+		void *data;
+		void *arg;
+
+		u16 data_len;
+		u16 arg_size;
+
+		u16 seq;
+		u16 status;
+
+		int id;
+	} cmd;
+
+	struct {
+		struct skw_edma_chn cmd_chn;
+		struct skw_edma_chn short_event_chn;
+		struct skw_edma_chn long_event_chn;
+
+		struct skw_edma_chn tx_chn[SKW_MAX_LMAC_SUPPORT];
+		struct skw_edma_chn tx_resp_chn[SKW_MAX_LMAC_SUPPORT];
+		struct skw_edma_chn rx_chn[SKW_MAX_LMAC_SUPPORT];
+		struct skw_edma_chn rx_req_chn[SKW_MAX_LMAC_SUPPORT];
+		struct skw_edma_chn filter_chn[SKW_MAX_LMAC_SUPPORT];
+	} edma;
+
+	struct {
+		bool fw_enabled;
+		u64 last_pulse_ts;
+		unsigned long flags;
+
+		struct list_head skw_pulse_pool;
+		struct list_head skw_pseq_pool;
+		spinlock_t skw_pool_lock;
+
+		enum nl80211_dfs_regions region;
+		struct cfg80211_chan_def chan;
+		const struct skw_radar_info *info;
+	} dfs;
+
+	struct {
+		atomic_t loop;
+		u8 cmd_idx, dat_idx;
+		u8 nr_cmd, nr_dat;
+		struct skw_dbg_cmd cmd[SKW_DBG_NR_CMD];
+		struct skw_dbg_dat dat[SKW_DBG_NR_DAT];
+	} dbg;
+};
+
+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
+
+struct skw_calib_param {
+	u8 seq;
+	u8 end;
+	u16 len;
+	u8 data[512];
+} __packed;
+
+#define SKW_WIPHY_DENTRY(w) (((struct skw_core *)wiphy_priv(w))->dentry)
+#define SKW_WIPHY_PENTRY(w) (((struct skw_core *)wiphy_priv(w))->pentry)
+
+static inline int skw_wifi_enable(void *pdata)
+{
+	struct sv6160_platform_data *pd = pdata;
+
+	if (pd && pd->service_start)
+		return pd->service_start();
+
+	return -ENOTSUPP;
+}
+
+static inline int skw_wifi_disable(void *pdata)
+{
+	struct sv6160_platform_data *pd = pdata;
+
+	if (pd && pd->service_stop)
+		return pd->service_stop();
+
+	return -ENOTSUPP;
+}
+
+static inline int skw_power_on_chip(void)
+{
+	return 0;
+}
+
+static inline int skw_power_off_chip(void)
+{
+	return 0;
+}
+
+static inline int skw_hw_reset_chip(struct skw_core *skw)
+{
+	return 0;
+}
+
+static inline int skw_hw_get_chip_id(struct skw_core *skw)
+{
+	return 0;
+}
+
+static inline struct skw_tx_cb *SKW_SKB_TXCB(struct sk_buff *skb)
+{
+	return (struct skw_tx_cb *)skb->cb;
+}
+
+void skw_dbg_dump(struct skw_core *skw);
+static inline int skw_hw_assert(struct skw_core *skw, bool dump)
+{
+	if (test_and_set_bit(SKW_FLAG_FW_ASSERT, &skw->flags))
+		return 0;
+
+	if (dump)
+		skw_dbg_dump(skw);
+
+	if (skw->hw_pdata->modem_assert)
+		skw->hw_pdata->modem_assert();
+
+	return 0;
+}
+
+static inline int skw_hw_request_ack(struct skw_core *skw)
+{
+	if (!skw->hw_pdata || !skw->hw_pdata->rx_thread_wakeup)
+		return -ENOTSUPP;
+
+	skw->hw_pdata->rx_thread_wakeup();
+
+	return 0;
+}
+
+static inline int skw_register_rx_cb(struct skw_core *skw, int port,
+			      rx_submit_fn rx_cb, void *data)
+{
+	if (!skw->hw_pdata || !skw->hw_pdata->callback_register)
+		return -ENOTSUPP;
+
+	return skw->hw_pdata->callback_register(port, (void *)rx_cb, data);
+}
+
+static inline int skw_register_tx_cb(struct skw_core *skw, int port,
+			      rx_submit_fn tx_cb, void *data)
+{
+	if (!skw->hw_pdata || !skw->hw_pdata->tx_callback_register)
+		return -ENOTSUPP;
+
+	return skw->hw_pdata->tx_callback_register(port, (void *)tx_cb, data);
+}
+
+static inline bool skw_need_extra_hdr(struct skw_core *skw)
+{
+	return test_bit(SKW_HW_FLAG_EXTRA_HDR, &skw->hw.flags);
+}
+
+static inline void skw_set_extra_hdr(struct skw_core *skw, void *extra_hdr,
+				u8 chn, u16 len, u16 pad, u8 eof)
+{
+	u32 *hdr = extra_hdr;
+	struct skw_hw_extra *ext = &skw->hw.extra;
+
+	*hdr = chn << ext->chn_offset |
+	       (len - ext->hdr_len) << ext->len_offset |
+	       (!!eof) << ext->eof_offset;
+}
+
+static inline int skw_uart_open(struct skw_core *skw)
+{
+	u8 port;
+
+	if (!skw->hw_pdata || !skw->hw_pdata->at_ops.open)
+		return -ENOTSUPP;
+
+	if (test_bit(SKW_FLAG_FW_UART_OPEND, &skw->flags))
+		return 0;
+
+	port = skw->hw_pdata->at_ops.port;
+
+	set_bit(SKW_FLAG_FW_UART_OPEND, &skw->flags);
+
+	return skw->hw_pdata->at_ops.open(port, NULL, NULL);
+}
+
+static inline int skw_uart_write(struct skw_core *skw, char *cmd, int len)
+{
+	u8 port;
+
+	if (!skw->hw_pdata || !skw->hw_pdata->at_ops.write)
+		return -ENOTSUPP;
+
+	port = skw->hw_pdata->at_ops.port;
+
+	return skw->hw_pdata->at_ops.write(port, cmd, len);
+}
+
+static inline int skw_uart_read(struct skw_core *skw, char *buf, int buf_len)
+{
+	u8 port;
+
+	if (!skw->hw_pdata || !skw->hw_pdata->at_ops.read)
+		return -ENOTSUPP;
+
+	port = skw->hw_pdata->at_ops.port;
+
+	return skw->hw_pdata->at_ops.read(port, buf, buf_len);
+}
+
+static inline int skw_uart_close(struct skw_core *skw)
+{
+	u8 port;
+
+	if (!skw->hw_pdata || !skw->hw_pdata->at_ops.close)
+		return -ENOTSUPP;
+
+	port = skw->hw_pdata->at_ops.port;
+
+	return skw->hw_pdata->at_ops.close(port);
+}
+
+static inline int skw_register_bsp_notifier(struct skw_core *skw,
+					struct notifier_block *nb)
+{
+	if (!skw->hw_pdata || !skw->hw_pdata->modem_register_notify)
+		return -ENOTSUPP;
+
+	skw->hw_pdata->modem_register_notify(nb);
+
+	return 0;
+}
+
+static inline int skw_unregister_bsp_notifier(struct skw_core *skw,
+					struct notifier_block *nb)
+{
+	if (!skw->hw_pdata || !skw->hw_pdata->modem_unregister_notify)
+		return -ENOTSUPP;
+
+	skw->hw_pdata->modem_unregister_notify(nb);
+
+	return 0;
+}
+
+static inline void skw_wakeup_tx(struct skw_core *skw, unsigned long delay)
+{
+#ifdef CONFIG_SWT6621S_TX_WORKQUEUE
+	mod_delayed_work(skw->tx_wq, &skw->tx_worker, delay);
+#else
+	if (atomic_add_return(1, &skw->tx_wake) == 1)
+		wake_up(&skw->tx_wait_q);
+#endif
+}
+
+static inline void skw_wakeup_rx(struct skw_core *skw)
+{
+	int i;
+
+	//wake_up_process(skw->rx_thread);
+	if (skw->hw.bus == SKW_BUS_PCIE) {
+		for (i = 0; i < skw->hw.nr_lmac; i++)
+			if (skw->hw.lmac->iface_bitmap != 0)
+				napi_schedule(&skw->hw.lmac[i].napi_rx);
+	} else
+		wake_up_process(skw->rx_thread);
+	//napi_schedule(&skw->napi_rx);
+}
+
+static inline struct skw_iface *to_skw_iface(struct skw_core *skw, int id)
+{
+	if (!skw || id & 0xfffffffc)
+		return NULL;
+
+	return skw->vif.iface[id];
+}
+
+static inline void skw_sync_credit(struct skw_core *skw, int lmac_id)
+{
+	int new, done;
+	struct skw_edma_chn *chn;
+	struct skw_lmac *lmac = &skw->hw.lmac[lmac_id];
+
+	if (!skw->hw_pdata || !skw->hw_pdata->edma_get_node_tot_cnt)
+		return;
+
+	chn = &skw->edma.tx_chn[lmac_id];
+	done = skw->hw_pdata->edma_get_node_tot_cnt(SKW_EDMA_WIFI_TX0_CHN+lmac_id);
+	new = chn->max_node_num - atomic_read(&chn->nr_node) - done;
+
+	atomic_add(new, &chn->nr_node);
+	atomic_set(&lmac->fw_credit, atomic_read(&chn->nr_node) * SKW_EDMA_TX_CHN_CREDIT);
+}
+
+static inline int skw_get_hw_credit(struct skw_core *skw, int lmac_id)
+{
+	struct skw_lmac *lmac = &skw->hw.lmac[lmac_id];
+
+#if 0
+	if (!(lmac->flags & SKW_LMAC_FLAG_ACTIVED))
+		return 0;
+#endif
+	if (test_bit(SKW_FLAG_FW_IGNORE_CRED, &skw->flags))
+		return INT_MAX;
+
+	//for test
+	if (tx_wait_time == 99)
+		return INT_MAX;
+
+	if (skw->hw.bus == SKW_BUS_PCIE)
+		skw_sync_credit(skw, lmac_id);
+
+	return atomic_read(&lmac->fw_credit);
+}
+
+static inline void skw_set_trans_start(struct net_device *dev)
+{
+	unsigned int i;
+
+	for (i = 0; i < dev->num_tx_queues; i++)
+		netdev_get_tx_queue(dev, i)->trans_start = jiffies;
+}
+
+static inline void skw_start_dev_queue(struct skw_core *skw)
+{
+	int i;
+	struct skw_iface *iface;
+
+	for (i = 0; i < SKW_NR_IFACE; i++) {
+		iface = skw->vif.iface[i];
+		if (!iface || !iface->ndev)
+			continue;
+		if (iface->ndev->flags & IFF_UP) {
+			netif_tx_start_all_queues(iface->ndev);
+			skw_set_trans_start(iface->ndev);
+			netif_tx_schedule_all(iface->ndev);
+		}
+	}
+}
+
+static inline void skw_stop_dev_queue(struct skw_core *skw)
+{
+	int i;
+	struct skw_iface *iface;
+
+	for (i = 0; i < SKW_NR_IFACE; i++) {
+		iface = skw->vif.iface[i];
+		if (!iface || !iface->ndev)
+			continue;
+		if (iface->ndev->flags & IFF_UP) {
+			netif_tx_stop_all_queues(iface->ndev);
+			smp_mb();
+		}
+	}
+}
+
+static inline void skw_sub_credit(struct skw_core *skw, int lmac_id, int used)
+{
+	smp_rmb();
+	atomic_sub(used, &skw->hw.lmac[lmac_id].fw_credit);
+}
+
+static inline bool is_skw_local_addr6(struct in6_addr *addr)
+{
+	return ipv6_addr_type(addr) &
+		(IPV6_ADDR_LINKLOCAL | IPV6_ADDR_LOOPBACK);
+}
+
+static inline dma_addr_t skw_pci_map_single(struct skw_core *skw, void *ptr,
+			size_t size, int direction)
+{
+	struct device *dev = priv_to_wiphy(skw)->dev.parent;
+
+	return dma_map_single(dev, ptr, size, direction);
+}
+
+static inline void skw_pci_unmap_single(struct skw_core *skw,
+		dma_addr_t dma_addr, size_t size, int direction)
+{
+	struct device *dev = priv_to_wiphy(skw)->dev.parent;
+
+	return dma_unmap_single(dev, dma_addr, size, direction);
+}
+
+static inline int
+skw_pcie_mapping_error(struct skw_core *skw, dma_addr_t dma_addr)
+{
+	struct device *dev = priv_to_wiphy(skw)->dev.parent;
+
+	return dma_mapping_error(dev, dma_addr);
+}
+
+static inline bool skw_lmac_is_actived(struct skw_core *skw, int lmac_id)
+{
+	return (skw->hw.lmac[lmac_id].flags & SKW_LMAC_FLAG_ACTIVED);
+}
+
+static inline const char *skw_bus_name(int bus)
+{
+	static const char name[][8] = {"sdio", "usb", "pcie", "null"};
+
+	return name[bus & 0x3];
+}
+
+struct skw_peer_ctx *skw_get_ctx(struct skw_core *skw, u8 lmac_id, u8 idx);
+int skw_lmac_bind_iface(struct skw_core *skw, struct skw_iface *iface, int lmac_id);
+int skw_lmac_unbind_iface(struct skw_core *skw, int lmac_id, int iface_id);
+int skw_netdev_init(struct wiphy *wiphy, struct net_device *ndev, u8 *addr);
+void skw_netdev_deinit(struct net_device *ndev);
+void skw_add_credit(struct skw_core *skw, int lmac_id, int cred);
+int skw_sync_chip_info(struct wiphy *wiphy, struct skw_chip_info *chip);
+int skw_sync_cmd_event_version(struct wiphy *wiphy);
+void skw_get_dev_ip(struct net_device *ndev);
+void skw_set_ip_to_fw(struct wiphy *wiphy, struct net_device *ndev);
+int skw_calib_download(struct wiphy *wiphy, const char *fname);
+struct skw_ctx_entry *skw_get_ctx_entry(struct skw_core *skw, const u8 *addr);
+
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_db.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_db.c
new file mode 100755
index 0000000..a299346
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_db.c
@@ -0,0 +1,3417 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+/*
+ * DO NOT EDIT -- file generated from data in db.txt
+ */
+
+#include <linux/nl80211.h>
+#include <net/cfg80211.h>
+#include "skw_db.h"
+
+// database 2024.10.07
+
+static const struct ieee80211_regdomain regdom_XW = {
+	.alpha2 = "XW",
+	.reg_rules = {
+		REG_RULE_EXT(2402, 2472, 40, 0, 20, 0, 0),
+		REG_RULE_EXT(2457, 2482, 20, 0, 20, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(2474, 2494, 20, 0, 20, 0,
+			SKW_RRF_NO_OFDM | 0),
+		REG_RULE_EXT(5170, 5250, 80, 0, 20, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 20, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 20, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(57240, 63720, 2160, 0, 0, 0, 0),
+	},
+	.n_reg_rules = 8
+};
+
+static const struct ieee80211_regdomain regdom_00 = {
+	.alpha2 = "00",
+	.reg_rules = {
+		REG_RULE_EXT(755, 928, 2, 0, 20, 0,
+			SKW_RRF_NO_IR | 0),
+		REG_RULE_EXT(2402, 2472, 40, 0, 20, 0, 0),
+		REG_RULE_EXT(2457, 2482, 20, 0, 20, 0,
+			SKW_RRF_NO_IR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(2474, 2494, 20, 0, 20, 0,
+			SKW_RRF_NO_IR |
+			SKW_RRF_NO_OFDM | 0),
+		REG_RULE_EXT(5170, 5250, 80, 0, 20, 0,
+			SKW_RRF_NO_IR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_NO_IR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 20, 0,
+			SKW_RRF_NO_IR |
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 20, 0,
+			SKW_RRF_NO_IR | 0),
+		REG_RULE_EXT(57240, 63720, 2160, 0, 0, 0, 0),
+	},
+	.n_reg_rules = 9
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_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 = 6
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 24, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 6
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+	},
+	.n_reg_rules = 5
+};
+
+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,
+			SKW_RRF_DFS | 0),
+	},
+	.n_reg_rules = 3
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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(5170, 5250, 80, 0, 17, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 7125, 320, 0, 12, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 6
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+static const struct ieee80211_regdomain regdom_AU = {
+	.alpha2 = "AU",
+	.dfs_region = NL80211_DFS_ETSI,
+	.reg_rules = {
+		REG_RULE_EXT(915, 920, 4, 0, 30, 0, 0),
+		REG_RULE_EXT(920, 928, 8, 0, 30, 0, 0),
+		REG_RULE_EXT(2400, 2483, 40, 0, 36, 0, 0),
+		REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW |
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5470, 5600, 80, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5650, 5730, 80, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5730, 5850, 80, 0, 36, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5850, 5875, 20, 0, 14, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5925, 6425, 160, 0, 24, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 71000, 2160, 0, 43, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 11
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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(2402, 2482, 40, 0, 20, 0, 0),
+		REG_RULE_EXT(5170, 5250, 80, 0, 18, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 18, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+	},
+	.n_reg_rules = 3
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_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 = 6
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+	},
+	.n_reg_rules = 4
+};
+
+static const struct ieee80211_regdomain regdom_BD = {
+	.alpha2 = "BD",
+	.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_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+static const struct ieee80211_regdomain regdom_BH = {
+	.alpha2 = "BH",
+	.dfs_region = NL80211_DFS_ETSI,
+	.reg_rules = {
+		REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+		REG_RULE_EXT(5150, 5350, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5470, 5725, 80, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 6
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_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,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 27, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5725, 5850, 80, 0, 30, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5925, 7125, 320, 0, 12, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+	},
+	.n_reg_rules = 4
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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(2402, 2472, 40, 0, 30, 0, 0),
+		REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5600, 80, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5650, 5730, 80, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 7125, 320, 0, 12, 0,
+			SKW_RRF_NO_OUTDOOR | 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,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5490, 5730, 40, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 71000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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_JP,
+	.reg_rules = {
+		REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+		REG_RULE_EXT(5170, 5250, 80, 0, 20, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 20, 0, 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 12, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 5
+};
+
+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, 5250, 80, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 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 = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 7125, 320, 0, 12, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 6
+};
+
+static const struct ieee80211_regdomain regdom_CR = {
+	.alpha2 = "CR",
+	.dfs_region = NL80211_DFS_FCC,
+	.reg_rules = {
+		REG_RULE_EXT(2402, 2482, 40, 0, 36, 0, 0),
+		REG_RULE_EXT(5170, 5250, 20, 0, 30, 0, 0),
+		REG_RULE_EXT(5250, 5330, 20, 0, 30, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5490, 5730, 20, 0, 30, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 20, 0, 36, 0, 0),
+		REG_RULE_EXT(5875, 5925, 20, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 7125, 320, 0, 30, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_NO_IR |
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5470, 5725, 80, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 7125, 320, 0, 15, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 5
+};
+
+static const struct ieee80211_regdomain regdom_DZ = {
+	.alpha2 = "DZ",
+	.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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5670, 160, 0, 23, 0,
+			SKW_RRF_DFS | 0),
+	},
+	.n_reg_rules = 4
+};
+
+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,
+			SKW_RRF_AUTO_BW |
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 21, 0,
+			SKW_RRF_AUTO_BW |
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 21, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 71000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5730, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5850, 80, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5925, 6425, 160, 0, 24, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 71000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 18, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 4
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+	},
+	.n_reg_rules = 4
+};
+
+static const struct ieee80211_regdomain regdom_GH = {
+	.alpha2 = "GH",
+	.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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+	},
+	.n_reg_rules = 5
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+static const struct ieee80211_regdomain regdom_GT = {
+	.alpha2 = "GT",
+	.reg_rules = {
+		REG_RULE_EXT(2400, 2483, 40, 0, 27, 0, 0),
+		REG_RULE_EXT(5150, 5350, 80, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 24, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5725, 5850, 160, 0, 27, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(6425, 6525, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(6525, 6875, 320, 0, 22, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(6875, 7125, 320, 0, 22, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 13, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 9
+};
+
+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,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5490, 5730, 20, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 23, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW |
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW |
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5470, 5730, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 12, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_NO_IR | 0),
+		REG_RULE_EXT(57240, 71000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5150, 5350, 80, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5725, 5825, 80, 0, 23, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+static const struct ieee80211_regdomain regdom_IL = {
+	.alpha2 = "IL",
+	.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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5945, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 6
+};
+
+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,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 30, 0, 0),
+	},
+	.n_reg_rules = 5
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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_ETSI,
+	.reg_rules = {
+		REG_RULE_EXT(2400, 2483, 40, 0, 20, 0, 0),
+		REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 80, 0, 27, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 71000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_NO_OFDM | 0),
+		REG_RULE_EXT(4910, 4990, 40, 0, 23, 0, 0),
+		REG_RULE_EXT(5170, 5250, 80, 0, 20, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 23, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 23, 0,
+			SKW_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, 33, 0, 0),
+		REG_RULE_EXT(5150, 5250, 80, 0, 17, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 17, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 80, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 40, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 6
+};
+
+static const struct ieee80211_regdomain regdom_KH = {
+	.alpha2 = "KH",
+	.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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+	},
+	.n_reg_rules = 4
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 30, 0,
+			SKW_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,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5490, 5630, 20, 0, 30, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5230, 5250, 20, 0, 17, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 20, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5850, 80, 0, 23, 0, 0),
+		REG_RULE_EXT(5925, 7125, 160, 0, 15, 0,
+			SKW_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(2400, 2483, 40, 0, 20, 0, 0),
+		REG_RULE_EXT(5170, 5250, 80, 0, 23, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 17, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5825, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 5
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5850, 80, 0, 20, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 30, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5490, 5730, 20, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 4
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+	},
+	.n_reg_rules = 5
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+	},
+	.n_reg_rules = 5
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+	},
+	.n_reg_rules = 5
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_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 = 6
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 100, 0, 0),
+	},
+	.n_reg_rules = 6
+};
+
+static const struct ieee80211_regdomain regdom_MO = {
+	.alpha2 = "MO",
+	.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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5730, 160, 0, 30, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5850, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 24, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5945, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 6
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5725, 5850, 80, 0, 20, 0, 0),
+	},
+	.n_reg_rules = 4
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 12, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 6
+};
+
+static const struct ieee80211_regdomain regdom_MY = {
+	.alpha2 = "MY",
+	.dfs_region = NL80211_DFS_FCC,
+	.reg_rules = {
+		REG_RULE_EXT(2402, 2482, 40, 0, 27, 0, 0),
+		REG_RULE_EXT(5170, 5250, 80, 0, 30, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 30, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5650, 160, 0, 30, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 6
+};
+
+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, 20, 0,
+			SKW_RRF_AUTO_BW |
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW |
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 21, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 7
+};
+
+static const struct ieee80211_regdomain regdom_NG = {
+	.alpha2 = "NG",
+	.dfs_region = NL80211_DFS_ETSI,
+	.reg_rules = {
+		REG_RULE_EXT(2402, 2482, 40, 0, 20, 0, 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 30, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+	},
+	.n_reg_rules = 3
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 71000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_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(2400, 2483, 40, 0, 36, 0, 0),
+		REG_RULE_EXT(5150, 5250, 80, 0, 30, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 27, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5730, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 36, 0, 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 24, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 71000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 30, 0,
+			SKW_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
+};
+
+static const struct ieee80211_regdomain regdom_PE = {
+	.alpha2 = "PE",
+	.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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 7125, 320, 0, 12, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 6
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+	},
+	.n_reg_rules = 5
+};
+
+static const struct ieee80211_regdomain regdom_PH = {
+	.alpha2 = "PH",
+	.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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5850, 80, 0, 24, 0, 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 24, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 24, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+static const struct ieee80211_regdomain regdom_PK = {
+	.alpha2 = "PK",
+	.reg_rules = {
+		REG_RULE_EXT(2400, 2500, 40, 0, 30, 0, 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5945, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 3
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5850, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5850, 5875, 80, 0, 24, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 8
+};
+
+static const struct ieee80211_regdomain regdom_RU = {
+	.alpha2 = "RU",
+	.reg_rules = {
+		REG_RULE_EXT(2400, 2483, 40, 0, 100, 0, 0),
+		REG_RULE_EXT(5150, 5350, 160, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5650, 5850, 160, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5925, 6425, 160, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 5
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5925, 7125, 320, 0, 24, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 5
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5730, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5850, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5945, 6425, 320, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5875, 80, 0, 14, 0, 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 7
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 20, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 7125, 320, 0, 12, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 5
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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(2400, 2483, 40, 0, 20, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5150, 5350, 80, 0, 20, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5470, 5850, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 0, 0, 0, 0, 0),
+	},
+	.n_reg_rules = 5
+};
+
+static const struct ieee80211_regdomain regdom_TH = {
+	.alpha2 = "TH",
+	.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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 24, 0,
+			SKW_RRF_NO_OUTDOOR | 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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_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,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5945, 6425, 160, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 6
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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(5150, 5250, 80, 0, 23, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5730, 160, 0, 23, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5850, 80, 0, 30, 0, 0),
+		REG_RULE_EXT(5945, 6425, 320, 0, 23, 0,
+			SKW_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",
+	.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,
+			SKW_RRF_AUTO_BW |
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW |
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5725, 5850, 80, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5945, 6425, 320, 0, 23, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+	},
+	.n_reg_rules = 6
+};
+
+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,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5150, 5250, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5725, 160, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(5725, 5850, 80, 0, 20, 0,
+			SKW_RRF_NO_OUTDOOR | 0),
+		REG_RULE_EXT(57000, 66000, 2160, 0, 16, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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 = {
+		REG_RULE_EXT(902, 904, 2, 0, 30, 0, 0),
+		REG_RULE_EXT(904, 920, 16, 0, 30, 0, 0),
+		REG_RULE_EXT(920, 928, 8, 0, 30, 0, 0),
+		REG_RULE_EXT(2400, 2472, 40, 0, 30, 0, 0),
+		REG_RULE_EXT(5150, 5250, 80, 0, 23, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5350, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5470, 5730, 160, 0, 24, 0,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5730, 5850, 80, 0, 30, 0,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5850, 5895, 40, 0, 27, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_AUTO_BW |
+			SKW_RRF_NO_IR | 0),
+		REG_RULE_EXT(5925, 7125, 320, 0, 12, 0,
+			SKW_RRF_NO_OUTDOOR |
+			SKW_RRF_NO_IR | 0),
+		REG_RULE_EXT(57240, 71000, 2160, 0, 40, 0, 0),
+	},
+	.n_reg_rules = 11
+};
+
+static const struct ieee80211_regdomain regdom_UY = {
+	.alpha2 = "UY",
+	.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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5735, 5835, 80, 0, 30, 0, 0),
+	},
+	.n_reg_rules = 4
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 23, 0,
+			SKW_RRF_DFS |
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5490, 5730, 80, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 24, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5730, 160, 0, 24, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_DFS | 0),
+		REG_RULE_EXT(5490, 5710, 40, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+	},
+	.n_reg_rules = 4
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 30, 0, 0),
+		REG_RULE_EXT(5925, 6425, 320, 0, 14, 0, 0),
+	},
+	.n_reg_rules = 5
+};
+
+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,
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5250, 5330, 80, 0, 20, 0,
+			SKW_RRF_DFS |
+			SKW_RRF_AUTO_BW | 0),
+		REG_RULE_EXT(5490, 5710, 160, 0, 27, 0,
+			SKW_RRF_DFS | 0),
+	},
+	.n_reg_rules = 4
+};
+
+const struct ieee80211_regdomain *skw_regdb[] = {
+	&regdom_XW,
+	&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_CU,
+	&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_MV,
+	&regdom_MW,
+	&regdom_MX,
+	&regdom_MY,
+	&regdom_NA,
+	&regdom_NG,
+	&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_TZ,
+	&regdom_UA,
+	&regdom_UG,
+	&regdom_US,
+	&regdom_UY,
+	&regdom_UZ,
+	&regdom_VC,
+	&regdom_VE,
+	&regdom_VI,
+	&regdom_VN,
+	&regdom_VU,
+	&regdom_WF,
+	&regdom_WS,
+	&regdom_YE,
+	&regdom_YT,
+	&regdom_ZA,
+	&regdom_ZW,
+};
+
+int skw_regdb_size = ARRAY_SIZE(skw_regdb);
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_db.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_db.h
new file mode 100755
index 0000000..9d5d0d8
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_db.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_DB_H__
+#define __SKW_DB_H__
+
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0)
+#define REG_RULE_EXT(start, end, bw, gain, eirp, dfs_cac, reg_flags) \
+	REG_RULE(start, end, bw, gain, eirp, reg_flags)
+#endif
+
+#define SKW_RRF_NO_OFDM                 BIT(0)
+#define SKW_RRF_NO_OUTDOOR              BIT(3)
+#define SKW_RRF_DFS                     BIT(4)
+#define SKW_RRF_NO_IR                   BIT(7)
+#define SKW_RRF_AUTO_BW                 BIT(11)
+
+extern int skw_regdb_size;
+extern const struct ieee80211_regdomain *skw_regdb[];
+
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dentry.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dentry.c
new file mode 100755
index 0000000..708329f
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dentry.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <generated/utsrelease.h>
+#include "skw_core.h"
+#include "skw_dentry.h"
+#include "skw_compat.h"
+#include "version.h"
+
+static struct dentry *skw_debugfs_root;
+static struct proc_dir_entry *skw_proc_root;
+
+
+static int skw_proc_show(struct seq_file *seq, void *v)
+{
+#define SKW_CONFIG_INT(conf)    seq_printf(seq, "%s=%d\n", #conf, conf)
+#define SKW_CONFIG_STRING(conf) seq_printf(seq, "%s=\"%s\"\n", #conf, conf)
+
+#define SKW_CONFIG_BOOL(conf)                                         \
+	do {                                                          \
+		if (IS_ENABLED(conf))                                 \
+			seq_printf(seq, "%s=y\n", #conf);             \
+		else                                                  \
+			seq_printf(seq, "# %s is not set\n", #conf);  \
+	} while (0)
+
+
+	seq_puts(seq, "\n");
+	seq_printf(seq, "Kernel Version:  \t%s\n"
+			"Wi-Fi Driver:    \t%s\n"
+			"Wi-Fi Branch:    \t%s\n",
+			UTS_RELEASE,
+			SKW_VERSION,
+			SKW_BRANCH);
+
+	seq_puts(seq, "\n");
+
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_STA_SME_EXT);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_SAP_SME_EXT);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_SCAN_RANDOM_MAC);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_LEGACY_P2P);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_TX_WORKQUEUE);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_HIGH_PRIORITY);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_VENDOR);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_REGD_SELF_MANAGED);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_TDLS);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_DFS_MASTER);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_REPEATER_MODE);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_EDMA);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_OFFCHAN_TX);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_CALIB_DPD);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_CALIB_APPEND_BUS_ID);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_6GHZ);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_USB3_WORKAROUND);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_LOG_ERROR);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_LOG_WARN);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_LOG_INFO);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_LOG_DEBUG);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_LOG_DETAIL);
+	SKW_CONFIG_BOOL(CONFIG_SWT6621S_SKB_RECYCLE);
+
+#ifdef CONFIG_SWT6621S_RX_REORDER_TIMEOUT
+	SKW_CONFIG_INT(CONFIG_SWT6621S_RX_REORDER_TIMEOUT);
+#endif
+
+#ifdef CONFIG_SWT6621S_PROJECT_NAME
+	SKW_CONFIG_STRING(CONFIG_SWT6621S_PROJECT_NAME);
+#endif
+
+#ifdef CONFIG_SWT6621S_DEFAULT_COUNTRY
+	SKW_CONFIG_STRING(CONFIG_SWT6621S_DEFAULT_COUNTRY);
+#endif
+
+#ifdef CONFIG_SWT6621S_CHIP_ID
+	SKW_CONFIG_STRING(CONFIG_SWT6621S_CHIP_ID);
+#endif
+
+	seq_puts(seq, "\n");
+
+	return 0;
+}
+
+static int skw_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, skw_proc_show, NULL);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_proc_fops = {
+	.proc_open = skw_proc_open,
+	.proc_read = seq_read,
+	.proc_lseek = seq_lseek,
+	.proc_release = single_release,
+};
+#else
+static const struct file_operations skw_proc_fops = {
+	.open = skw_proc_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+#endif
+
+struct dentry *skw_debugfs_subdir(const char *name, struct dentry *parent)
+{
+	struct dentry *de, *pentry;
+
+	pentry = parent ? parent : skw_debugfs_root;
+	if (!pentry)
+		return NULL;
+
+	de = debugfs_create_dir(name, pentry);
+
+	return IS_ERR(de) ? NULL : de;
+}
+
+struct dentry *skw_debugfs_file(struct dentry *parent,
+				const char *name, umode_t mode,
+				const struct file_operations *fops, void *data)
+{
+	struct dentry *de, *pentry;
+
+	pentry = parent ? parent : skw_debugfs_root;
+	if (!pentry)
+		return NULL;
+
+	de = debugfs_create_file(name, mode, pentry, data, fops);
+
+	return IS_ERR(de) ? NULL : de;
+}
+
+struct proc_dir_entry *skw_procfs_subdir(const char *name,
+				struct proc_dir_entry *parent)
+{
+	struct proc_dir_entry *dentry = parent ? parent : skw_proc_root;
+
+	if (!dentry)
+		return NULL;
+
+	return proc_mkdir_data(name, 0, dentry, NULL);
+}
+
+struct proc_dir_entry *skw_procfs_file(struct proc_dir_entry *parent,
+				       const char *name, umode_t mode,
+				       const void *fops, void *data)
+{
+	struct proc_dir_entry *dentry = parent ? parent : skw_proc_root;
+
+	if (!dentry)
+		return NULL;
+
+	return proc_create_data(name, mode, dentry, fops, data);
+}
+
+int skw_dentry_init(void)
+{
+	skw_proc_root = proc_mkdir("skwifid", NULL);
+	if (!skw_proc_root)
+		pr_err("creat proc skwifid failed\n");
+
+	skw_procfs_file(skw_proc_root, "profile", 0, &skw_proc_fops, NULL);
+
+	skw_debugfs_root = debugfs_create_dir("skwifid", NULL);
+	if (IS_ERR(skw_debugfs_root))
+		skw_debugfs_root = NULL;
+
+	return 0;
+}
+
+void skw_dentry_deinit(void)
+{
+	debugfs_remove_recursive(skw_debugfs_root);
+	proc_remove(skw_proc_root);
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dentry.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dentry.h
new file mode 100755
index 0000000..d06dd12
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dentry.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_DENTRY_H__
+#define __SKW_DENTRY_H__
+
+#include <linux/fs.h>
+#include <linux/debugfs.h>
+#include <linux/proc_fs.h>
+
+static inline void  skw_remove_debugfs(struct dentry *dentry)
+{
+	debugfs_remove(dentry);
+}
+
+struct dentry *skw_debugfs_subdir(const char *name, struct dentry *parent);
+struct dentry *skw_debugfs_file(struct dentry *parent,
+				const char *name, umode_t mode,
+				const struct file_operations *fops, void *data);
+struct proc_dir_entry *skw_procfs_subdir(const char *name,
+				struct proc_dir_entry *parent);
+struct proc_dir_entry *skw_procfs_file(struct proc_dir_entry *parent,
+				       const char *name, umode_t mode,
+				       const void *proc_fops, void *data);
+int skw_dentry_init(void);
+void skw_dentry_deinit(void);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dfs.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dfs.c
new file mode 100755
index 0000000..f7db0bd
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dfs.c
@@ -0,0 +1,729 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+// #include <linux/kernel.h>
+#include <net/cfg80211.h>
+
+#include "skw_dfs.h"
+#include "skw_util.h"
+#include "skw_cfg80211.h"
+
+#define SKW_MIN_PPB_THRESH            50
+#define SKW_PPB_THRESH_RATE(PPB, RATE) ((PPB * RATE + 100 - RATE) / 100)
+#define SKW_PPB_THRESH(PPB) SKW_PPB_THRESH_RATE(PPB, SKW_MIN_PPB_THRESH)
+#define SKW_PRF2PRI(PRF) ((1000000 + PRF / 2) / PRF)
+
+/* percentage of pulse width tolerance */
+#define SKW_WIDTH_TOLERANCE           5
+#define SKW_WIDTH_LOWER(W) ((W * (100 - SKW_WIDTH_TOLERANCE) + 50) / 100)
+#define SKW_WIDTH_UPPER(W) ((W * (100 + SKW_WIDTH_TOLERANCE) + 50) / 100)
+
+#define SKW_ETSI_PATTERN(ID, WMIN, WMAX, PMIN, PMAX, PRF, PPB, CHIRP)        \
+{                                                                            \
+	.rule = {                                                            \
+		.type_id = ID,                                               \
+		.width_min = SKW_WIDTH_LOWER(WMIN),                          \
+		.width_max = SKW_WIDTH_UPPER(WMAX),                          \
+		.pri_min = (SKW_PRF2PRI(PMAX) - SKW_PRI_TOLERANCE),          \
+		.pri_max = (SKW_PRF2PRI(PMIN) * PRF + SKW_PRI_TOLERANCE),    \
+		.nr_pri = PRF,                                               \
+		.ppb = PPB * PRF,                                            \
+		.ppb_thresh = SKW_PPB_THRESH(PPB),                           \
+		.max_pri_tolerance = SKW_PRI_TOLERANCE,                      \
+		.chirp = CHIRP                                               \
+	}                                                                    \
+}
+
+/* radar types as defined by ETSI EN-301-893 v1.5.1 */
+static struct skw_radar_cfg skw_radar_etsi_cfgs[] = {
+	SKW_ETSI_PATTERN(0,  0,  1,  700,  700, 1, 18, false),
+	SKW_ETSI_PATTERN(1,  0,  5,  200, 1000, 1, 10, false),
+	SKW_ETSI_PATTERN(2,  0, 15,  200, 1600, 1, 15, false),
+	SKW_ETSI_PATTERN(3,  0, 15, 2300, 4000, 1, 25, false),
+	SKW_ETSI_PATTERN(4, 20, 30, 2000, 4000, 1, 20, false),
+	SKW_ETSI_PATTERN(5,  0,  2,  300,  400, 3, 10, false),
+	SKW_ETSI_PATTERN(6,  0,  2,  400, 1200, 3, 15, false),
+};
+
+#define SKW_FCC_PATTERN(ID, WMIN, WMAX, PMIN, PMAX, PRF, PPB, CHIRP)         \
+{                                                                            \
+	.rule = {                                                            \
+		.type_id = ID,                                               \
+		.width_min = SKW_WIDTH_LOWER(WMIN),                          \
+		.width_max = SKW_WIDTH_UPPER(WMAX),                          \
+		.pri_min = PMIN - SKW_PRI_TOLERANCE,                         \
+		.pri_max = PMAX * PRF + SKW_PRI_TOLERANCE,                   \
+		.nr_pri = PRF,                                               \
+		.ppb = PPB * PRF,                                            \
+		.ppb_thresh = SKW_PPB_THRESH(PPB),                           \
+		.max_pri_tolerance = SKW_PRI_TOLERANCE,                      \
+		.chirp = CHIRP                                               \
+	}                                                                    \
+}
+
+static struct skw_radar_cfg skw_radar_fcc_cfgs[] = {
+	SKW_FCC_PATTERN(0, 0, 1, 1428, 1428, 1, 18, false),
+	SKW_FCC_PATTERN(101, 0, 1, 518, 938, 1, 57, false),
+	SKW_FCC_PATTERN(102, 0, 1, 938, 2000, 1, 27, false),
+	SKW_FCC_PATTERN(103, 0, 1, 2000, 3066, 1, 18, false),
+	SKW_FCC_PATTERN(2, 0, 5, 150, 230, 1, 23, false),
+	SKW_FCC_PATTERN(3, 6, 10, 200, 500, 1, 16, false),
+	SKW_FCC_PATTERN(4, 11, 20, 200, 500, 1, 12, false),
+	SKW_FCC_PATTERN(5, 50, 100, 1000, 2000, 1, 1, true),
+	SKW_FCC_PATTERN(6, 0, 1, 333, 333, 1, 9, false),
+};
+
+#define SKW_JP_PATTERN(ID, WMIN, WMAX, PMIN, PMAX, PRF, PPB, RATE, CHIRP)    \
+{                                                                            \
+	.rule = {                                                            \
+		.type_id = ID,                                               \
+		.width_min = SKW_WIDTH_LOWER(WMIN),                          \
+		.width_max = SKW_WIDTH_UPPER(WMAX),                          \
+		.pri_min = PMIN - SKW_PRI_TOLERANCE,                         \
+		.pri_max = PMAX * PRF + SKW_PRI_TOLERANCE,                   \
+		.nr_pri = PRF,                                               \
+		.ppb = PPB * PRF,                                            \
+		.ppb_thresh = SKW_PPB_THRESH_RATE(PPB, RATE),                \
+		.max_pri_tolerance = SKW_PRI_TOLERANCE,                      \
+		.chirp = CHIRP,                                              \
+	}                                                                    \
+}
+
+static struct skw_radar_cfg skw_radar_jp_cfgs[] = {
+	SKW_JP_PATTERN(0, 0, 1, 1428, 1428, 1, 18, 29, false),
+	SKW_JP_PATTERN(1, 2, 3, 3846, 3846, 1, 18, 29, false),
+	SKW_JP_PATTERN(2, 0, 1, 1388, 1388, 1, 18, 50, false),
+	SKW_JP_PATTERN(3, 0, 4, 4000, 4000, 1, 18, 50, false),
+	SKW_JP_PATTERN(4, 0, 5, 150, 230, 1, 23, 50, false),
+	SKW_JP_PATTERN(5, 6, 10, 200, 500, 1, 16, 50, false),
+	SKW_JP_PATTERN(6, 11, 20, 200, 500, 1, 12, 50, false),
+	SKW_JP_PATTERN(7, 50, 100, 1000, 2000, 1, 3, 50, true),
+	SKW_JP_PATTERN(5, 0, 1, 333, 333, 1, 9, 50, false),
+};
+
+static const struct skw_radar_info skw_radar_infos[] = {
+	[NL80211_DFS_UNSET] = {
+		.nr_cfg = 0,
+		.cfgs = NULL,
+	},
+	[NL80211_DFS_FCC] = {
+		.nr_cfg = ARRAY_SIZE(skw_radar_fcc_cfgs),
+		.cfgs = skw_radar_fcc_cfgs,
+	},
+	[NL80211_DFS_ETSI] = {
+		.nr_cfg = ARRAY_SIZE(skw_radar_etsi_cfgs),
+		.cfgs = skw_radar_etsi_cfgs,
+	},
+	[NL80211_DFS_JP] = {
+		.nr_cfg = ARRAY_SIZE(skw_radar_jp_cfgs),
+		.cfgs = skw_radar_jp_cfgs,
+	},
+};
+
+static void pool_put_pseq_elem(struct skw_core *skw, struct skw_pri_sequence *pse)
+{
+	spin_lock_bh(&skw->dfs.skw_pool_lock);
+
+	list_add(&pse->head, &skw->dfs.skw_pseq_pool);
+
+	spin_unlock_bh(&skw->dfs.skw_pool_lock);
+}
+
+static void pool_put_pulse_elem(struct skw_core *skw, struct skw_pulse_elem *pe)
+{
+	spin_lock_bh(&skw->dfs.skw_pool_lock);
+
+	list_add(&pe->head, &skw->dfs.skw_pulse_pool);
+
+	spin_unlock_bh(&skw->dfs.skw_pool_lock);
+}
+
+static struct skw_pulse_elem *pool_get_pulse_elem(struct skw_core *skw)
+{
+	struct skw_pulse_elem *pe = NULL;
+
+	spin_lock_bh(&skw->dfs.skw_pool_lock);
+
+	if (!list_empty(&skw->dfs.skw_pulse_pool)) {
+		pe = list_first_entry(&skw->dfs.skw_pulse_pool, struct skw_pulse_elem, head);
+		list_del(&pe->head);
+	}
+
+	spin_unlock_bh(&skw->dfs.skw_pool_lock);
+
+	return pe;
+}
+
+static struct skw_pulse_elem *pulse_queue_get_tail(struct skw_pri_detector *pde)
+{
+	struct list_head *l = &pde->pulses;
+
+	if (list_empty(l))
+		return NULL;
+
+	return list_entry(l->prev, struct skw_pulse_elem, head);
+}
+
+static bool pulse_queue_dequeue(struct skw_core *skw, struct skw_pri_detector *pde)
+{
+	struct skw_pulse_elem *p = pulse_queue_get_tail(pde);
+
+	if (p != NULL) {
+		list_del_init(&p->head);
+		pde->count--;
+		/* give it back to pool */
+		pool_put_pulse_elem(skw, p);
+	}
+
+	return (pde->count > 0);
+}
+
+/* remove pulses older than window */
+static void pulse_queue_check_window(struct skw_core *skw, struct skw_pri_detector *pde)
+{
+	u64 min_valid_ts;
+	struct skw_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(skw, pde);
+	}
+}
+
+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;
+}
+
+static u32 pseq_handler_add_to_existing_seqs(struct skw_core *skw, struct skw_radar_cfg *cfg, u64 ts)
+{
+	u32 max_count = 0;
+	struct skw_pri_sequence *ps, *ps2;
+
+	list_for_each_entry_safe(ps, ps2, &cfg->pri.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(skw, ps);
+			continue;
+		}
+
+		delta_ts = ts - ps->last_ts;
+		factor = pde_get_multiple(delta_ts, ps->pri,
+				cfg->rule.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;
+}
+
+static struct skw_pri_sequence *pool_get_pseq_elem(struct skw_core *skw)
+{
+	struct skw_pri_sequence *pse = NULL;
+
+	spin_lock_bh(&skw->dfs.skw_pool_lock);
+
+	if (!list_empty(&skw->dfs.skw_pseq_pool)) {
+		pse = list_first_entry(&skw->dfs.skw_pseq_pool, struct skw_pri_sequence, head);
+		list_del(&pse->head);
+	}
+
+	spin_unlock_bh(&skw->dfs.skw_pool_lock);
+
+	return pse;
+}
+
+#define GET_PRI_TO_USE(MIN, MAX, RUNTIME) \
+	(MIN + SKW_PRI_TOLERANCE == MAX - SKW_PRI_TOLERANCE ? \
+	 MIN + SKW_PRI_TOLERANCE : RUNTIME)
+static bool pseq_handler_create_sequences(struct skw_core *skw,
+		struct skw_radar_cfg *cfg, u64 ts, u32 min_count)
+{
+	struct skw_pulse_elem *p;
+
+	list_for_each_entry(p, &cfg->pri.pulses, head) {
+		struct skw_pri_sequence ps, *new_ps;
+		struct skw_pulse_elem *p2;
+		u32 tmp_false_count;
+		u64 min_valid_ts;
+		u32 delta_ts = ts - p->ts;
+
+		if (delta_ts < cfg->rule.pri_min)
+			/* ignore too small pri */
+			continue;
+
+		if (delta_ts > cfg->rule.pri_max)
+			/* stop on too large pri (sorted list) */
+			break;
+
+		/* build a new sequence with new potential pri */
+		ps.count = 2;
+		ps.count_falses = 0;
+		ps.first_ts = p->ts;
+		ps.last_ts = ts;
+		ps.pri = GET_PRI_TO_USE(cfg->rule.pri_min, cfg->rule.pri_max, ts - p->ts);
+		ps.dur = ps.pri * (cfg->rule.ppb - 1) + 2 * cfg->rule.max_pri_tolerance;
+
+		p2 = p;
+		tmp_false_count = 0;
+		min_valid_ts = ts - ps.dur;
+		/* check which past pulses are candidates for new sequence */
+		list_for_each_entry_continue(p2, &cfg->pri.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,
+					cfg->rule.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;
+		new_ps = pool_get_pseq_elem(skw);
+		if (new_ps == NULL) {
+			new_ps = kmalloc(sizeof(*new_ps), GFP_ATOMIC);
+			if (new_ps == NULL)
+				return false;
+		}
+
+		memcpy(new_ps, &ps, sizeof(ps));
+		INIT_LIST_HEAD(&new_ps->head);
+		list_add(&new_ps->head, &cfg->pri.sequences);
+	}
+
+	return true;
+}
+
+static void pri_detector_reset(struct skw_core *skw, struct skw_pri_detector *pde, u64 ts)
+{
+	struct skw_pri_sequence *ps, *ps0;
+	struct skw_pulse_elem *p, *p0;
+
+	list_for_each_entry_safe(ps, ps0, &pde->sequences, head) {
+		list_del_init(&ps->head);
+		pool_put_pseq_elem(skw, ps);
+	}
+
+	list_for_each_entry_safe(p, p0, &pde->pulses, head) {
+		list_del_init(&p->head);
+		pool_put_pulse_elem(skw, p);
+	}
+
+	pde->count = 0;
+	pde->last_ts = ts;
+}
+
+static struct skw_pri_sequence *pseq_handler_check_detection(struct skw_radar_cfg *cfg)
+{
+	struct skw_pri_sequence *ps;
+
+	if (list_empty(&cfg->pri.sequences))
+		return NULL;
+
+	list_for_each_entry(ps, &cfg->pri.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 >= cfg->rule.ppb_thresh) &&
+		    (ps->count * cfg->rule.nr_pri >= ps->count_falses))
+			return ps;
+	}
+
+	return NULL;
+}
+
+static bool pulse_queue_enqueue(struct skw_core *skw, struct skw_pri_detector *pde, u64 ts)
+{
+	struct skw_pulse_elem *p = pool_get_pulse_elem(skw);
+
+	if (p == NULL) {
+		p = kmalloc(sizeof(*p), GFP_ATOMIC);
+		if (p == NULL)
+			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(skw, pde);
+
+	if (pde->count >= pde->max_count)
+		pulse_queue_dequeue(skw, pde);
+
+	return true;
+}
+
+int skw_dfs_add_pulse(struct wiphy *wiphy, struct net_device *dev, struct skw_pulse_info *pulse)
+{
+	int i;
+	bool reset = false;
+	u32 max_updated_seq;
+	struct skw_pri_sequence *ps;
+	struct skw_iface *iface = netdev_priv(dev);
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	if (!iface->sap.dfs.flags)
+		return 0;
+
+	if (skw->dfs.last_pulse_ts > pulse->ts)
+		reset = true;
+
+	skw->dfs.last_pulse_ts = pulse->ts;
+
+	for (i = 0; i < skw->dfs.info->nr_cfg; i++) {
+		struct skw_radar_cfg *cfg = &skw->dfs.info->cfgs[i];
+		const struct skw_radar_rule *rule = &cfg->rule;
+
+		if (reset) {
+			pri_detector_reset(skw, &cfg->pri, skw->dfs.last_pulse_ts);
+			continue;
+		}
+
+		if ((rule->width_min > pulse->width) || (rule->width_max < pulse->width)) {
+			skw_detail("invalid pulse width, (%d - %d), pulse width: %d\n",
+				 rule->width_min, rule->width_max, pulse->width);
+			continue;
+		}
+
+		if (rule->chirp && rule->chirp != pulse->chirp) {
+			skw_detail("invalid chirp\n");
+			continue;
+		}
+
+		if ((pulse->ts - cfg->pri.last_ts) < rule->max_pri_tolerance) {
+			skw_detail("invalid timestap, %lld - %lld = %lld, max: %d\n",
+				 pulse->ts, cfg->pri.last_ts, pulse->ts - cfg->pri.last_ts,
+				 rule->max_pri_tolerance);
+			continue;
+		}
+
+		max_updated_seq = pseq_handler_add_to_existing_seqs(skw, cfg, pulse->ts);
+		if (!pseq_handler_create_sequences(skw, cfg, pulse->ts, max_updated_seq)) {
+			pri_detector_reset(skw, &cfg->pri, pulse->ts);
+			continue;
+		}
+
+		ps = pseq_handler_check_detection(cfg);
+		if (ps) {
+			skw_info("radar deteced, iface dfs flags: 0x%lx\n", iface->sap.dfs.flags);
+
+			skw_dfs_deinit(wiphy, dev);
+
+			cfg80211_radar_event(wiphy, &skw->dfs.chan, GFP_KERNEL);
+
+			break;
+		}
+
+		pulse_queue_enqueue(skw, &cfg->pri, pulse->ts);
+	}
+
+	return 0;
+}
+
+static void skw_dfs_cac_work(struct work_struct *work)
+{
+	struct delayed_work *dwk = to_delayed_work(work);
+	struct skw_iface *iface = container_of(dwk, struct skw_iface,
+						sap.dfs.cac_work);
+
+	skw_dbg("dev: %s finished\n", netdev_name(iface->ndev));
+
+	skw_wdev_lock(&iface->wdev);
+
+	if (iface->wdev.cac_started) {
+		skw_dfs_stop_cac(priv_to_wiphy(iface->skw), iface->ndev);
+
+		cfg80211_cac_event(iface->ndev, &iface->skw->dfs.chan,
+				NL80211_RADAR_CAC_FINISHED, GFP_KERNEL);
+	}
+
+	skw_wdev_unlock(&iface->wdev);
+}
+
+int skw_dfs_chan_init(struct wiphy *wiphy, struct net_device *dev,
+		      struct cfg80211_chan_def *chandef, u32 cac_time_ms)
+{
+	int i;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_dbg("chan: %d, dfs region: %d, flags: 0x%lx\n",
+		chandef->chan->hw_value, skw->dfs.region,
+		skw->dfs.flags);
+
+	if (!skw->dfs.fw_enabled)
+		return -ENOTSUPP;
+
+	if (skw->dfs.flags) {
+		if (skw->dfs.chan.width != chandef->width ||
+		    skw->dfs.chan.chan != chandef->chan) {
+			skw_warn("current chan: %d, require chan: %d\n",
+				 skw->dfs.chan.chan->hw_value,
+				 chandef->chan->hw_value);
+
+			return -EBUSY;
+		}
+	}
+
+	if (skw->dfs.region >= ARRAY_SIZE(skw_radar_infos)) {
+		skw_err("invalid dfs region: %d\n", skw->dfs.region);
+
+		return -EINVAL;
+	}
+
+	skw->dfs.info = &skw_radar_infos[skw->dfs.region];
+	if (!skw->dfs.info->nr_cfg || !skw->dfs.info->cfgs) {
+		skw_err("invalid, region: %d, nr_cfg: %d\n",
+			skw->dfs.region, skw->dfs.info->nr_cfg);
+
+		return -EINVAL;
+	}
+
+	iface->sap.dfs.cac_time_ms = cac_time_ms;
+	skw->dfs.last_pulse_ts = 0;
+	skw->dfs.chan = *chandef;
+
+	for (i = 0; i < skw->dfs.info->nr_cfg; i++) {
+		struct skw_radar_cfg *cfg = &skw->dfs.info->cfgs[i];
+		const struct skw_radar_rule *rule = &cfg->rule;
+
+		INIT_LIST_HEAD(&cfg->pri.sequences);
+		INIT_LIST_HEAD(&cfg->pri.pulses);
+
+		cfg->pri.window_size = rule->pri_max * rule->ppb * rule->nr_pri;
+		cfg->pri.max_count = rule->ppb * 2;
+	}
+
+	return 0;
+}
+
+int skw_dfs_start_cac(struct wiphy *wiphy, struct net_device *dev)
+{
+	int ret;
+	struct skw_dfs_cac cac;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_dbg("%s\n", netdev_name(dev));
+
+	if (!skw->dfs.fw_enabled)
+		return -ENOTSUPP;
+
+	cac.type = SKW_DFS_START_CAC;
+	cac.len = sizeof(struct skw_cac_params);
+
+	cac.params.chn = skw->dfs.chan.chan->hw_value;
+	cac.params.center_chn1 = skw_freq_to_chn(skw->dfs.chan.center_freq1);
+	cac.params.center_chn2 = skw_freq_to_chn(skw->dfs.chan.center_freq2);
+	cac.params.band_width = to_skw_bw(skw->dfs.chan.width);
+	cac.params.region = skw->dfs.region;
+	cac.params.time_ms = iface->sap.dfs.cac_time_ms;
+
+	ret = skw_send_msg(wiphy, dev, SKW_CMD_DFS, &cac, sizeof(cac), NULL, 0);
+	if (!ret) {
+		set_bit(SKW_DFS_FLAG_CAC_MODE, &iface->sap.dfs.flags);
+		set_bit(SKW_DFS_FLAG_CAC_MODE, &skw->dfs.flags);
+	}
+
+	return ret;
+}
+
+int skw_dfs_stop_cac(struct wiphy *wiphy, struct net_device *dev)
+{
+	struct skw_dfs_cac cac;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_dbg("%s\n", netdev_name(dev));
+
+	if (!skw->dfs.fw_enabled)
+		return -ENOTSUPP;
+
+	cac.type = SKW_DFS_STOP_CAC;
+	cac.len = 0;
+
+	clear_bit(SKW_DFS_FLAG_CAC_MODE, &iface->sap.dfs.flags);
+	clear_bit(SKW_DFS_FLAG_CAC_MODE, &skw->dfs.flags);
+
+	return skw_send_msg(wiphy, dev, SKW_CMD_DFS, &cac, sizeof(cac), NULL, 0);
+}
+
+int skw_dfs_start_monitor(struct wiphy *wiphy, struct net_device *dev)
+{
+	int ret;
+	struct skw_dfs_cac cac;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_dbg("%s\n", netdev_name(dev));
+
+	if (!skw->dfs.fw_enabled)
+		return -ENOTSUPP;
+
+	cac.type = SKW_DFS_START_MONITOR;
+	cac.len = 0;
+
+	ret = skw_send_msg(wiphy, dev, SKW_CMD_DFS, &cac, sizeof(cac), NULL, 0);
+	if (!ret) {
+		set_bit(SKW_DFS_FLAG_MONITOR_MODE, &skw->dfs.flags);
+		set_bit(SKW_DFS_FLAG_MONITOR_MODE, &iface->sap.dfs.flags);
+	}
+
+	return ret;
+}
+
+int skw_dfs_stop_monitor(struct wiphy *wiphy, struct net_device *dev)
+{
+	struct skw_dfs_cac cac;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct skw_iface *iface = netdev_priv(dev);
+
+	skw_dbg("%s\n", netdev_name(dev));
+
+	if (!skw->dfs.fw_enabled)
+		return -ENOTSUPP;
+
+	cac.type = SKW_DFS_STOP_MONITOR;
+	cac.len = 0;
+
+	clear_bit(SKW_DFS_FLAG_MONITOR_MODE, &skw->dfs.flags);
+	clear_bit(SKW_DFS_FLAG_MONITOR_MODE, &iface->sap.dfs.flags);
+
+	return skw_send_msg(wiphy, dev, SKW_CMD_DFS, &cac, sizeof(cac), NULL, 0);
+}
+
+int skw_dfs_init(struct wiphy *wiphy, struct net_device *dev)
+{
+	int i, j;
+	struct skw_iface *iface = netdev_priv(dev);
+
+	BUILD_BUG_ON(IS_ENABLED(CONFIG_SWT6621S_REGD_SELF_MANAGED));
+
+	iface->sap.dfs.flags = 0;
+	INIT_DELAYED_WORK(&iface->sap.dfs.cac_work, skw_dfs_cac_work);
+
+	for (i = 0; i < ARRAY_SIZE(skw_radar_infos); i++) {
+		const struct skw_radar_info *info = &skw_radar_infos[i];
+
+		for (j = 0; j < info->nr_cfg; j++) {
+			INIT_LIST_HEAD(&info->cfgs[j].pri.sequences);
+			INIT_LIST_HEAD(&info->cfgs[j].pri.pulses);
+		}
+	}
+
+	return 0;
+}
+
+int skw_dfs_deinit(struct wiphy *wiphy, struct net_device *dev)
+{
+	int i, j;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct skw_iface *iface = netdev_priv(dev);
+
+	if (test_bit(SKW_DFS_FLAG_CAC_MODE, &iface->sap.dfs.flags)) {
+
+		cancel_delayed_work_sync(&iface->sap.dfs.cac_work);
+
+		skw_dfs_stop_cac(wiphy, dev);
+
+		cfg80211_cac_event(dev, &skw->dfs.chan,
+				NL80211_RADAR_CAC_ABORTED, GFP_KERNEL);
+	}
+
+	if (test_bit(SKW_DFS_FLAG_MONITOR_MODE, &iface->sap.dfs.flags))
+		skw_dfs_stop_monitor(wiphy, dev);
+
+	for (i = 0; i < ARRAY_SIZE(skw_radar_infos); i++) {
+		const struct skw_radar_info *info = &skw_radar_infos[i];
+
+		for (j = 0; j < info->nr_cfg; j++)
+			pri_detector_reset(skw, &info->cfgs[j].pri, 0);
+	}
+
+	return 0;
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dfs.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dfs.h
new file mode 100755
index 0000000..292182b
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_dfs.h
@@ -0,0 +1,179 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_DFS_H__
+#define __SKW_DFS_H__
+
+#include <linux/ieee80211.h>
+#include <net/cfg80211.h>
+#include <linux/inetdevice.h>
+
+#define SKW_PRI_TOLERANCE                     16
+
+#define SKW_DFS_FLAG_CAC_MODE                 1
+#define SKW_DFS_FLAG_MONITOR_MODE             2
+
+struct skw_pri_detector {
+	u64 last_ts;
+	u32 window_size;
+	u32 count, max_count;
+
+	struct list_head sequences;
+	struct list_head pulses;
+};
+
+struct skw_radar_rule {
+	u8 type_id;
+	u8 width_min;
+	u8 width_max;
+	u16 pri_min;
+	u16 pri_max;
+	u8 nr_pri;
+	u8 ppb;
+	u8 ppb_thresh;
+	u8 max_pri_tolerance;
+	bool chirp;
+};
+
+struct skw_radar_cfg {
+	const struct skw_radar_rule rule;
+	struct skw_pri_detector pri;
+};
+
+struct skw_radar_info {
+	int nr_cfg;
+	struct skw_radar_cfg *cfgs;
+};
+
+enum SKW_DFS_ACTION {
+	SKW_DFS_START_CAC = 1,
+	SKW_DFS_STOP_CAC,
+	SKW_DFS_START_MONITOR,
+	SKW_DFS_STOP_MONITOR,
+};
+
+struct skw_cac_params {
+	u8 chn;
+	u8 center_chn1;
+	u8 center_chn2;
+	u8 band_width;
+
+	u32 time_ms;
+	u8 region;
+} __packed;
+
+struct skw_dfs_cac {
+	u16 type;
+	u16 len;
+	struct skw_cac_params params;
+};
+
+struct skw_pulse_data {
+	u64 chirp: 1;
+	u64 rssi: 5;
+	u64 width: 8;
+	u64 ts: 24;
+	u64 resv:26;
+};
+
+struct skw_radar_pulse {
+	u8 nr_pulse;
+	u8 resv;
+	struct skw_pulse_data data[0];
+} __packed;
+
+struct skw_pulse_info {
+	u64 ts;
+	u16 freq;
+	u8 width;
+	s8 rssi;
+	bool chirp;
+};
+
+struct skw_pulse_elem {
+	struct list_head head;
+	u64 ts;
+};
+
+struct skw_pri_sequence {
+	struct list_head head;
+	u32 pri;
+	u32 dur;
+	u32 count;
+	u32 count_falses;
+	u64 first_ts;
+	u64 last_ts;
+	u64 deadline_ts;
+};
+
+#ifdef CONFIG_SWT6621S_DFS_MASTER
+int skw_dfs_chan_init(struct wiphy *wiphy, struct net_device *dev,
+		      struct cfg80211_chan_def *chandef, u32 cac_time_ms);
+
+int skw_dfs_add_pulse(struct wiphy *wiphy, struct net_device *dev,
+			struct skw_pulse_info *pulse);
+
+int skw_dfs_start_cac(struct wiphy *wiphy, struct net_device *dev);
+int skw_dfs_stop_cac(struct wiphy *wiphy, struct net_device *ndev);
+int skw_dfs_start_monitor(struct wiphy *wiphy, struct net_device *dev);
+int skw_dfs_stop_monitor(struct wiphy *wiphy, struct net_device *dev);
+int skw_dfs_init(struct wiphy *wiphy, struct net_device *dev);
+int skw_dfs_deinit(struct wiphy *wiphy, struct net_device *dev);
+#else
+static inline int skw_dfs_chan_init(struct wiphy *wiphy, struct net_device *dev,
+				struct cfg80211_chan_def *chandef, u32 cac_time_ms)
+{
+	return -ENOTSUPP;
+}
+
+static inline int skw_dfs_add_pulse(struct wiphy *wiphy, struct net_device *dev,
+				struct skw_pulse_info *pulse)
+{
+	return 0;
+}
+
+static inline int skw_dfs_start_cac(struct wiphy *wiphy, struct net_device *dev)
+{
+	return -ENOTSUPP;
+}
+
+static inline int skw_dfs_stop_cac(struct wiphy *wiphy, struct net_device *ndev)
+{
+	return 0;
+}
+
+static inline int skw_dfs_start_monitor(struct wiphy *wiphy, struct net_device *dev)
+{
+	return -ENOTSUPP;
+}
+
+static inline int skw_dfs_stop_monitor(struct wiphy *wiphy, struct net_device *dev)
+{
+	return 0;
+}
+static inline int skw_dfs_init(struct wiphy *wiphy, struct net_device *dev)
+{
+	return 0;
+}
+
+static inline int skw_dfs_deinit(struct wiphy *wiphy, struct net_device *dev)
+{
+	return 0;
+}
+#endif
+
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_edma.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_edma.c
new file mode 100755
index 0000000..9df7785
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_edma.c
@@ -0,0 +1,1380 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/percpu-defs.h>
+#include <linux/skbuff.h>
+#include <linux/version.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0)
+#include <linux/dma-direct.h>
+#else
+#include <linux/dma-mapping.h>
+#endif
+
+#include "skw_core.h"
+#include "skw_compat.h"
+#include "skw_edma.h"
+#include "skw_util.h"
+#include "skw_log.h"
+#include "skw_msg.h"
+#include "skw_rx.h"
+#include "skw_tx.h"
+#include "trace.h"
+
+static struct kmem_cache *skw_edma_node_cache;
+static DEFINE_PER_CPU(struct page_frag_cache, skw_edma_alloc_cache);
+
+static void *skw_edma_alloc_frag(size_t fragsz, gfp_t gfp_mask)
+{
+	struct page_frag_cache *nc;
+	unsigned long flags;
+	void *data;
+
+	local_irq_save(flags);
+	nc = this_cpu_ptr(&skw_edma_alloc_cache);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
+	data = page_frag_alloc(nc, fragsz, gfp_mask);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
+	data = __alloc_page_frag(nc, fragsz, gfp_mask);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+	data = napi_alloc_frag(fragsz);
+#else
+	data = NULL;
+#endif
+
+	local_irq_restore(flags);
+
+	return data;
+}
+
+static int skw_lmac_show(struct seq_file *seq, void *data)
+{
+	struct skw_lmac *lmac = seq->private;
+	struct skw_edma_node *tmp;
+	struct list_head *pos, *n;
+	struct sk_buff *skb, *skb_tmp;
+
+	seq_printf(seq, "edma_free_list len:%d avail_skb len:%d rx_dat_q len:%d\n",
+		READ_ONCE(lmac->edma_free_list.qlen),
+		READ_ONCE(lmac->avail_skb.qlen),
+		READ_ONCE(lmac->rx_dat_q.qlen));
+
+	skw_detail("each skb address of avail_skb queue\n");
+	skb_queue_walk_safe(&lmac->avail_skb, skb, skb_tmp)
+		skw_detail("skb->data:%p\n", skb->data);
+
+	skw_detail("each skb address of edma_free_list queue\n");
+	skb_queue_walk_safe(&lmac->edma_free_list, skb, skb_tmp)
+		skw_detail("SKW_SKB_TXCB(skb)->e.pa:%llx\n", (u64)SKW_SKB_TXCB(skb)->e.pa);
+
+	seq_printf(seq, "edma_tx_chn Info: current_node:%d nr_node:%d\n",
+		lmac->skw->edma.tx_chn[lmac->id].current_node->node_id,
+		atomic_read(&lmac->skw->edma.tx_chn[lmac->id].nr_node));
+	list_for_each_safe(pos, n, &lmac->skw->edma.tx_chn[lmac->id].node_list) {
+		tmp = list_entry(pos, struct skw_edma_node, list);
+		seq_printf(seq, "  node_id:%d used:%d dma_addr:%pad\n",
+			tmp->node_id, tmp->used, &tmp->dma_addr);
+	}
+
+	seq_puts(seq, "\n");
+	seq_printf(seq, "avail_skb queue len : %d\n",
+		READ_ONCE(lmac->avail_skb.qlen));
+	seq_printf(seq, "edma_rx_req_chn Info: current_node:%d nr_node:%d avail_skb_num:%d\n",
+		lmac->skw->edma.rx_req_chn[lmac->id].current_node->node_id,
+		atomic_read(&lmac->skw->edma.rx_req_chn[lmac->id].nr_node),
+		atomic_read(&lmac->avail_skb_num));
+	list_for_each_safe(pos, n, &lmac->skw->edma.rx_req_chn[lmac->id].node_list) {
+		tmp = list_entry(pos, struct skw_edma_node, list);
+		seq_printf(seq, "  node_id:%d used:%d dma_addr:%pad\n",
+			tmp->node_id, tmp->used, &tmp->dma_addr);
+	}
+
+	seq_puts(seq, "\n");
+	seq_printf(seq, "edma_filter_ch Info: current_node:%d nr_node:%d rx_node_count:%d\n",
+		lmac->skw->edma.filter_chn[lmac->id].current_node->node_id,
+		atomic_read(&lmac->skw->edma.filter_chn[lmac->id].nr_node),
+		lmac->skw->edma.filter_chn[lmac->id].rx_node_count);
+
+	list_for_each_safe(pos, n, &lmac->skw->edma.filter_chn[lmac->id].node_list) {
+		tmp = list_entry(pos, struct skw_edma_node, list);
+		seq_printf(seq, "  node_id:%d dma_addr:%pad\n",
+			tmp->node_id, &tmp->dma_addr);
+	}
+
+	seq_puts(seq, "\n");
+	seq_printf(seq, "edma_rx_chn Info: current_node:%d nr_node:%d rx_node_count:%d\n",
+		lmac->skw->edma.rx_chn[lmac->id].current_node->node_id,
+		atomic_read(&lmac->skw->edma.rx_chn[lmac->id].nr_node),
+		lmac->skw->edma.rx_chn[lmac->id].rx_node_count);
+
+	list_for_each_safe(pos, n, &lmac->skw->edma.rx_chn[lmac->id].node_list) {
+		tmp = list_entry(pos, struct skw_edma_node, list);
+		seq_printf(seq, "  node_id:%d dma_addr:%pad\n",
+			tmp->node_id, &tmp->dma_addr);
+	}
+
+	return 0;
+}
+
+static int skw_lmac_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, skw_lmac_show, inode->i_private);
+}
+
+static const struct file_operations skw_lmac_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_lmac_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static int skw_edma_alloc_skb(struct skw_core *skw, struct skw_edma_chn *edma, int num, int lmac_id)
+{
+	int i;
+	struct wiphy *wiphy = priv_to_wiphy(skw);
+	struct device *dev = priv_to_wiphy(skw)->dev.parent;
+	u64 skb_pcie_addr;
+	dma_addr_t skb_dma_addr;
+	struct sk_buff *skb;
+	struct skw_lmac *lmac;
+
+	if (unlikely(!dev)) {
+		skw_err("dev is null\n");
+		return -ENODEV;
+	}
+
+	edma->tx_node_count = 0;
+	lmac = &skw->hw.lmac[lmac_id];
+	for (i = 0; i < num; i++) {
+		skb = dev_alloc_skb(SKW_EDMA_SKB_DATA_LEN);
+		if (!skb) {
+			skw_err("alloc skb addr fail!\n");
+			return -ENOMEM;
+		}
+
+		skb_put(skb, SKW_EDMA_SKB_DATA_LEN);
+
+		skb_dma_addr = skw_pci_map_single(skw, skb->data,
+			SKW_EDMA_SKB_DATA_LEN, DMA_FROM_DEVICE);
+		if (unlikely(skw_pcie_mapping_error(skw, skb_dma_addr))) {
+			skw_err("dma mapping error :%d\n", __LINE__);
+			BUG_ON(1);
+		}
+
+		skb_pcie_addr = skw_dma_to_pcie(skb_dma_addr);
+		skw_edma_set_data(wiphy, edma, &skb_pcie_addr, sizeof(u64));
+		skb_queue_tail(&lmac->avail_skb, skb);
+	}
+
+	skw_detail("used:%d edma->tx_node_count:%d num:%d\n",
+		edma->current_node->used, edma->tx_node_count, num);
+	if (skw->hw_pdata->submit_list_to_edma_channel(edma->channel,
+		0, edma->tx_node_count) < 0)
+		skw_err("submit_list_to_edma_channel failed\n");
+	else
+		atomic_add(num, &lmac->avail_skb_num);
+	edma->tx_node_count = 0;
+
+	return 0;
+}
+
+static inline void skw_edma_reset_refill(void *priv, u8 lmac_id)
+{
+	struct skw_core *skw = (struct skw_core *)priv;
+	struct skw_edma_chn *edma = NULL;
+
+	edma = &skw->edma.rx_req_chn[lmac_id];
+	atomic_set(&edma->chn_refill, 0);
+}
+
+bool skw_edma_is_txc_completed(struct skw_core *skw)
+{
+	u8 lmac_id;
+	struct skw_lmac *lmac;
+
+	for (lmac_id = 0; lmac_id < skw->hw.nr_lmac; lmac_id++) {
+		lmac = &skw->hw.lmac[lmac_id];
+
+		if (lmac != NULL &&
+			skw_lmac_is_actived(skw, lmac_id)) {
+			spin_lock(&lmac->edma_free_list.lock);
+			if (!skb_queue_empty(&lmac->edma_free_list)) {
+				skw_dbg("txc is not completed");
+				spin_unlock(&lmac->edma_free_list.lock);
+				return false;
+			}
+			spin_unlock(&lmac->edma_free_list.lock);
+		}
+	}
+
+	return true;
+}
+
+void skw_edma_inc_refill(void *priv, u8 lmac_id)
+{
+	struct skw_core *skw = (struct skw_core *)priv;
+	struct skw_edma_chn *edma = NULL;
+
+	edma = &skw->edma.rx_req_chn[lmac_id];
+	atomic_inc(&edma->chn_refill);
+}
+
+void skw_edma_dec_refill(void *priv, u8 lmac_id)
+{
+	struct skw_core *skw = (struct skw_core *)priv;
+	struct skw_edma_chn *edma  = NULL;
+
+	edma = &skw->edma.rx_req_chn[lmac_id];
+
+	if (atomic_read(&edma->chn_refill) > 0)
+		atomic_dec(&edma->chn_refill);
+
+	if (atomic_read(&edma->chn_refill) == 0)
+		skw_edma_unmask_irq(skw, lmac_id);
+}
+
+int skw_edma_get_refill(void *priv, u8 lmac_id)
+{
+	struct skw_core *skw = (struct skw_core *)priv;
+	struct skw_edma_chn *edma  = NULL;
+
+	edma = &skw->edma.rx_req_chn[lmac_id];
+	return atomic_read(&edma->chn_refill);
+}
+
+static inline struct skw_edma_chn *skw_edma_get_refill_chan(void *priv, u8 lmac_id)
+{
+	struct skw_core *skw = (struct skw_core *)priv;
+
+	return &skw->edma.rx_req_chn[lmac_id];
+}
+
+static void skw_edma_refill_skb(struct skw_core *skw, u8 lmac_id)
+{
+	u32 total = RX_FREE_BUF_ADDR_CNT * SKW_EDMA_RX_FREE_CHN_NODE_NUM; //TBD:
+	u16 avail_skb_num = atomic_read(&skw->hw.lmac[lmac_id].avail_skb_num);
+	struct skw_edma_chn *refill_chn = skw_edma_get_refill_chan((void *)skw, lmac_id);
+
+	if (total - avail_skb_num >= RX_FREE_BUF_ADDR_CNT
+		&& skw_edma_get_refill((void *)skw, lmac_id) > 0)
+		skw_edma_alloc_skb(skw, refill_chn,
+			round_down(total - avail_skb_num, RX_FREE_BUF_ADDR_CNT), lmac_id);
+}
+
+static inline void skw_dma_free_coherent(struct skw_core *skw,
+		dma_addr_t *dma_handle, void *cpu_addr, size_t size)
+{
+	struct device *dev = priv_to_wiphy(skw)->dev.parent;
+
+	dma_free_coherent(dev, size, cpu_addr, *dma_handle);
+}
+
+static inline void *skw_dma_alloc_coherent(struct skw_core *skw,
+		dma_addr_t *dma_handle, size_t size, gfp_t flag)
+{
+	struct device *dev = priv_to_wiphy(skw)->dev.parent;
+
+	return dma_alloc_coherent(dev, size, dma_handle, flag);
+}
+
+struct skw_edma_node *skw_edma_next_node(struct skw_edma_chn *chn)
+{
+	unsigned long flags;
+
+	chn->current_node->dma_addr = skw_pci_map_single(chn->skw,
+				chn->current_node->buffer,
+				chn->current_node->buffer_len, DMA_TO_DEVICE);
+	spin_lock_irqsave(&chn->edma_chan_lock, flags);
+
+	if (list_is_last(&chn->current_node->list, &chn->node_list)) {
+		chn->current_node = list_first_entry(&chn->node_list,
+				struct skw_edma_node, list);
+	} else {
+		chn->current_node = list_next_entry(chn->current_node, list);
+	}
+
+	chn->current_node->used = 0;
+	chn->tx_node_count++;
+	atomic_dec(&chn->nr_node);
+
+	spin_unlock_irqrestore(&chn->edma_chan_lock, flags);
+
+	return chn->current_node;
+}
+
+int skw_edma_set_data(struct wiphy *wiphy, struct skw_edma_chn *edma,
+		void *data, int len)
+{
+	struct skw_edma_node *node = edma->current_node;
+	unsigned long flags;
+	u8 *buff = NULL;
+
+	spin_lock_irqsave(&edma->edma_chan_lock, flags);
+	buff = (u8 *)node->buffer;
+	//skw_dbg("chan:%d node_id:%d node->used:%d buff:%px +used:%px\n",
+		//edma->channel, node->node_id, node->used, buff,
+		//(buff + node->used));
+	//skw_dbg("data:%px\n", data);
+	memcpy(buff + node->used, data, len);
+	//skw_dbg("%d channel:%d node:%px\n", __LINE__, edma->channel, node);
+	node->used += len;
+	edma->hdr[node->node_id].data_len = node->used;
+	spin_unlock_irqrestore(&edma->edma_chan_lock, flags);
+	BUG_ON(len > node->buffer_len);
+	if (node->used + len > node->buffer_len)
+		node = skw_edma_next_node(edma);
+
+	return 0;
+}
+
+int skw_edma_tx(struct wiphy *wiphy, struct skw_edma_chn *edma, int tx_len)
+{
+	int tx_count;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	u64 pa = 0;
+
+	if (edma->current_node->used)
+		skw_edma_next_node(edma);
+	tx_count = edma->tx_node_count;
+	pa = edma->hdr->hdr_next;
+	//skw_dbg("channel:%d tx_node_count:%d pa:0x%llx\n",
+		//edma->channel, tx_count, pa);
+	edma->tx_node_count = 0;
+
+	return  skw->hw_pdata->hw_adma_tx(edma->channel, NULL,
+					tx_count, tx_len);
+}
+
+int skw_edma_init_data_chan(void *priv, u8 lmac_id)
+{
+	int ret = 0;
+	unsigned long flags;
+	struct skw_core *skw = priv;
+	struct skw_edma_chn *edma_chn = NULL;
+
+	edma_chn = &skw->edma.filter_chn[lmac_id];
+
+	spin_lock_irqsave(&edma_chn->edma_chan_lock, flags);
+
+	ret = skw->hw_pdata->submit_list_to_edma_channel(edma_chn->channel,
+		0, edma_chn->rx_node_count);
+
+	edma_chn->rx_node_count = 0;
+	spin_unlock_irqrestore(&edma_chn->edma_chan_lock, flags);
+
+	edma_chn = &skw->edma.rx_chn[lmac_id];
+	spin_lock_irqsave(&edma_chn->edma_chan_lock, flags);
+
+	ret = skw->hw_pdata->submit_list_to_edma_channel(edma_chn->channel,
+		0,
+		//(void *)(skw_dma_to_pcie(edma_chn->edma_hdr_pa) + 8),
+		edma_chn->rx_node_count);
+	edma_chn->rx_node_count = 0;
+	spin_unlock_irqrestore(&edma_chn->edma_chan_lock, flags);
+
+	edma_chn = &skw->edma.tx_resp_chn[lmac_id];
+	spin_lock_irqsave(&edma_chn->edma_chan_lock, flags);
+	ret = skw->hw_pdata->submit_list_to_edma_channel(edma_chn->channel,
+		0,
+		//(void *)(skw_dma_to_pcie(edma_chn->edma_hdr_pa) + 8),
+		edma_chn->rx_node_count);
+	edma_chn->rx_node_count = 0;
+	spin_unlock_irqrestore(&edma_chn->edma_chan_lock, flags);
+
+	edma_chn = &skw->edma.rx_req_chn[lmac_id];
+	skw_edma_alloc_skb(skw, edma_chn,
+		RX_FREE_BUF_ADDR_CNT * SKW_EDMA_RX_FREE_CHN_NODE_NUM, lmac_id);
+
+	skw_edma_inc_refill((void *)skw, lmac_id);
+
+	return ret;
+}
+
+static inline int skw_submit_edma_chn(struct skw_core *skw, int chn,
+		u64 pcie_addr, int count)
+{
+	if (!skw->hw_pdata || !skw->hw_pdata->submit_list_to_edma_channel)
+		return -ENOTSUPP;
+
+	return skw->hw_pdata->submit_list_to_edma_channel(chn, (u64)pcie_addr, count);
+}
+
+static void skw_edma_chn_deinit(struct skw_core *skw, struct skw_edma_chn *edma)
+{
+	struct skw_edma_node *node = NULL, *tmp = NULL;
+	unsigned long flags;
+	u8 direction = edma->direction ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+
+	// TODO: stop edma channel transmit
+
+	if (!edma) {
+		skw_err("emda is null\n");
+		return;
+	}
+
+	skw_dbg("chan: %d\n", edma->channel);
+
+	spin_lock_irqsave(&edma->edma_chan_lock, flags);
+	skw->hw_pdata->hw_channel_deinit(edma->channel);
+	list_for_each_entry_safe(node, tmp, &edma->node_list, list) {
+		list_del(&node->list);
+		skw_detail("channel: %d, node_id: %d, buffer: 0x%p, buffer_pa: 0x%pad\n",
+			edma->channel, node->node_id, node->buffer, &node->dma_addr);
+
+		if (node->dma_addr)
+			skw_pci_unmap_single(skw, node->dma_addr, node->buffer_len,
+					direction);
+
+		if (direction == DMA_FROM_DEVICE)
+			skw_compat_page_frag_free(node->buffer);
+		else
+			SKW_KFREE(node->buffer);
+
+		kmem_cache_free(skw_edma_node_cache, node);
+	}
+
+	edma->current_node = NULL;
+	atomic_set(&edma->nr_node, 0);
+	spin_unlock_irqrestore(&edma->edma_chan_lock, flags);
+
+	skw_dma_free_coherent(skw, &edma->edma_hdr_pa, (void *)edma->hdr,
+				edma->edma_hdr_size);
+}
+
+static int skw_edma_chn_init(struct skw_core *skw, struct skw_lmac *lmac,
+			     struct skw_edma_chn *edma, int channel,
+			     int max_node, int node_buff_len,
+			     enum SKW_EDMA_DIRECTION direction,
+			     skw_edma_isr isr, int irq_threshold,
+			     enum SKW_EDMA_CHN_PRIORITY priority,
+			     enum SKW_EDMA_CHN_BUFF_ATTR attr,
+			     enum SKW_EDMA_CHN_BUFF_TYPE buff_type,
+			     enum SKW_EDMA_CHN_TRANS_MODE trans_mode,
+			     bool submit_immediately)
+{
+	int i, ret;
+	gfp_t flags;
+	u64 hdr_start;
+	struct skw_edma_node *node;
+	struct skw_channel_cfg cfg;
+	enum dma_data_direction dma_direction;
+	int hdr_size = sizeof(struct skw_edma_hdr);
+	struct device *dev = priv_to_wiphy(skw)->dev.parent;
+	void *(*skw_alloc_func)(size_t len, gfp_t gfp);
+
+	skw_dbg("%d channel:%d edma->edma_hdr_pa:%pad\n", __LINE__, channel,
+			&edma->edma_hdr_pa);
+
+	if (direction == SKW_FW_TO_HOST) {
+		flags = GFP_ATOMIC;
+		dma_direction = DMA_FROM_DEVICE;
+		skw_alloc_func = skw_edma_alloc_frag;
+	} else {
+		flags = GFP_DMA;
+		dma_direction = DMA_TO_DEVICE;
+		skw_alloc_func = kzalloc;
+	}
+
+	INIT_LIST_HEAD(&edma->node_list);
+	spin_lock_init(&edma->edma_chan_lock);
+	atomic_set(&edma->nr_node, max_node);
+
+	edma->skw = skw;
+	edma->lmac = lmac;
+	edma->direction = direction;
+	edma->n_pld_size = node_buff_len;
+	edma->max_node_num = max_node;
+	edma->channel = channel;
+	edma->tx_node_count = 0;
+	edma->rx_node_count = 0;
+	edma->swtail = 0;
+	edma->edma_hdr_size = PAGE_ALIGN(max_node * hdr_size);
+
+	edma->hdr = dma_alloc_coherent(dev, edma->edma_hdr_size, &edma->edma_hdr_pa, GFP_DMA);
+	if (!edma->hdr)
+		return -ENOMEM;
+
+	memset((void *)edma->hdr, 0x6a, edma->edma_hdr_size);
+
+	hdr_start = SKW_EDMA_HEADR_RESVD + skw_dma_to_pcie(edma->edma_hdr_pa);
+
+	for (i = 0; i < max_node; i++) {
+		node = kmem_cache_alloc(skw_edma_node_cache, GFP_KERNEL);
+		if (!node)
+			goto node_failed;
+
+		node->buffer = skw_alloc_func(node_buff_len, flags);
+		if (!node->buffer)
+			goto node_failed;
+
+		memset(node->buffer, 0x5a, node_buff_len);
+
+		node->used = 0;
+		node->node_id = i;
+		node->buffer_len = node_buff_len;
+		node->dma_addr = skw_pci_map_single(skw, node->buffer,
+					node_buff_len, dma_direction);
+
+		INIT_LIST_HEAD(&node->list);
+		list_add_tail(&node->list, &edma->node_list);
+
+		edma->hdr[i].hdr_next = hdr_start + ((i + 1) % max_node) * hdr_size;
+		edma->hdr[i].pcie_addr = skw_dma_to_pcie(node->dma_addr);
+
+		if (channel == SKW_EDMA_WIFI_RX0_FREE_CHN || channel == SKW_EDMA_WIFI_RX1_FREE_CHN)
+			edma->hdr[i].data_len = node_buff_len;
+	}
+
+	edma->current_node = list_first_entry(&edma->node_list,
+				struct skw_edma_node, list);
+
+	memset(&cfg, 0, sizeof(struct skw_channel_cfg));
+	cfg.priority = priority;
+	cfg.split = attr;
+	cfg.ring = buff_type;
+	cfg.req_mode = trans_mode;
+	cfg.irq_threshold = irq_threshold;
+	cfg.node_count = max_node;
+	cfg.header = hdr_start;
+	cfg.complete_callback = isr;
+	cfg.direction = direction;
+	cfg.context = edma;
+
+	ret = skw->hw_pdata->hw_channel_init(channel, &cfg, NULL);
+
+	if (submit_immediately)
+		ret = skw_submit_edma_chn(skw, channel, hdr_start, max_node);
+
+	return ret;
+
+node_failed:
+	skw_edma_chn_deinit(skw, edma);
+
+	return -ENOMEM;
+}
+
+static int
+skw_edma_tx_node_isr(void *priv, u64 first_pa, u64 last_pa, int count)
+{
+	struct skw_edma_chn *edma_chn = priv;
+	struct skw_core *skw = edma_chn->skw;
+	struct skw_edma_hdr *edma_hdr = NULL;
+	int i = 0;
+	u64 pa = 0, hdr_next = 0;
+	int offset = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&edma_chn->edma_chan_lock, flags);
+	hdr_next = edma_chn->hdr->hdr_next;
+	offset = skw_pcie_to_dma(first_pa) - 8 - edma_chn->edma_hdr_pa;
+
+	edma_hdr = (struct skw_edma_hdr *) ((u8 *)edma_chn->hdr + offset);
+
+	//skw_dbg("edma_hdr:%p\n", edma_hdr);
+	while (i < count) {
+		pa = edma_hdr->pcie_addr; //pcie address
+		//skw_dbg("i:%d edma pcie addr:0x%llx, phy addrs:0x%llx\n",
+		//		i, pa, skw_pcie_to_dma(pa));
+		skw_pci_unmap_single(skw,
+			skw_pcie_to_dma(edma_hdr->pcie_addr),
+			edma_chn->current_node->buffer_len, DMA_TO_DEVICE);
+		atomic_inc(&edma_chn->nr_node);
+		edma_hdr++;
+		i++;
+	}
+	spin_unlock_irqrestore(&edma_chn->edma_chan_lock, flags);
+
+	return 0;
+}
+
+static inline void *skw_edma_coherent_rcvheader_to_cpuaddr(u64 rcv_pcie_addr,
+	struct skw_edma_chn *edma_chn)
+{
+	u32 offset;
+	u64 *cpu_addr;
+
+	offset = rcv_pcie_addr - edma_chn->edma_hdr_pa;
+	cpu_addr = (u64 *)((u64)edma_chn->hdr + offset);
+
+	return cpu_addr;
+}
+
+int skw_edma_txrx_isr(void *priv, u64 first_pa, u64 last_pa, int count)
+{
+	struct skw_edma_chn *edma_chn = priv;
+
+	if (unlikely(priv == NULL)) {
+		skw_err("Ignore spurious isr\n");
+		return 0;
+	}
+
+	skw_detail("call channel:%d\n", edma_chn->channel);
+	//skw->hw_pdata->edma_mask_irq(edma_chn->channel);
+
+	if (edma_chn->channel == SKW_EDMA_WIFI_TX0_FREE_CHN ||
+	    edma_chn->channel == SKW_EDMA_WIFI_TX1_FREE_CHN)
+		napi_schedule(&edma_chn->lmac->napi_tx);
+	else if (edma_chn->channel == SKW_EDMA_WIFI_RX0_CHN ||
+		edma_chn->channel == SKW_EDMA_WIFI_RX1_CHN ||
+		edma_chn->channel == SKW_EDMA_WIFI_RX0_FITER_CHN ||
+		edma_chn->channel == SKW_EDMA_WIFI_RX1_FITER_CHN) {
+		napi_schedule(&edma_chn->lmac->napi_rx);
+	}
+
+	return 0;
+}
+
+static void skw_pci_edma_tx_free(struct skw_core *skw,
+		struct sk_buff_head *free_list, void *data, u16 data_len)
+{
+	int count;
+	unsigned long flags;
+	struct sk_buff *skb;
+	struct sk_buff_head qlist;
+	u64 *p = (u64 *) data;
+	u64 p_data = 0;
+	unsigned long *skb_addr, *addr;
+	struct skw_edma_node *node;
+
+	__skb_queue_head_init(&qlist);
+
+	spin_lock_irqsave(&free_list->lock, flags);
+	skb_queue_splice_tail_init(free_list, &qlist);
+	spin_unlock_irqrestore(&free_list->lock, flags);
+
+	trace_skw_tx_pcie_edma_free(data_len/8);
+	for (count = 0; count < data_len; count = count + 8, p++) {
+		p_data = *p & 0xFFFFFFFFFF;
+		skw_detail("p_data:%llx\n", p_data);
+
+		skb_addr = skw_pcie_to_va(p_data) - 2 - sizeof(unsigned long);
+		skb = (struct sk_buff *)*skb_addr;
+		if (unlikely(skb < (struct sk_buff *)PAGE_OFFSET)) {
+			/* Invalid skb pointer */
+			skw_dbg("wrong address p_data:0x%llx from FW\n", p_data);
+			continue;
+		}
+		addr = skw_pcie_to_va(p_data) - 2 - sizeof(unsigned long)*2;
+		node = (struct skw_edma_node *)*addr;
+		if (node->dma_addr) {
+			skw_pci_unmap_single(skw,
+				skw_pcie_to_dma(node->dma_addr),
+				node->buffer_len, DMA_TO_DEVICE);
+			node->dma_addr = 0;
+			skw->hw_pdata->edma_clear_src_node_count(SKW_EDMA_WIFI_TX0_CHN + SKW_SKB_TXCB(skb)->lmac_id);
+		}
+
+		__skb_unlink(skb, &qlist);
+		skw_pci_unmap_single(skw, SKW_SKB_TXCB(skb)->skb_data_pa,
+			skb->len, DMA_TO_DEVICE);
+		skb->dev->stats.tx_packets++;
+		skb->dev->stats.tx_bytes += SKW_SKB_TXCB(skb)->skb_native_len;
+		//dev_kfree_skb_any(skb);
+		skw_skb_kfree(skw, skb);
+	}
+
+	if (qlist.qlen) {
+		spin_lock_irqsave(&free_list->lock, flags);
+		skb_queue_splice_tail_init(&qlist, free_list);
+		spin_unlock_irqrestore(&free_list->lock, flags);
+	}
+}
+
+static struct sk_buff *
+skw_check_skb_address_available(struct skw_core *skw, u8 lmac_id, u64 addr)
+{
+	struct sk_buff *skb, *tmp;
+
+	skb_queue_walk_safe(&skw->hw.lmac[lmac_id].avail_skb, skb, tmp) {
+		skw_detail("lmac_id: %d, skb->data: 0x%p, addr_pcie: %llx, addr_vir: 0x%p",
+			lmac_id, skb->data, addr, skw_pcie_to_va(addr));
+		if (skb->data == skw_pcie_to_va(addr)) {
+			skb_unlink(skb, &skw->hw.lmac[lmac_id].avail_skb);
+			return skb;
+		}
+	}
+
+	return NULL;
+}
+
+static void skw_pci_edma_rx_data(struct skw_edma_chn *edma_chn, void *data, int data_len)
+{
+	struct skw_core *skw = edma_chn->skw;
+	struct skw_rx_desc *desc = NULL;
+	struct sk_buff *skb;
+	struct skw_skb_rxcb *cb = NULL;
+	int i, total_len;
+	//u64 p_data = 0;
+	u64 *p = NULL;
+
+	for (i = 0; i < data_len; i += 8) {
+		p = (u64 *)((u8 *)data + i);
+
+		skb = skw_check_skb_address_available(skw, edma_chn->lmac->id, *p & 0xFFFFFFFFFF);
+		if (!skb) {
+			skw_dbg("wrong rx data from CP %llx\n", *p & 0xFFFFFFFFFF);
+			skw_warn("rxc node address:%llx\n", virt_to_phys(data));
+			skw_hw_assert(skw, false);
+			continue;
+		}
+
+		cb = SKW_SKB_RXCB(skb);
+		cb->lmac_id = edma_chn->lmac->id;
+
+		skw_pci_unmap_single(skw,
+				skw_pcie_to_dma(*p & 0xFFFFFFFFFF),
+				skb->len, DMA_FROM_DEVICE);
+		//p_data = skw_pcie_to_va(*p & 0xFFFFFFFFFF);
+
+		//desc = (struct skw_rx_desc *) ((u8 *) (p_data + 52));
+		desc = (struct skw_rx_desc *) ((u8 *) (skb->data + 52));
+
+		//FW use this way to return unused buff
+		if (unlikely(!desc->msdu_len)) {
+			//skw_compat_page_frag_free((void *)p_data);
+			skw_detail("free skb\n");
+			kfree_skb(skb);
+			atomic_dec(&edma_chn->lmac->avail_skb_num);
+			continue;
+		}
+		atomic_dec(&edma_chn->lmac->avail_skb_num); //TBD: whether to check the value is minus
+
+		if (desc->snap_match)
+			total_len = desc->msdu_len + 80;
+		else
+			total_len = desc->msdu_len + 88;
+
+		if (unlikely(total_len > SKW_ADMA_BUFF_LEN)) {
+			skw_hw_assert(skw, false);
+			skw_warn("total len: %d\n", total_len);
+			skw_warn("rxc node address:%llx skb->data:%llx\n", virt_to_phys(data), virt_to_phys(skb->data));
+			skw_hex_dump("invalid rx skb:", skb->data, skb->len, true);
+
+			//skw_compat_page_frag_free((void *)p_data);
+			//kfree_skb(skb);
+			continue;
+		}
+
+		skb_trim(skb, total_len);
+		skb_pull(skb, 8);
+		__net_timestamp(skb);
+		skw_hex_dump("rx skb:", skb->data, skb->len, false);
+
+		skb_queue_tail(&edma_chn->lmac->rx_dat_q, skb);
+		skw->rx_packets++;
+	}
+}
+
+static void skw_pci_edma_rx_filter_data(struct skw_core *skw, void *data, int data_len, u8 lmac_id)
+{
+	struct sk_buff *skb;
+	struct skw_skb_rxcb *cb = NULL;
+	int total_len;
+
+	total_len = SKB_DATA_ALIGN(data_len) + skw->skb_share_len;
+
+	if (unlikely(total_len > SKW_ADMA_BUFF_LEN)) {
+		skw_warn("total_len: %d\n", total_len);
+		skw_compat_page_frag_free(data);
+		return;
+	}
+
+	skb = build_skb((void *)data, total_len);
+	if (!skb) {
+		skw_err("build skb failed, len: %d\n", total_len);
+		skw_compat_page_frag_free(data);
+		return;
+	}
+
+	cb = SKW_SKB_RXCB(skb);
+	cb->lmac_id = lmac_id;
+	skb_put(skb, data_len);
+	__net_timestamp(skb);
+
+	skb_queue_tail(&skw->hw.lmac[lmac_id].rx_dat_q, skb);
+	skw->rx_packets++;
+}
+
+static void skw_pcie_edma_rx_cb(struct skw_edma_chn *edma, void *data, u16 data_len)
+{
+	u16 channel = 0;
+	int ret = 0, total_len = 0;
+	struct skw_core *skw = edma->skw;
+	struct skw_iface *iface = NULL;
+	struct skw_event_work *work = NULL;
+	struct sk_buff *skb = NULL;
+	struct skw_msg *msg = NULL;
+
+	channel = edma->channel;
+
+	//skw_dbg("phy data:0x%llx len:%u\n", virt_to_phys(data), data_len);
+	//short & long event channel
+	//skw_dbg("channel:%d\n", channel);
+	if (channel == SKW_EDMA_WIFI_SHORT_EVENT_CHN || channel == SKW_EDMA_WIFI_LONG_EVENT_CHN) {
+		//skw_hex_dump("rx_cb data", data, 16, 1);
+
+		total_len = SKB_DATA_ALIGN(data_len) + skw->skb_share_len;
+		if (unlikely(total_len > SKW_ADMA_BUFF_LEN)) {
+			skw_warn("data: %d\n", data_len);
+			skw_compat_page_frag_free(data);
+			return;
+		}
+
+		skb = build_skb(data, total_len);
+		if (!skb) {
+			skw_compat_page_frag_free(data);
+			skw_err("build skb failed, len: %d\n", data_len);
+			return;
+		}
+		skb_put(skb, data_len);
+
+		msg = (struct skw_msg *) skb->data;
+		switch (msg->type) {
+		case SKW_MSG_CMD_ACK:
+			skw_cmd_ack_handler(skw, skb->data, skb->len);
+			kfree_skb(skb);
+			break;
+
+		case SKW_MSG_EVENT:
+			if (++skw->skw_event_sn != msg->seq) {
+				skw_warn("invalid event seq:%d, expect:%d\n",
+					 msg->seq, skw->skw_event_sn);
+				// skw_hw_assert(skw, false);
+				// kfree_skb(skb);
+				// break;
+			}
+
+			if (msg->id == SKW_EVENT_CREDIT_UPDATE) {
+				skw_warn("PCIE doesn't support CREDIT");
+				kfree_skb(skb);
+				break;
+			}
+
+			iface = to_skw_iface(skw, msg->inst_id);
+			if (iface)
+				work = &iface->event_work;
+			else
+				work = &skw->event_work;
+
+			ret = skw_queue_event_work(priv_to_wiphy(skw),
+						work, skb);
+			if (ret < 0) {
+				skw_err("inst: %d, drop event %d\n",
+					msg->inst_id, msg->id);
+				kfree_skb(skb);
+			}
+			break;
+
+		default:
+			skw_warn("invalid: type: %d, id: %d, seq: %d\n",
+				 msg->type, msg->id, msg->seq);
+			kfree_skb(skb);
+			break;
+		}
+	} else if (channel == SKW_EDMA_WIFI_TX0_FREE_CHN ||
+			channel == SKW_EDMA_WIFI_TX1_FREE_CHN) {
+		struct sk_buff_head *edma_free_list = NULL;
+
+		//skw_dbg("channel:%d received tx free data\n", channel);
+		edma_free_list = &edma->lmac->edma_free_list;
+
+		skw_pci_edma_tx_free(skw, edma_free_list, data, data_len);
+	} else if (channel == SKW_EDMA_WIFI_RX0_CHN ||
+			channel == SKW_EDMA_WIFI_RX1_CHN) {
+		//skw_dbg("channel:%d received data\n", channel);
+
+
+		skw_pci_edma_rx_data(edma, data, data_len);
+		skw_edma_refill_skb(skw, edma->lmac->id);
+	} else if (channel == SKW_EDMA_WIFI_RX0_FITER_CHN ||
+			channel == SKW_EDMA_WIFI_RX1_FITER_CHN) {
+		//skw_dbg("channel:%d received filter data\n", channel);
+		//skw_hex_dump("filter data", data, data_len, 1);
+		skw_pci_edma_rx_filter_data(skw, data, data_len, edma->lmac->id);
+	}
+}
+
+int skw_edma_napi_txrx_compl_task(void *priv, int *quota)
+{
+	struct skw_edma_chn *edma_chn = priv;
+	u16 channel = edma_chn->channel;
+	struct skw_core *skw = edma_chn->skw;
+	struct skw_edma_hdr *edma_hdr = NULL;
+	unsigned long flags;
+	//int times = 0;
+	int count = 0;
+	void *p_addr = NULL;
+
+	spin_lock_irqsave(&edma_chn->edma_chan_lock, flags);
+
+	//while (edma_chn->hdr[edma_chn->swtail].done == 0 && times++ < edma_chn->max_node_num)
+	//	edma_chn->swtail = skw_edma_hdr_tail(edma_chn);
+
+	edma_hdr = (struct skw_edma_hdr *)((u8 *)edma_chn->hdr +
+		edma_chn->swtail * sizeof(struct skw_edma_hdr));
+
+	while (edma_hdr->done && (*quota > 0)) {
+		skw_detail("channel: %d, node_id: %d, node_pa: %pad, edma_pa: %llx, buffer: %p\n",
+			channel, edma_chn->current_node->node_id,
+			&edma_chn->current_node->dma_addr, (u64)edma_hdr->pcie_addr,
+			edma_chn->current_node->buffer);
+
+		count += edma_hdr->data_len / 8;
+		if (*quota >= count)
+			*quota -= count;
+		else
+			*quota = 0;
+		edma_chn->swtail = skw_edma_hdr_tail(edma_chn);
+
+		if (edma_chn->current_node->dma_addr) {
+			skw_pci_unmap_single(skw,
+				skw_pcie_to_dma(edma_hdr->pcie_addr),
+				edma_chn->current_node->buffer_len, DMA_FROM_DEVICE);
+			edma_chn->current_node->dma_addr = 0;
+		}
+
+		skw_pcie_edma_rx_cb(edma_chn, skw_pcie_to_va(edma_hdr->pcie_addr),
+				edma_hdr->data_len);
+		edma_chn->rx_node_count++;
+
+		if (channel == SKW_EDMA_WIFI_RX0_FITER_CHN ||
+			channel ==  SKW_EDMA_WIFI_RX1_FITER_CHN) {
+
+			p_addr = skw_edma_alloc_frag(edma_chn->n_pld_size, GFP_ATOMIC);
+			if (!p_addr) {
+				skw_err("Alloc memory for channel:%d failed\n", channel);
+				return -ENOMEM;
+			}
+
+			edma_chn->current_node->dma_addr = skw_pci_map_single(skw, p_addr,
+				edma_chn->n_pld_size, DMA_FROM_DEVICE);
+			edma_hdr->pcie_addr = skw_dma_to_pcie(edma_chn->current_node->dma_addr);
+			edma_chn->current_node->buffer = p_addr;
+		}
+
+		if (edma_chn->rx_node_count == edma_chn->max_node_num) {
+			edma_chn->rx_node_count = 0;
+			skw->hw_pdata->submit_list_to_edma_channel(
+				edma_chn->channel, 0, edma_chn->max_node_num);
+		}
+
+		edma_hdr->done = 0;
+		edma_hdr++;
+		if (edma_chn->current_node->dma_addr == 0)
+			edma_chn->current_node->dma_addr = skw_pci_map_single(skw,
+				edma_chn->current_node->buffer,
+				edma_chn->current_node->buffer_len, DMA_FROM_DEVICE);
+		if (list_is_last(&edma_chn->current_node->list,
+				&edma_chn->node_list)) {
+			edma_chn->current_node = list_first_entry(&edma_chn->node_list,
+					struct skw_edma_node, list);
+		} else {
+			edma_chn->current_node = list_next_entry(edma_chn->current_node,
+				list);
+		}
+	}
+	spin_unlock_irqrestore(&edma_chn->edma_chan_lock, flags);
+
+	return count;
+}
+
+static int
+skw_edma_rx_node_isr(void *priv, u64 first_pa, u64 last_pa, int count)
+{
+	struct skw_edma_chn *edma_chn = priv;
+	u16 channel = edma_chn->channel;
+	struct skw_core *skw = edma_chn->skw;
+	struct skw_edma_hdr *edma_hdr = NULL;
+	struct skw_edma_hdr *last_edma_hdr = NULL;
+	int i = 0;
+	u64 hdr_next = 0;
+	int offset = 0;
+	unsigned long flags;
+	void *p_addr = NULL;
+
+	// Wait till tail done bit is set
+	last_edma_hdr = (struct skw_edma_hdr *)skw_edma_coherent_rcvheader_to_cpuaddr(((u64)last_pa) - 8, edma_chn);
+	while (!last_edma_hdr->done) {
+		mdelay(1);
+		barrier();
+	}
+
+	spin_lock_irqsave(&edma_chn->edma_chan_lock, flags);
+	edma_chn->rx_node_count += count;
+	hdr_next = edma_chn->hdr->hdr_next;
+
+	offset = skw_pcie_to_dma(first_pa) - 8 - edma_chn->edma_hdr_pa;
+	edma_hdr = (struct skw_edma_hdr *)((u8 *)edma_chn->hdr + offset);
+	while (i < count) {
+		skw_detail("channel:%d node_id:%d current_node->buffer_pa:%pad edma_hdr->buffer_pa:%llx\n",
+			channel, edma_chn->current_node->node_id, &edma_chn->current_node->dma_addr, (u64)edma_hdr->pcie_addr);
+		if (edma_chn->current_node->dma_addr) {
+			skw_pci_unmap_single(skw,
+				skw_pcie_to_dma(edma_hdr->pcie_addr),
+				edma_chn->current_node->buffer_len, DMA_FROM_DEVICE);
+			edma_chn->current_node->dma_addr = 0;
+		}
+
+		skw_pcie_edma_rx_cb(edma_chn,
+			(void *)skw_pcie_to_va(edma_hdr->pcie_addr),
+			edma_hdr->data_len);
+
+		if (channel == SKW_EDMA_WIFI_SHORT_EVENT_CHN ||
+			channel == SKW_EDMA_WIFI_LONG_EVENT_CHN) {
+
+			// This payload of edma channel will be freed in asyn way
+			p_addr = skw_edma_alloc_frag(edma_chn->n_pld_size, GFP_ATOMIC);
+			if (!p_addr) {
+				skw_err("Alloc memory for channel:%d failed\n", channel);
+				return -ENOMEM;
+			}
+
+			edma_chn->current_node->dma_addr = skw_pci_map_single(skw, p_addr,
+				edma_chn->n_pld_size, DMA_FROM_DEVICE);
+			edma_hdr->pcie_addr = skw_dma_to_pcie(edma_chn->current_node->dma_addr);
+			edma_chn->current_node->buffer = p_addr;
+		} else {
+			// This payload will be reused
+			// TBD: reset the payload
+		}
+
+		if (edma_chn->rx_node_count == edma_chn->max_node_num) { //TBD: How to improve rx data channel
+			//skw_dbg("resubmit for channel:%d\n", edma_chn->channel);
+			edma_chn->rx_node_count = 0;
+			skw->hw_pdata->submit_list_to_edma_channel(
+				edma_chn->channel, 0, edma_chn->max_node_num);
+		}
+
+		edma_hdr++;
+		i++;
+		if (list_is_last(&edma_chn->current_node->list, &edma_chn->node_list)) {
+			edma_chn->current_node = list_first_entry(&edma_chn->node_list,
+					struct skw_edma_node, list);
+		} else {
+			edma_chn->current_node = list_next_entry(edma_chn->current_node, list);
+		}
+	}
+
+	spin_unlock_irqrestore(&edma_chn->edma_chan_lock, flags);
+
+	last_edma_hdr->done = 0;
+
+	return 0;
+}
+
+static int skw_netdev_poll_tx(struct napi_struct *napi, int budget)
+{
+	int quota = budget;
+	struct skw_lmac *lmac = container_of(napi, struct skw_lmac, napi_tx);
+	struct skw_core *skw = lmac->skw;
+
+	skw_edma_napi_txrx_compl_task(&skw->edma.tx_resp_chn[lmac->id], &quota);
+
+	if (quota) {
+		napi_complete(napi);
+		skw->hw_pdata->edma_unmask_irq(skw->edma.tx_resp_chn[lmac->id].channel);
+		return 0;
+	}
+
+	return budget;
+}
+
+static int skw_netdev_poll_rx(struct napi_struct *napi, int budget)
+{
+	int quota = budget;
+	struct skw_lmac *lmac = container_of(napi, struct skw_lmac, napi_rx);
+	struct skw_core *skw = lmac->skw;
+
+	skw_edma_napi_txrx_compl_task(&skw->edma.rx_chn[lmac->id], &quota);
+	skw_edma_napi_txrx_compl_task(&skw->edma.filter_chn[lmac->id], &quota);
+
+	skw_rx_process(skw, &lmac->rx_dat_q, &lmac->rx_todo_list);
+	if (lmac->rx_todo_list.count || !quota)
+		return budget;
+
+	napi_complete(napi);
+	skw->hw_pdata->edma_unmask_irq(skw->edma.rx_chn[lmac->id].channel);
+	skw->hw_pdata->edma_unmask_irq(skw->edma.filter_chn[lmac->id].channel);
+
+	return 0;
+}
+
+static int skw_edma_cache_init(struct skw_core *skw)
+{
+	skw_edma_node_cache = kmem_cache_create("skw_edma_node_cache",
+						sizeof(struct skw_edma_node),
+						0, 0, NULL);
+	if (skw_edma_node_cache == NULL)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static void skw_edma_cache_deinit(struct skw_core *skw)
+{
+	kmem_cache_destroy(skw_edma_node_cache);
+}
+
+int skw_edma_init(struct wiphy *wiphy)
+{
+	int ret, i;
+	struct skw_lmac *lmac = NULL;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	char name[32] = {0};
+
+	if (skw->hw.bus != SKW_BUS_PCIE)
+		return 0;
+
+	ret = skw_edma_cache_init(skw);
+	if (ret < 0) {
+		skw_err("edma cached init failed, ret: %d\n", ret);
+		return ret;
+	}
+
+	// cmd channel
+	ret = skw_edma_chn_init(skw, NULL, &skw->edma.cmd_chn,
+				SKW_EDMA_WIFI_CMD_CHN,
+				1, SKW_MSG_BUFFER_LEN,
+				SKW_HOST_TO_FW, skw_edma_tx_node_isr,
+				1, SKW_EDMA_CHN_PRIORITY_0,
+				SKW_EDMA_CHN_BUFF_NON_LINNER,
+				SKW_EDMA_CHN_RING_BUFF,
+				SKW_EDMA_CHN_LINKLIST_MODE, false);
+
+	// short event channel
+	ret = skw_edma_chn_init(skw, NULL, &skw->edma.short_event_chn,
+				SKW_EDMA_WIFI_SHORT_EVENT_CHN,
+				SKW_EDMA_EVENT_CHN_NODE_NUM,
+				SKW_MSG_BUFFER_LEN, SKW_FW_TO_HOST,
+				skw_edma_rx_node_isr,
+				1, SKW_EDMA_CHN_PRIORITY_0,
+				SKW_EDMA_CHN_BUFF_NON_LINNER,
+				SKW_EDMA_CHN_LIST_BUFF,
+				SKW_EDMA_CHN_LINKLIST_MODE, true);
+
+	// long event channel
+	ret = skw_edma_chn_init(skw, NULL, &skw->edma.long_event_chn,
+				SKW_EDMA_WIFI_LONG_EVENT_CHN,
+				SKW_EDMA_EVENT_CHN_NODE_NUM,
+				SKW_MSG_BUFFER_LEN, SKW_FW_TO_HOST,
+				skw_edma_rx_node_isr,
+				1, SKW_EDMA_CHN_PRIORITY_0,
+				SKW_EDMA_CHN_BUFF_NON_LINNER,
+				SKW_EDMA_CHN_LIST_BUFF,
+				SKW_EDMA_CHN_LINKLIST_MODE, true);
+
+	// RX0 filter channel
+	ret = skw_edma_chn_init(skw, &skw->hw.lmac[0],
+				&skw->edma.filter_chn[0],
+				SKW_EDMA_WIFI_RX0_FITER_CHN,
+				SKW_EDMA_FILTER_CHN_NODE_NUM,
+				SKW_MSG_BUFFER_LEN, SKW_FW_TO_HOST,
+				skw_edma_txrx_isr,
+				1, SKW_EDMA_CHN_PRIORITY_0,
+				SKW_EDMA_CHN_BUFF_NON_LINNER,
+				SKW_EDMA_CHN_LIST_BUFF,
+				SKW_EDMA_CHN_LINKLIST_MODE, true);
+
+	// RX1 filter channel
+	ret = skw_edma_chn_init(skw, &skw->hw.lmac[1],
+				&skw->edma.filter_chn[1],
+				SKW_EDMA_WIFI_RX1_FITER_CHN,
+				SKW_EDMA_FILTER_CHN_NODE_NUM,
+				SKW_MSG_BUFFER_LEN, SKW_FW_TO_HOST,
+				skw_edma_txrx_isr,
+				1, SKW_EDMA_CHN_PRIORITY_0,
+				SKW_EDMA_CHN_BUFF_NON_LINNER,
+				SKW_EDMA_CHN_LIST_BUFF,
+				SKW_EDMA_CHN_LINKLIST_MODE, true);
+
+	// TX0 chan
+	ret = skw_edma_chn_init(skw, &skw->hw.lmac[0],
+				&skw->edma.tx_chn[0],
+				SKW_EDMA_WIFI_TX0_CHN,
+				SKW_EDMA_TX_CHN_NODE_NUM,
+				SKW_EDMA_DATA_LEN, SKW_HOST_TO_FW,
+				NULL,
+				1, SKW_EDMA_CHN_PRIORITY_0,
+				SKW_EDMA_CHN_BUFF_NON_LINNER,
+				SKW_EDMA_CHN_RING_BUFF,
+				SKW_EDMA_CHN_LINKLIST_MODE, false);
+	skw->hw_pdata->edma_mask_irq(SKW_EDMA_WIFI_TX0_CHN);
+
+	// TX1 chan
+	ret = skw_edma_chn_init(skw, &skw->hw.lmac[1],
+				&skw->edma.tx_chn[1],
+				SKW_EDMA_WIFI_TX1_CHN,
+				SKW_EDMA_TX_CHN_NODE_NUM,
+				SKW_EDMA_DATA_LEN, SKW_HOST_TO_FW,
+				NULL,
+				1, SKW_EDMA_CHN_PRIORITY_0,
+				SKW_EDMA_CHN_BUFF_NON_LINNER,
+				SKW_EDMA_CHN_RING_BUFF,
+				SKW_EDMA_CHN_LINKLIST_MODE, false);
+	skw->hw_pdata->edma_mask_irq(SKW_EDMA_WIFI_TX1_CHN);
+
+	// TX0 free chan
+	ret = skw_edma_chn_init(skw, &skw->hw.lmac[0],
+				&skw->edma.tx_resp_chn[0],
+				SKW_EDMA_WIFI_TX0_FREE_CHN,
+				SKW_EDMA_TX_FREE_CHN_NODE_NUM,
+				TX_FREE_BUF_ADDR_CNT * sizeof(long long),
+				SKW_FW_TO_HOST, skw_edma_txrx_isr,
+				1, SKW_EDMA_CHN_PRIORITY_0,
+				SKW_EDMA_CHN_BUFF_NON_LINNER,
+				SKW_EDMA_CHN_RING_BUFF,
+				SKW_EDMA_CHN_LINKLIST_MODE, true);
+
+	// TX1 free chan
+	ret = skw_edma_chn_init(skw, &skw->hw.lmac[1],
+				&skw->edma.tx_resp_chn[1],
+				SKW_EDMA_WIFI_TX1_FREE_CHN,
+				SKW_EDMA_TX_FREE_CHN_NODE_NUM,
+				TX_FREE_BUF_ADDR_CNT * sizeof(long long),
+				SKW_FW_TO_HOST, skw_edma_txrx_isr,
+				1, SKW_EDMA_CHN_PRIORITY_0,
+				SKW_EDMA_CHN_BUFF_NON_LINNER,
+				SKW_EDMA_CHN_RING_BUFF,
+				SKW_EDMA_CHN_LINKLIST_MODE, true);
+	// RX0 free chan
+	ret = skw_edma_chn_init(skw, &skw->hw.lmac[0],
+				&skw->edma.rx_req_chn[0],
+				SKW_EDMA_WIFI_RX0_FREE_CHN,
+				SKW_EDMA_RX_FREE_CHN_NODE_NUM,
+				RX_FREE_BUF_ADDR_CNT * sizeof(long long),
+				SKW_HOST_TO_FW, skw_edma_tx_node_isr,
+				1, SKW_EDMA_CHN_PRIORITY_0,
+				SKW_EDMA_CHN_BUFF_NON_LINNER,
+				SKW_EDMA_CHN_RING_BUFF,
+				SKW_EDMA_CHN_LINKLIST_MODE, false);
+
+	// RX1 free chan
+	ret = skw_edma_chn_init(skw, &skw->hw.lmac[1],
+				&skw->edma.rx_req_chn[1],
+				SKW_EDMA_WIFI_RX1_FREE_CHN,
+				SKW_EDMA_RX_FREE_CHN_NODE_NUM,
+				RX_FREE_BUF_ADDR_CNT * sizeof(long long),
+				SKW_HOST_TO_FW, skw_edma_tx_node_isr,
+				1, SKW_EDMA_CHN_PRIORITY_0,
+				SKW_EDMA_CHN_BUFF_NON_LINNER,
+				SKW_EDMA_CHN_RING_BUFF,
+				SKW_EDMA_CHN_LINKLIST_MODE, false);
+
+	// RX0 chan
+	ret = skw_edma_chn_init(skw, &skw->hw.lmac[0],
+				&skw->edma.rx_chn[0],
+				SKW_EDMA_WIFI_RX0_CHN,
+				SKW_EDMA_RX_CHN_NODE_NUM,
+				RX_PKT_ADDR_BUF_CNT * sizeof(long long),
+				SKW_FW_TO_HOST,
+				skw_edma_txrx_isr,
+				1, SKW_EDMA_CHN_PRIORITY_0,
+				SKW_EDMA_CHN_BUFF_NON_LINNER,
+				SKW_EDMA_CHN_RING_BUFF,
+				SKW_EDMA_CHN_LINKLIST_MODE, true);
+
+	// RX1 chan
+	ret = skw_edma_chn_init(skw, &skw->hw.lmac[1],
+				&skw->edma.rx_chn[1],
+				SKW_EDMA_WIFI_RX1_CHN,
+				SKW_EDMA_RX_CHN_NODE_NUM,
+				RX_PKT_ADDR_BUF_CNT * sizeof(long long),
+				SKW_FW_TO_HOST,
+				skw_edma_txrx_isr,
+				1, SKW_EDMA_CHN_PRIORITY_0,
+				SKW_EDMA_CHN_BUFF_NON_LINNER,
+				SKW_EDMA_CHN_RING_BUFF,
+				SKW_EDMA_CHN_LINKLIST_MODE, true);
+
+
+	// data tx/rx channel
+	for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
+		lmac = &skw->hw.lmac[i];
+
+		sprintf(name, "mac%d", i);
+		skw_debugfs_file(SKW_WIPHY_DENTRY(wiphy), name, 0444, &skw_lmac_fops, lmac);
+
+		init_dummy_netdev(&lmac->dummy_dev);
+		skw_compat_netif_napi_add_weight(&lmac->dummy_dev, &lmac->napi_tx, skw_netdev_poll_tx, 64);
+		skw_compat_netif_napi_add_weight(&lmac->dummy_dev, &lmac->napi_rx, skw_netdev_poll_rx, 64);
+
+		skb_queue_head_init(&lmac->edma_free_list);
+		skb_queue_head_init(&lmac->avail_skb);
+
+		skb_queue_head_init(&lmac->rx_dat_q);
+		skw_list_init(&lmac->rx_todo_list);
+
+		skw_edma_reset_refill((void *) skw, i);
+		lmac->flags = SKW_LMAC_FLAG_INIT;
+
+		napi_enable(&lmac->napi_tx);
+		napi_enable(&lmac->napi_rx);
+	}
+
+	return 0;
+}
+
+void skw_edma_deinit(struct wiphy *wiphy)
+{
+	int i = 0;
+	struct skw_lmac *lmac = NULL;
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	if (skw->hw.bus != SKW_BUS_PCIE)
+		return;
+
+	skw_edma_chn_deinit(skw, &skw->edma.cmd_chn);
+	skw_edma_chn_deinit(skw, &skw->edma.short_event_chn);
+	skw_edma_chn_deinit(skw, &skw->edma.long_event_chn);
+
+	for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
+		lmac = &skw->hw.lmac[i];
+		napi_disable(&lmac->napi_tx);
+		napi_disable(&lmac->napi_rx);
+		netif_napi_del(&lmac->napi_tx);
+		netif_napi_del(&lmac->napi_rx);
+		skw_edma_chn_deinit(skw, &skw->edma.tx_chn[i]);
+		skw_edma_chn_deinit(skw, &skw->edma.tx_resp_chn[i]);
+		skw_edma_chn_deinit(skw, &skw->edma.rx_chn[i]);
+		skw_edma_chn_deinit(skw, &skw->edma.rx_req_chn[i]);
+		skw_edma_chn_deinit(skw, &skw->edma.filter_chn[i]);
+		skb_queue_purge(&lmac->edma_free_list);
+		skb_queue_purge(&lmac->avail_skb);
+		skb_queue_purge(&lmac->rx_dat_q);
+		skw_rx_todo(&lmac->rx_todo_list);
+	}
+
+	skw_edma_cache_deinit(skw);
+}
+
+void skw_edma_mask_irq(struct skw_core *skw, u8 lmac_id)
+{
+	if (skw->hw.bus != SKW_BUS_PCIE)
+		return;
+
+	skw->hw_pdata->edma_mask_irq(SKW_EDMA_WIFI_TX0_CHN + lmac_id);
+}
+
+void skw_edma_unmask_irq(struct skw_core *skw, u8 lmac_id)
+{
+	if (skw->hw.bus != SKW_BUS_PCIE)
+		return;
+
+	skw->hw_pdata->edma_unmask_irq(SKW_EDMA_WIFI_TX0_CHN + lmac_id);
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_edma.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_edma.h
new file mode 100755
index 0000000..4589053
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_edma.h
@@ -0,0 +1,227 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_EDMA_H__
+#define __SKW_EDMA_H__
+
+#define SKW_NR_EDMA_NODE                                32
+#define SKW_NR_EDMA_ELEMENT                             64
+#define SKW_EDMA_DATA_LEN                               768
+#define SKW_EDMA_SKB_DATA_LEN                           2048
+
+#define SKW_EDMA_WIFI_CMD_CHN                           14
+#define SKW_EDMA_WIFI_SHORT_EVENT_CHN                   15
+#define SKW_EDMA_WIFI_LONG_EVENT_CHN                    16
+#define SKW_EDMA_WIFI_RX0_FITER_CHN                     17
+#define SKW_EDMA_WIFI_RX1_FITER_CHN                     18
+#define SKW_EDMA_WIFI_TX0_CHN                           19
+#define SKW_EDMA_WIFI_TX1_CHN                           20
+#define SKW_EDMA_WIFI_TX0_FREE_CHN                      21
+#define SKW_EDMA_WIFI_TX1_FREE_CHN                      22
+#define SKW_EDMA_WIFI_RX0_FREE_CHN                      23
+#define SKW_EDMA_WIFI_RX1_FREE_CHN                      24
+#define SKW_EDMA_WIFI_RX0_CHN                           25
+#define SKW_EDMA_WIFI_RX1_CHN                           26
+
+
+#define SKW_EDMA_EVENT_CHN_NODE_NUM                     2
+#define SKW_EDMA_FILTER_CHN_NODE_NUM                    8
+#define SKW_EDMA_TX_CHN_NODE_NUM                        64
+#define SKW_EDMA_TX_FREE_CHN_NODE_NUM                   16
+#define SKW_EDMA_RX_CHN_NODE_NUM                        32
+#define SKW_EDMA_RX_FREE_CHN_NODE_NUM                   24
+
+#define SKW_EDMA_TX_CHN_CREDIT                          16
+
+#define TX_BUF_ADDR_CNT                                 64
+#define TX_FREE_BUF_ADDR_CNT                            64
+#define RX_PKT_ADDR_BUF_CNT                             64
+#define RX_FREE_BUF_ADDR_CNT                            32
+
+#define SKW_EDMA_HEADR_RESVD                            8
+#define skw_dma_to_pcie(addr)                           ((addr) + 0x8000000000)
+#define skw_pcie_to_dma(addr)                           ((addr) - 0x8000000000)
+#define skw_pcie_to_va(addr)                            phys_to_virt(skw_pcie_to_dma(addr))
+
+typedef int (*skw_edma_isr)(void *priv, u64 first_pa, u64 last_pa, int cnt);
+typedef int (*skw_edma_empty_isr)(void *priv);
+
+enum SKW_EDMA_DIRECTION {
+	SKW_HOST_TO_FW = 0,
+	SKW_FW_TO_HOST,
+};
+
+enum SKW_EDMA_CHN_PRIORITY {
+	SKW_EDMA_CHN_PRIORITY_0,
+	SKW_EDMA_CHN_PRIORITY_1,
+	SKW_EDMA_CHN_PRIORITY_2,
+	SKW_EDMA_CHN_PRIORITY_3
+};
+
+enum SKW_EDMA_CHN_BUFF_ATTR {
+	SKW_EDMA_CHN_BUFF_LINNER,
+	SKW_EDMA_CHN_BUFF_NON_LINNER,
+};
+
+enum SKW_EDMA_CHN_BUFF_TYPE {
+	SKW_EDMA_CHN_LIST_BUFF,
+	SKW_EDMA_CHN_RING_BUFF
+};
+
+enum SKW_EDMA_CHN_TRANS_MODE {
+	SKW_EDMA_CHN_STD_MODE,
+	SKW_EDMA_CHN_LINKLIST_MODE,
+};
+
+struct skw_edma_elem {
+	u64 pa:40;
+	u64 rsv:8;
+
+	u64 eth_type:16;
+
+	u8 id_rsv:2;
+	u8 mac_id:2;
+	u8 tid:4;
+
+	u8 peer_idx:5;
+	u8 prot:1;
+	u8 encry_dis:1;
+	u8 rate:1;
+
+	u16 msdu_len:12;
+	u16 resv:4;
+} __packed;
+
+struct skw_edma_hdr {
+	u64 pcie_addr:40;
+	u64 rsv0:16;
+	u64 tx_int:1;
+	u64 rsv1:6;
+	u64 done:1;
+
+	u64 hdr_next:40;
+	u64 rsv2:8;
+	u64 data_len:16;
+} __packed;
+
+struct skw_edma_node {
+	struct list_head list;
+	void *buffer;
+	dma_addr_t dma_addr;
+	int buffer_len;
+	u16 used;
+	u16 node_id;
+};
+
+struct skw_edma_chn {
+	struct skw_core *skw;
+	struct skw_lmac *lmac;
+	struct list_head node_list;
+	struct skw_edma_hdr *hdr;
+	struct skw_edma_node *current_node;
+	atomic_t nr_node;
+	dma_addr_t edma_hdr_pa;
+	u16 edma_hdr_size;
+	u16 n_pld_size;
+	u16 swtail;
+	u16 channel;
+	u16 max_node_num;
+	u16 tx_node_count;
+	u16 rx_node_count;
+	u16 direction;
+	atomic_t chn_refill;
+	spinlock_t edma_chan_lock;
+};
+
+#ifdef CONFIG_SWT6621S_EDMA
+int skw_edma_init(struct wiphy *wiphy);
+void skw_edma_deinit(struct wiphy *wiphy);
+int skw_edma_set_data(struct wiphy *wiphy, struct skw_edma_chn *edma,
+			void *data, int len);
+int skw_edma_tx(struct wiphy *wiphy, struct skw_edma_chn *edma, int tx_len);
+int skw_edma_init_data_chan(void *priv, u8 lmac_id);
+int skw_edma_get_refill(void *priv, u8 lmac_id);
+void skw_edma_inc_refill(void *priv, u8 lmac_id);
+void skw_edma_dec_refill(void *priv, u8 lmac_id);
+bool skw_edma_is_txc_completed(struct skw_core *skw);
+void skw_edma_mask_irq(struct skw_core *skw, u8 lmac_id);
+void skw_edma_unmask_irq(struct skw_core *skw, u8 lmac_id);
+
+static inline u16 skw_edma_hdr_tail(struct skw_edma_chn *edma_chn)
+{
+	return (edma_chn->swtail + 1) % edma_chn->max_node_num;
+}
+#else
+static inline int skw_edma_init(struct wiphy *wiphy)
+{
+	return 0;
+}
+
+static inline void skw_edma_deinit(struct wiphy *wiphy)
+{
+}
+
+static inline int skw_edma_set_data(struct wiphy *wiphy,
+		struct skw_edma_chn *edma, void *data, int len)
+{
+	return 0;
+}
+
+static inline int skw_edma_tx(struct wiphy *wiphy,
+		struct skw_edma_chn *edma, int tx_len)
+{
+	return 0;
+}
+
+static inline int skw_edma_init_data_chan(void *priv, u8 lmac_id)
+{
+	return 0;
+}
+
+static inline int skw_edma_get_refill(void *priv, u8 lmac_id)
+{
+	return 0;
+}
+
+static inline void skw_edma_inc_refill(void *priv, u8 lmac_id)
+{
+}
+
+static inline void skw_edma_dec_refill(void *priv, u8 lmac_id)
+{
+}
+
+static inline u16 skw_edma_hdr_tail(struct skw_edma_chn *edma_chn)
+{
+	return (edma_chn->swtail + 1) % edma_chn->max_node_num;
+}
+
+static inline bool skw_edma_is_txc_completed(struct skw_core *skw)
+{
+	return true;
+}
+
+static inline void skw_edma_mask_irq(struct skw_core *skw, u8 lmac_id)
+{
+}
+
+static inline void skw_edma_unmask_irq(struct skw_core *skw, u8 lmac_id)
+{
+}
+
+#endif
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iface.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iface.c
new file mode 100755
index 0000000..db84efd
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iface.c
@@ -0,0 +1,1395 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include "skw_core.h"
+#include "skw_iface.h"
+#include "skw_rx.h"
+#include "skw_mlme.h"
+#include "skw_cfg80211.h"
+#include "skw_timer.h"
+#include "skw_tx.h"
+#include "skw_dfs.h"
+#include "trace.h"
+
+static inline const char *skw_width_name(enum SKW_CHAN_BW_INFO state)
+{
+	static const char * const wd_name[] = {"20M", "40M", "80M",
+					"80+80M", "160M"};
+
+	if (state >= ARRAY_SIZE(wd_name))
+		return "unknown";
+
+	return wd_name[state];
+}
+
+static inline const char *skw_80211_mode_name(enum skw_rate_info_flags mode)
+{
+	static const char * const md_name[] = {"legacy", "ieee80211n", "ieee80211ac", "ieee80211ax"};
+
+	if (mode >= ARRAY_SIZE(md_name))
+		return "unknown";
+
+	return md_name[mode];
+}
+
+static int skw_iface_show(struct seq_file *seq, void *data)
+{
+	u32 peer_idx_map, idx;
+	struct skw_peer_ctx *ctx;
+	struct skw_bss_cfg *bss = NULL;
+	struct net_device *ndev = seq->private;
+	struct skw_iface *iface = netdev_priv(ndev);
+	int i;
+
+	seq_puts(seq, "\n");
+	seq_printf(seq, "Iface: \t%s (id: %d)\n"
+			"    addr:  \t%pM\n"
+			"    mode:  \t%s\n"
+			"    cpu_id:  \t%d\n",
+			netdev_name(iface->ndev),
+			iface->id,
+			iface->addr,
+			skw_iftype_name(iface->wdev.iftype),
+			iface->cpu_id);
+
+	switch (iface->wdev.iftype) {
+	case NL80211_IFTYPE_STATION:
+		if (iface->flags & SKW_IFACE_FLAG_LEGACY_P2P_DEV)
+			break;
+
+		skw_fallthrough;
+
+	case NL80211_IFTYPE_P2P_CLIENT:
+		bss = &iface->sta.core.bss;
+		seq_printf(seq, "    state: \t%s\n"
+					"    connect width: %s\n",
+				skw_state_name(iface->sta.core.sm.state),
+				bss && bss->channel ? NL80211_BAND_2GHZ != bss->channel->band ? skw_width_name(bss->width) :
+				skw_width_name(min(bss->width, bss->ht_cap_chwidth)) : "");
+		break;
+
+	case NL80211_IFTYPE_AP:
+	case NL80211_IFTYPE_P2P_GO:
+		bss = &iface->sap.cfg;
+		seq_printf(seq, "    max sta: \t%d\n",
+				iface->sap.max_sta_allowed);
+		break;
+
+	default:
+		break;
+	}
+
+	seq_printf(seq, "\nBSS Info: %s\n", bss ? "" : "null");
+	if (bss) {
+		seq_printf(seq, "    SSID:  \t%s\n"
+				"    BSSID: \t%pM\n"
+				"    channel:\t%d\n"
+				"    width: \t%s\n",
+				bss->ssid,
+				bss->bssid,
+				bss->channel ? bss->channel->hw_value : -1,
+				skw_width_name(bss->width));
+
+	}
+
+	peer_idx_map = atomic_read(&iface->peer_map);
+
+	seq_printf(seq, "\nPEER Info: %s\n", peer_idx_map ? "" : "null");
+	while (peer_idx_map) {
+		idx = ffs(peer_idx_map) - 1;
+		SKW_CLEAR(peer_idx_map, BIT(idx));
+
+		ctx = &iface->skw->hw.lmac[iface->lmac_id].peer_ctx[idx];
+
+		mutex_lock(&ctx->lock);
+
+		if (ctx->peer) {
+			s16 rssi = ctx->peer->rx.rssi >> 3;
+
+			if (ctx->peer->rx.rssi & BIT(10))
+				rssi |= 0xff00;
+
+			seq_printf(seq, "    %pM (%d) %s\n",
+					ctx->peer->addr,
+					ctx->peer->idx,
+					skw_state_name(ctx->peer->sm.state));
+
+			seq_printf(seq, "        TX: tidmap: 0x%x, %s: %d, nss:%d, %s, psr: %d, tx_failed: %d\n"
+					"        RX: tidmap: 0x%x, %s: %d, nss:%d, %s\n"
+					"        rssi: %d, data: %d\n"
+					"        rx data band width :%s\n"
+					"        filter stats :\n",
+					ctx->peer->txba.bitmap,
+					ctx->peer->tx.rate.flags ?
+					 "mcs" : "legacy_rate",
+					ctx->peer->tx.rate.flags ?
+					 ctx->peer->tx.rate.mcs_idx :
+					 ctx->peer->tx.rate.legacy_rate,
+					 ctx->peer->tx.rate.nss,
+					 skw_80211_mode_name(ctx->peer->tx.rate.flags),
+					ctx->peer->tx.tx_psr,
+					ctx->peer->tx.tx_failed,
+					ctx->peer->rx_tid_map,
+					ctx->peer->rx.rate.flags ?
+					 "mcs" : "legacy_rate",
+					ctx->peer->rx.rate.flags ?
+					 ctx->peer->rx.rate.mcs_idx :
+					 ctx->peer->rx.rate.legacy_rate,
+					 ctx->peer->rx.rate.nss,
+					 skw_80211_mode_name(ctx->peer->rx.rate.flags),
+					ctx->peer->tx.rssi,
+					rssi, skw_width_name(ctx->peer->rx.rate.bw));
+			seq_puts(seq, "            fliter:");
+
+			for (i = 0; i < 35; i++)
+				seq_printf(seq, "%d ", ctx->peer->rx.filter_cnt[i]);
+
+			seq_puts(seq, "\n            filter drop:");
+
+			for (i = 0; i < 35; i++)
+				seq_printf(seq, "%d ", ctx->peer->rx.filter_drop_offload_cnt[i]);
+
+			seq_printf(seq, "\n        Channnel occupancy: tx: %d, rx_idle: %d\n",
+				 ctx->peer->tx.percent, ctx->peer->rx.percent);
+
+			seq_puts(seq, "\n");
+		}
+
+		mutex_unlock(&ctx->lock);
+	}
+
+	seq_puts(seq, "\nTXQ Info:\n");
+	seq_printf(seq, "    [VO]: stoped: %d, qlen: %d tx_cache:%d\n"
+			"    [VI]: stoped: %d, qlen: %d tx_cache:%d\n"
+			"    [BE]: stoped: %d, qlen: %d tx_cache:%d\n"
+			"    [BK]: stoped: %d, qlen: %d tx_cache:%d\n",
+			SKW_TXQ_STOPED(ndev, SKW_WMM_AC_VO),
+			skb_queue_len(&iface->txq[SKW_WMM_AC_VO]),
+			skb_queue_len(&iface->tx_cache[SKW_WMM_AC_VO]),
+			SKW_TXQ_STOPED(ndev, SKW_WMM_AC_VI),
+			skb_queue_len(&iface->txq[SKW_WMM_AC_VI]),
+			skb_queue_len(&iface->tx_cache[SKW_WMM_AC_VI]),
+			SKW_TXQ_STOPED(ndev, SKW_WMM_AC_BE),
+			skb_queue_len(&iface->txq[SKW_WMM_AC_BE]),
+			skb_queue_len(&iface->tx_cache[SKW_WMM_AC_BE]),
+			SKW_TXQ_STOPED(ndev, SKW_WMM_AC_BK),
+			skb_queue_len(&iface->txq[SKW_WMM_AC_BK]),
+			skb_queue_len(&iface->tx_cache[SKW_WMM_AC_BK]));
+
+	if (iface->skw->hw.bus != SKW_BUS_PCIE)
+		seq_printf(seq, "\nskw->rx_dat_q:%d\n", READ_ONCE(iface->skw->rx_dat_q.qlen));
+
+	return 0;
+}
+
+static int skw_iface_open(struct inode *inode, struct file *file)
+{
+	// return single_open(file, skw_iface_show, inode->i_private);
+	return single_open(file, skw_iface_show, skw_pde_data(inode));
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_iface_fops = {
+	.proc_open = skw_iface_open,
+	.proc_read = seq_read,
+	.proc_lseek = seq_lseek,
+	.proc_release = single_release,
+};
+#else
+static const struct file_operations skw_iface_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_iface_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+#endif
+
+/*
+ * skw_acl_allowed: check if sta is in acl list
+ * return: true - allowd to access
+ *         false - denied to access
+ */
+bool skw_acl_allowed(struct skw_iface *iface, u8 *addr)
+{
+	int i;
+	bool match = false;
+
+	if (!iface->sap.acl)
+		return true;
+
+	for (i = 0; i < iface->sap.acl->n_acl_entries; i++) {
+		u8 *mac = iface->sap.acl->mac_addrs[i].addr;
+
+		if (ether_addr_equal(addr, mac)) {
+			match = true;
+			break;
+		}
+	}
+
+	/* white list */
+	if (iface->sap.acl->acl_policy == NL80211_ACL_POLICY_DENY_UNLESS_LISTED)
+		return match;
+
+	return !match;
+}
+
+int skw_cmd_open_dev(struct wiphy *wiphy, int inst, const u8 *mac_addr,
+		enum nl80211_iftype type, u16 flags)
+{
+	int mode, ret;
+	struct skw_open_dev_param open_param;
+
+	skw_dbg("%s, inst: %d, mac: %pM, flags: 0x%x\n",
+		skw_iftype_name(type), inst, mac_addr, flags);
+
+	BUG_ON(!is_valid_ether_addr(mac_addr));
+
+	switch (type) {
+	case NL80211_IFTYPE_ADHOC:
+		mode = SKW_IBSS_MODE;
+		break;
+	case NL80211_IFTYPE_STATION:
+		mode = SKW_STA_MODE;
+		break;
+	case NL80211_IFTYPE_AP:
+		mode = SKW_AP_MODE;
+		break;
+	case NL80211_IFTYPE_P2P_CLIENT:
+		mode = SKW_GC_MODE;
+		break;
+	case NL80211_IFTYPE_P2P_GO:
+		mode = SKW_GO_MODE;
+		break;
+	case NL80211_IFTYPE_P2P_DEVICE:
+		mode = SKW_P2P_DEV_MODE;
+		break;
+	case NL80211_IFTYPE_MONITOR:
+		mode = SKW_MONITOR_MODE;
+		break;
+	default:
+		skw_err("iftype: %d not support\n", type);
+		return -EINVAL;
+	}
+
+	skw_ether_copy(open_param.mac_addr, mac_addr);
+	open_param.mode = mode;
+	open_param.flags = flags;
+
+#ifdef CONFIG_SWT6621S_OFFCHAN_TX
+	open_param.flags |= SKW_OPEN_FLAG_OFFCHAN_TX;
+#endif
+
+	ret = skw_msg_xmit(wiphy, inst, SKW_CMD_OPEN_DEV, &open_param,
+			   sizeof(open_param), NULL, 0);
+
+	return ret;
+}
+
+static int skw_cmd_close_dev(struct wiphy *wiphy, int dev_id)
+{
+	skw_dbg("dev id: %d\n", dev_id);
+
+	return skw_msg_xmit(wiphy, dev_id, SKW_CMD_CLOSE_DEV, NULL, 0, NULL, 0);
+}
+
+void skw_purge_survey_data(struct skw_iface *iface)
+{
+	struct skw_survey_info *sinfo = NULL;
+	LIST_HEAD(flush_list);
+
+	list_replace_init(&iface->survey_list, &flush_list);
+
+	while (!list_empty(&flush_list)) {
+		sinfo = list_first_entry(&flush_list,
+				 struct skw_survey_info, list);
+
+		list_del(&sinfo->list);
+		SKW_KFREE(sinfo);
+	}
+}
+
+void skw_iface_event_work(struct work_struct *work)
+{
+	struct sk_buff *skb;
+	struct skw_msg *msg_hdr;
+	struct skw_iface *iface;
+
+	iface = container_of(work, struct skw_iface, event_work.work);
+
+	while ((skb = skb_dequeue(&iface->event_work.qlist))) {
+		msg_hdr = (struct skw_msg *)skb->data;
+		skb_pull(skb, sizeof(*msg_hdr));
+
+		skw_event_handler(iface->skw, iface, msg_hdr,
+				  skb->data, skb->len, SKW_SKB_RXCB(skb));
+
+		kfree_skb(skb);
+	}
+}
+
+static int skw_add_vif(struct wiphy *wiphy, struct skw_iface *iface)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	skw_dbg("iface: 0x%x, bitmap: 0x%x\n", iface->id, skw->vif.bitmap);
+
+	if (iface->id == SKW_INVALID_ID)
+		return 0;
+
+	BUG_ON(skw->vif.iface[iface->id]);
+
+	spin_lock_bh(&skw->vif.lock);
+
+	skw->vif.iface[iface->id] = iface;
+
+	spin_unlock_bh(&skw->vif.lock);
+
+	return 0;
+}
+
+static void skw_del_vif(struct wiphy *wiphy, struct skw_iface *iface)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	if (!iface)
+		return;
+
+	skw_dbg("iface id: %d\n", iface->id);
+
+	BUG_ON(iface->id >= SKW_NR_IFACE);
+
+	spin_lock_bh(&skw->vif.lock);
+
+	skw->vif.iface[iface->id] = NULL;
+
+	spin_unlock_bh(&skw->vif.lock);
+}
+
+static int skw_alloc_inst(struct wiphy *wiphy, u8 id)
+{
+	int inst = SKW_INVALID_ID;
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	spin_lock_bh(&skw->vif.lock);
+
+	if (id == SKW_INVALID_ID) {
+		for (id = 0; id < SKW_NR_IFACE; id++) {
+			if (!(skw->vif.bitmap & BIT(id))) {
+				inst = id;
+				break;
+			}
+		}
+	} else if ((id != (id & 0xf)) || (skw->vif.bitmap & BIT(id))) {
+		inst = SKW_INVALID_ID;
+	} else {
+		inst = id;
+	}
+
+	if (inst != SKW_INVALID_ID)
+		SKW_SET(skw->vif.bitmap, BIT(id));
+
+	spin_unlock_bh(&skw->vif.lock);
+
+	return inst;
+}
+
+static void skw_release_inst(struct wiphy *wiphy, int id)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	if (id != (id & 0xf))
+		return;
+
+	spin_lock_bh(&skw->vif.lock);
+
+	SKW_CLEAR(skw->vif.bitmap, BIT(id));
+
+	spin_unlock_bh(&skw->vif.lock);
+}
+
+static void skw_sta_work(struct work_struct *wk)
+{
+	bool run_again = false;
+	bool connect_failed = false;
+	u64 cookie;
+	struct skw_iface *iface = container_of(wk, struct skw_iface, sta.work);
+	struct net_device *ndev = iface->ndev;
+	struct skw_sta_core *core = &iface->sta.core;
+	struct wiphy *wiphy = priv_to_wiphy(iface->skw);
+
+	skw_wdev_lock(&iface->wdev);
+
+	skw_dbg("inst: %d, state: %s, retry: %d jiff: %d step_start: %d ctx_start: %d auth_start: %d redo:%d\n",
+		core->sm.inst, skw_state_name(core->sm.state),
+		core->pending.retry, jiffies_to_msecs(jiffies),
+		jiffies_to_msecs(core->pending.step_start),
+		jiffies_to_msecs(core->pending.ctx_start),
+		jiffies_to_msecs(core->auth_start),
+		core->pending.redo);
+
+	if (time_after(jiffies, core->auth_start + SKW_AUTH_TIMEOUT + SKW_ASSOC_TIMEOUT))
+		connect_failed = true;
+
+	switch (core->sm.state) {
+	case SKW_STATE_AUTHED:
+		if (time_after(jiffies, core->pending.step_start + SKW_STEP_TIMEOUT)) {
+			iface->sta.report_deauth = false;
+
+			skw_sta_leave(wiphy, ndev, core->bss.bssid, 3, false);
+
+			if (iface->sta.sme_external)
+				skw_compat_auth_timeout(ndev, core->bss.bssid);
+			else
+				skw_disconnected(ndev, 3, NULL, 0, true, GFP_KERNEL);
+		} else {
+			run_again = true;
+		}
+
+		break;
+
+	case SKW_STATE_AUTHING:
+		if (time_after(jiffies, core->pending.step_start + SKW_STEP_TIMEOUT)) {
+			if (++core->pending.retry >= SKW_MAX_AUTH_RETRY_NUM &&
+				iface->wdev.iftype == NL80211_IFTYPE_STATION &&
+				core->pending.auth_type == NL80211_AUTHTYPE_OPEN_SYSTEM) {
+				if (time_after(jiffies, core->pending.ctx_start + core->pending.ctx_to) ||
+					core->pending.redo >= SKW_MAX_REAUTH_REDO_NUM) {
+					connect_failed = true;
+				} else {
+					if (core->sm.rty_state == SKW_RETRY_NONE)
+						core->sm.rty_state = SKW_RETRY_AUTH;
+
+					if (skw_mgmt_frame_with_reason(iface, core->bss.bssid,
+						&cookie, core->bss.bssid, core->bss.channel,
+						IEEE80211_STYPE_DEAUTH, WLAN_REASON_DEAUTH_LEAVING, false))
+						connect_failed = true;
+					else {
+						skw_set_state(&iface->sta.core.sm, SKW_STATE_NONE);
+						core->pending.redo++;
+
+						if (skw_msg_xmit_timeout(wiphy,
+							SKW_NDEV_ID(ndev),
+							SKW_CMD_AUTH,
+							core->pending.auth_cmd,
+							core->pending.auth_cmd_len,
+							NULL, 0, "SKW_CMD_AUTH",
+							msecs_to_jiffies(300), 0))
+							connect_failed = true;
+						else {
+							core->pending.retry = 0;
+							core->pending.step_start = jiffies;
+							skw_set_state(&iface->sta.core.sm, SKW_STATE_AUTHING);
+						}
+					}
+				}
+			} else if (core->pending.retry >= SKW_MAX_AUTH_RETRY_NUM)
+				connect_failed = true;
+			else {
+				skw_set_state(&core->sm, SKW_STATE_AUTHING);
+
+				if (skw_msg_xmit_timeout(wiphy,
+					SKW_NDEV_ID(ndev),
+					SKW_CMD_AUTH,
+					core->pending.auth_cmd,
+					core->pending.auth_cmd_len,
+					NULL, 0, "SKW_CMD_AUTH",
+					msecs_to_jiffies(300), 0))
+					connect_failed = true;
+			}
+		}
+
+		if (connect_failed) {
+			iface->sta.report_deauth = false;
+			skw_sta_leave(wiphy, ndev, core->bss.bssid, 3, false);
+
+			if (iface->sta.sme_external)
+				skw_compat_auth_timeout(ndev, core->bss.bssid);
+			else
+				skw_disconnected(ndev, 3, NULL, 0, true,
+					GFP_KERNEL);
+		} else {
+			run_again = true;
+		}
+
+		break;
+
+	case SKW_STATE_ASSOCING:
+		if (time_after(jiffies, core->pending.step_start + SKW_STEP_TIMEOUT)) {
+			if ((++core->pending.retry >= SKW_MAX_ASSOC_RETRY_NUM) &&
+				iface->wdev.iftype == NL80211_IFTYPE_STATION &&
+				core->pending.auth_type == NL80211_AUTHTYPE_OPEN_SYSTEM) {
+				if (time_after(jiffies, core->pending.ctx_start + core->pending.ctx_to) ||
+					core->pending.redo >= SKW_MAX_REAUTH_REDO_NUM) {
+					connect_failed = true;
+				} else {
+					core->sm.rty_state = SKW_RETRY_ASSOC;
+
+					if (skw_mgmt_frame_with_reason(iface, core->bss.bssid,
+						&cookie, core->bss.bssid, core->bss.channel,
+						IEEE80211_STYPE_DEAUTH, WLAN_REASON_DEAUTH_LEAVING, false))
+						connect_failed = true;
+					else {
+						skw_set_state(&iface->sta.core.sm, SKW_STATE_NONE);
+						core->pending.redo++;
+
+						if (skw_msg_xmit_timeout(wiphy,
+							SKW_NDEV_ID(ndev),
+							SKW_CMD_AUTH,
+							core->pending.auth_cmd,
+							core->pending.auth_cmd_len,
+							NULL, 0, "SKW_CMD_AUTH",
+							msecs_to_jiffies(300), 0))
+							connect_failed = true;
+						else {
+							core->pending.retry = 0;
+							core->pending.step_start = jiffies;
+							skw_set_state(&iface->sta.core.sm, SKW_STATE_AUTHING);
+						}
+					}
+				}
+
+			} else if (core->pending.retry >= SKW_MAX_ASSOC_RETRY_NUM)
+				connect_failed = true;
+			 else {
+				skw_set_state(&core->sm, SKW_STATE_ASSOCING);
+
+				if (skw_msg_xmit_timeout(wiphy,
+							 SKW_NDEV_ID(ndev),
+							 SKW_CMD_ASSOC,
+							 core->pending.assoc_cmd,
+							 core->pending.assoc_cmd_len,
+							 NULL, 0, "SKW_CMD_ASSOC",
+							 msecs_to_jiffies(300), 0))
+					connect_failed = true;
+			}
+		}
+
+		if (connect_failed) {
+			iface->sta.report_deauth = false;
+			skw_sta_leave(wiphy, ndev, core->bss.bssid, 3, false);
+
+			if (iface->sta.sme_external)
+				skw_compat_assoc_failure(ndev, core->cbss, true);
+			else
+				skw_disconnected(ndev, 3, NULL, 0, true, GFP_KERNEL);
+		} else {
+			run_again = true;
+		}
+
+		break;
+
+	default:
+		break;
+	}
+
+	skw_dbg("state: %s, connect failed: %d, run again: %d\n",
+		skw_state_name(core->sm.state), connect_failed, run_again);
+
+	if (run_again)
+		skw_set_sta_timer(core, SKW_STEP_TIMEOUT);
+
+	skw_wdev_unlock(&iface->wdev);
+}
+
+static void skw_sta_timer(struct timer_list *t)
+{
+	struct skw_iface *iface = skw_from_timer(iface, t, sta.core.timer);
+
+	queue_work(iface->skw->event_wq, &iface->sta.work);
+}
+
+void skw_set_sta_timer(struct skw_sta_core *core, unsigned long timeout)
+{
+	if (!timer_pending(&core->timer))
+		mod_timer(&core->timer, jiffies + timeout);
+}
+
+static int skw_mode_init(struct wiphy *wiphy, struct skw_iface *iface,
+			enum nl80211_iftype type, int id)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct skw_sta_core *core = &iface->sta.core;
+
+	switch (type) {
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_P2P_CLIENT:
+		memset(&iface->sta, 0x0, sizeof(iface->sta));
+
+		iface->sta.sme_external = true;
+		core->bss.ctx_idx = SKW_INVALID_ID;
+
+		mutex_init(&core->lock);
+		core->pending.auth_cmd = SKW_ZALLOC(SKW_2K_SIZE, GFP_KERNEL);
+		if (!core->pending.auth_cmd)
+			return -ENOMEM;
+
+		core->pending.assoc_cmd = SKW_ZALLOC(SKW_2K_SIZE, GFP_KERNEL);
+		if (!core->pending.assoc_cmd) {
+			SKW_KFREE(core->pending.auth_cmd);
+			return -ENOMEM;
+		}
+
+		core->assoc_req_ie = SKW_ZALLOC(SKW_2K_SIZE, GFP_KERNEL);
+		if (!core->assoc_req_ie) {
+			SKW_KFREE(core->pending.assoc_cmd);
+			SKW_KFREE(core->pending.auth_cmd);
+			return -ENOMEM;
+		}
+
+		core->sm.inst = id;
+		core->sm.iface_iftype = type;
+		core->sm.state = SKW_STATE_NONE;
+		core->sm.addr = core->bss.bssid;
+
+		INIT_WORK(&iface->sta.work, skw_sta_work);
+		skw_compat_setup_timer(&core->timer, skw_sta_timer);
+
+		iface->sta.conn = NULL;
+		spin_lock_init(&iface->sta.roam_data.lock);
+
+		if (!(test_bit(SKW_FLAG_STA_SME_EXTERNAL, &skw->flags))) {
+			iface->sta.sme_external = false;
+
+			iface->sta.conn = SKW_ZALLOC(sizeof(*iface->sta.conn),
+						  GFP_KERNEL);
+			if (!iface->sta.conn) {
+				iface->sta.conn = NULL;
+				SKW_KFREE(core->pending.assoc_cmd);
+				SKW_KFREE(core->pending.auth_cmd);
+				SKW_KFREE(core->assoc_req_ie);
+
+				return -ENOMEM;
+			}
+
+			mutex_init(&iface->sta.conn->lock);
+			iface->sta.conn->channel = NULL;
+
+			iface->sta.conn->assoc_ie = SKW_ZALLOC(SKW_2K_SIZE,
+							GFP_KERNEL);
+			if (!iface->sta.conn->assoc_ie) {
+				SKW_KFREE(core->pending.assoc_cmd);
+				SKW_KFREE(core->pending.auth_cmd);
+				SKW_KFREE(core->assoc_req_ie);
+				SKW_KFREE(iface->sta.conn);
+
+				return -ENOMEM;
+			}
+		}
+
+		break;
+
+	case NL80211_IFTYPE_AP:
+	case NL80211_IFTYPE_P2P_GO:
+		memset(&iface->sap, 0x0, sizeof(iface->sap));
+
+		skw_list_init(&iface->sap.mlme_client_list);
+		iface->sap.max_sta_allowed = skw->fw.max_num_sta;
+
+		skw_dfs_init(wiphy, iface->ndev);
+
+		if (test_bit(SKW_FLAG_SAP_SME_EXTERNAL, &skw->flags))
+			iface->sap.sme_external = true;
+
+		iface->sap.probe_resp = SKW_ZALLOC(SKW_2K_SIZE, GFP_KERNEL);
+		if (!iface->sap.probe_resp)
+			return -ENOMEM;
+
+		memset(&iface->buf_keys, 0, sizeof(iface->buf_keys));
+		iface->buf_keys_idx = 0;
+		SKW_CLEAR(iface->flags, SKW_IFACE_FLAG_BUF_KEY);
+		SKW_CLEAR(iface->flags, SKW_IFACE_FLAG_AP_STARTED);
+
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static void skw_mode_deinit(struct wiphy *wiphy, struct skw_iface *iface,
+			enum nl80211_iftype iftype)
+{
+	struct skw_core *skw = iface->skw;
+	struct skw_peer_ctx *ctx;
+
+	switch (iftype) {
+	case NL80211_IFTYPE_AP:
+	case NL80211_IFTYPE_P2P_GO:
+		skw_dfs_deinit(wiphy, iface->ndev);
+
+		SKW_KFREE(iface->sap.acl);
+		SKW_KFREE(iface->sap.cfg.ht_cap);
+		SKW_KFREE(iface->sap.cfg.vht_cap);
+		SKW_KFREE(iface->sap.probe_resp);
+
+		break;
+
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_P2P_CLIENT:
+		if (iface->flags & SKW_IFACE_FLAG_LEGACY_P2P_DEV)
+			break;
+
+		if (iface->sta.conn) {
+			SKW_KFREE(iface->sta.conn->assoc_ie);
+			SKW_KFREE(iface->sta.conn);
+		}
+
+		del_timer_sync(&iface->sta.core.timer);
+		cancel_work_sync(&iface->sta.work);
+
+		skw_set_state(&iface->sta.core.sm, SKW_STATE_NONE);
+		SKW_KFREE(iface->sta.core.pending.auth_cmd);
+		SKW_KFREE(iface->sta.core.pending.assoc_cmd);
+		SKW_KFREE(iface->sta.core.assoc_req_ie);
+		ctx = skw_get_ctx(skw, iface->lmac_id, iface->sta.core.bss.ctx_idx);
+		skw_peer_ctx_bind(iface, ctx, NULL);
+		memset(&iface->sta.core.bss, 0, sizeof(struct skw_bss_cfg));
+		iface->sta.core.bss.ctx_idx = SKW_INVALID_ID;
+
+		break;
+
+	default:
+		break;
+	}
+}
+
+int skw_iface_setup(struct wiphy *wiphy, struct net_device *dev,
+		    struct skw_iface *iface, const u8 *addr,
+		    enum nl80211_iftype iftype, int id)
+{
+	int i, ret;
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	skw_dbg("%s, addr: %pM\n", skw_iftype_name(iftype), addr);
+
+	BUG_ON(!addr || !is_valid_ether_addr(addr));
+
+	iface->ndev = dev;
+	iface->wdev.wiphy = wiphy;
+	iface->skw = wiphy_priv(wiphy);
+	iface->default_multicast = -1;
+
+	mutex_init(&iface->lock);
+	atomic_set(&iface->peer_map, 0);
+	atomic_set(&iface->actived_ctx, 0);
+
+	INIT_LIST_HEAD(&iface->survey_list);
+
+	mutex_init(&iface->key_conf.lock);
+	for (i = 0; i < SKW_NUM_MAX_KEY; i++) {
+		iface->key_conf.installed_bitmap = 0;
+		RCU_INIT_POINTER(iface->key_conf.key[i], NULL);
+	}
+
+	skw_event_work_init(&iface->event_work, skw_iface_event_work);
+
+	for (i = 0; i < SKW_MAX_DEFRAG_ENTRY; i++) {
+		iface->frag[i].id = i;
+		iface->frag[i].tid = SKW_INVALID_ID;
+		skb_queue_head_init(&iface->frag[i].skb_list);
+	}
+
+	for (i = 0; i < SKW_WMM_AC_MAX + 1; i++) {
+		skb_queue_head_init(&iface->txq[i]);
+		skb_queue_head_init(&iface->tx_cache[i]);
+	}
+
+	ret = skw_mode_init(wiphy, iface, iftype, id);
+	if (ret) {
+		skw_err("init failed, iface: %d, iftype: %d, ret: %d\n",
+			id, iftype, ret);
+
+		return ret;
+	}
+
+	ret = skw_cmd_open_dev(wiphy, id, addr, iftype, 0);
+	if (ret) {
+		skw_err("open failed, iface: %d, iftype: %d, ret:%d\n",
+			id, iftype, ret);
+		goto iface_deinit;
+	}
+
+	spin_lock_bh(&skw->vif.lock);
+	SKW_SET(iface->flags, SKW_IFACE_FLAG_OPENED);
+	skw->vif.opened_dev++;
+	spin_unlock_bh(&skw->vif.lock);
+
+	iface->id = id;
+	iface->wdev.iftype = iftype;
+	skw_ether_copy(iface->addr, addr);
+	iface->cpu_id = -1;
+
+	return 0;
+
+iface_deinit:
+	skw_mode_deinit(wiphy, iface, iftype);
+
+	return ret;
+}
+
+void skw_purge_key_conf(struct skw_key_conf *conf)
+{
+	int idx;
+	struct skw_key *key;
+
+	if (!conf)
+		return;
+
+	mutex_lock(&conf->lock);
+
+	for (idx = 0; idx < SKW_NUM_MAX_KEY; idx++) {
+		key = rcu_dereference_protected(conf->key[idx],
+				lockdep_is_held(&conf->lock));
+
+		RCU_INIT_POINTER(conf->key[idx], NULL);
+		if (key)
+			kfree_rcu(key, rcu);
+	}
+
+	conf->flags = 0;
+	conf->installed_bitmap = 0;
+	conf->skw_cipher = 0;
+
+	mutex_unlock(&conf->lock);
+}
+
+int skw_iface_teardown(struct wiphy *wiphy, struct skw_iface *iface)
+{
+	int i, ret;
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	skw_dbg("iface id: %d\n", iface->id);
+
+	skw_scan_done(skw, iface, true);
+	skw_purge_survey_data(iface);
+
+	for (i = SKW_WMM_AC_VO; i < SKW_WMM_AC_MAX + 1; i++) {
+		skb_queue_purge(&iface->txq[i]);
+		skb_queue_purge(&iface->tx_cache[i]);
+	}
+
+	for (i = 0; i < SKW_MAX_DEFRAG_ENTRY; i++) {
+		skb_queue_purge(&iface->frag[i].skb_list);
+		iface->frag[i].tid = SKW_INVALID_ID;
+	}
+
+	skw_event_work_deinit(&iface->event_work);
+
+	skw_mode_deinit(wiphy, iface, iface->wdev.iftype);
+
+	skw_purge_key_conf(&iface->key_conf);
+
+	ret = skw_cmd_close_dev(wiphy, iface->id);
+	if (ret < 0)
+		return ret;
+
+	spin_lock_bh(&skw->vif.lock);
+
+	SKW_CLEAR(iface->flags, SKW_IFACE_FLAG_OPENED);
+	skw->vif.opened_dev--;
+	if (!skw->vif.opened_dev) {
+		for (i = 0; i < skw->hw.nr_lmac; i++) {
+			atomic_set(&skw->hw.lmac[i].fw_credit, 0);
+			skw->rx_packets = 0;
+			skw->tx_packets = 0;
+		}
+	}
+
+	spin_unlock_bh(&skw->vif.lock);
+
+	return 0;
+}
+
+struct skw_iface *skw_add_iface(struct wiphy *wiphy, const char *name,
+				enum nl80211_iftype iftype, u8 *mac,
+				u8 id, bool need_ndev)
+{
+	u8 *addr;
+	int priv_size, ret;
+	struct skw_iface *iface;
+	struct net_device *ndev = NULL;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	int inst = skw_alloc_inst(wiphy, id);
+
+	skw_info("%s, inst: %d, mac: %pM, bitmap: 0x%x\n",
+		 skw_iftype_name(iftype), inst, mac, skw->vif.bitmap);
+
+	if (inst == SKW_INVALID_ID) {
+		skw_err("invalid inst: %d, bitmap: 0x%x\n",
+			inst, skw->vif.bitmap);
+
+		return ERR_PTR(-EINVAL);
+	}
+
+	priv_size = sizeof(struct skw_iface);
+	if (need_ndev) {
+		ndev = alloc_netdev_mqs(priv_size, name,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
+					NET_NAME_ENUM,
+#endif
+					ether_setup, SKW_WMM_AC_MAX, 1);
+
+		if (!ndev) {
+			skw_err("alloc netdev failed, iftype: %d\n", iftype);
+			skw_release_inst(wiphy, inst);
+
+			return ERR_PTR(-ENOMEM);
+		}
+
+		iface = netdev_priv(ndev);
+	} else {
+		iface = SKW_ZALLOC(priv_size, GFP_KERNEL);
+		if (!iface) {
+			skw_release_inst(wiphy, inst);
+			return ERR_PTR(-ENOMEM);
+		}
+	}
+
+	if (mac && is_valid_ether_addr(mac))
+		addr = mac;
+	else
+		addr = wiphy->addresses[inst].addr;
+
+	ret = skw_iface_setup(wiphy, ndev, iface, addr, iftype, inst);
+	if (ret) {
+		skw_err("iface setup failed, iftype: %d, ret: %d\n",
+			iftype, ret);
+
+		goto free_iface;
+	}
+
+	skw_add_vif(wiphy, iface);
+
+	if (ndev) {
+		ret = skw_netdev_init(wiphy, ndev, addr);
+		if (ret) {
+			skw_err("init netdev failed\n");
+			goto iface_teardown;
+		}
+
+		ret = skw_register_netdevice(ndev);
+		if (ret) {
+			skw_err("register netdev failed\n");
+			// free_percpu(ndev->tstats);
+			goto ndev_teardown;
+		}
+
+		iface->procfs = skw_procfs_file(SKW_WIPHY_PENTRY(wiphy),
+						netdev_name(ndev), 0444,
+						&skw_iface_fops, ndev);
+	} else {
+		skw_ether_copy(iface->wdev.address, addr);
+	}
+
+	return iface;
+
+ndev_teardown:
+	skw_netdev_deinit(iface->ndev);
+iface_teardown:
+	skw_iface_teardown(wiphy, iface);
+	skw_del_vif(wiphy, iface);
+
+free_iface:
+	if (ndev)
+		free_netdev(ndev);
+	else
+		SKW_KFREE(iface);
+
+	skw_release_inst(wiphy, inst);
+
+	return ERR_PTR(-EBUSY);
+}
+
+int skw_del_iface(struct wiphy *wiphy, struct skw_iface *iface)
+{
+	if (!iface)
+		return 0;
+
+	ASSERT_RTNL();
+
+	skw_dbg("iftype = %d, iface id: %d\n", iface->wdev.iftype, iface->id);
+
+	skw_iface_teardown(wiphy, iface);
+	skw_del_vif(wiphy, iface);
+	skw_release_inst(wiphy, iface->id);
+
+	if (iface->ndev) {
+		proc_remove(iface->procfs);
+		skw_netdev_deinit(iface->ndev);
+		skw_unregister_netdevice(iface->ndev);
+	} else if (iface->wdev.iftype == NL80211_IFTYPE_P2P_DEVICE) {
+		cfg80211_unregister_wdev(&iface->wdev);
+		SKW_KFREE(iface);
+	}
+
+	return 0;
+}
+
+struct skw_peer *skw_peer_alloc(void)
+{
+	int len;
+
+	len = ALIGN(sizeof(struct skw_peer), SKW_PEER_ALIGN);
+	len += sizeof(struct skw_ctx_entry);
+
+	return SKW_ZALLOC(len, GFP_KERNEL);
+}
+
+static void skw_peer_release(struct rcu_head *head)
+{
+	struct skw_ctx_entry *entry;
+
+	entry = container_of(head, struct skw_ctx_entry, rcu);
+
+	// NOTE: DO NOT USE SKW_FREE HERE
+	kfree(entry->peer);
+}
+
+void skw_peer_free(struct skw_peer *peer)
+{
+	int i;
+	struct skw_ctx_entry *entry;
+
+	if (!peer)
+		return;
+
+	for (i = 0; i < SKW_NR_TID; i++)
+		skw_del_tid_rx(peer, i);
+
+	skw_purge_key_conf(&peer->ptk_conf);
+	skw_purge_key_conf(&peer->gtk_conf);
+
+	entry = skw_ctx_entry(peer);
+
+#ifdef CONFIG_SWT6621S_GKI_DRV
+	skw_call_rcu(peer->iface->skw, &entry->rcu, skw_peer_release);
+#else
+	call_rcu(&entry->rcu, skw_peer_release);
+#endif
+}
+
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0)
+static void skw_reorder_timeout(struct timer_list *timer)
+#else
+static void skw_reorder_timeout(unsigned long timer)
+#endif
+{
+	struct skw_reorder_rx *reorder;
+
+	reorder = container_of((void *)timer, struct skw_reorder_rx, timer);
+
+	skw_dbg("tid: %d, expired sn: %d\n", reorder->tid, reorder->expired.sn);
+
+	if (atomic_read(&reorder->ref_cnt) != reorder->expired.ref_cnt)
+		return;
+
+	trace_skw_rx_reorder_timeout(reorder->inst, reorder->peer_idx,
+				reorder->tid, reorder->expired.sn);
+
+	spin_lock_bh(&reorder->todo.lock);
+
+	if (!reorder->todo.actived) {
+		reorder->todo.actived = true;
+		reorder->todo.seq = reorder->expired.sn;
+		reorder->todo.reason = SKW_RELEASE_EXPIRED;
+		INIT_LIST_HEAD(&reorder->todo.list);
+
+		skw_list_add(&reorder->skw->rx_todo_list, &reorder->todo.list);
+	}
+
+	spin_unlock_bh(&reorder->todo.lock);
+
+	skw_wakeup_rx(reorder->skw);
+}
+
+void skw_peer_init(struct skw_peer *peer, const u8 *addr, int idx)
+{
+	int i;
+	struct skw_ctx_entry *entry;
+
+	if (WARN_ON(!peer))
+		return;
+
+	if (idx >= SKW_MAX_PEER_SUPPORT)
+		peer->flags |= SKW_PEER_FLAG_BAD_ID;
+
+	if (!addr)
+		peer->flags |= SKW_PEER_FLAG_BAD_ADDR;
+
+	atomic_set(&peer->rx_filter, 0);
+	mutex_init(&peer->ptk_conf.lock);
+	mutex_init(&peer->gtk_conf.lock);
+
+	for (i = 0; i < SKW_NR_TID; i++) {
+		atomic_set(&peer->reorder[i].ref_cnt, 0);
+		skw_compat_setup_timer(&peer->reorder[i].timer, skw_reorder_timeout);
+		INIT_LIST_HEAD(&peer->reorder[i].todo.list);
+		spin_lock_init(&peer->reorder[i].todo.lock);
+		spin_lock_init(&peer->reorder[i].lock);
+	}
+
+	peer->idx = idx;
+	peer->iface = NULL;
+	peer->sm.addr = peer->addr;
+	peer->sm.state = SKW_STATE_NONE;
+
+	entry = skw_ctx_entry(peer);
+	entry->peer = peer;
+	entry->idx = idx;
+
+	if (addr) {
+		skw_ether_copy(entry->addr, addr);
+		skw_ether_copy(peer->addr, addr);
+	}
+}
+
+void __skw_peer_ctx_transmit(struct skw_peer_ctx *ctx, bool enable)
+{
+	struct skw_ctx_entry *entry;
+
+	if (WARN_ON(!ctx))
+		return;
+
+	lockdep_assert_held(&ctx->lock);
+
+	if (enable) {
+		if (WARN_ON(!ctx->peer || ctx->peer->flags))
+			return;
+
+		entry = skw_ctx_entry(ctx->peer);
+		rcu_assign_pointer(ctx->entry, entry);
+		atomic_inc(&ctx->peer->iface->actived_ctx);
+		SKW_SET(ctx->peer->flags, SKW_PEER_FLAG_ACTIVE);
+
+	} else {
+		entry = rcu_dereference_protected(ctx->entry,
+				lockdep_is_held(&ctx->lock));
+		if (entry) {
+			atomic_dec(&entry->peer->iface->actived_ctx);
+			SKW_CLEAR(entry->peer->flags, SKW_PEER_FLAG_ACTIVE);
+		}
+
+		RCU_INIT_POINTER(ctx->entry, NULL);
+	}
+}
+
+void skw_peer_ctx_transmit(struct skw_peer_ctx *ctx, bool enable)
+{
+	if (!ctx)
+		return;
+
+	skw_peer_ctx_lock(ctx);
+	__skw_peer_ctx_transmit(ctx, enable);
+	skw_peer_ctx_unlock(ctx);
+}
+
+int __skw_peer_ctx_bind(struct skw_iface *iface, struct skw_peer_ctx *ctx,
+			struct skw_peer *peer)
+{
+	if (WARN_ON(!iface || !ctx))
+		return -EINVAL;
+
+	lockdep_assert_held(&ctx->lock);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
+	atomic_and(~BIT(ctx->idx), &iface->peer_map);
+#else
+	atomic_set(&iface->peer_map, atomic_read(&iface->peer_map) & (~BIT(ctx->idx)));
+#endif
+
+	skw_peer_free(ctx->peer);
+	ctx->peer = NULL;
+
+	if (peer) {
+		peer->iface = iface;
+		peer->sm.inst = iface->id;
+		peer->sm.addr = peer->addr;
+		peer->sm.iface_iftype = iface->wdev.iftype;
+		ctx->peer = peer;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
+		atomic_or(BIT(ctx->idx), &iface->peer_map);
+#else
+		atomic_set(&iface->peer_map, atomic_read(&iface->peer_map) | BIT(ctx->idx));
+#endif
+	}
+
+	return 0;
+}
+
+int skw_peer_ctx_bind(struct skw_iface *iface, struct skw_peer_ctx *ctx,
+			struct skw_peer *peer)
+{
+	int ret;
+
+	if (!iface || !ctx)
+		return -EINVAL;
+
+	skw_dbg("ctx: %d, %s\n", ctx->idx, peer ? "bind" : "unbind");
+
+	mutex_lock(&ctx->lock);
+	ret = __skw_peer_ctx_bind(iface, ctx, peer);
+	mutex_unlock(&ctx->lock);
+
+	return ret;
+}
+
+struct skw_peer_ctx *skw_peer_ctx(struct skw_iface *iface, const u8 *mac)
+{
+	int idx;
+	u32 peer_idx_map;
+	struct skw_peer_ctx *ctx = NULL;
+	struct skw_core *skw = iface->skw;
+
+	peer_idx_map = atomic_read(&iface->peer_map);
+
+	if (!peer_idx_map || !mac)
+		return NULL;
+
+	while (peer_idx_map) {
+
+		idx = ffs(peer_idx_map) - 1;
+
+		SKW_CLEAR(peer_idx_map, BIT(idx));
+
+		ctx = &skw->hw.lmac[iface->lmac_id].peer_ctx[idx];
+		if (!ctx)
+			continue;
+
+		mutex_lock(&ctx->lock);
+
+		if (ctx->peer && ether_addr_equal(mac, ctx->peer->addr)) {
+			mutex_unlock(&ctx->lock);
+			return ctx;
+		}
+
+		mutex_unlock(&ctx->lock);
+	}
+
+	return NULL;
+}
+
+void skw_iface_set_wmm_capa(struct skw_iface *iface, const u8 *ies, size_t len)
+{
+	int i, j, tmp;
+	struct skw_wmm *wmm;
+	int ac[4] = {-1, -1, -1, -1};
+	unsigned int oui = SKW_OUI(0x00, 0x50, 0xF2);
+
+#define SKW_WMM_SUBTYPE     2
+#define SKW_WMM_ACM         BIT(4)
+
+	wmm = (void *)cfg80211_find_vendor_ie(oui, SKW_WMM_SUBTYPE, ies, len);
+	if (!wmm)
+		goto default_wmm;
+
+	if (wmm->version != 1)
+		goto default_wmm;
+
+	iface->wmm.qos_enabled = true;
+
+	for (i = 3; i >= 0; i--) {
+		for (tmp = i, j = 0; j < 4; j++) {
+			int id = ac[j];
+
+			if (id < 0)
+				break;
+
+			if (wmm->ac[id].aifsn > wmm->ac[tmp].aifsn) {
+				tmp = id;
+				ac[j] = i;
+			}
+		}
+
+		if (j < 4)
+			ac[j] = tmp;
+	}
+
+	for (i = 0; i < 4; i++) {
+		int aci = ac[i];
+
+		switch (aci) {
+		case 0:
+			iface->wmm.ac[i].aci = SKW_WMM_AC_BE;
+			if (wmm->ac[aci].acm)
+				iface->wmm.acm |= BIT(SKW_WMM_AC_BE);
+
+			iface->wmm.factor[SKW_WMM_AC_BE] = SKW_WMM_AC_BE - i;
+			break;
+		case 1:
+			iface->wmm.ac[i].aci = SKW_WMM_AC_BK;
+			if (wmm->ac[aci].acm)
+				iface->wmm.acm |= BIT(SKW_WMM_AC_BK);
+
+			iface->wmm.factor[SKW_WMM_AC_BK] = SKW_WMM_AC_BK - i;
+			break;
+		case 2:
+			iface->wmm.ac[i].aci = SKW_WMM_AC_VI;
+			if (wmm->ac[aci].acm)
+				iface->wmm.acm |= BIT(SKW_WMM_AC_VI);
+
+			iface->wmm.factor[SKW_WMM_AC_VI] = (SKW_WMM_AC_VI - i) << 2;
+			break;
+		case 3:
+			iface->wmm.ac[i].aci = SKW_WMM_AC_VO;
+			if (wmm->ac[aci].acm)
+				iface->wmm.acm |= BIT(SKW_WMM_AC_VI);
+
+			iface->wmm.factor[SKW_WMM_AC_VO] = (SKW_WMM_AC_VO - i) << 1;
+			break;
+		default:
+			break;
+		}
+
+		iface->wmm.ac[i].aifsn = wmm->ac[aci].aifsn;
+		iface->wmm.ac[i].txop_limit = le16_to_cpu(wmm->ac[aci].txop_limit);
+
+		skw_dbg("aci: %d, aifsn: %d, txop_limit: %d, factor: %d\n",
+			iface->wmm.ac[i].aci, iface->wmm.ac[i].aifsn,
+			iface->wmm.ac[i].txop_limit, iface->wmm.factor[i]);
+	}
+
+	for (i = 0; i < SKW_WMM_AC_MAX; i++)
+		if (iface->wmm.factor[i] < 0)
+			iface->wmm.factor[i] = 0;
+
+	skw_dbg("wmm_acm: 0x%x\n", iface->wmm.acm);
+	return;
+
+default_wmm:
+	iface->wmm.acm = 0;
+
+	iface->wmm.ac[0].aci = 0;
+	iface->wmm.ac[0].aifsn = 2;
+	iface->wmm.ac[0].txop_limit = 47;
+
+	iface->wmm.ac[1].aci = 1;
+	iface->wmm.ac[1].aifsn = 2;
+	iface->wmm.ac[1].txop_limit = 94;
+
+	iface->wmm.ac[2].aci = 2;
+	iface->wmm.ac[2].aifsn = 3;
+	iface->wmm.ac[2].txop_limit = 0;
+
+	iface->wmm.ac[3].aci = 3;
+	iface->wmm.ac[3].aifsn = 7;
+	iface->wmm.ac[3].txop_limit = 0;
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iface.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iface.h
new file mode 100755
index 0000000..e46c949
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iface.h
@@ -0,0 +1,678 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_IFACE_H__
+#define __SKW_IFACE_H__
+
+#include <linux/ieee80211.h>
+#include <net/cfg80211.h>
+#include "skw_work.h"
+#include "skw_util.h"
+#include "skw_dfs.h"
+
+#ifndef IEEE80211_CCMP_PN_LEN
+#define IEEE80211_CCMP_PN_LEN		6
+#endif
+
+#define SKW_PN_LEN                      6
+#define SKW_NR_TID                      8
+#define SKW_MAX_DEFRAG_ENTRY            4
+
+/* enable 80211W */
+#define SKW_NUM_DEFAULT_KEY             4
+#define SKW_NUM_DEFAULT_MGMT_KEY        2
+
+/* SKW_NUM_DEFAULT_KEY + SKW_NUM_DEFAULT_MGMT_KEY */
+#define SKW_NUM_MAX_KEY                 6
+
+#define SKW_INVALID_ID                  0xff
+#define SKW_PEER_ALIGN                  32
+
+#define SKW_PEER_FLAG_TAINT             BIT(0)
+#define SKW_PEER_FLAG_BAD_ID            BIT(1)
+#define SKW_PEER_FLAG_BAD_ADDR          BIT(2)
+#define SKW_PEER_FLAG_ACTIVE            BIT(3)
+#define SKW_PEER_FLAG_DEAUTHED          BIT(4)
+
+#define SKW_IFACE_FLAG_LEGACY_P2P_DEV   BIT(0)
+#define SKW_IFACE_FLAG_DEAUTH           BIT(1)
+#define SKW_IFACE_FLAG_OPENED           BIT(2)
+#define SKW_IFACE_FLAG_AP_STARTED       BIT(3)
+#define SKW_IFACE_FLAG_BUF_KEY          BIT(4)
+
+#define SKW_IFACE_STA_ROAM_FLAG_CQM_LOW		BIT(0)
+/**
+ * enum SKW_STATES - STA state
+ *
+ * @SKW_STATE_NONE: STA exists without special state
+ * @SKW_STATE_AUTHING: STA is trying to authentiacate with a BSS
+ * @SKW_STATE_AUTHED: STA is authenticated
+ * @SKW_STATE_ASSOCING: STA is trying to assoc with a BSS
+ * @SKW_STATE_ASSOCED: STA is associated
+ * @SKW_STATE_COMPLETED, STA connection is compeleted
+ */
+enum  SKW_STATES {
+	SKW_STATE_NONE,
+	SKW_STATE_AUTHING,
+	SKW_STATE_AUTHED,
+	SKW_STATE_ASSOCING,
+	SKW_STATE_ASSOCED,
+	SKW_STATE_COMPLETED,
+};
+
+enum  SKW_STA_WORK_RETRY_STATE {
+	SKW_RETRY_NONE,
+	SKW_RETRY_AUTH,
+	SKW_RETRY_ASSOC,
+};
+
+enum SKW_WMM_AC {
+	SKW_WMM_AC_VO = 0,
+	SKW_WMM_AC_VI,
+	SKW_WMM_AC_BE,
+	SKW_WMM_AC_BK,
+	SKW_WMM_AC_MAX,
+};
+
+#define SKW_ACK_TXQ                      SKW_WMM_AC_MAX
+
+#define SKW_FRAG_STATUS_ACTIVE           BIT(0)
+#define SKW_FRAG_STATUS_CHK_PN           BIT(1)
+
+enum skw_wireless_mode {
+	SKW_WIRELESS_11B = 1,
+	SKW_WIRELESS_11G,
+	SKW_WIRELESS_11A,
+	SKW_WIRELESS_11N,
+	SKW_WIRELESS_11AC,
+	SKW_WIRELESS_11AX,
+	SKW_WIRELESS_11G_ONLY,
+	SKW_WIRELESS_11N_ONLY,
+};
+
+enum interface_mode {
+	SKW_NONE_MODE = 0,
+	SKW_STA_MODE = 1,
+	SKW_AP_MODE = 2,
+	SKW_GC_MODE = 3,
+	SKW_GO_MODE = 4,
+	SKW_P2P_DEV_MODE = 5,
+	SKW_IBSS_MODE = 6,
+	SKW_MONITOR_MODE = 7,
+
+	MAX_MODE_TYPE,
+};
+
+enum SKW_CHAN_BW_INFO {
+	SKW_CHAN_WIDTH_20,
+	SKW_CHAN_WIDTH_40,
+	SKW_CHAN_WIDTH_80,
+	SKW_CHAN_WIDTH_80P80,
+	SKW_CHAN_WIDTH_160,
+
+	SKW_CHAN_WIDTH_MAX,
+};
+
+enum skw_rate_info_flags {
+	SKW_RATE_INFO_FLAGS_LEGACY,
+	SKW_RATE_INFO_FLAGS_HT,
+	SKW_RATE_INFO_FLAGS_VHT,
+	SKW_RATE_INFO_FLAGS_HE,
+};
+
+#define SKW_OPEN_FLAG_OFFCHAN_TX         BIT(0)
+struct skw_open_dev_param {
+	u16 mode;
+	u16 flags; /* reference SKW_OPEN_FLAG_ */
+	u8 mac_addr[6];
+} __packed;
+
+struct skw_frag_entry {
+	u8 id;
+	u8 status; /* reference SKW_FRAG_STATUS */
+	u16 pending_len;
+	u8 tid;
+	u8 frag_num;
+	u16 sn;
+	unsigned long start;
+	struct sk_buff_head skb_list;
+
+	/* PN of the last fragment if CCMP was used */
+	u8 last_pn[IEEE80211_CCMP_PN_LEN];
+};
+
+struct skw_key {
+	struct rcu_head rcu;
+	u32 key_len;
+	u8 key_data[WLAN_MAX_KEY_LEN];
+	u8 rx_pn[IEEE80211_NUM_TIDS][SKW_PN_LEN];
+};
+
+#define SKW_KEY_FLAG_WEP_SHARE        BIT(0)
+#define SKW_KEY_FLAG_WEP_UNICAST      BIT(1)
+#define SKW_KEY_FLAG_WEP_MULTICAST    BIT(2)
+
+struct skw_key_conf {
+	u8 skw_cipher;
+	u8 installed_bitmap;
+	u8 flags; /* reference to SKW_KEY_FLAG_ */
+	u8 wep_idx;
+	struct mutex lock;
+	struct skw_key __rcu *key[SKW_NUM_MAX_KEY];
+};
+
+struct skw_tid_rx {
+	u16 win_start;
+	u16 win_size;
+	u32 stored_num;
+	int ref_cnt;
+	struct rcu_head rcu_head;
+	struct skw_reorder_rx *reorder;
+	struct sk_buff_head *reorder_buf;
+};
+
+struct skw_rx_todo {
+	spinlock_t lock;
+	struct list_head list;
+	u16 seq;
+	u16 reason;
+	bool actived;
+};
+
+struct skw_rx_timer {
+	u16 sn;
+	u16 resv;
+	int ref_cnt;
+};
+
+struct skw_reorder_rx {
+	u32 tid: 4;
+	u32 inst: 2;
+	u32 peer_idx: 5;
+	u32 resv: 21;
+
+	atomic_t ref_cnt;
+	struct skw_core *skw;
+	struct skw_peer *peer;
+	struct timer_list timer;
+	struct skw_rx_timer expired;
+
+	struct skw_rx_todo todo;
+
+	spinlock_t lock;
+	struct skw_tid_rx __rcu *tid_rx;
+};
+
+struct skw_ctx_entry {
+	u8 idx;
+	u8 padding;
+	u8 addr[ETH_ALEN];
+	struct rcu_head rcu;
+	struct skw_peer *peer;
+};
+
+#define SKW_SM_FLAG_SAE_RX_CONFIRM     BIT(0)
+
+struct skw_sm {
+	u8 *addr;
+	u8 inst;
+	u8 iface_iftype;
+	u16 flags; /* reference SKW_SM_FLAG_ */
+	enum SKW_STATES state;
+	enum SKW_STA_WORK_RETRY_STATE rty_state;
+};
+
+struct skw_txba_ctrl {
+	u16 bitmap;
+	u16 blacklist;
+	u8 tx_try[SKW_NR_TID];
+};
+
+enum skw_msdu_filter {
+	SKW_MSDU_FILTER_SUCCESS,
+	SKW_MSDU_FILTER_SNAP_MISMATCH,
+	SKW_MSDU_FILTER_ARP,
+	SKW_MSDU_FILTER_VLAN,
+	SKW_MSDU_FILTER_WAPI,
+	SKW_MSDU_FILTER_EAP = 5,
+	SKW_MSDU_FILTER_PPPOE,
+	SKW_MSDU_FILTER_TDLS,
+	SKW_MSDU_FILTER_DHCP = 11,
+	SKW_MSDU_FILTER_DHCPV6 = 12,
+};
+
+#define SKW_RX_FILTER_NONE      0
+#define SKW_RX_FILTER_SET       (BIT(SKW_MSDU_FILTER_EAP) | BIT(SKW_MSDU_FILTER_WAPI))
+
+#define SKW_RX_FILTER_EXCL      (BIT(SKW_MSDU_FILTER_EAP) |  \
+				 BIT(SKW_MSDU_FILTER_WAPI) | \
+				 BIT(SKW_MSDU_FILTER_ARP) |  \
+				 BIT(SKW_MSDU_FILTER_DHCP) | \
+				 BIT(SKW_MSDU_FILTER_DHCPV6))
+
+#define SKW_RX_FILTER_DBG       (BIT(SKW_MSDU_FILTER_EAP) |  \
+				 BIT(SKW_MSDU_FILTER_WAPI) | \
+				 BIT(SKW_MSDU_FILTER_ARP) |  \
+				 BIT(SKW_MSDU_FILTER_DHCP) | \
+				 BIT(SKW_MSDU_FILTER_DHCPV6))
+
+enum SKW_RX_MPDU_DESC_PPDUMODE {
+	SKW_PPDUMODE_11B_SHORT = 0,
+	SKW_PPDUMODE_11B_LONG,
+	SKW_PPDUMODE_11G,
+	SKW_PPDUMODE_HT_MIXED,
+	SKW_PPDUMODE_VHT_SU,
+	SKW_PPDUMODE_VHT_MU,
+	SKW_PPDUMODE_HE_SU,
+	SKW_PPDUMODE_HE_TB,
+	SKW_PPDUMODE_HE_ER_SU,
+	SKW_PPDUMODE_HE_MU,
+};
+
+struct skw_stats_info {
+	s16 rssi;
+	u64 pkts;
+	u64 bytes;
+	u64 drops;
+	u64 cal_time;
+	u64 cal_bytes;
+	u8  tx_psr;
+	u32 tx_failed;
+	u16 filter_cnt[35];
+	u16 filter_drop_offload_cnt[35];
+	u8 percent;
+	struct skw_rate rate;
+};
+
+struct skw_peer {
+	u8 idx;
+	u8 flags; /* reference SKW_PEER_FLAG_ */
+	u8 addr[ETH_ALEN];
+	u16 channel;
+	u16 rx_tid_map;
+	__be32 ip_addr;
+
+	atomic_t rx_filter;
+	struct skw_sm sm;
+	struct skw_iface *iface;
+	struct skw_key_conf ptk_conf, gtk_conf;
+
+	struct skw_txba_ctrl txba;
+	struct skw_reorder_rx reorder[SKW_NR_TID];
+	struct skw_stats_info tx, rx;
+};
+
+struct skw_bss_cfg {
+	u8 ssid[IEEE80211_MAX_SSID_LEN];
+	u8 ssid_len;
+	u8 ctx_idx;
+	u8 bssid[ETH_ALEN];
+
+	enum nl80211_auth_type auth_type;
+	enum SKW_CHAN_BW_INFO  width;
+	enum SKW_CHAN_BW_INFO  ht_cap_chwidth;
+	struct cfg80211_crypto_settings crypto;
+
+	struct ieee80211_channel *channel;
+	struct ieee80211_ht_cap *ht_cap;
+	struct ieee80211_vht_cap *vht_cap;
+};
+
+struct skw_survey_data {
+	u32 time;
+	u32 time_busy;
+	u32 time_ext_busy;
+	u8 chan;
+	u8 band;
+	s8 noise;
+	u8 resv;
+} __packed;
+
+struct skw_survey_info {
+	struct list_head list;
+	struct skw_survey_data data;
+};
+
+struct skw_ac_param {
+	u8 aifsn:4;
+	u8 acm:1;
+	u8 aci:2;
+	u8 recv:1;
+	u8 ecw;
+	u16 txop_limit;
+} __packed;
+
+struct skw_wmm {
+	u8 id;
+	u8 len;
+	u8 oui[3];
+	u8 type;
+	u8 sub_type;
+	u8 version;
+	u8 qos;
+	u8 resv;
+	struct skw_ac_param ac[SKW_WMM_AC_MAX];
+} __packed;
+
+struct skw_list {
+	int count;
+	spinlock_t lock;
+	struct list_head list;
+};
+
+struct skw_peer_ctx {
+	int idx;
+	struct mutex lock;
+	struct skw_peer *peer;
+	struct skw_ctx_entry __rcu *entry;
+};
+
+struct skw_iftype_ext_cap {
+	u8 iftype;
+	u8 ext_cap[10];
+	u8 ext_cap_len;
+};
+
+struct skw_ctx_pending {
+	unsigned long ctx_start;
+	unsigned long step_start;
+	unsigned long ctx_to;
+	u8 *auth_cmd;
+	int auth_cmd_len;
+	u8 *assoc_cmd;
+	int assoc_cmd_len;
+	int retry;
+	int redo;
+	enum nl80211_auth_type auth_type;
+};
+
+struct skw_sta_core {
+	struct mutex lock;
+	struct timer_list timer;
+	struct skw_ctx_pending pending;
+
+	struct skw_sm sm;
+	struct skw_bss_cfg bss;
+
+	unsigned long auth_start;
+
+	u8 *assoc_req_ie;
+	u32 assoc_req_ie_len;
+
+	struct cfg80211_bss *cbss;
+};
+
+struct skw_wmm_info {
+	u8 acm;
+	bool qos_enabled;
+	s8 factor[SKW_WMM_AC_MAX];
+	struct skw_ac_param ac[SKW_WMM_AC_MAX];
+};
+
+#define SKW_MAX_BUF_KEYS     4
+struct skw_key_params {
+	u8 mac_addr[ETH_ALEN];
+	u8 key_type;
+	u8 cipher_type;
+	u8 pn[6];
+	u8 key_id;
+	u8 key_len;
+	u8 key[WLAN_MAX_KEY_LEN];
+} __packed;
+
+
+#define SKW_AID_DWORD BITS_TO_LONGS(64)
+
+struct skw_monitor_dbg_iface {
+	u8 addr[ETH_ALEN];
+	struct net_device *ndev;
+	u32 frame_cnt;
+};
+
+struct skw_iface {
+	u8 id;
+	u8 lmac_id;
+	u8 addr[ETH_ALEN];
+
+	atomic_t peer_map;
+	atomic_t actived_ctx;
+
+	struct mutex lock;
+	struct skw_core *skw;
+	struct net_device *ndev;
+	struct wireless_dev wdev;
+	struct list_head survey_list;
+	struct skw_key_conf key_conf;
+	struct cfg80211_qos_map *qos_map;
+	struct skw_event_work event_work;
+	struct proc_dir_entry *procfs;
+	struct skw_wmm_info wmm;
+
+	u8 flags;  /* reference SKW_IFACE_FLAG_ */
+	u8 rand_mac_oui[3];
+	u8 buf_keys_idx;
+	s16 default_multicast;
+	u16 mgmt_frame_bitmap;
+	int cpu_id;
+
+	struct sk_buff_head txq[SKW_WMM_AC_MAX + 1];
+	struct sk_buff_head tx_cache[SKW_WMM_AC_MAX + 1];
+	struct skw_frag_entry frag[SKW_MAX_DEFRAG_ENTRY];
+	struct skw_key_params buf_keys[SKW_MAX_BUF_KEYS];
+
+	struct {
+		enum skw_wireless_mode wireless_mode;
+		u16 scan_band_filter;
+		u16 resv;
+	} extend;
+
+	union {
+		struct {
+			bool sme_external;
+			struct skw_bss_cfg cfg;
+
+			u8 max_sta_allowed;
+			struct cfg80211_acl_data *acl;
+
+			bool ht_required, vht_required;
+
+			/* sme external */
+			struct skw_list mlme_client_list;
+			unsigned long aid_map[SKW_AID_DWORD];
+
+			u8 *probe_resp;
+			size_t probe_resp_len;
+			int ap_isolate;
+
+			struct {
+				u32 cac_time_ms;
+				unsigned long flags;
+				struct delayed_work cac_work;
+			} dfs;
+		} sap;
+
+		struct {
+			bool sme_external;
+			bool is_roam_connect;
+			bool is_wep;
+			bool report_deauth;
+			struct skw_sta_core core;
+			struct work_struct work;
+			struct skw_connect_param *conn;
+
+			struct {
+				spinlock_t lock;
+				u8 flags;
+				u8 target_bssid[ETH_ALEN];
+				u8 target_chn;
+			} roam_data;
+
+			u16 last_seq_ctrl;
+		} sta;
+
+		struct {
+			u8 ssid[IEEE80211_MAX_SSID_LEN];
+			u8 bssid[ETH_ALEN];
+			u8 ssid_len;
+			u8 bw;
+			u16 flags;
+			bool joined;
+			u8 channel;
+			u8 band;
+			u16 beacon_int;
+			u32 center_freq1;
+			u32 center_freq2;
+			struct cfg80211_chan_def chandef;
+		} ibss;
+	};
+};
+
+bool skw_acl_allowed(struct skw_iface *iface, u8 *addr);
+
+static inline const char *skw_state_name(enum SKW_STATES state)
+{
+	static const char * const st_name[] = {"NONE", "AUTHING", "AUTHED",
+					"ASSOCING", "ASSOCED", "COMPLETED"};
+
+	if (state >= ARRAY_SIZE(st_name))
+		return "unknown";
+
+	return st_name[state];
+}
+
+static inline void skw_list_init(struct skw_list *list)
+{
+	spin_lock_init(&list->lock);
+	INIT_LIST_HEAD(&list->list);
+	list->count = 0;
+}
+
+static inline void skw_list_add(struct skw_list *list, struct list_head *entry)
+{
+	spin_lock_bh(&list->lock);
+	list_add_tail(entry, &list->list);
+	list->count++;
+	spin_unlock_bh(&list->lock);
+}
+
+static inline void skw_list_del(struct skw_list *list, struct list_head *entry)
+{
+	spin_lock_bh(&list->lock);
+	list_del(entry);
+	list->count--;
+	spin_unlock_bh(&list->lock);
+}
+
+static inline void *skw_ctx_entry(const struct skw_peer *peer)
+{
+	return (char *)peer + ALIGN(sizeof(struct skw_peer), SKW_PEER_ALIGN);
+}
+
+static inline bool is_skw_ap_mode(struct skw_iface *iface)
+{
+	return iface->wdev.iftype == NL80211_IFTYPE_AP ||
+	       iface->wdev.iftype == NL80211_IFTYPE_P2P_GO;
+}
+
+static inline bool is_skw_sta_mode(struct skw_iface *iface)
+{
+	return iface->wdev.iftype == NL80211_IFTYPE_STATION ||
+	       iface->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT;
+}
+
+static inline void skw_peer_ctx_lock(struct skw_peer_ctx *ctx)
+{
+	if (WARN_ON(!ctx))
+		return;
+
+	mutex_lock(&ctx->lock);
+}
+
+static inline void skw_peer_ctx_unlock(struct skw_peer_ctx *ctx)
+{
+	if (WARN_ON(!ctx))
+		return;
+
+	mutex_unlock(&ctx->lock);
+}
+#if 0
+static inline void skw_sta_lock(struct skw_sta_core *core)
+{
+	mutex_lock(&core->lock);
+}
+
+static inline void skw_sta_unlock(struct skw_sta_core *core)
+{
+	mutex_unlock(&core->lock);
+}
+#endif
+static inline void skw_sta_assert_lock(struct skw_sta_core *core)
+{
+	lockdep_assert_held(&core->lock);
+}
+
+static inline void skw_wdev_lock(struct wireless_dev *wdev)
+	__acquires(wdev)
+{
+	mutex_lock(&wdev->mtx);
+	__acquire(wdev->mtx);
+}
+
+static inline void skw_wdev_unlock(struct wireless_dev *wdev)
+	__releases(wdev)
+{
+	__release(wdev->mtx);
+	mutex_unlock(&wdev->mtx);
+}
+
+static inline void skw_wdev_assert_lock(struct skw_iface *iface)
+{
+	lockdep_assert_held(&iface->wdev.mtx);
+}
+
+struct skw_iface *skw_add_iface(struct wiphy *wiphy, const char *name,
+				enum nl80211_iftype iftype, u8 *mac,
+				u8 id, bool need_ndev);
+int skw_del_iface(struct wiphy *wiphy, struct skw_iface *iface);
+
+void skw_iface_set_wmm_capa(struct skw_iface *iface, const u8 *ies,
+					size_t ies_len);
+
+int skw_iface_setup(struct wiphy *wiphy, struct net_device *dev,
+		    struct skw_iface *iface, const u8 *addr,
+		    enum nl80211_iftype iftype, int id);
+
+int skw_iface_teardown(struct wiphy *wiphy, struct skw_iface *iface);
+
+int skw_cmd_open_dev(struct wiphy *wiphy, int inst, const u8 *mac_addr,
+		enum nl80211_iftype type, u16 flags);
+void skw_purge_survey_data(struct skw_iface *iface);
+void skw_ap_check_sta_throughput(void *data);
+void skw_set_sta_timer(struct skw_sta_core *core, unsigned long timeout);
+
+struct skw_peer *skw_peer_alloc(void);
+void skw_peer_init(struct skw_peer *peer, const u8 *addr, int idx);
+struct skw_peer_ctx *skw_peer_ctx(struct skw_iface *iface, const u8 *mac);
+void skw_peer_ctx_transmit(struct skw_peer_ctx *ctx, bool enable);
+void __skw_peer_ctx_transmit(struct skw_peer_ctx *ctx, bool enable);
+int skw_peer_ctx_bind(struct skw_iface *iface, struct skw_peer_ctx *ctx,
+			struct skw_peer *peer);
+int __skw_peer_ctx_bind(struct skw_iface *iface, struct skw_peer_ctx *ctx,
+			struct skw_peer *peer);
+void skw_peer_free(struct skw_peer *peer);
+void skw_purge_key_conf(struct skw_key_conf *conf);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iw.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iw.c
new file mode 100755
index 0000000..fdb2dc2
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iw.c
@@ -0,0 +1,4109 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <net/iw_handler.h>
+#include <linux/udp.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <net/cfg80211-wext.h>
+
+#include "skw_core.h"
+#include "skw_cfg80211.h"
+#include "skw_iface.h"
+#include "skw_iw.h"
+#include "skw_log.h"
+
+static int skw_iw_commit(struct net_device *dev, struct iw_request_info *info,
+			 union iwreq_data *wrqu, char *extra)
+{
+	skw_dbg("traced\n");
+
+	return 0;
+}
+
+static int skw_iw_get_name(struct net_device *dev, struct iw_request_info *info,
+			   union iwreq_data *wrqu, char *extra)
+{
+	skw_dbg("traced\n");
+
+	return 0;
+}
+
+static int skw_iw_set_freq(struct net_device *dev, struct iw_request_info *info,
+			   union iwreq_data *wrqu, char *extra)
+{
+	skw_dbg("traced\n");
+
+	return 0;
+}
+
+static int skw_iw_get_freq(struct net_device *dev, struct iw_request_info *info,
+			   union iwreq_data *wrqu, char *extra)
+{
+	skw_dbg("traced\n");
+
+	return 0;
+}
+
+static int skw_iw_set_mode(struct net_device *dev, struct iw_request_info *info,
+			   union iwreq_data *wrqu, char *extra)
+{
+	skw_dbg("traced\n");
+
+	return 0;
+}
+
+static int skw_iw_get_mode(struct net_device *dev, struct iw_request_info *info,
+			   union iwreq_data *wrqu, char *extra)
+{
+	skw_dbg("traced\n");
+
+	return 0;
+}
+
+static struct iw_statistics *skw_get_wireless_stats(struct net_device *dev)
+{
+	skw_dbg("traced\n");
+
+	return NULL;
+}
+
+static const iw_handler skw_iw_standard_handlers[] = {
+	IW_HANDLER(SIOCSIWCOMMIT, (iw_handler)skw_iw_commit),
+	IW_HANDLER(SIOCGIWNAME, (iw_handler)skw_iw_get_name),
+	IW_HANDLER(SIOCSIWFREQ, (iw_handler)skw_iw_set_freq),
+	IW_HANDLER(SIOCGIWFREQ, (iw_handler)skw_iw_get_freq),
+	IW_HANDLER(SIOCSIWMODE,	(iw_handler)skw_iw_set_mode),
+	IW_HANDLER(SIOCGIWMODE,	(iw_handler)skw_iw_get_mode),
+#ifdef CONFIG_CFG80211_WEXT_EXPORT
+	IW_HANDLER(SIOCGIWRANGE, (iw_handler)cfg80211_wext_giwrange),
+	IW_HANDLER(SIOCSIWSCAN,	(iw_handler)cfg80211_wext_siwscan),
+	IW_HANDLER(SIOCGIWSCAN,	(iw_handler)cfg80211_wext_giwscan),
+#endif
+};
+
+#ifdef CONFIG_WEXT_PRIV
+
+#define SKW_SET_LEN_64                  64
+#define SKW_SET_LEN_128                 128
+#define SKW_SET_LEN_256                 256
+#define SKW_SET_LEN_512                 512
+#define SKW_GET_LEN_512                 512
+#define SKW_SET_LEN_1024                1024
+#define SKW_GET_LEN_1024                1024
+#define SKW_KEEP_BUF_SIZE               1024
+#define SKW_IW_WOW_BUF_SIZE             1024
+
+/* max to 16 commands */
+#define SKW_IW_PRIV_SET                (SIOCIWFIRSTPRIV + 1)
+#define SKW_IW_PRIV_GET                (SIOCIWFIRSTPRIV + 3)
+#define SKW_IW_PRIV_AT                 (SIOCIWFIRSTPRIV + 5)
+#define SKW_IW_PRIV_80211MODE          (SIOCIWFIRSTPRIV + 6)
+#define SKW_IW_PRIV_GET_80211MODE      (SIOCIWFIRSTPRIV + 7)
+#define SKW_IW_PRIV_KEEP_ALIVE         (SIOCIWFIRSTPRIV + 8)
+#define SKW_IW_PRIV_WOW_FILTER         (SIOCIWFIRSTPRIV + 9)
+
+#define SKW_IW_PRIV_LAST               SIOCIWLASTPRIV
+
+static struct skw_keep_active_setup kp_set = {0,};
+static u8 skw_wow_flted[256];
+
+static int skw_keep_alive_add_checksum(u8 *buff, u32 len)
+{
+	u8 *ptr = buff;
+	struct iphdr *ip;
+	struct udphdr *udp;
+	__sum16 sum;
+	__wsum sum1;
+	u32 udp_len;
+
+	ptr += sizeof(struct ethhdr);
+	ip = (struct iphdr *)ptr;
+	ip->check = 0;
+	ip->check = cpu_to_le16(ip_compute_csum(ip, 20));
+
+	ptr += sizeof(struct iphdr);
+	udp = (struct udphdr *)ptr;
+	udp->check = 0;
+
+	udp_len = len - sizeof(struct ethhdr)
+		 - sizeof(struct iphdr);
+	sum1 = csum_partial(ptr,
+					udp_len, 0);
+	sum = csum_tcpudp_magic(ip->saddr, ip->daddr,
+				udp_len, IPPROTO_UDP, sum1);
+	udp->check = cpu_to_le16(sum);
+
+	skw_dbg("chsum %x %x  ip:%x %x sum1:%x udp_len:%d\n", ip->check, sum,
+		 ip->saddr, ip->daddr, sum1, udp_len);
+	return 0;
+}
+
+static int skw_keep_active_rule_save(struct skw_core *skw,
+	 struct skw_keep_active_rule *kp, u8 idx, u8 en, u32 flags)
+{
+	int ret;
+
+	if (!skw || idx >= SKW_KEEPACTIVE_RULE_MAX) {
+		ret = -EFAULT;
+		return ret;
+	}
+
+	if (kp) {
+		if (kp_set.rule[idx])
+			SKW_KFREE(kp_set.rule[idx]);
+
+		kp_set.rule[idx] = SKW_ZALLOC(kp->payload_len
+			 + sizeof(*kp), GFP_KERNEL);
+		memcpy(kp_set.rule[idx], kp, kp->payload_len + sizeof(*kp));
+
+		if (SKW_KEEPALIVE_ALWAYS_FLAG & flags)
+			kp_set.rule[idx]->always = 1;
+		else
+			kp_set.rule[idx]->always = 0;
+
+		if ((SKW_KEEPALIVE_NEEDCHKSUM_FLAG & flags) &&
+			!(SKW_KEEPALIVE_FWCHKSUM_FLAG & flags)) {
+			skw_keep_alive_add_checksum(kp_set.rule[idx]->data[0].payload,
+					kp_set.rule[idx]->payload_len
+					- sizeof(struct skw_keep_active_rule_data));
+			kp_set.rule[idx]->data[0].is_chksumed = 0;
+		} else if ((SKW_KEEPALIVE_NEEDCHKSUM_FLAG & flags) &&
+				(SKW_KEEPALIVE_FWCHKSUM_FLAG & flags))
+			kp_set.rule[idx]->data[0].is_chksumed = 1;
+		else
+			kp_set.rule[idx]->data[0].is_chksumed = 0;
+
+		kp_set.flags[idx] = flags;
+	}
+
+	if (en)
+		kp_set.en_bitmap |= BIT(idx);
+	else
+		kp_set.en_bitmap &= ~BIT(idx);
+
+	skw_dbg("enable bitmap 0x%x\n", kp_set.en_bitmap);
+	skw_hex_dump("kpsave", &kp_set, sizeof(kp_set), false);
+
+	return 0;
+}
+
+static int skw_keep_active_disable_cmd(struct net_device *ndev)
+{
+	struct skw_spd_action_param spd;
+	int ret = 0;
+
+	spd.sub_cmd = ACTION_DIS_KEEPALIVE;
+	spd.len = 0;
+
+	skw_hex_dump("dpdis:", &spd, sizeof(spd), true);
+	ret = skw_send_msg(ndev->ieee80211_ptr->wiphy, ndev,
+			 SKW_CMD_SET_SPD_ACTION, &spd, sizeof(spd), NULL, 0);
+	if (ret)
+		skw_err("failed, ret: %d\n", ret);
+
+	return ret;
+}
+
+static int skw_keep_active_append_cmd(struct net_device *ndev,
+		 struct skw_core *skw, u32 idx_map, u32 idx)
+{
+	int ret = 0;
+	u32 rules = 0;
+	int total, fixed, len = 0, offset = 0;
+	struct skw_spd_action_param *spd = NULL;
+	struct skw_keep_active_param *kp_param = NULL;
+
+	fixed = sizeof(struct skw_spd_action_param) +
+		 sizeof(struct skw_keep_active_param);
+	total = fixed + SKW_KEEPACTIVE_CMD_BUF_MAX;
+
+	spd = SKW_ZALLOC(total, GFP_KERNEL);
+	if (!spd) {
+		skw_err("malloc failed, size: %d\n", total);
+		return -ENOMEM;
+	}
+
+	kp_param = (struct skw_keep_active_param *)((u8 *)spd
+			+ sizeof(*spd));
+	offset = fixed;
+
+	while (idx_map) {
+		idx = ffs(idx_map) - 1;
+
+		if (!kp_set.rule[idx]) {
+			skw_err("rule exception\n");
+			break;
+		}
+
+		if (offset + sizeof(struct skw_keep_active_rule)
+			 + kp_set.rule[idx]->payload_len > total)
+			break;
+
+		memcpy((u8 *)spd + offset, kp_set.rule[idx],
+				 sizeof(struct skw_keep_active_rule)
+				 + kp_set.rule[idx]->payload_len);
+
+		offset += sizeof(struct skw_keep_active_rule)
+			+ kp_set.rule[idx]->payload_len;
+
+		if (++rules > (SKW_KEEPACTIVE_RULE_MAX >> 1))
+			break;
+
+		SKW_CLEAR(idx_map, BIT(idx));
+	}
+
+	kp_param->rule_num = rules;
+	spd->len = offset - sizeof(struct skw_spd_action_param);
+	len = offset;
+
+	spd->sub_cmd = ACTION_EN_KEEPALIVE_APPEND;
+
+	skw_dbg("len:%d rule num:%d\n", len, rules);
+	if (rules) {
+		skw_hex_dump("actvapd:", spd, len, true);
+		ret = skw_send_msg(ndev->ieee80211_ptr->wiphy, ndev,
+				SKW_CMD_SET_SPD_ACTION, spd, len, NULL, 0);
+		if (ret)
+			skw_err("failed, ret: %d\n", ret);
+	}
+
+	SKW_KFREE(spd);
+	return ret;
+}
+
+static int skw_keep_active_cmd(struct net_device *ndev, struct skw_core *skw,
+		 u8 en, u32 flags)
+{
+	int ret = 0, append = 0;
+	u32 idx_map, idx, rules = 0;
+	int total, fixed, len = 0, offset = 0;
+	struct skw_spd_action_param *spd = NULL;
+	struct skw_keep_active_param *kp_param = NULL;
+
+	fixed = sizeof(struct skw_spd_action_param) +
+		 sizeof(struct skw_keep_active_param);
+	total = fixed + SKW_KEEPACTIVE_CMD_BUF_MAX;
+
+	spd = SKW_ZALLOC(total, GFP_KERNEL);
+	if (!spd) {
+		skw_err("malloc failed, size: %d\n", total);
+		return -ENOMEM;
+	}
+
+	kp_param = (struct skw_keep_active_param *)((u8 *)spd
+			+ sizeof(*spd));
+	offset = fixed;
+	idx_map = kp_set.en_bitmap;
+
+	while (idx_map) {
+		idx = ffs(idx_map) - 1;
+		SKW_CLEAR(idx_map, BIT(idx));
+
+		if (!kp_set.rule[idx]) {
+			skw_err("rule exception\n");
+			break;
+		}
+
+		if (offset + sizeof(struct skw_keep_active_rule)
+			 + kp_set.rule[idx]->payload_len > total)
+			break;
+
+		memcpy((u8 *)spd + offset, kp_set.rule[idx],
+				 sizeof(struct skw_keep_active_rule)
+				 + kp_set.rule[idx]->payload_len);
+
+		offset += sizeof(struct skw_keep_active_rule)
+			+ kp_set.rule[idx]->payload_len;
+
+		SKW_CLEAR(idx_map, BIT(idx));
+
+		if (++rules >= (SKW_KEEPACTIVE_RULE_MAX >> 1)) {
+			append++;
+			break;
+		}
+	}
+
+	kp_param->rule_num = rules;
+	spd->len = offset - sizeof(struct skw_spd_action_param);
+	len = offset;
+
+	ret = skw_keep_active_disable_cmd(ndev);
+
+	skw_dbg("len:%d rule num:%d\n", len, rules);
+	if (rules) {
+		spd->sub_cmd = ACTION_EN_KEEPALIVE;
+		skw_hex_dump("actv:", spd, len, true);
+		ret = skw_send_msg(ndev->ieee80211_ptr->wiphy, ndev,
+				SKW_CMD_SET_SPD_ACTION, spd, len, NULL, 0);
+		if (ret)
+			skw_err("failed, ret: %d\n", ret);
+	}
+
+	SKW_KFREE(spd);
+
+	if (append)
+		ret = skw_keep_active_append_cmd(ndev, skw, idx_map, idx);
+
+	return ret;
+}
+
+//iwpriv wlan0 keep_alive idx=0,en=1,period=1000,flags=0/1,
+//pkt=7c:7a:3c:81:e5:72:00:0b
+static int skw_keep_active_set(struct net_device *dev, u8 *param, int len)
+{
+	int result_len = 0;
+	u8 *ch, *result_val;
+	char *hex = NULL;
+	char *tmp_hex = NULL;
+	u8 idx, en = 0, get_pkt = 0, send_cnt = 1;
+	u32 flags = 0;
+	u8 keep_alive[SKW_KEEPACTIVE_LENGTH_MAX];
+	struct skw_keep_active_rule *kp =
+		(struct skw_keep_active_rule *)keep_alive;
+	int pos = 0, ret = 0;
+	struct skw_iface *iface = netdev_priv(dev);
+	struct skw_core *skw = iface->skw;
+
+	memset(kp, 0, sizeof(*kp));
+
+	kp->send_cnt = SKW_KEEPACTIVE_RULE_SEND_CNT_DEF;
+	hex = param;
+
+	hex = strstr(hex, "idx=");
+	if (hex) {
+		ch = strsep(&hex, "=");
+		if ((ch == NULL) || (strlen(ch) == 0)) {
+			skw_err("idx param\n");
+			ret = -EFAULT;
+			goto error;
+		}
+
+		ch = strsep(&hex, ",");
+		if ((ch == NULL) || (strlen(ch) == 0)) {
+			skw_err("idx param\n");
+			ret = -ERANGE;
+			goto error;
+		}
+
+		ret = kstrtou8(ch, 0, &idx);
+		if (ret) {
+			skw_err("idx param\n");
+			ret = -EINVAL;
+			goto error;
+		}
+	} else {
+		skw_err("idx not found\n");
+		ret = -EFAULT;
+		goto error;
+	}
+
+	if (!hex) {
+		ret = -EBADF;
+		goto error;
+	}
+
+	hex = strstr(hex, "en=");
+	if (hex) {
+		ch = strsep(&hex, "=");
+		if ((ch == NULL) || (strlen(ch) == 0)) {
+			skw_err("en param\n");
+			ret = -EFAULT;
+			goto error;
+		}
+
+		ch = strsep(&hex, ",");
+		if ((ch == NULL) || (strlen(ch) == 0)) {
+			skw_err("en param\n");
+			ret = -ERANGE;
+			goto error;
+		}
+
+		ret = kstrtou8(ch, 0, &en);
+		if (ret) {
+			skw_err("en param\n");
+			ret = -EINVAL;
+			goto error;
+		}
+	} else {
+		skw_err("en not found\n");
+		ret = -EFAULT;
+		goto error;
+	}
+
+	if (!hex)
+		goto done;
+
+	hex = strstr(hex, "period=");
+	if (hex) {
+		ch = strsep(&hex, "=");
+		if ((ch == NULL) || (strlen(ch) == 0)) {
+			skw_err("period param\n");
+			ret = -EFAULT;
+			goto error;
+		}
+
+		ch = strsep(&hex, ",");
+		if ((ch == NULL) || (strlen(ch) == 0)) {
+			skw_err("period param\n");
+			ret = -ERANGE;
+			goto error;
+		}
+
+		ret = kstrtou32(ch, 0, &kp->keep_interval);
+		if (ret) {
+			skw_err("period param\n");
+			ret = -EINVAL;
+			goto error;
+		}
+	}
+
+	if (!hex)
+		goto done;
+
+	tmp_hex = hex;
+	hex = strstr(tmp_hex, "send_cnt=");
+	if (hex) {
+		ch = strsep(&hex, "=");
+		if ((ch == NULL) || (strlen(ch) == 0)) {
+			skw_err("send cnt\n");
+			ret = -EFAULT;
+			goto error;
+		}
+
+		ch = strsep(&hex, ",");
+		if ((ch == NULL) || (strlen(ch) == 0)) {
+			skw_err("send cnt\n");
+			ret = -ERANGE;
+			goto error;
+		}
+
+		ret = kstrtou8(ch, 0, &send_cnt);
+		if (ret) {
+			skw_err("send cnt invalid value\n");
+			ret = -EINVAL;
+			goto error;
+		}
+
+		kp->send_cnt = send_cnt;
+		if (kp->send_cnt <= 0) {
+			kp->send_cnt = SKW_KEEPACTIVE_RULE_SEND_CNT_DEF;
+			skw_err("send cnt is bigger than %d",
+				SKW_KEEPACTIVE_RULE_SEND_CNT_MAX);
+		}
+
+		if (kp->send_cnt > SKW_KEEPACTIVE_RULE_SEND_CNT_MAX) {
+			kp->send_cnt = SKW_KEEPACTIVE_RULE_SEND_CNT_MAX;
+			skw_err("send cnt is bigger than %d",
+				SKW_KEEPACTIVE_RULE_SEND_CNT_MAX);
+		}
+	} else
+		hex = tmp_hex;
+
+	hex = strstr(hex, "flags=");
+	if (hex) {
+		ch = strsep(&hex, "=");
+		if ((ch == NULL) || (strlen(ch) == 0)) {
+			skw_err("flags param\n");
+			ret = -EFAULT;
+			goto error;
+		}
+
+		ch = strsep(&hex, ",");
+		if ((ch == NULL) || (strlen(ch) == 0)) {
+			skw_err("flags param\n");
+			ret = -ERANGE;
+			goto error;
+		}
+
+		ret = kstrtou32(ch, 0, &flags);
+		if (ret) {
+			skw_err("flags param\n");
+			ret = -EINVAL;
+			goto error;
+		}
+	}
+
+	if (!hex)
+		goto done;
+
+	hex = strstr(hex, "pkt=");
+	if (hex) {
+		ch = strsep(&hex, "=");
+		if ((ch == NULL) || (strlen(ch) == 0)) {
+			skw_err("pkt param\n");
+			ret = -EFAULT;
+			goto error;
+		}
+
+		result_val = kp->data[0].payload;
+		while (1) {
+			u8 temp = 0;
+			char *cp = strchr(hex, ':');
+
+			if (cp) {
+				*cp = 0;
+				cp++;
+			}
+
+			ret = kstrtou8(hex, 16, &temp);
+			if (ret) {
+				skw_err("pkt param\n");
+				ret = -EINVAL;
+				goto error;
+			}
+
+			if (temp > 255) {
+				skw_err("pkt param\n");
+				ret = -ERANGE;
+				goto error;
+			}
+
+			result_val[pos] = temp;
+			result_len++;
+			pos++;
+
+			if (!cp)
+				break;
+
+			if (result_len + sizeof(*kp) +
+				sizeof(struct skw_keep_active_rule_data) >=
+				SKW_KEEPACTIVE_LENGTH_MAX) {
+				skw_err("len overload\n");
+				ret = -ENOSPC;
+				goto error;
+			}
+
+			hex = cp;
+		}
+		get_pkt = 1;
+	}
+
+	kp->payload_len = result_len + sizeof(struct skw_keep_active_rule_data);
+
+done:
+	skw_dbg("idx:%d en:%d pr:%d cnt:%d pkt:%d len:%d\n", idx, en,
+		 kp->keep_interval, kp->send_cnt, get_pkt, result_len);
+	skw_hex_dump("kp", kp, sizeof(*kp) + kp->payload_len, false);
+
+	if (!(kp->keep_interval && get_pkt))
+		kp = NULL;
+
+	ret = skw_keep_active_rule_save(skw, kp, idx, en, flags);
+	if (ret) {
+		skw_err("save rule\n");
+		goto error;
+	}
+
+	ret = skw_keep_active_cmd(dev, skw, en, flags);
+	if (ret) {
+		skw_err("send rule\n");
+		goto error;
+	}
+
+	return 0;
+
+error:
+	skw_err("error:%d\n", ret);
+	return ret;
+}
+
+//iwpriv wlan0 wow_filter idx=0,pattern=6+7c:7a:3c:81:e5:72#20+!ee:66#50+*3b:27:26:5e:2a
+//iwpriv wlan0 wow_filter disable
+//iwpriv wlan0 wow_filter enable/enable_whitelist
+//iwpriv wlan0 wow_filter "list idx=0"
+static struct skw_wow_rules_set wow_rules_set = {0,0,0 };
+static struct skw_wow_user_rules wow_user_rules = {0,};
+
+static inline int ffs64(u64 x)
+{
+	int r = 1;
+
+	if (!x)
+	        return 0;
+
+	if (!(x & 0xffffffffffffff)) {
+		x >>= 56;
+		r += 56;
+	}
+
+	if (!(x & 0xffffffffffff)) {
+		x >>= 48;
+		r += 48;
+	}
+
+	if (!(x & 0xffffffffff)) {
+		x >>= 40;
+		r += 40;
+	}
+
+	if (!(x & 0xffffffff)) {
+		x >>= 32;
+		r += 32;
+	}
+
+	if (!(x & 0xffffff)) {
+		x >>= 24;
+		r += 24;
+	}
+
+	if (!(x & 0xffff)) {
+		x >>= 16;
+		r += 16;
+	}
+
+	if (!(x & 0xff)) {
+		x >>= 8;
+		r += 8;
+	}
+
+	if (!(x & 0xf)) {
+		x >>= 4;
+		r += 4;
+	}
+
+	if (!(x & 3)) {
+		x >>= 2;
+		r += 2;
+	}
+
+	if (!(x & 1)) {
+		x >>= 1;
+		r += 1;
+	}
+
+	return r;
+}
+
+static int skw_send_wow_filter_append_cmd(struct net_device *ndev,
+	struct skw_core *skw, u64 idx_map)
+{
+	int ret = 0, append = 0;
+	u8 idx, rules = 0;
+	int total, fixed, offset = 0;
+	struct skw_spd_action_param *spd = NULL;
+	struct skw_wow_input_param *wow_param = NULL;
+	struct skw_wow_rule *send_rule = NULL;
+
+	fixed = sizeof(struct skw_spd_action_param) +
+		 sizeof(struct skw_wow_input_param);
+	total = fixed + SKW_WOW_RULE_CMD_BUF_MAX; //SKW_MAX_WOW_RULE_NUM_ONE_TIME * struct skw_wow_rule
+
+	spd = SKW_ZALLOC(total, GFP_KERNEL);
+	if (!spd) {
+		skw_err("malloc failed, size: %d\n", total);
+		return -ENOMEM;
+	}
+
+	wow_param = (struct skw_wow_input_param *)((u8 *)spd
+			+ sizeof(*spd));
+	offset = fixed;
+
+	while (idx_map) {
+		idx = ffs64(idx_map) - 1;
+
+		if (offset + sizeof(struct skw_wow_input_param)
+			+ wow_rules_set.rules[idx].len > total)
+			break;
+
+		spd->sub_cmd = ACTION_EN_WOW_APPEND;
+		wow_param->wow_flags = wow_rules_set.wow_flags;
+		memcpy(&wow_param->rules[rules], &wow_rules_set.rules[idx],
+			sizeof(struct skw_wow_rule));
+
+		send_rule = &wow_param->rules[rules];
+		skw_hex_dump("wow_param->rules",  &wow_param->rules[rules], sizeof(*send_rule), false);
+
+		SKW_CLEAR(idx_map, (1ULL << idx));
+
+		if (++rules >= SKW_MAX_WOW_RULE_NUM_ONE_TIME) {
+			append++;
+			break;
+		}
+	}
+	wow_param->rule_num = rules;
+	spd->len = sizeof(struct skw_wow_input_param) +
+		sizeof(struct skw_wow_rule) * wow_param->rule_num;
+
+	skw_dbg("wow_param->rule_num:%d len:%d %d\n", wow_param->rule_num, spd->len, total);
+
+	skw_hex_dump("append spd", spd, total, false);
+
+	ret = skw_msg_xmit(ndev->ieee80211_ptr->wiphy, 0,
+		 SKW_CMD_SET_SPD_ACTION, spd, total, NULL, 0);
+	if (ret)
+		skw_err("failed, ret: %d\n", ret);
+	else {
+		memset(skw_wow_flted, 0, sizeof(skw_wow_flted));
+		//memcpy(skw_wow_flted, param, len);   //TBD
+	}
+
+	if (append)
+		ret = skw_send_wow_filter_append_cmd(ndev, skw, idx_map);
+
+	return ret;
+}
+
+static int skw_send_wow_filter_cmd(struct net_device *ndev, struct skw_core *skw)
+{
+	int ret = 0, append = 0;
+	u64 idx_map  = 0;
+	u8 idx, rules = 0;
+	int total, fixed, offset = 0;
+	struct skw_spd_action_param *spd = NULL;
+	struct skw_wow_input_param *wow_param = NULL;
+	struct skw_wow_rule *send_rule = NULL;
+
+	fixed = sizeof(struct skw_spd_action_param) +
+		 sizeof(struct skw_wow_input_param);
+	total = fixed + SKW_WOW_RULE_CMD_BUF_MAX;
+
+	spd = SKW_ZALLOC(total, GFP_KERNEL);
+	if (!spd) {
+		skw_err("malloc failed, size: %d\n", total);
+		return -ENOMEM;
+	}
+
+	wow_param = (struct skw_wow_input_param *)((u8 *)spd
+			+ sizeof(*spd));
+	offset = fixed;
+
+	idx_map = wow_rules_set.en_bitmap;
+
+	while (idx_map) {
+		idx = ffs64(idx_map) - 1;
+
+		if (offset + sizeof(struct skw_wow_input_param)
+			+ wow_rules_set.rules[idx].len > total)
+			break;
+
+		spd->sub_cmd = ACTION_EN_WOW;
+		wow_param->wow_flags = wow_rules_set.wow_flags;
+		memcpy(&wow_param->rules[rules], &wow_rules_set.rules[idx],
+			sizeof(struct skw_wow_rule));
+
+		send_rule = &wow_param->rules[rules];
+
+		skw_hex_dump("wow_param->rules",  &wow_param->rules[rules], sizeof(*send_rule), false);
+		SKW_CLEAR(idx_map, (1ULL << idx));
+
+		if (++rules >= SKW_MAX_WOW_RULE_NUM_ONE_TIME) {
+			append++;
+			break;
+		}
+	}
+	wow_param->rule_num = rules;
+	spd->len = sizeof(struct skw_wow_input_param) +
+		sizeof(struct skw_wow_rule) * wow_param->rule_num;
+
+	skw_dbg("wow_param->rule_num:%d len:%d total:%d\n",  wow_param->rule_num, spd->len, total);
+
+	skw_hex_dump("spd", spd, sizeof(struct skw_spd_action_param) + spd->len, false);
+
+	ret = skw_msg_xmit(ndev->ieee80211_ptr->wiphy, 0,
+		 SKW_CMD_SET_SPD_ACTION, spd, total, NULL, 0);
+	if (ret)
+		skw_err("failed, ret: %d\n", ret);
+	else {
+		memset(skw_wow_flted, 0, sizeof(skw_wow_flted));
+		//memcpy(skw_wow_flted, param, len);   //TBD
+	}
+
+	if (append)
+		ret = skw_send_wow_filter_append_cmd(ndev, skw, idx_map);
+
+	return ret;
+}
+
+int skw_wow_filter_set(struct net_device *ndev, u8 *param, int len, char *resp, int *extra_len)
+{
+	u8 *ch, *result_val, rule_idx = 0;
+	char *hex,*tmp_ch, *ptr;
+	struct skw_wow_rule *rule = NULL;
+	int pos = 0, ret = 0, offset, result_len = 0;
+	struct skw_pkt_pattern *ptn;
+	u8 *data;
+	u32 temp, resp_len = 0;
+	char help[] = "Usage:['list idx=0']|[disable]|[idx=0,pattern=6+10#23+!11][enable/enable_whitelist]";
+	struct skw_iface *iface = netdev_priv(ndev);
+	struct skw_core *skw = iface->skw;
+
+	if (!param)
+		return -EINVAL;
+
+	data = SKW_ZALLOC(SKW_IW_WOW_BUF_SIZE, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	memcpy(data, param, len);
+	hex = data;
+	ptr = hex;
+
+	if (!strncmp(hex, "list idx=", strlen("list idx="))) {
+		if (len <= strlen("list idx=")) {
+			resp_len = sprintf(resp, "ERROR: %s\n",
+				"list cmd");
+			ret = -EFAULT;
+			goto free;
+		}
+
+		hex += strlen("list idx=");
+
+		ret = kstrtou8(hex, 0, &rule_idx);
+		if (ret) {
+			resp_len = sprintf(resp, "ERROR: %s\n",
+				"list cmd idx param");
+			skw_err("idx param\n");
+			ret = -EINVAL;
+			goto free;
+		}
+
+		if (rule_idx >= SKW_MAX_WOW_RULE_NUM) {
+			resp_len = sprintf(resp, "ERROR: %s\n",
+				"list cmd idx param  is too big");
+			skw_err("rule_idx param:%d is too big\n", rule_idx);
+			ret = -EINVAL;
+			goto free;
+		}
+
+		if (!((1ULL << rule_idx) & wow_user_rules.en_bitmap)) {
+			resp_len = sprintf(resp, "ERROR: %s\n",
+				"list cmd idx param  is unset");
+			skw_err("rule_idx param:%d is unset\n", rule_idx);
+			ret = -EINVAL;
+			goto free;
+		}
+
+		resp_len = sprintf(resp, "List: %s\n", wow_user_rules.rules[rule_idx]);
+		goto free;
+	}
+
+	if (!strcmp(hex, "disable")) {
+		if (len != sizeof("disable")) {
+			resp_len = sprintf(resp, "ERROR: %s\n",
+				 "dis cmd");
+			ret = -EFAULT;
+			goto free;
+		}
+		ret = skw_wow_disable(ndev->ieee80211_ptr->wiphy);
+		if (!ret) {
+			memset(skw_wow_flted, 0, sizeof(skw_wow_flted));
+			memcpy(skw_wow_flted, param, len);
+			memset(&wow_rules_set, 0, sizeof(wow_rules_set));
+			memset(&wow_user_rules, 0, sizeof(wow_user_rules));
+		}
+
+		goto free;
+	}
+
+	//Add wow filter by idx
+	if (!strncmp(hex, "idx=", strlen("idx="))) {
+		ch = strsep(&hex, ",");
+		if ((ch == NULL) || (strlen(ch) == 0)) {
+			skw_err("idx param\n");
+			resp_len = sprintf(resp, "ERROR: %s\n",
+				 "idx param");
+			ret = -ERANGE;
+			goto free;
+		}
+
+		tmp_ch = strsep((char **)&ch, "=");
+		if ((tmp_ch == NULL) || (strlen(tmp_ch) == 0)) {
+			skw_err("idx param\n");
+			resp_len = sprintf(resp, "ERROR: %s\n",
+				 "idx param");
+			ret = -EFAULT;
+			goto free;
+		}
+
+		ret = kstrtou8(ch, 0, &rule_idx);
+		if (ret) {
+			skw_err("idx param\n");
+			resp_len = sprintf(resp, "ERROR: %s\n",
+				 "idx param");
+			ret = -EINVAL;
+			goto free;
+		}
+
+		if (rule_idx >= SKW_MAX_WOW_RULE_NUM) {
+			resp_len = sprintf(resp, "ERROR: %s\n",
+				 "idx param is too big");
+			skw_err("rule_idx param:%d is too big\n", rule_idx);
+			ret = -EINVAL;
+			goto free;
+		}
+
+		//Store the rule
+		wow_user_rules.en_bitmap |=  (1ULL << rule_idx);
+
+		strncpy(wow_user_rules.rules[rule_idx], param, len);
+
+		//Translate the wow filter to skw rules
+		rule = &wow_rules_set.rules[rule_idx];
+		result_len = 0;
+
+		ret = strncmp(hex, "pattern=", strlen("pattern="));
+		if (!ret) {
+			hex += strlen("pattern=");
+			result_val = rule->rule;
+
+			while (hex < ptr + len - 1) {
+				ret = sscanf(hex, "%d+%02x",
+					&offset, &temp);
+				if (ret != 2) {
+					ret = sscanf(hex, "%d+!%02x",
+						&offset, &temp);
+					if (ret != 2) {
+						ret = sscanf(hex, "%d+*%02x",
+							&offset, &temp);
+						if (ret != 2) {
+							resp_len = sprintf(resp,
+								"ERROR: %s\n",
+								"match char + +*");
+							ret = -EINVAL;
+							goto free;
+						}
+					}
+				}
+
+				if (offset > ETH_DATA_LEN) {
+					resp_len = sprintf(resp,
+						"ERROR: offset:%d over limit\n",
+						offset);
+					ret = -EINVAL;
+					goto free;
+				}
+
+				ptn = (struct skw_pkt_pattern *)result_val;
+				result_val += sizeof(*ptn);
+				result_len += sizeof(*ptn);
+
+				if (result_len >= sizeof(rule->rule)) {
+					resp_len = sprintf(resp,
+						"ERROR: %s\n",
+						"ptn over limit\n");
+					ret = -ERANGE;
+					goto free;
+				}
+
+				ptn->type_offset = PAT_TYPE_ETH;
+				ptn->offset = offset;
+
+				ch = (u8 *)strsep(&hex, "+");
+				if ((ch == NULL) || (strlen(ch) == 0)) {
+					resp_len = sprintf(resp,
+						"ERROR: %s\n",
+						"match char +\n");
+					ret = -EINVAL;
+					goto free;
+				}
+
+				if (hex[0] == '!') {
+					ptn->op = PAT_OP_TYPE_DIFF;
+					ch = strsep(&hex, "!");
+				} else if (hex[0] == '*') {
+					ptn->op = PAT_OP_TYPE_CONTINUE_MATCH;
+					ch = strsep(&hex, "*");
+				}
+
+				pos = 0;
+				while (hex < ptr + len - 1) {
+					char *cp;
+
+					if (!hex) {
+						ret = -EINVAL;
+						goto free;
+					}
+
+					if (isxdigit(hex[0]) &&
+						isxdigit(hex[1]) &&
+						(sscanf(hex, "%2x", &temp)
+							== 1)) {
+					} else {
+						resp_len = sprintf(resp,
+							"ERROR: match char %c%c end\n",
+							hex[0], hex[1]);
+						ret = -EINVAL;
+						goto free;
+					}
+
+					result_val[pos] = temp;
+					result_len++;
+					pos++;
+
+					if (result_len >= sizeof(rule->rule)) {
+						resp_len = sprintf(resp,
+							"ERROR: %s\n",
+							"size over limit\n");
+						ret = -ERANGE;
+						goto free;
+					}
+
+					if (hex[2] == ',' || hex[2] == '#')
+						break;
+					else if (hex[2] == '\0') {
+						hex += 2;
+						break;
+					} else  if (hex[2] != ':') {
+						resp_len = sprintf(resp,
+							"ERROR: char data %c\n",
+							hex[2]);
+						ret = -EINVAL;
+						goto free;
+					}
+
+					cp = strchr(hex, ':');
+					if (cp) {
+						*cp = 0;
+						cp++;
+					}
+
+					hex = cp;
+				}
+
+				result_val += pos;
+				ptn->len = pos;
+
+				if (hex[2] == ',') {
+					hex += 2;
+					break;
+				} else if (hex[2] == '#')
+					ch = strsep(&hex, "#");
+			}
+		} else {
+			resp_len = sprintf(resp, "ERROR: %s\n",
+				"match char pattern=\n");
+			ret = -EINVAL;
+			goto free;
+		}
+
+		rule->len = result_len;
+		wow_rules_set.en_bitmap |= (1ULL << rule_idx);
+		skw_hex_dump("rule", rule, sizeof(*rule), false);
+		ret = 0;
+
+		goto free;
+	}
+	//Make sure enable/enable_whitelist is the final cmd
+	ret = strncmp(hex, "enable", strlen("enable"));
+	if (ret) {
+		resp_len = sprintf(resp, "ERROR\n");
+		ret = -EFAULT;
+		goto free;
+	}
+
+	if (len != sizeof("enable") && len != sizeof("enable_whitelist")) {
+		skw_err("Error: enable");
+		resp_len = sprintf(resp, "Error: enable\n");
+		ret = EINVAL;
+		goto free;
+	}
+
+	hex += strlen("enable");
+	ret = strncmp(hex, "_whitelist", strlen("_whitelist"));
+	if (!ret)
+		hex += strlen("_whitelist");
+	else
+		wow_rules_set.wow_flags |= SKW_WOW_BLACKLIST_FILTER;
+
+	//Send the rules to FW
+	ret = skw_send_wow_filter_cmd(ndev, skw);
+	if (ret) {
+		resp_len = sprintf(resp, "Error: send filter cmd\n");
+		skw_err("Error: send filter cmd");
+	}
+
+free:
+
+	if (ret)
+		resp_len += sprintf(resp + resp_len, " %s\n", help);
+	else
+		resp_len += sprintf(resp + resp_len, "%s\n", "OK");
+
+	*extra_len = resp_len;
+
+	SKW_KFREE(data);
+	return 0;
+}
+
+static int skw_iwpriv_keep_alive(struct net_device *dev,
+			struct iw_request_info *info,
+			union iwreq_data *wrqu, char *extra)
+{
+	char *param = NULL;
+	char help[] = "ERROR useage:[idx=0,en=0/1,period=100,flags=0/1,pkt=7c:11]";
+	int ret = 0;
+
+	WARN_ON(wrqu->data.length > SKW_KEEP_BUF_SIZE);
+
+	param = SKW_ZALLOC(SKW_KEEP_BUF_SIZE, GFP_KERNEL);
+	if (!param) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	if (copy_from_user(param, wrqu->data.pointer, SKW_KEEP_BUF_SIZE)) {
+		skw_err("copy failed, length: %d\n",
+			wrqu->data.length);
+
+		ret = -EFAULT;
+		goto free;
+	}
+
+	skw_dbg("cmd: 0x%x, (len: %d)\n",
+		info->cmd, wrqu->data.length);
+	param[SKW_KEEP_BUF_SIZE - 1] = '\0';
+	skw_hex_dump("param:", param, SKW_KEEP_BUF_SIZE, false);
+
+	ret = skw_keep_active_set(dev, param, SKW_KEEP_BUF_SIZE);
+	if (ret)
+		memcpy(extra, help, sizeof(help));
+	else
+		memcpy(extra, "OK", sizeof("OK"));
+
+	wrqu->data.length = SKW_GET_LEN_512;
+
+	skw_dbg("resp: %s\n", extra);
+
+free:
+	SKW_KFREE(param);
+
+out:
+	return ret;
+}
+
+static int skw_iwpriv_wow_filter(struct net_device *dev,
+			struct iw_request_info *info,
+			union iwreq_data *wrqu, char *extra)
+{
+	char *param;
+	int ret;
+	int extra_len;
+
+	WARN_ON(wrqu->data.length > SKW_IW_WOW_BUF_SIZE);
+
+	param = SKW_ZALLOC(SKW_IW_WOW_BUF_SIZE, GFP_KERNEL);
+	if (!param) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	if (copy_from_user(param, wrqu->data.pointer, SKW_IW_WOW_BUF_SIZE)) {
+		skw_err("copy failed, length: %d\n",
+			wrqu->data.length);
+
+		ret = -EFAULT;
+		goto free;
+	}
+
+	skw_hex_dump("flt", param, SKW_IW_WOW_BUF_SIZE, false);
+
+	ret = skw_wow_filter_set(dev, param,
+		min_t(int, SKW_IW_WOW_BUF_SIZE, (int)wrqu->data.length),
+		extra, &extra_len);
+
+	wrqu->data.length = extra_len;
+
+free:
+	SKW_KFREE(param);
+
+out:
+	return ret;
+}
+
+static int skw_send_at_cmd(struct skw_core *skw, char *cmd, int cmd_len,
+			char *buf, int buf_len)
+{
+	int ret, len, resp_len, offset;
+	char *command, *resp;
+
+	len = round_up(cmd_len, 4);
+	if (len > SKW_SET_LEN_256)
+		return -E2BIG;
+
+	command = SKW_ZALLOC(SKW_SET_LEN_512, GFP_KERNEL);
+	if (!command) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	offset = (long)command & 0x7;
+	if (offset) {
+		offset = 8 - offset;
+		skw_detail("command: 0x%p, offset: %d\n", command, offset);
+	}
+
+	resp_len = round_up(buf_len, skw->hw_pdata->align_value);
+	resp = SKW_ZALLOC(resp_len, GFP_KERNEL);
+	if (!resp) {
+		ret = -ENOMEM;
+		goto fail_alloc_resp;
+	}
+
+	ret = skw_uart_open(skw);
+	if (ret < 0)
+		goto failed;
+
+	memcpy(command + offset, cmd, cmd_len);
+	ret = skw_uart_write(skw, command + offset, len);
+	if (ret < 0)
+		goto failed;
+
+	ret = skw_uart_read(skw, resp, resp_len);
+	if (ret < 0)
+		goto failed;
+
+	memcpy(buf, resp, buf_len);
+	ret = 0;
+
+failed:
+	SKW_KFREE(resp);
+
+fail_alloc_resp:
+	SKW_KFREE(command);
+out:
+	if (ret < 0)
+		skw_err("failed: ret: %d\n", ret);
+
+	return ret;
+}
+
+static int skw_iwpriv_mode(struct net_device *dev,
+			   struct iw_request_info *info,
+			   union iwreq_data *wrqu, char *extra)
+{
+	int i;
+	char param[32] = {0};
+	struct skw_iface *iface = (struct skw_iface *)netdev_priv(dev);
+
+	struct skw_iw_wireless_mode {
+		char *name;
+		enum skw_wireless_mode mode;
+	} modes[] = {
+		{"11B", SKW_WIRELESS_11B},
+		{"11G", SKW_WIRELESS_11G},
+		{"11A", SKW_WIRELESS_11A},
+		{"11N", SKW_WIRELESS_11N},
+		{"11AC", SKW_WIRELESS_11AC},
+		{"11AX", SKW_WIRELESS_11AX},
+		{"11G_ONLY", SKW_WIRELESS_11G_ONLY},
+		{"11N_ONLY", SKW_WIRELESS_11N_ONLY},
+
+		/*keep last*/
+		{NULL, 0}
+	};
+
+	WARN_ON(sizeof(param) < wrqu->data.length);
+
+	if (copy_from_user(param, wrqu->data.pointer, sizeof(param))) {
+		skw_err("copy failed, length: %d\n",
+			wrqu->data.length);
+
+		return -EFAULT;
+	}
+
+	param[31] = '\0';
+	skw_dbg("cmd: 0x%x, %s(len: %d)\n",
+		info->cmd, param, wrqu->data.length);
+
+	for (i = 0; modes[i].name; i++) {
+		if (!strcmp(modes[i].name, (char *)param)) {
+			iface->extend.wireless_mode = modes[i].mode;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int skw_iwpriv_get_mode(struct net_device *dev,
+			struct iw_request_info *info,
+			union iwreq_data *wrqu, char *extra)
+{
+	skw_dbg("traced\n");
+	return 0;
+}
+
+static int skw_iwpriv_help(struct skw_iface *iface, void *param, char *args,
+			char *resp, int resp_len)
+{
+	int len = 0;
+	struct skw_iwpriv_cmd *cmd = param;
+
+	len = sprintf(resp, " %s:\n", cmd->help_info);
+	cmd++;
+
+	while (cmd->handler) {
+		len += sprintf(resp + len, "%-4.4s %s\n", "", cmd->help_info);
+		cmd++;
+	}
+
+	return 0;
+}
+
+static int skw_iwpriv_set_bandcfg(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	u16 res;
+	int ret;
+
+	if (args == NULL)
+		return -EINVAL;
+
+	ret = kstrtou16(args, 10, &res);
+	if (!ret && res < 3) {
+		if (res == 0)
+			iface->extend.scan_band_filter = 0;
+		else if (res == 1)
+			iface->extend.scan_band_filter = BIT(NL80211_BAND_2GHZ);
+		else if (res == 2)
+			iface->extend.scan_band_filter = BIT(NL80211_BAND_5GHZ);
+
+		sprintf(resp, "ok");
+	} else
+		sprintf(resp, "failed");
+
+	return ret;
+}
+
+static int skw_iwpriv_get_bandcfg(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	if (!iface->extend.scan_band_filter)
+		sprintf(resp, "bandcfg=%s", "Auto");
+	else if (iface->extend.scan_band_filter & BIT(NL80211_BAND_2GHZ))
+		sprintf(resp, "bandcfg=%s", "2G");
+	else if (iface->extend.scan_band_filter & BIT(NL80211_BAND_5GHZ))
+		sprintf(resp, "bandcfg=%s", "5G");
+
+	return 0;
+}
+
+int skw_set_cca_thre_ofdm(struct wiphy *wiphy, struct net_device *dev,
+								struct skw_cca_thre_ofdm *p_ofdm)
+
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	ret = skw_tlv_alloc(&conf, 512, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_CCA_THRE_OFDM, p_ofdm, sizeof(struct skw_cca_thre_ofdm))) {
+		skw_err("add cca thre ofdm tlv failed\n");
+		skw_tlv_free(&conf);
+
+	return -EINVAL;
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+		conf.total_len, NULL, 0);
+	if (ret)
+		skw_err("failed, ret: %d\n", ret);
+
+	}
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+int skw_set_cca_thre_11b(struct wiphy *wiphy, struct net_device *dev,
+								struct skw_cca_thre_11b *p_cca_11b)
+
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	ret = skw_tlv_alloc(&conf, 512, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_CCA_THRE_11B, p_cca_11b, sizeof(struct skw_cca_thre_11b))) {
+		skw_err("add cca thre 11b tlv failed\n");
+		skw_tlv_free(&conf);
+
+	return -EINVAL;
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+		conf.total_len, NULL, 0);
+	if (ret)
+		skw_err("failed, ret: %d\n", ret);
+
+	}
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+int skw_set_cca_thre_nowifi(struct wiphy *wiphy, struct net_device *dev,
+								struct skw_cca_thre_nowifi *p_nowifi)
+
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	ret = skw_tlv_alloc(&conf, 512, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_CCA_THRE_NOWIFI, p_nowifi, sizeof(struct skw_cca_thre_nowifi))) {
+		skw_err("add cca thre nowifi tlv failed\n");
+		skw_tlv_free(&conf);
+
+	return -EINVAL;
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+		conf.total_len, NULL, 0);
+	if (ret)
+		skw_err("failed, ret: %d\n", ret);
+
+	}
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+
+int skw_set_edca_params(struct wiphy *wiphy, struct net_device *dev,
+								struct skw_edca_param_s *p_edca_params)
+
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	ret = skw_tlv_alloc(&conf, 512, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_EDCA_PARAM, p_edca_params, sizeof(struct skw_edca_param_s))) {
+		skw_err("add max ppdu dur tlv failed\n");
+		skw_tlv_free(&conf);
+
+	return -EINVAL;
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+		conf.total_len, NULL, 0);
+	if (ret)
+		skw_err("failed, ret: %d\n", ret);
+
+	}
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+int skw_set_max_ppdu_dur(struct wiphy *wiphy, struct net_device *dev,
+			struct skw_max_ppdu_dur *p_mppdu_dur)
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	skw_dbg("idx: %d, max ppdu dur %d\n", p_mppdu_dur->idx, p_mppdu_dur->max_ppdu_dur);
+
+	ret = skw_tlv_alloc(&conf, 512, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_MAX_PPDU_DUR, p_mppdu_dur, sizeof(struct skw_max_ppdu_dur))) {
+		skw_err("add max ppdu dur tlv failed\n");
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+				conf.total_len, NULL, 0);
+		if (ret)
+			skw_err("failed, ret: %d\n", ret);
+
+	}
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+int skw_set_force_rts_rate(struct wiphy *wiphy, struct net_device *dev,
+			struct skw_force_rts_rate *rate)
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	skw_dbg("2.4G rate: %d, 5G rate %d\n", rate->rts_rate_24G, rate->rts_rate_5G);
+
+	ret = skw_tlv_alloc(&conf, 512, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve force rts rate tlv failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_FORCE_RTS_RATE, rate, sizeof(struct skw_force_rts_rate))) {
+		skw_err("add force rts rate tlv failed\n");
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+				conf.total_len, NULL, 0);
+		if (ret)
+			skw_err("failed, ret: %d\n", ret);
+
+	}
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+int skw_set_force_rx_rsp_rate(struct wiphy *wiphy, struct net_device *dev,
+		struct skw_force_rx_rsp_rate *rate)
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	skw_dbg("11b long rate: %d, 11b short rate %d, ofdm rate %d\n", rate->rx_rsp_rate_11b_long,
+			rate->rx_rsp_rate_11b_short, rate->rx_rsp_rate_ofdm);
+
+	ret = skw_tlv_alloc(&conf, 512, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve force rx rsp rate tlv failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_FORCE_RX_RSP_RATE, rate, sizeof(struct skw_force_rx_rsp_rate))) {
+		skw_err("add force rx rsp rate tlv failed\n");
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+				conf.total_len, NULL, 0);
+		if (ret)
+			skw_err("failed, ret: %d\n", ret);
+
+	}
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+int skw_set_scan_time_cmd(struct wiphy *wiphy, struct net_device *dev,
+			struct skw_set_scan_time *time)
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	skw_dbg("active dwell time: %d, bypass active scan auto time %d\n",
+		time->active_dwell_time, time->bypass_active_acan_auto_time);
+
+	ret = skw_tlv_alloc(&conf, 512, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve scan time failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_SCAN_TIME, time, sizeof(struct skw_set_scan_time))) {
+		skw_err("add scan time failed\n");
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+				conf.total_len, NULL, 0);
+		if (ret)
+			skw_err("failed, ret: %d\n", ret);
+	}
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+int skw_set_tcp_disconn_wakeup_host(struct wiphy *wiphy, struct net_device *dev,
+			struct skw_set_tcpd_wakeup_host *flag)
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	skw_dbg("wakeup host:%d\n", flag->enable);
+
+	ret = skw_tlv_alloc(&conf, 512, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve wakeup host flag failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_TCP_DISCONN_WAKEUP_HOST, flag, sizeof(struct skw_set_tcpd_wakeup_host))) {
+		skw_err("add wakeup host flag failed\n");
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+				conf.total_len, NULL, 0);
+		if (ret)
+			skw_err("failed, ret: %d\n", ret);
+	}
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+int skw_set_rc_min_rate(struct wiphy *wiphy, struct net_device *dev,
+			struct skw_set_rate_control_min_rate *rate)
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	skw_dbg("rc min rate:%d\n", rate->rstrict_min_rate);
+
+	ret = skw_tlv_alloc(&conf, 512, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve rc min rate failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_RATE_CTRL_MIN_RATE, rate, sizeof(struct skw_set_rate_control_min_rate))) {
+		skw_err("add rc min rate failed\n");
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+				conf.total_len, NULL, 0);
+		if (ret)
+			skw_err("failed, ret: %d\n", ret);
+	}
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+int skw_set_rate_control_rate_change(struct wiphy *wiphy, struct net_device *dev,
+			struct skw_set_rate_control_rate_change *rate)
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	ret = skw_tlv_alloc(&conf, 512, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_RATE_CTRL_RATE_CHANGE_PARAM, rate, sizeof(struct skw_set_rate_control_rate_change))) {
+		skw_err("add failed\n");
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+				conf.total_len, NULL, 0);
+		if (ret)
+			skw_err("failed, ret: %d\n", ret);
+	}
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+int skw_set_rc_spe_rate(struct wiphy *wiphy, struct net_device *dev,
+		struct skw_set_rate_control_special_rate *rate)
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+
+	ret = skw_tlv_alloc(&conf, 512, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc tlv failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve tlv failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, SKW_MIB_SET_RATE_CTRL_SPECIAL_FRM_RATE, rate, sizeof(struct skw_set_rate_control_special_rate))) {
+		skw_err("add tlv failed\n");
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+				conf.total_len, NULL, 0);
+		if (ret)
+			skw_err("failed, ret: %d\n", ret);
+	}
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
+
+static int skw_iwpriv_set_max_ppdu_dur(struct skw_iface *iface, void *param,
+	char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL;
+	struct skw_max_ppdu_dur max_ppdu_dur = {0};
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("args: %s\n", args);
+
+	p = strchr(args, ',');
+	if (!p) {
+		skw_err("idx not found\n");
+		return -ENOTSUPP;
+	}
+
+	skw_dbg("idx found %s\n", p - 1);
+
+	max_ppdu_dur.idx = simple_strtol(p - 1, NULL, 10);
+	if (max_ppdu_dur.idx < 0 || max_ppdu_dur.idx > 5) {
+		skw_err("idx out of range\n");
+		return -EINVAL;
+	}
+
+	p = p + strlen(",");
+	if (!p) {
+		skw_err("mppdu dur not found\n");
+		return -ENOTSUPP;
+	}
+
+	skw_dbg("mppdu dur found %s\n", p);
+	max_ppdu_dur.max_ppdu_dur = simple_strtol(p, NULL, 10);
+	switch (max_ppdu_dur.idx) {
+	case 0:
+		if (max_ppdu_dur.max_ppdu_dur > 0xFFFF) {
+			max_ppdu_dur.max_ppdu_dur = 0xFFFF;
+			skw_warn("The max ppdu dur of idx 0 is 0xFFFF\n");
+		}
+
+		break;
+
+	case 1:
+		if (max_ppdu_dur.max_ppdu_dur > 0x7FFF) {
+			max_ppdu_dur.max_ppdu_dur = 0x7FFF;
+			skw_warn("The max ppdu dur of idx 1 is 0x7FFF\n");
+		}
+
+		break;
+
+	case 2:
+	case 3:
+	case 4:
+	case 5:
+		if (max_ppdu_dur.max_ppdu_dur > 0x1FFF) {
+			max_ppdu_dur.max_ppdu_dur = 0x1FFF;
+			skw_warn("The max ppdu dur of idx 1 is 0x1FFF\n");
+		}
+
+		break;
+
+	default:
+		break;
+	}
+
+	skw_dbg("max ppdu dur idx %d, dur %d\n", max_ppdu_dur.idx, max_ppdu_dur.max_ppdu_dur);
+
+	ret = skw_set_max_ppdu_dur(iface->wdev.wiphy, iface->ndev, &max_ppdu_dur);
+	if (!ret)
+		sprintf(resp, "set max ppdu dur ok ");
+	else
+		sprintf(resp, "set max ppdu dur failed");
+
+	return ret;
+}
+
+static u16 skw_iwpriv_convert_string_to_u (char *ptr, u8 length)
+{
+	char *buffer = NULL;
+	unsigned long num = 0;
+	int ret = 0;
+
+	buffer = kmalloc(length + 1, GFP_KERNEL);
+	if (!buffer) {
+		skw_err("mem alloc failed\n");
+		return -ENOMEM;
+	}
+	memcpy(buffer, ptr, length);
+	buffer[length] = '\0';
+
+	ret = kstrtoul(buffer, 0, &num);
+	if (ret == 0) {
+		if (num <= 0xFFFF) {
+			kfree(buffer);
+			return (u16)num;
+		} else if (num <= 0xFF) {
+			kfree(buffer);
+			return (u8)num;
+		} else {
+			skw_err("tred beyond u8 and u16\n");
+			kfree(buffer);
+			return -ERANGE;
+		}
+	} else {
+		skw_err("transfer fail\n");
+		kfree(buffer);
+		return -EINVAL;
+	}
+}
+
+static u32 skw_iwpriv_convert_string_to_u32(char *ptr, unsigned int length)
+{
+	if (ptr == NULL || length <= 0) {
+		skw_err("Invalid input parameters");
+		return -EINVAL;
+	}
+
+	if (length >= 2 && ptr[0] == '0' && (ptr[1] == 'x' || ptr[1] == 'X')) {
+		char *buffer = NULL;
+		unsigned long num = 0;
+		int ret = 0;
+
+		buffer = kmalloc(length + 1, GFP_KERNEL);
+		if (!buffer) {
+			skw_err("mem alloc failed\n");
+			return -ENOMEM;
+		}
+		memcpy(buffer, ptr, length);
+		buffer[length] = '\0';
+
+		ret = kstrtoul(buffer, 16, &num);
+		if (ret == 0) {
+			if (num <= 0xFFFFFFFF) {
+				kfree(buffer);
+				return (u32)num;
+			} else {
+				skw_err("value exceeds u32 range");
+				kfree(buffer);
+				return -ERANGE;
+			}
+		} else {
+			skw_err("hex conversion failed");
+			kfree(buffer);
+			return -EINVAL;
+		}
+	} else {
+		skw_err("not a valid hex string");
+		return -EINVAL;
+	}
+}
+
+int skw_iwpriv_set_edca_params(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL, *q = NULL;
+	struct skw_edca_param_s edca_params = {0};
+	u8 parmas_len = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("args: %s\n", args);
+
+	parmas_len = strlen(args);
+	if (parmas_len == 1) {
+		ret = simple_strtol(args, NULL, 10);
+		if (!ret) {
+			skw_dbg(" not set edca params\n");
+			edca_params.enable = 0;
+			goto SETPARA;
+		}
+
+		skw_err(" need more edca params\n");
+		goto SETFAIL;
+
+	} else if (parmas_len > 1) {
+		p = strchr(args, ',');
+		if (!p) {
+			skw_dbg("flag not found\n");
+			return -ENOTSUPP;
+		}
+
+		skw_dbg("params found %s\n", p - 1);
+
+		edca_params.enable = simple_strtol(p - 1, NULL, 10);
+		if (edca_params.enable != 0 && edca_params.enable != 1) {
+			skw_err("flag error %d\n", edca_params.enable);
+			return -EINVAL;
+		}
+
+		if (edca_params.enable == 0) {
+			skw_err("not set edca params\n");
+			goto SETPARA;
+		}
+
+		skw_dbg("start set edca params ...\n");
+	}
+
+	/* parse AC_BE parametes */
+	/** AciAifn **/
+	//p = p + strlen(",");
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("AC_BE AciAifn not found\n");
+		return -ENOTSUPP;
+	}
+
+	//edca_params.ac_best_effort.aci_aifn = simple_strtol(p, NULL, 10);
+	edca_params.ac_best_effort.aci_aifn = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (edca_params.ac_best_effort.aci_aifn > 0xFF) {
+		skw_warn("AC_BE AciAifn limit is 0xFF\n");
+		edca_params.ac_best_effort.aci_aifn = 0xFF;
+	}
+
+	skw_dbg("AC_BE Set AciAifn %d\n", edca_params.ac_best_effort.aci_aifn);
+
+	//return 0;
+	/** EcWminEcWmax **/
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("AC_BE EcWminEcWmax not found\n");
+		return -ENOTSUPP;
+	}
+
+	//edca_params.ac_best_effort.ec_wmin_wmax = simple_strtol(p, NULL, 10);
+	edca_params.ac_best_effort.ec_wmin_wmax = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (edca_params.ac_best_effort.ec_wmin_wmax > 0xFF) {
+		skw_dbg("AC_BE EcWminEcWmax limit is 0xFF\n");
+		edca_params.ac_best_effort.ec_wmin_wmax = 0xFF;
+	}
+
+	skw_dbg("AC_BE Set EcWminEcWmax %d\n", edca_params.ac_best_effort.ec_wmin_wmax);
+
+	/** TxopLimit **/
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("AC_BE TxopLimit not found\n");
+		return -ENOTSUPP;
+	}
+
+	//edca_params.ac_best_effort.txop_limit = simple_strtol(p, NULL, 10);
+	edca_params.ac_best_effort.txop_limit = skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (edca_params.ac_best_effort.txop_limit > 0xFFFF) {
+		skw_dbg("AC_BE TxopLimit limit is 0xFF\n");
+		edca_params.ac_best_effort.txop_limit = 0xFFFF;
+	}
+
+	skw_dbg("AC_BE Set TxopLimit %d\n", edca_params.ac_best_effort.txop_limit);
+
+	/* parse AC_BG parametes */
+	/** AciAifn **/
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("AC_BG AciAifn not found\n");
+		return -ENOTSUPP;
+	}
+
+	//edca_params.ac_background.aci_aifn = simple_strtol(p, NULL, 10);
+	edca_params.ac_background.aci_aifn = skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (edca_params.ac_background.aci_aifn > 0xFF) {
+		skw_warn("AC_BG AciAifn limit is 0xFF\n");
+		edca_params.ac_background.aci_aifn = 0xFF;
+	}
+
+	skw_dbg("AC_BG Set AciAifn %d\n", edca_params.ac_background.aci_aifn);
+
+	/** EcWminEcWmax **/
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("AC_BG EcWminEcWmax not found\n");
+		return -ENOTSUPP;
+	}
+
+	//edca_params.ac_background.ec_wmin_wmax = simple_strtol(p, NULL, 10);
+	edca_params.ac_background.ec_wmin_wmax = skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (edca_params.ac_background.ec_wmin_wmax > 0xFF) {
+		skw_warn("AC_BG EcWminEcWmax limit is 0xFF\n");
+		edca_params.ac_background.ec_wmin_wmax = 0xFF;
+	}
+
+	skw_dbg("AC_BG Set EcWminEcWmax %d\n", edca_params.ac_background.ec_wmin_wmax);
+
+	/** TxopLimit **/
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("AC_BG TxopLimit not found\n");
+		return -ENOTSUPP;
+	}
+
+	//edca_params.ac_best_effort.txop_limit = simple_strtol(p, NULL, 10);
+	edca_params.ac_background.txop_limit = skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (edca_params.ac_background.txop_limit > 0xFFFF) {
+		skw_warn("AC_BG TxopLimit limit is 0xFF\n");
+		edca_params.ac_background.txop_limit = 0xFFFF;
+	}
+
+	skw_dbg("AC_BG Set TxopLimit %d\n", edca_params.ac_best_effort.txop_limit);
+
+	/* parse AC_VIDEO parametes */
+	/** AciAifn **/
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("AC_VIDEO AciAifn not found\n");
+		return -ENOTSUPP;
+	}
+
+	edca_params.ac_video.aci_aifn = skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (edca_params.ac_video.aci_aifn > 0xFF) {
+		skw_warn("AC_VIDEO AciAifn limit is 0xFF\n");
+		edca_params.ac_video.aci_aifn = 0xFF;
+	}
+
+	skw_dbg("AC_VIDEO Set AciAifn %d\n", edca_params.ac_video.aci_aifn);
+
+	/** EcWminEcWmax **/
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("AC_VIDEO EcWminEcWmax not found\n");
+		return -ENOTSUPP;
+	}
+
+	edca_params.ac_video.ec_wmin_wmax = skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (edca_params.ac_video.ec_wmin_wmax > 0xFF) {
+		skw_warn("AC_VIDEO EcWminEcWmax limit is 0xFF\n");
+		edca_params.ac_video.ec_wmin_wmax = 0xFF;
+	}
+
+	skw_dbg("AC_VIDEO Set EcWminEcWmax %d\n", edca_params.ac_video.ec_wmin_wmax);
+
+	/** TxopLimit **/
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("AC_VIDEO TxopLimit not found\n");
+		return -ENOTSUPP;
+	}
+
+	edca_params.ac_video.txop_limit = skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (edca_params.ac_video.txop_limit > 0xFFFF) {
+		skw_warn("AC_VIDEO TxopLimit limit is 0xFF\n");
+		edca_params.ac_video.txop_limit = 0xFFFF;
+	}
+
+	skw_dbg("AC_VIDEO Set TxopLimit %d\n", edca_params.ac_video.txop_limit);
+
+	/* parse AC_VOICE parametes */
+	/** AciAifn **/
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("AC_VOICE AciAifn not found\n");
+		return -ENOTSUPP;
+	}
+
+	edca_params.ac_voice.aci_aifn = skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (edca_params.ac_voice.aci_aifn > 0xFF) {
+		skw_warn("AC_VOICE AciAifn limit is 0xFF\n");
+		edca_params.ac_voice.aci_aifn = 0xFF;
+	}
+
+	skw_dbg("AC_VOICE Set AciAifn %d\n", edca_params.ac_voice.aci_aifn);
+
+	/** EcWminEcWmax **/
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("AC_VOICE EcWminEcWmax not found\n");
+		return -ENOTSUPP;
+	}
+
+	edca_params.ac_voice.ec_wmin_wmax = skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (edca_params.ac_voice.ec_wmin_wmax > 0xFF) {
+		skw_warn("AC_VOICE EcWminEcWmax limit is 0xFF\n");
+		edca_params.ac_voice.ec_wmin_wmax = 0xFF;
+	}
+
+	skw_dbg("AC_VOICE Set EcWminEcWmax %d\n", edca_params.ac_voice.ec_wmin_wmax);
+
+	/** TxopLimit **/
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p) {
+		skw_err("AC_VOICE TxopLimit not found\n");
+		return -ENOTSUPP;
+	}
+
+	edca_params.ac_voice.txop_limit = simple_strtol(p + 1, NULL, 10);
+	if (edca_params.ac_voice.txop_limit > 0xFFFF) {
+		skw_warn("AC_VOICE TxopLimit limit is 0xFF\n");
+		edca_params.ac_voice.txop_limit = 0xFFFF;
+	}
+
+	skw_dbg("AC_VOICE Set TxopLimit %d\n", edca_params.ac_voice.txop_limit);
+
+SETPARA:
+	ret = skw_set_edca_params(iface->wdev.wiphy, iface->ndev, &edca_params);
+	if (!ret)
+		sprintf(resp, "set edca params ok ");
+	else
+		sprintf(resp, "set edca params failed");
+
+	return ret;
+
+SETFAIL:
+
+	sprintf(resp, "set edca params failed");
+
+	return -EINVAL;
+}
+
+static int skw_iwpriv_set_cca_thre_nowifi(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL;
+	struct skw_cca_thre_nowifi nowifi = {0};
+	int temp_val = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("args: %s\n", args);
+
+	p = args;
+	if (!p) {
+		skw_err("nowifi not found\n");
+		return -ENOTSUPP;
+	} else {
+		skw_err("nowifi found %s\n", p);
+		temp_val = simple_strtol(p, NULL, 10);
+		if (temp_val >= 0 || temp_val < -255) {
+			skw_warn("The max CCA Thre NOWIFI is 0xFF\n");
+			return -EINVAL;
+		}
+		nowifi.val = 0 - temp_val;
+	}
+
+	skw_dbg("nowifi found %s\n", p);
+
+	temp_val = simple_strtol(p, NULL, 10);
+	if (temp_val >= 0 || temp_val < -255) {
+		skw_warn("The max CCA Thre NOWIFI is 0xFF\n");
+		return -EINVAL;
+	}
+
+	nowifi.val = 0 - temp_val;
+
+	skw_dbg("Set CCA Thre NOWIFI %d\n", nowifi.val);
+
+	ret = skw_set_cca_thre_nowifi(iface->wdev.wiphy, iface->ndev, &nowifi);
+	if (!ret)
+		sprintf(resp, "set CCA Thre NOWIFI ok ");
+	else
+		sprintf(resp, "set CCA Thre NOWIFI failed");
+
+	return ret;
+}
+
+static int skw_iwpriv_set_cca_thre_11b(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL;
+	struct skw_cca_thre_11b cca_11b = {0};
+	int temp_val = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("args: %s\n", args);
+
+	p = args;
+	if (!p) {
+		skw_err("11b not found\n");
+		return -ENOTSUPP;
+	} else {
+		skw_err("11b found %s\n", p);
+		temp_val = simple_strtol(p, NULL, 10);
+		if (temp_val >= 0 || temp_val < -255) {
+			skw_warn("The max CCA Thre 11b is 0xFF\n");
+			return -EINVAL;
+		}
+		cca_11b.val = 0 - temp_val;
+	}
+
+	skw_dbg("11b found %s\n", p);
+
+	temp_val = simple_strtol(p, NULL, 10);
+	if (temp_val >= 0 || temp_val < -255) {
+		skw_warn("The max CCA Thre 11b is 0xFF\n");
+		return -EINVAL;
+	}
+
+	cca_11b.val = 0 - temp_val;
+
+	skw_dbg("Set CCA Thre 11b %d\n", cca_11b.val);
+
+	ret = skw_set_cca_thre_11b(iface->wdev.wiphy, iface->ndev, &cca_11b);
+	if (!ret)
+		sprintf(resp, "set CCA Thre 11b ok ");
+	else
+		sprintf(resp, "set CCA Thre 11b failed");
+
+	return ret;
+}
+
+static int skw_iwpriv_set_cca_thre_ofdm(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL;
+	struct skw_cca_thre_ofdm ofdm = {0};
+	int temp_val = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("args: %s\n", args);
+
+	p = args;
+	if (!p) {
+		skw_err("ofdm not found\n");
+		return -ENOTSUPP;
+	} else {
+		skw_err("ofdm found %s\n", p);
+		temp_val = simple_strtol(p, NULL, 10);
+		if (temp_val >= 0 || temp_val < -255) {
+			skw_warn("The max CCA Thre ofdm is 0xFF\n");
+			return -EINVAL;
+		}
+		ofdm.val = 0 - temp_val;
+	}
+
+	skw_dbg("ofdm found %s\n", p);
+
+	temp_val = simple_strtol(p, NULL, 10);
+	if (temp_val >= 0 || temp_val < -255) {
+		skw_warn("The max CCA Thre ofdm is 0xFF\n");
+		return -EINVAL;
+	}
+
+	ofdm.val = 0 - temp_val;
+
+	skw_dbg("Set CCA Thre OFDM %d\n", ofdm.val);
+
+	ret = skw_set_cca_thre_ofdm(iface->wdev.wiphy, iface->ndev, &ofdm);
+	if (!ret)
+		sprintf(resp, "set CCA Thre ofdm ok ");
+	else
+		sprintf(resp, "set CCA Thre ofdm failed");
+
+	return ret;
+}
+
+static int skw_is_value_in_enum(u8 value)
+{
+	switch (value) {
+	case LEGA_11B_SHORT_2M:
+	case LEGA_11B_SHORT_55M:
+	case LEGA_11B_SHORT_11M:
+	case LEGA_11B_LONG_1M:
+	case LEGA_11B_LONG_2M:
+	case LEGA_11B_LONG_55M:
+	case LEGA_11B_LONG_11M:
+	case OFDM_6M:
+	case OFDM_9M:
+	case OFDM_12M:
+	case OFDM_18M:
+	case OFDM_24M:
+	case OFDM_36M:
+	case OFDM_48M:
+	case OFDM_54M:
+	case HT_MCS_0:
+	case HT_MCS_1:
+	case HT_MCS_2:
+	case HT_MCS_3:
+	case HT_MCS_4:
+	case HT_MCS_5:
+	case HT_MCS_6:
+	case HT_MCS_7:
+	case HT_MCS_8:
+	case HT_MCS_9:
+	case HT_MCS_10:
+	case HT_MCS_11:
+	case HT_MCS_12:
+	case HT_MCS_13:
+	case HT_MCS_14:
+	case HT_MCS_15:
+	case HT_MCS_16:
+	case HT_MCS_17:
+	case HT_MCS_18:
+	case HT_MCS_19:
+	case HT_MCS_20:
+	case HT_MCS_21:
+	case HT_MCS_22:
+	case HT_MCS_23:
+	case HT_MCS_24:
+	case HT_MCS_25:
+	case HT_MCS_26:
+	case HT_MCS_27:
+	case HT_MCS_28:
+	case HT_MCS_29:
+	case HT_MCS_30:
+	case HT_MCS_31:
+	case VHT_MCS_0:
+	case VHT_MCS_1:
+	case VHT_MCS_2:
+	case VHT_MCS_3:
+	case VHT_MCS_4:
+	case VHT_MCS_5:
+	case VHT_MCS_6:
+	case VHT_MCS_7:
+	case VHT_MCS_8:
+	case VHT_MCS_9:
+	case HE_MCS_0:
+	case HE_MCS_1:
+	case HE_MCS_2:
+	case HE_MCS_3:
+	case HE_MCS_4:
+	case HE_MCS_5:
+	case HE_MCS_6:
+	case HE_MCS_7:
+	case HE_MCS_8:
+	case HE_MCS_9:
+	case HE_MCS_10:
+	case HE_MCS_11:
+	case ER_NDCM_1SS_242TONE_MCS0:
+	case ER_NDCM_1SS_242TONE_MCS1:
+	case ER_NDCM_1SS_242TONE_MCS2:
+	case ER_NDCM_1SS_106TONE_MCS0:
+	case ER_DCM_1SS_242TONE_MCS0:
+	case ER_DCM_1SS_242TONE_MCS1:
+	case ER_DCM_1SS_106TONE_MCS0:
+	case NER_DCM_1SS_MCS0:
+	case NER_DCM_1SS_MCS1:
+	case NER_DCM_1SS_MCS3:
+	case NER_DCM_1SS_MCS4:
+	case NER_DCM_2SS_MCS0:
+	case NER_DCM_2SS_MCS1:
+	case NER_DCM_2SS_MCS3:
+	case NER_DCM_2SS_MCS4:
+		return 1;
+
+	default:
+		return 0;
+	}
+}
+
+static int skw_iwpriv_set_force_rts_rate(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL, *q = NULL;
+	struct skw_force_rts_rate rts_rate = {0};
+	u8 parmas_len = 0, val = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("args: %s\n", args);
+
+	/* parse rts rate flag */
+	parmas_len = strlen(args);
+	if (parmas_len == 1) {
+		ret = simple_strtol(args, NULL, 10);
+		if (!ret) {
+			skw_warn(" not force set rts rate\n");
+			rts_rate.enable = 0;
+			goto SETPARA;
+		}
+
+		skw_warn(" need more rate params\n");
+		goto SETFAIL;
+
+	} else if (parmas_len > 1) {
+		p = strchr(args, ',');
+		if (!p) {
+			skw_err("flag not found\n");
+			return -ENOTSUPP;
+		}
+
+		skw_dbg("params found %s\n", p - 1);
+
+		rts_rate.enable = simple_strtol(p - 1, NULL, 10);
+		if (rts_rate.enable != 0 && rts_rate.enable != 1) {
+			skw_err("flag error %d\n", rts_rate.enable);
+			return -EINVAL;
+		}
+
+		if (rts_rate.enable == 0) {
+			skw_err("not set rts_rate params\n");
+			goto SETPARA;
+		}
+
+		skw_err("start set rts_rate params ...\n");
+	}
+
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("rts rate 2.4G not found\n");
+		return -ENOTSUPP;
+	}
+
+	val = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (!skw_is_value_in_enum(val)) {
+		skw_warn("invalid 2.4G rate %d\n", val);
+		return -EINVAL;
+	}
+
+	rts_rate.rts_rate_24G = val;
+
+	skw_dbg("rts rate 2.4G: %d\n", rts_rate.rts_rate_24G);
+
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p) {
+		skw_err("rts rate 5G not found\n");
+		return -ENOTSUPP;
+	}
+
+	val = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (!skw_is_value_in_enum(val)) {
+		skw_warn("invalid 5G rate %d\n", val);
+		return -EINVAL;
+	}
+
+	rts_rate.rts_rate_5G = val;
+	skw_dbg("rts_rate_5G: %d\n", rts_rate.rts_rate_5G);
+
+SETPARA:
+	ret = skw_set_force_rts_rate(iface->wdev.wiphy, iface->ndev, &rts_rate);
+	if (!ret)
+		sprintf(resp, "set force rts rate ok ");
+	else
+		sprintf(resp, "set force rts rate failed");
+
+	return ret;
+
+SETFAIL:
+	sprintf(resp, "set force rts rate failed");
+
+	return -EINVAL;
+}
+
+static int skw_iwpriv_set_force_rx_rsp_rate(struct skw_iface *iface, void *param,
+	char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL, *q = NULL;
+	struct skw_force_rx_rsp_rate rate = {0};
+	u8 parmas_len = 0, val = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("skw_iwpriv_set_force_rts_rate args %s\n", args);
+
+	/* parse rts rate flag */
+	parmas_len = strlen(args);
+	if (parmas_len == 1) {
+		ret = simple_strtol(args, NULL, 10);
+		if (!ret) {
+			skw_warn(" not force set rx rsp rate\n");
+			goto SETPARA;
+		} else if (ret == 1) {
+			skw_warn(" need more rate params\n");
+			goto SETFAIL;
+		} else {
+			goto SETFAIL;
+		}
+	} else if (parmas_len > 1) {
+		p = strchr(args, ',');
+		if (!p) {
+			skw_err("flag not found\n");
+			return -ENOTSUPP;
+		}
+
+		skw_dbg("params found %s\n", p - 1);
+
+		rate.enable = simple_strtol(p - 1, NULL, 10);
+		if (rate.enable != 0 && rate.enable != 1) {
+			skw_err("flag error %d\n", rate.enable);
+			return -EINVAL;
+		}
+
+		if (rate.enable == 0) {
+			skw_err("not set rx rsp rate params\n");
+			goto SETPARA;
+		}
+
+		skw_dbg("start set rx rsp rate params ...\n");
+	}
+
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("11b long not found\n");
+		return -ENOTSUPP;
+	}
+
+	val = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (!skw_is_value_in_enum(val)) {
+		skw_warn("invalid 11b long rate %d\n", val);
+		return -EINVAL;
+	}
+
+	rate.rx_rsp_rate_11b_long = val;
+	skw_dbg("11b long rate: %d\n", rate.rx_rsp_rate_11b_long);
+
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("11b short not found\n");
+		return -ENOTSUPP;
+	}
+
+	val = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (!skw_is_value_in_enum(val)) {
+		skw_warn("invalid 11b short rate %d\n", val);
+		return -EINVAL;
+	}
+
+	rate.rx_rsp_rate_11b_short = val;
+	skw_dbg("11b short rate: %d\n", rate.rx_rsp_rate_11b_short);
+
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p) {
+		skw_err("ofdm not found\n");
+		return -ENOTSUPP;
+	}
+
+	val = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	if (!skw_is_value_in_enum(val)) {
+		skw_warn("invalid ofdm rate %d\n", val);
+		return -EINVAL;
+	}
+
+	rate.rx_rsp_rate_ofdm = val;
+	skw_dbg("ofdm: %d\n", rate.rx_rsp_rate_ofdm);
+
+SETPARA:
+	ret = skw_set_force_rx_rsp_rate(iface->wdev.wiphy, iface->ndev, &rate);
+	if (!ret)
+		sprintf(resp, "set force rx rsp rate ok ");
+	else
+		sprintf(resp, "set force rx rsp rate failed");
+
+	return ret;
+
+SETFAIL:
+	sprintf(resp, "set force rts rate failed");
+
+	return -EINVAL;
+}
+
+static int skw_iwpriv_set_scan_time(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL, *q = NULL;
+	struct skw_set_scan_time time = {0};
+	u8 parmas_len = 0;
+	u16 val = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("args: %s\n", args);
+
+	/* parse rts rate flag */
+	parmas_len = strlen(args);
+
+	p = args;
+	q = strchr(args, ',');
+	if (!q) {
+		skw_err("parameter error\n");
+		return -ENOTSUPP;
+	}
+
+	skw_dbg("params found %s\n", p - 1);
+	val = skw_iwpriv_convert_string_to_u(p, q - p);
+	if (val > 0xFF) {
+		skw_warn("active dwell limit to 0xFF\n");
+		time.active_dwell_time = 0xFF;
+	} else {
+		time.active_dwell_time = val;
+	}
+
+	skw_dbg("active dwell time: %d\n", time.active_dwell_time);
+
+	p = q + strlen(",");
+	if (!p) {
+		skw_warn("bypass active scan auto time flag not found\n");
+		return -ENOTSUPP;
+	}
+
+	val = simple_strtol(p, NULL, 10);
+	if (val != 0 && val != 1) {
+		skw_err("flag error %d\n", val);
+		return -EINVAL;
+	}
+
+	time.bypass_active_acan_auto_time = val;
+
+	skw_dbg("bypass active scan auto time: %d\n", time.bypass_active_acan_auto_time);
+
+	ret = skw_set_scan_time_cmd(iface->wdev.wiphy, iface->ndev, &time);
+	if (!ret)
+		sprintf(resp, "set scan time ok ");
+	else
+		sprintf(resp, "set scan time failed");
+
+	return ret;
+}
+
+static int skw_iwpriv_set_tcpd_wakeup_host(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	struct skw_set_tcpd_wakeup_host set_flag = {0};
+	u8 parmas_len = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("set_tcpd_wakeup_host args %s\n", args);
+
+	parmas_len = strlen(args);
+	if (parmas_len == 1) {
+		ret = simple_strtol(args, NULL, 10);
+		if (!ret) {
+			skw_dbg("flag: %d\n", ret);
+			set_flag.enable = 0;
+		} else if (ret == 1) {
+			skw_dbg("flag: %d\n", ret);
+			set_flag.enable = 1;
+		} else {
+			goto SETFAIL;
+		}
+	} else if (parmas_len > 1) {
+		skw_err("params error\n");
+		goto SETFAIL;
+	}
+
+	skw_dbg("wakeup host: %d\n", set_flag.enable);
+
+	ret = skw_set_tcp_disconn_wakeup_host(iface->wdev.wiphy, iface->ndev, &set_flag);
+	if (!ret)
+		sprintf(resp, "set tcp disc wakeup host ok ");
+	else
+		sprintf(resp, "set tcp disc wakeup host failed");
+
+	return ret;
+
+SETFAIL:
+	sprintf(resp, "set tcp disc wakeup host failed(param error)");
+
+	return ret;
+}
+
+static int skw_iwpriv_set_rc_min_rate(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	struct skw_set_rate_control_min_rate rate = {0};
+	u8 parmas_len = 0, val = 0;
+	char *p = NULL;
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("set_rc_min_rate args %s\n", args);
+
+	parmas_len = strlen(args);
+	if (!parmas_len)
+		return -EINVAL;
+
+	p = args;
+	val = (u8)skw_iwpriv_convert_string_to_u(p, parmas_len);
+	if (!skw_is_value_in_enum(val)) {
+		skw_dbg("val not valid %d\n", val);
+		goto SETFAIL;
+	}
+
+	rate.rstrict_min_rate = val;
+	skw_dbg("rc min rate: %d\n", rate.rstrict_min_rate);
+
+	ret = skw_set_rc_min_rate(iface->wdev.wiphy, iface->ndev, &rate);
+	if (!ret)
+		sprintf(resp, "set rc min rate ok");
+	else
+		sprintf(resp, "set rc min rate failed");
+
+	return ret;
+
+SETFAIL:
+	sprintf(resp, "set rc min rate failed (value not support)");
+
+	return ret;
+}
+
+static int skw_iwpriv_set_rc_rate_change(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL, *q = NULL;
+	struct skw_set_rate_control_rate_change rate = {0};
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("set_rc_rate_change args %s\n", args);
+
+	//1/5 up rate class num
+	p = args;
+	q = strchr(args, ',');
+	if (!p || !q) {
+		skw_err("up rate class num not found\n");
+		return -ENOTSUPP;
+	}
+
+	rate.up_rate_class_num = (u8)skw_iwpriv_convert_string_to_u(p, q - p);
+
+	skw_dbg("up_rate_class_num: %d\n", rate.up_rate_class_num);
+
+	//2/5 down rate class num
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("down rate class num not found\n");
+		return -ENOTSUPP;
+	}
+
+	rate.down_rate_class_num = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+
+	skw_dbg("down_rate_class_num: %d\n", rate.down_rate_class_num);
+
+	//3/5 hw rty limit
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("hw rty limit not found\n");
+		return -ENOTSUPP;
+	}
+
+	rate.hw_rty_limit = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	skw_dbg("hw_rty_limit: %d\n", rate.hw_rty_limit);
+
+	//4/5 per rate hw rty limit
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("per rate hw rty limit not found\n");
+		return -ENOTSUPP;
+	}
+
+	rate.per_rate_hw_rty_limit = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+
+	skw_dbg("per rate hw_rty_limit: %d\n", rate.per_rate_hw_rty_limit);
+
+	//5/5 per rate probe hw rty limit
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p) {
+		skw_err("per rate probe hw rty limit not found\n");
+		return -ENOTSUPP;
+	}
+
+	rate.per_rate_probe_hw_rty_limit = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+
+	skw_dbg("per rate probe hw_rty_limit: %d\n", rate.per_rate_probe_hw_rty_limit);
+
+	ret = skw_set_rate_control_rate_change(iface->wdev.wiphy, iface->ndev, &rate);
+	if (!ret)
+		sprintf(resp, "set force rx rsp rate ok ");
+	else
+		sprintf(resp, "set force rx rsp rate failed");
+
+	return ret;
+}
+
+static int skw_iwpriv_set_rc_spe_rate(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	struct skw_set_rate_control_special_rate rate = {0};
+	u8 parmas_len = 0, val = 0;
+	char *p = NULL;
+
+	if (!args)
+		return -EINVAL;
+
+	parmas_len = strlen(args);
+	if (!parmas_len)
+		return -EINVAL;
+
+	p = args;
+	val = (u8)skw_iwpriv_convert_string_to_u(p, parmas_len);
+	if (!skw_is_value_in_enum(val)) {
+		skw_dbg("val not valid %d\n", val);
+		goto SETFAIL;
+	}
+
+	rate.special_frm_rate = val;
+	skw_dbg("rc special rate: %d\n", rate.special_frm_rate);
+
+	ret = skw_set_rc_spe_rate(iface->wdev.wiphy, iface->ndev, &rate);
+	if (!ret)
+		sprintf(resp, "set rc special rate ok ");
+	else
+		sprintf(resp, "set rc special rate failed");
+
+	return ret;
+
+SETFAIL:
+	sprintf(resp, "set rc spe rate failed (value not support)");
+
+	return ret;
+}
+
+static int skw_generic_tlv_operation(struct wiphy *wiphy, struct net_device *dev,
+		void *param, size_t param_len, enum SKW_MIB_ID tlv_type)
+{
+	int ret = 0;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+	struct skw_tlv_get_assign_addr_rsp resp = {0};
+
+	ret = skw_tlv_alloc(&conf, 512, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc tlv failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve tlv failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, tlv_type, param, param_len)) {
+		skw_err("add tlv failed\n");
+		skw_tlv_free(&conf);
+		return -EINVAL;
+	}
+
+	if (conf.total_len) {
+		*plen = conf.total_len;
+		if (tlv_type == SKW_MIB_GET_ASSIGN_ADDR_VAL_E) {
+			ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+					conf.total_len, &resp, sizeof(resp));
+
+			if (ret) {
+				skw_err("failed, ret: %d\n", ret);
+				goto SETFAIL;
+			}
+			skw_dbg("read done val: 0x%08x\n", resp.val);
+		} else {
+			ret = skw_send_msg(wiphy, dev, SKW_CMD_SET_MIB, conf.buff,
+					conf.total_len, NULL, 0);
+			if (ret) {
+				skw_err("failed, ret: %d\n", ret);
+				goto SETFAIL;
+			}
+		}
+	}
+SETFAIL:
+	skw_tlv_free(&conf);
+	return ret;
+}
+
+static int skw_set_tx_lifetime(struct wiphy *wiphy, struct net_device *dev,
+			struct skw_tlv_set_tx_lifetime *lifetime)
+{
+	return skw_generic_tlv_operation(wiphy, dev, lifetime,
+			sizeof(struct skw_tlv_set_tx_lifetime),
+			SKW_MIB_SET_TX_LIFETIME);
+}
+
+static int skw_iwpriv_set_tx_lifetime(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	struct skw_tlv_set_tx_lifetime lftm = {0};
+	u8 parmas_len = 0;
+	char *p = NULL;
+
+	if (!args)
+		return -EINVAL;
+
+	parmas_len = strlen(args);
+	if (!parmas_len)
+		return -EINVAL;
+
+	p = args;
+	lftm.lifetime = skw_iwpriv_convert_string_to_u(p, parmas_len);
+
+	skw_dbg("set tx lifetime: %d\n", lftm.lifetime);
+
+	ret = skw_set_tx_lifetime(iface->wdev.wiphy, iface->ndev, &lftm);
+	if (!ret)
+		sprintf(resp, "set ok ");
+	else
+		sprintf(resp, "set failed");
+
+	return ret;
+}
+
+static int skw_set_tx_retry_cnt(struct wiphy *wiphy, struct net_device *dev,
+		struct skw_tlv_set_retry_cnt *rtycnt)
+{
+	return skw_generic_tlv_operation(wiphy, dev, rtycnt,
+			sizeof(struct skw_tlv_set_retry_cnt),
+			SKW_MIB_SET_TX_RETRY_CNT);
+}
+
+static int skw_iwpriv_set_tx_retry_cnt(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	struct skw_tlv_set_retry_cnt cnt = {0};
+	u8 parmas_len = 0;
+	char *p = NULL;
+
+	if (!args)
+		return -EINVAL;
+
+	parmas_len = strlen(args);
+	if (!parmas_len)
+		return -EINVAL;
+
+	p = args;
+	cnt.rtycnt = (u8)skw_iwpriv_convert_string_to_u(p, parmas_len);
+
+	skw_dbg("set tx retry cnt: %d\n", cnt.rtycnt);
+
+	ret = skw_set_tx_retry_cnt(iface->wdev.wiphy, iface->ndev, &cnt);
+	if (!ret)
+		sprintf(resp, "set ok ");
+	else
+		sprintf(resp, "set failed");
+
+	return ret;
+}
+
+static int skw_set_tx_rts_thrd(struct wiphy *wiphy, struct net_device *dev,
+		struct skw_tlv_set_tx_rts_thrd *thrd)
+{
+	return skw_generic_tlv_operation(wiphy, dev, thrd,
+			sizeof(struct skw_tlv_set_tx_rts_thrd),
+			SKW_MIB_SET_TX_RTS_THRD);
+}
+
+static int skw_iwpriv_set_tx_rts_thrd(struct skw_iface *iface, void *param,
+	char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	struct skw_tlv_set_tx_rts_thrd thrd = {0};
+	u8 parmas_len = 0;
+	char *p = NULL;
+
+	if (!args)
+		return -EINVAL;
+
+	parmas_len = strlen(args);
+	if (!parmas_len)
+		return -EINVAL;
+
+	p = args;
+	thrd.rts_thrd = skw_iwpriv_convert_string_to_u(p, parmas_len);
+
+	skw_dbg("set tx rts thrd: %d\n", thrd.rts_thrd);
+
+	ret = skw_set_tx_rts_thrd(iface->wdev.wiphy, iface->ndev, &thrd);
+	if (!ret)
+		sprintf(resp, "set ok ");
+	else
+		sprintf(resp, "set failed");
+
+	return ret;
+}
+
+static int skw_set_rx_sepcial_80211_frame(struct wiphy *wiphy, struct net_device *dev,
+		struct skw_tlv_set_rx_special_80211_frame *frm)
+{
+	return skw_generic_tlv_operation(wiphy, dev, frm,
+			sizeof(struct skw_tlv_set_rx_special_80211_frame),
+			SKW_MIB_SET_FORCE_RX_SPECIAL_80211FRAME);
+}
+
+static int skw_iwpriv_set_rx_sepcial_80211_frame(struct skw_iface *iface, void *param,
+	char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL, *q = NULL;
+	struct skw_tlv_set_rx_special_80211_frame frm = {0};
+	u8 parmas_len = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("set_rx_sepcial_80211_frame args %s\n", args);
+
+	parmas_len = strlen(args);
+	if (parmas_len == 1) {
+		ret = simple_strtol(args, NULL, 10);
+		if (!ret) {
+			skw_warn("disable rx\n");
+			frm.en = 0;
+			goto SETPARA;
+		} else if (ret == 1) {
+			skw_warn("need more params\n");
+			goto SETFAIL;
+		} else {
+			goto SETFAIL;
+		}
+	} else if (parmas_len > 1) {
+		p = args;
+		q = strchr(args, ',');
+		if (!p || !q) {
+			skw_err("flag not found\n");
+			return -ENOTSUPP;
+		}
+
+		frm.en = (u8)skw_iwpriv_convert_string_to_u(p, q - p);
+		if (frm.en != 0 && frm.en != 1) {
+			skw_err("flag error %d\n", frm.en);
+			return -EINVAL;
+		}
+
+		if (frm.en == 0) {
+			skw_warn("disable rx\n");
+			frm.en = 0;
+			goto SETPARA;
+		}
+
+		skw_err("start set type and sub type params ...\n");
+	}
+
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !q) {
+		skw_err("param error\n");
+		return -ENOTSUPP;
+	}
+
+	frm.type = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+
+	skw_dbg("type: %d\n", frm.type);
+
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p) {
+		skw_err("sub type not found\n");
+		return -ENOTSUPP;
+	}
+
+	frm.sub_type = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+
+	skw_dbg("sub type: %d\n", frm.sub_type);
+
+SETPARA:
+	ret = skw_set_rx_sepcial_80211_frame(iface->wdev.wiphy, iface->ndev, &frm);
+	if (!ret)
+		sprintf(resp, "set ok ");
+	else
+		sprintf(resp, "set failed");
+
+	return ret;
+
+SETFAIL:
+
+	sprintf(resp, "set failed");
+
+	return -EINVAL;
+}
+
+static int skw_set_rx_update_nav(struct wiphy *wiphy, struct net_device *dev,
+		struct skw_tlv_set_rx_update_nav *nav)
+{
+	return skw_generic_tlv_operation(wiphy, dev, nav,
+			sizeof(struct skw_tlv_set_rx_update_nav),
+			SKW_MIB_SET_FORCE_RX_UPDATE_NAV);
+}
+
+static int skw_iwpriv_set_rx_update_nav(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL, *q = NULL;
+	struct skw_tlv_set_rx_update_nav nav = {0};
+	int temp_val = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("args %s\n", args);
+
+	p = args;
+	q = strchr(args, ',');
+	if(!p || !q)
+	{
+		skw_err("parameter error\n");
+		return -ENOTSUPP;
+	} else {
+		skw_dbg("params found %s\n", p);
+		temp_val = simple_strtol(p, NULL, 10);
+		if (temp_val >= 0 || temp_val < -255) {
+			skw_warn("parameter error %d\n", temp_val);
+			return -EINVAL;
+		}
+		nav.intra_rssi = 0x80 | (0 - temp_val);
+	}
+	skw_dbg("intra rssi: %d\n", nav.intra_rssi);
+
+	p = q;
+	q = strchr(p + 1, ',');
+	if(!p || !p)
+	{
+		skw_err("parameter error\n");
+		return -ENOTSUPP;
+	} else {
+		skw_dbg("params found %s\n", p + 1);
+		temp_val = simple_strtol(p + 1, NULL, 10);
+		if (temp_val >= 0 || temp_val < -255) {
+			skw_warn("parameter error %d\n", temp_val);
+			return -EINVAL;
+		}
+		nav.basic_rssi = 0x80 | (0 - temp_val);
+	}
+	skw_dbg("basic rssi: %d\n", nav.basic_rssi);
+
+	p = q;
+	p = q + strlen(",");
+	if(!p)
+	{
+		skw_warn("nav max time not found\n");
+		return -ENOTSUPP;
+	} else {
+		 nav.nav_max_time = simple_strtol(p, NULL, 10);
+	}
+	skw_dbg("nav max time: %d\n", nav.nav_max_time);
+
+	ret = skw_set_rx_update_nav(iface->wdev.wiphy, iface->ndev, &nav);
+
+	if (!ret ) {
+		sprintf(resp, "set ok ");
+	} else
+		sprintf(resp, "set failed");
+
+	return ret;
+}
+
+//TLV 70
+static int skw_set_apgo_timap(struct wiphy *wiphy, struct net_device *dev,
+		struct skw_tlv_set_apgo_timap *timap)
+{
+	return skw_generic_tlv_operation(wiphy, dev, timap,
+			sizeof(struct skw_tlv_set_apgo_timap),
+			SKW_MIB_SET_APGO_TIMMAP);
+}
+
+static int skw_iwpriv_set_apgo_timap(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL, *q = NULL;
+	struct skw_tlv_set_apgo_timap timap = {0};
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("set_apgo_timap args %s\n", args);
+
+	p = args;
+	q = strchr(args, ',');
+	if (!p || !q) {
+		skw_err("parameter error\n");
+		return -ENOTSUPP;
+	}
+
+	timap.dtimforce0 = (u8)skw_iwpriv_convert_string_to_u(p, q - p);
+	skw_dbg("dtim force0: %d\n", timap.dtimforce0);
+
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !p) {
+		skw_err("parameter error\n");
+		return -ENOTSUPP;
+	}
+
+	timap.dtimforce1 = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+
+	skw_dbg("dtim force1: %d\n", timap.dtimforce1);
+
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p || !p) {
+		skw_err("parameter error\n");
+		return -ENOTSUPP;
+	}
+
+	timap.timforce0 = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	skw_dbg("tim force0: %d\n", timap.timforce0);
+
+	p = q;
+	q = strchr(p + 1, ',');
+	if (!p) {
+		skw_err("parameter error\n");
+		return -ENOTSUPP;
+	}
+
+	timap.timforce1 = (u8)skw_iwpriv_convert_string_to_u(p + 1, q - p - 1);
+	skw_dbg("tim force1: %d\n", timap.timforce1);
+
+	ret = skw_set_apgo_timap(iface->wdev.wiphy, iface->ndev, &timap);
+	if (!ret)
+		sprintf(resp, "set ok ");
+	else
+		sprintf(resp, "set failed");
+
+	return ret;
+}
+
+//TLV 71
+static int skw_set_dbdc_disable(struct wiphy *wiphy, struct net_device *dev,
+		struct skw_tlv_set_dbdc_disable *flag)
+{
+	return skw_generic_tlv_operation(wiphy, dev, flag,
+			sizeof(struct skw_tlv_set_dbdc_disable),
+			SKW_MIB_SET_DBDC_DISABLE);
+}
+
+static int skw_iwpriv_set_dbdc_disable(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	struct skw_tlv_set_dbdc_disable set_flag = {0};
+	u8 parmas_len = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	parmas_len = strlen(args);
+	if (parmas_len == 1) {
+		ret = simple_strtol(args, NULL, 10);
+		if (!ret) {
+			skw_dbg("flag: %d\n", ret);
+			set_flag.disable = 0;
+		} else if (ret == 1) {
+			skw_dbg("flag: %d\n", ret);
+			set_flag.disable = 1;
+		} else {
+			goto SETFAIL;
+		}
+	} else if (parmas_len > 1) {
+		skw_err("params error\n");
+		goto SETFAIL;
+	}
+
+	skw_dbg("dbdc disable: %d\n", set_flag.disable);
+
+	ret = skw_set_dbdc_disable(iface->wdev.wiphy, iface->ndev, &set_flag);
+	if (!ret)
+		sprintf(resp, "set ok ");
+	else
+		sprintf(resp, "set failed");
+
+	return ret;
+
+SETFAIL:
+	sprintf(resp, "set failed(param error)");
+
+	return ret;
+}
+
+//TLV 150
+static int skw_set_assign_address_val(struct wiphy *wiphy, struct net_device *dev,
+		struct skw_tlv_set_assign_addr_val *param)
+{
+	return skw_generic_tlv_operation(wiphy, dev, param,
+			sizeof(struct skw_tlv_set_assign_addr_val),
+			SKW_MIB_SET_ASSIGN_ADDRESS_VAL);
+}
+
+static int skw_iwpriv_set_assign_address_val(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL, *q = NULL;
+	struct skw_tlv_set_assign_addr_val params = {0};
+	u8 parmas_len = 0, remain_len = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	parmas_len = strlen(args);
+	skw_warn("args %s\n", args);
+
+	p = args;
+	q = strchr(args, ',');
+	if (!p || !q) {
+		skw_err("params error\n");
+		return -ENOTSUPP;
+	}
+
+	params.addr = skw_iwpriv_convert_string_to_u32(p, q - p);
+	skw_dbg("addr: 0x%08x\n", params.addr);
+
+	p = q;
+	remain_len = parmas_len - (q - p) - 1;
+	if (!p) {
+		skw_err("param error\n");
+		return -ENOTSUPP;
+	}
+
+	params.val = skw_iwpriv_convert_string_to_u32(p + 1, remain_len);
+	skw_dbg("val: 0x%08x\n", params.val);
+
+	ret = skw_set_assign_address_val(iface->wdev.wiphy, iface->ndev, &params);
+	if (!ret)
+		sprintf(resp, "set ok ");
+	else
+		sprintf(resp, "set failed");
+
+	return ret;
+}
+
+//TLV 250
+static int skw_read_addr(struct wiphy *wiphy, struct net_device *dev,
+		struct skw_tlv_get_assign_addr *param)
+{
+	return skw_generic_tlv_operation(wiphy, dev, param,
+			sizeof(struct skw_tlv_get_assign_addr),
+			SKW_MIB_GET_ASSIGN_ADDR_VAL_E);
+}
+
+static int skw_iwpriv_read_addr(struct skw_iface *iface, void *param,
+		char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL;
+	struct skw_tlv_get_assign_addr params = {0};
+	u8 parmas_len = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	parmas_len = strlen(args);
+	if (!parmas_len || parmas_len > 10) {
+		skw_err("params error\n");
+		return -ENOTSUPP;
+	}
+
+	skw_dbg("args %s\n", args);
+
+	p = args;
+	if (!p) {
+		skw_err("params error\n");
+		return -ENOTSUPP;
+	}
+
+	params.addr = skw_iwpriv_convert_string_to_u32(p, parmas_len);
+	skw_dbg("addr: 0x%08x\n", params.addr);
+
+	ret = skw_read_addr(iface->wdev.wiphy, iface->ndev, &params);
+	if (!ret)
+		sprintf(resp, "set ok ");
+	else
+		sprintf(resp, "set failed");
+
+	return ret;
+}
+
+//TLV 39 ageout thrd
+//
+static int skw_set_ageout_thrd(struct wiphy *wiphy, struct net_device *dev, struct skw_tlv_set_ageout_thrd *params)
+{
+	return skw_generic_tlv_operation(wiphy, dev, params, sizeof(struct skw_tlv_set_ageout_thrd), SKW_MIB_SET_AGEOUT_THOLD);
+}
+
+static int skw_iwpriv_set_ageout_thrd(struct skw_iface *iface, void *param,
+	char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL, *q = NULL;
+	struct skw_tlv_set_ageout_thrd thrd = {0};
+	u16 temp_val = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("args %s\n", args);
+
+	p = args;
+	q = strchr(args, ',');
+	if (!p || !q) {
+		skw_err("param err\n");
+		return -ENOTSUPP;
+	}
+
+	temp_val = simple_strtol(p, NULL, 10);
+	if (temp_val >= 0xFF) {
+		skw_warn("parameter out of range (0 ~ 255) %d\n", temp_val);
+		thrd.ageout_kick_thrd = 0xFF;
+	} else {
+		thrd.ageout_kick_thrd  = temp_val;
+	}
+
+	skw_dbg("tbtt cnt, kick out %d\n", thrd.ageout_kick_thrd);
+
+	p = q;
+	p = q + strlen(",");
+	if (!p) {
+		skw_warn("param err\n");
+		return -ENOTSUPP;
+	}
+
+	temp_val = simple_strtol(p, NULL, 10);
+	if (temp_val >= 0xFF) {
+		skw_warn("parameter out of range (0 ~ 255) %d\n", temp_val);
+		thrd.ageout_keep_alive_thrd = 0xFF;
+	} else {
+		thrd.ageout_keep_alive_thrd  = temp_val;
+	}
+
+	skw_dbg("keep alive send null thrd: %d\n", thrd.ageout_keep_alive_thrd);
+
+	ret = skw_set_ageout_thrd(iface->wdev.wiphy, iface->ndev, &thrd);
+
+	if (!ret)
+		sprintf(resp, "set ok");
+	else
+		sprintf(resp, "set failed");
+
+	return ret;
+
+}
+
+//tlv 42
+static int skw_set_reported_cqm_interval(struct wiphy *wiphy, struct net_device *dev, struct skw_tlv_set_report_cqm_rssi_low_itvl *params)
+{
+	return skw_generic_tlv_operation(wiphy, dev, params, sizeof(struct skw_tlv_set_report_cqm_rssi_low_itvl), SKW_MIB_SET_REPORT_CQM_RSSI_LOW_INT);
+}
+
+//tlv 54 ~ 57
+static int skw_set_ap_new_channel(struct wiphy *wiphy, struct net_device *dev, struct skw_tlv_set_ap_new_channel *params)
+{
+	return skw_generic_tlv_operation(wiphy, dev, params, sizeof(struct skw_tlv_set_ap_new_channel), SKW_MIB_SET_AP_NEW_CHAN);
+}
+
+static int skw_set_tx_retry_limit_en(struct wiphy *wiphy, struct net_device *dev, struct skw_tlv_set_tx_retry_limit_en *params)
+{
+	return skw_generic_tlv_operation(wiphy, dev, params, sizeof(struct skw_tlv_set_tx_retry_limit_en), SKW_MIB_SET_TX_RETRY_LIMIT_EN);
+}
+
+static int skw_partial_twt_sched(struct wiphy *wiphy, struct net_device *dev, struct skw_tlv_set_partial_twt_sched *params)
+{
+	return skw_generic_tlv_operation(wiphy, dev, params, sizeof(struct skw_tlv_set_partial_twt_sched), SKW_MIB_SET_PARTIAL_TWT_SCHED);
+}
+
+static int skw_set_thm_thre(struct wiphy *wiphy, struct net_device *dev, struct skw_tlv_set_thm_thrd *params)
+{
+	return skw_generic_tlv_operation(wiphy, dev, params, sizeof(struct skw_tlv_set_thm_thrd), SKW_MIB_SET_THM_THRD);
+}
+
+static int skw_set_normal_scan_with_acs(struct wiphy *wiphy, struct net_device *dev,
+										struct skw_tlv_set_normal_scan_with_acs *params)
+{
+	return skw_generic_tlv_operation(wiphy, dev, params, sizeof(struct skw_tlv_set_normal_scan_with_acs),
+									SKW_MIB_SET_NORMAL_SCAN_WITH_ACS);
+}
+
+extern int skw_set_dot11r_mib(struct wiphy *wiphy, struct net_device *dev, bool enable);
+
+static int skw_set_scan_send_probe_req_cnt(struct wiphy *wiphy, struct net_device *dev,
+										u8 cnt)
+{
+	return skw_generic_tlv_operation(wiphy, dev, &cnt, 1,
+								SKW_MIB_SET_SCAN_SEND_PROBE_REQ_CNT);
+}
+
+static int skw_iwpriv_set_params(struct skw_iface *iface, void *param,
+								char *args, char *resp, int resp_len)
+{
+	int ret = 0;
+	char *p = NULL, *q = NULL;
+	u32 params[16] = {0};
+	u32 params_num = 0;
+	struct skw_tlv_set_ap_new_channel new_chan = {0};
+	struct skw_tlv_set_tx_retry_limit_en txr_limit = {0};
+	struct skw_tlv_set_report_cqm_rssi_low_itvl itvl = {0};
+	struct skw_tlv_set_thm_thrd thrd = {0};
+	struct skw_tlv_set_partial_twt_sched twt_params = {0};
+	struct skw_tlv_set_normal_scan_with_acs normal_scan_params = {0};
+	u8 send_probe_req_cnt = 0;
+
+	if (!args)
+		return -EINVAL;
+
+	skw_dbg("args %s\n", args);
+
+	p = args;
+
+	while (p) {
+		params[params_num] = simple_strtol(p, NULL, 10);
+		params_num++;
+
+		q = strchr(p, ',');
+		if (!q) {
+			break;
+		}
+
+		p = q + 1; /* move past the comma */
+	}
+
+	if (params_num < 2) {
+		skw_warn("params error, at least 2\n");
+		return -ENOTSUPP;
+	}
+
+	switch (params[0]) {
+		case 1:
+			if (params_num != 6) {
+				skw_warn("params error, need 6 totally\n");
+				return -EINVAL;
+			}
+
+			new_chan.chan = params[1];
+			new_chan.center_chan = params[2];
+			new_chan.center_two_chan = params[3];
+			new_chan.bw = params[4];
+			new_chan.band = params[5];
+
+			ret = skw_set_ap_new_channel(iface->wdev.wiphy, iface->ndev, &new_chan);
+			skw_dbg("set ap new chan done\n");
+
+			break;
+
+		case 2:
+			if (params_num != 2) {
+				skw_warn("params error, need 2 totally\n");
+				return -EINVAL;
+			}
+
+			ret = skw_set_dot11r_mib(iface->wdev.wiphy, iface->ndev, (bool)params[1]);
+			skw_dbg("set 11r mib done\n");
+			break;
+
+		case 3:
+			if (params_num != 4) {
+				skw_warn("params error, need 4 totally\n");
+				return -EINVAL;
+			}
+
+			txr_limit.short_retry_check_en = !!params[1];
+			txr_limit.long_retry_check_en = !!params[2];
+			txr_limit.ampdu_retry_check_en = !!params[3];
+
+			ret = skw_set_tx_retry_limit_en(iface->wdev.wiphy, iface->ndev,&txr_limit);
+			skw_dbg("set tx retry limit done\n");
+
+			break;
+
+		case 4:
+			if (params_num != 3) {
+				skw_warn("params error, need 3 totally\n");
+				return -EINVAL;
+			}
+
+			itvl.report_cqm_low_intvl_min_dur = params[1];
+			itvl.report_cqm_low_intvl_max_dur = params[2];
+
+			ret = skw_set_reported_cqm_interval(iface->wdev.wiphy, iface->ndev,&itvl);
+			skw_dbg("set report cqm interval done\n");
+			break;
+
+		case 5:
+			if (params_num != 3) {
+				skw_warn("params error, need 3 totally\n");
+				return -EINVAL;
+			}
+
+			thrd.thm_high_thrd_tx_suspend = (s16)params[1];
+			thrd.thm_low_thrd_tx_resume = (s16)params[2];
+
+			ret = skw_set_thm_thre(iface->wdev.wiphy, iface->ndev, &thrd);
+			skw_dbg("set thm thre done\n");
+
+			break;
+
+		case 6:
+			if (params_num != 7) {
+				skw_warn("params error, need 7 totally\n");
+				return -EINVAL;
+			}
+
+			twt_params.en = params[1];
+			twt_params.start_time_l = params[2];
+			twt_params.start_time_h = params[3];
+			twt_params.interval = params[4];
+			twt_params.duration = params[5];
+			twt_params.duration_unit = params[6];
+			twt_params.sub_type = params[7];
+
+			ret = skw_partial_twt_sched(iface->wdev.wiphy, iface->ndev, &twt_params);
+			skw_dbg("set partial twt sched done\n");
+
+			break;
+		case 59:
+			if (params_num != 2) {
+				skw_warn("params error, need 2 totally\n");
+				return -EINVAL;
+			}
+
+			normal_scan_params.en = params[1];
+			ret = skw_set_normal_scan_with_acs(iface->wdev.wiphy, iface->ndev, &normal_scan_params);
+			skw_dbg("set normal scan with acs %d done\n", normal_scan_params.en);
+
+			break;
+		case 62:
+			if (params_num != 2) {
+				skw_warn("params error, need 2 totally    \n");
+				return -EINVAL;
+			}
+			send_probe_req_cnt = params[1];
+			ret = skw_set_scan_send_probe_req_cnt(iface->wdev.wiphy, iface->ndev, send_probe_req_cnt);
+			skw_dbg("set scan send probe req cnt %d done\n", send_probe_req_cnt);
+			break;
+
+		default:
+			return -ENOTSUPP;
+	}
+
+	if (!ret)
+		snprintf(resp, resp_len, "set ok");
+	else
+		snprintf(resp, resp_len, "set failed");
+
+	return ret;
+}
+
+static struct skw_iwpriv_cmd skw_iwpriv_set_cmds[] = {
+	/* keep first */
+	{"help", skw_iwpriv_help, "usage"},
+	{"bandcfg", skw_iwpriv_set_bandcfg, "bandcfg=0/1/2"},
+	{"mppdudur", skw_iwpriv_set_max_ppdu_dur, "mppdudur=0~5,1~65535"},
+	{"edca", skw_iwpriv_set_edca_params, "edca=0,x(13 total)"},
+	{"ccanowifi", skw_iwpriv_set_cca_thre_nowifi, "ccanowifi=-u8"},
+	{"cca11b", skw_iwpriv_set_cca_thre_11b, "cca11b=-u8"},
+	{"ccaofdm", skw_iwpriv_set_cca_thre_ofdm, "ccaofdm=-u8"},
+	{"rtsrate", skw_iwpriv_set_force_rts_rate, "rtsrate=u8,u8,u8"},
+	{"rxrsprate", skw_iwpriv_set_force_rx_rsp_rate, "rxrsprate=u8,u8,u8,u8"},
+	{"scantime", skw_iwpriv_set_scan_time, "scantime=u8,u8"},
+	{"tcpdwhost", skw_iwpriv_set_tcpd_wakeup_host, "tcpdwhost=u8"},
+	{"rcminrate", skw_iwpriv_set_rc_min_rate, "rcminrate=u8"},
+	{"rcratechg", skw_iwpriv_set_rc_rate_change, "rcratechg=u8,u8,u8,u8,u8"},
+	{"rcsperate", skw_iwpriv_set_rc_spe_rate, "rcsperate=u8"},
+	{"txlftm", skw_iwpriv_set_tx_lifetime, "txlftm=u8"},
+	{"txrtycnt", skw_iwpriv_set_tx_retry_cnt, "txrtycnt=u8"},
+	{"txrtsthrd", skw_iwpriv_set_tx_rts_thrd, "txrtsthrd=u8"},
+	{"rxsped11frm", skw_iwpriv_set_rx_sepcial_80211_frame, "rxsped11frm=u8,u8,u8"},
+	{"rxupdnav", skw_iwpriv_set_rx_update_nav, "rxupdnav=-u8,-u8,u8"},
+	{"apgotimap", skw_iwpriv_set_apgo_timap, "apgotimap=u8,u8,u8,u8"},
+	{"dbdcdis", skw_iwpriv_set_dbdc_disable, "dbdcdis=u8"},
+	{"addrval", skw_iwpriv_set_assign_address_val, "addrval=0x12345678,0x12345678"},
+	{"rdaddr", skw_iwpriv_read_addr, "rdaddr=0x12345678"},
+	{"ageout", skw_iwpriv_set_ageout_thrd, "ageout=u8,u8"},
+	{"params", skw_iwpriv_set_params, "params=index,val1,val2,val3,..."}, //for tlv42 and 54~57,59
+
+	/*keep last*/
+	{NULL, NULL, NULL}
+};
+
+static struct skw_iwpriv_cmd skw_iwpriv_get_cmds[] = {
+	/* keep first */
+	{"help", skw_iwpriv_help, "usage"},
+
+	{"bandcfg", skw_iwpriv_get_bandcfg, "bandcfg"},
+
+	/*keep last*/
+	{NULL, NULL, NULL}
+};
+
+static struct skw_iwpriv_cmd *skw_iwpriv_cmd_match(struct skw_iwpriv_cmd *cmds,
+					const char *key, int key_len)
+{
+	int i;
+
+	for (i = 0; cmds[i].name; i++) {
+		if (!memcmp(cmds[i].name, key, key_len))
+			return &cmds[i];
+	}
+
+	return NULL;
+}
+
+static int skw_iwpriv_set(struct net_device *dev,
+			   struct iw_request_info *info,
+			   union iwreq_data *wrqu, char *extra)
+{
+	int ret = 0;
+	int key_len;
+	char param[128] = {0};
+	char *token = NULL, *args = NULL;
+	struct skw_iwpriv_cmd *iwpriv_cmd;
+	struct skw_iface *iface = (struct skw_iface *)netdev_priv(dev);
+
+	WARN_ON(sizeof(param) < wrqu->data.length);
+
+	if (copy_from_user(param, wrqu->data.pointer, sizeof(param))) {
+		skw_err("copy failed, length: %d\n",
+			wrqu->data.length);
+
+		return -EFAULT;
+	}
+	param[127] = '\0';
+
+	token = strchr(param, '=');
+	if (!token) {
+		key_len = strlen(param);
+		args = NULL;
+	} else {
+		key_len = token - param;
+		args = token + 1;
+	}
+
+	iwpriv_cmd = skw_iwpriv_cmd_match(skw_iwpriv_set_cmds, param, key_len);
+	if (iwpriv_cmd)
+		ret = iwpriv_cmd->handler(iface, iwpriv_cmd, args,
+				extra, SKW_GET_LEN_512);
+	else
+		ret = skw_iwpriv_help(iface, skw_iwpriv_set_cmds, NULL,
+				extra, SKW_GET_LEN_512);
+
+	if (ret < 0)
+		sprintf(extra, " usage: %s\n", iwpriv_cmd->help_info);
+
+	wrqu->data.length = SKW_GET_LEN_1024;
+
+	skw_dbg("resp: %s\n", extra);
+
+	return 0;
+}
+
+static int skw_iwpriv_get(struct net_device *dev,
+			   struct iw_request_info *info,
+			   union iwreq_data *wrqu, char *extra)
+{
+	int ret;
+	char cmd[128] = {0};
+	struct skw_iwpriv_cmd *priv_cmd = NULL;
+	struct skw_iface *iface = (struct skw_iface *)netdev_priv(dev);
+
+	if (copy_from_user(cmd, wrqu->data.pointer, sizeof(cmd))) {
+		skw_err("copy failed, length: %d\n",
+			wrqu->data.length);
+
+		return -EFAULT;
+	}
+
+	skw_dbg("cmd: 0x%x, %s(len: %d)\n", info->cmd, cmd, wrqu->data.length);
+
+	cmd[127] = '\0';
+	priv_cmd = skw_iwpriv_cmd_match(skw_iwpriv_get_cmds, cmd, strlen(cmd));
+	if (priv_cmd)
+		ret = priv_cmd->handler(iface, priv_cmd, NULL, extra,
+				SKW_GET_LEN_512);
+	else
+		ret = skw_iwpriv_help(iface, skw_iwpriv_get_cmds, NULL,
+				extra, SKW_GET_LEN_512);
+
+	wrqu->data.length = SKW_GET_LEN_512;
+
+	skw_dbg("resp: %s\n", extra);
+
+	return ret;
+}
+
+static int skw_iwpriv_at(struct net_device *dev,
+			   struct iw_request_info *info,
+			   union iwreq_data *wrqu, char *extra)
+{
+	int ret;
+	char cmd[SKW_SET_LEN_256];
+	int len = wrqu->data.length;
+	struct skw_core *skw = ((struct skw_iface *)netdev_priv(dev))->skw;
+
+	BUG_ON(sizeof(cmd) < len);
+
+	if (copy_from_user(cmd, wrqu->data.pointer, sizeof(cmd))) {
+		skw_err("copy failed, length: %d\n", len);
+
+		return -EFAULT;
+	}
+
+	skw_dbg("cmd: %s, len: %d\n", cmd, len);
+
+	if (len + 2 > sizeof(cmd))
+		return -EINVAL;
+
+	cmd[len - 1] = 0xd;
+	cmd[len + 0] = 0xa;
+	cmd[len + 1] = 0x0;
+
+	ret = skw_send_at_cmd(skw, cmd, len + 2, extra, SKW_GET_LEN_512);
+
+	wrqu->data.length = SKW_GET_LEN_512;
+
+	skw_dbg("resp: %s", extra);
+
+	return ret;
+}
+
+static struct iw_priv_args skw_iw_priv_args[] = {
+	{
+		SKW_IW_PRIV_SET,
+		IW_PRIV_TYPE_CHAR | SKW_SET_LEN_128,
+		IW_PRIV_TYPE_CHAR | SKW_GET_LEN_1024,
+		"set",
+	},
+	{
+		SKW_IW_PRIV_GET,
+		IW_PRIV_TYPE_CHAR | SKW_SET_LEN_128,
+		IW_PRIV_TYPE_CHAR | SKW_GET_LEN_512,
+		"get",
+	},
+	{
+		SKW_IW_PRIV_AT,
+		IW_PRIV_TYPE_CHAR | SKW_SET_LEN_256,
+		IW_PRIV_TYPE_CHAR | SKW_GET_LEN_512,
+		"at",
+	},
+	{
+		SKW_IW_PRIV_80211MODE,
+		IW_PRIV_TYPE_CHAR | SKW_SET_LEN_128,
+		IW_PRIV_TYPE_CHAR | SKW_GET_LEN_512,
+		"mode",
+	},
+	{
+		SKW_IW_PRIV_GET_80211MODE,
+		IW_PRIV_TYPE_CHAR | SKW_SET_LEN_128,
+		IW_PRIV_TYPE_CHAR | SKW_GET_LEN_512,
+		"get_mode",
+	},
+	{
+		SKW_IW_PRIV_KEEP_ALIVE,
+		IW_PRIV_TYPE_CHAR | SKW_SET_LEN_1024,
+		IW_PRIV_TYPE_CHAR | SKW_GET_LEN_512,
+		"keep_alive",
+	},
+	{
+		SKW_IW_PRIV_WOW_FILTER,
+		IW_PRIV_TYPE_CHAR | SKW_SET_LEN_1024,
+		IW_PRIV_TYPE_CHAR | SKW_GET_LEN_512,
+		"wow_filter",
+	},
+	{0, 0, 0, {0} }
+};
+
+static const iw_handler skw_iw_priv_handlers[] = {
+	NULL,
+	skw_iwpriv_set,
+	NULL,
+	skw_iwpriv_get,
+	NULL,
+	skw_iwpriv_at,
+	skw_iwpriv_mode,
+	skw_iwpriv_get_mode,
+	skw_iwpriv_keep_alive,
+	skw_iwpriv_wow_filter,
+	//skw_iwpriv_get_wow_filter,
+};
+#endif
+
+static const struct iw_handler_def skw_iw_ops = {
+	.standard = skw_iw_standard_handlers,
+	.num_standard = ARRAY_SIZE(skw_iw_standard_handlers),
+#ifdef CONFIG_WEXT_PRIV
+	.private = skw_iw_priv_handlers,
+	.num_private = ARRAY_SIZE(skw_iw_priv_handlers),
+	.private_args = skw_iw_priv_args,
+	.num_private_args = ARRAY_SIZE(skw_iw_priv_args),
+#endif
+	.get_wireless_stats = skw_get_wireless_stats,
+};
+
+const void *skw_iw_handlers(void)
+{
+#ifdef CONFIG_WIRELESS_EXT
+	return &skw_iw_ops;
+#else
+	skw_info("CONFIG_WIRELESS_EXT not enabled\n");
+	return NULL;
+#endif
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iw.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iw.h
new file mode 100755
index 0000000..87e1641
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_iw.h
@@ -0,0 +1,345 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_IW_H__
+#define __SKW_IW_H__
+
+#define SKW_MAX_TLV_BUFF_LEN          1024
+
+#define SKW_KEEPACTIVE_RULE_SEND_CNT_DEF   1
+#define SKW_KEEPACTIVE_RULE_SEND_CNT_MAX   10
+#define SKW_KEEPACTIVE_RULE_MAX            18
+#define SKW_KEEPACTIVE_LENGTH_MAX            135
+#define SKW_KEEPACTIVE_CMD_BUF_MAX            1300
+#define SKW_KEEPALIVE_ALWAYS_FLAG          BIT(0)
+#define SKW_KEEPALIVE_NEEDCHKSUM_FLAG      BIT(1)
+#define SKW_KEEPALIVE_FWCHKSUM_FLAG        BIT(2)
+
+struct skw_keep_active_rule_data {
+	u16 is_chksumed;
+	u8 payload[0];
+} __packed;
+struct skw_keep_active_rule {
+	u32 keep_interval;
+	u8 send_cnt:7;
+	u8 always:1;
+	u8 payload_len;
+	struct skw_keep_active_rule_data data[0];
+} __packed;
+
+struct skw_keep_active_setup {
+	u32 en_bitmap;
+	u32 flags[SKW_KEEPACTIVE_RULE_MAX];
+	struct skw_keep_active_rule *rule[SKW_KEEPACTIVE_RULE_MAX];
+} __packed;
+
+struct skw_keep_active_param {
+	u8 rule_num;
+	struct skw_keep_active_rule rules[0];
+} __packed;
+
+typedef int (*skw_at_handler)(struct skw_core *skw, void *param,
+			char *args, char *resp, int resp_len);
+
+struct skw_at_cmd {
+	char *name;
+	skw_at_handler handler;
+	char *help_info;
+};
+
+typedef int (*skw_iwpriv_handler)(struct skw_iface *iface, void *param,
+			char *args, char *resp, int resp_len);
+
+struct skw_iwpriv_cmd {
+	char *name;
+	skw_iwpriv_handler handler;
+	char *help_info;
+};
+
+struct skw_max_ppdu_dur {
+	u8 idx;
+	u32 max_ppdu_dur;
+} __packed;
+
+struct skw_wmm_ac_param_s {
+	/* b0-b3 aifsn
+	 * b4    acm
+	 * b5-b6 aci
+	 * b7    revd
+	 */
+	u8 aci_aifn;
+
+	/* b0-b3 ECWmin
+	 * b4-b7 ECWmax
+	 */
+	u8 ec_wmin_wmax;
+	u16 txop_limit;
+} __packed;
+
+struct skw_edca_param_s {
+	u8 enable;
+	struct skw_wmm_ac_param_s ac_best_effort;
+	struct skw_wmm_ac_param_s ac_background;
+	struct skw_wmm_ac_param_s ac_video;
+	struct skw_wmm_ac_param_s ac_voice;
+} __packed;
+
+struct skw_cca_thre_nowifi {
+	u8 val;
+} __packed;
+
+struct skw_cca_thre_11b {
+	u8 val;
+} __packed;
+
+struct skw_cca_thre_ofdm {
+	u8 val;
+} __packed;
+
+enum skw_lega_ofdm_rate_map {
+	LEGA_11B_SHORT_2M  = 0x10,
+	LEGA_11B_SHORT_55M = 0x11,
+	LEGA_11B_SHORT_11M = 0x12,
+	LEGA_11B_LONG_1M   = 0x20,
+	LEGA_11B_LONG_2M   = 0x21,
+	LEGA_11B_LONG_55M  = 0x22,
+	LEGA_11B_LONG_11M  = 0x23,
+
+	OFDM_6M            = 0x30,
+	OFDM_9M            = 0x31,
+	OFDM_12M           = 0x32,
+	OFDM_18M           = 0x33,
+	OFDM_24M           = 0x34,
+	OFDM_36M           = 0x35,
+	OFDM_48M           = 0x36,
+	OFDM_54M           = 0x37,
+
+	HT_MCS_0           = 0x40,
+	HT_MCS_1           = 0x41,
+	HT_MCS_2           = 0x42,
+	HT_MCS_3           = 0x43,
+	HT_MCS_4           = 0x44,
+	HT_MCS_5           = 0x45,
+	HT_MCS_6           = 0x46,
+	HT_MCS_7           = 0x47,
+	HT_MCS_8           = 0x48,
+	HT_MCS_9           = 0x49,
+	HT_MCS_10           = 0x4a,
+	HT_MCS_11           = 0x4b,
+	HT_MCS_12           = 0x4c,
+	HT_MCS_13           = 0x4d,
+	HT_MCS_14           = 0x4e,
+	HT_MCS_15           = 0x4f,
+	HT_MCS_16           = 0x50,
+	HT_MCS_17           = 0x51,
+	HT_MCS_18           = 0x52,
+	HT_MCS_19           = 0x53,
+	HT_MCS_20           = 0x54,
+	HT_MCS_21           = 0x55,
+	HT_MCS_22           = 0x56,
+	HT_MCS_23           = 0x57,
+	HT_MCS_24           = 0x58,
+	HT_MCS_25           = 0x59,
+	HT_MCS_26           = 0x5a,
+	HT_MCS_27           = 0x5b,
+	HT_MCS_28           = 0x5c,
+	HT_MCS_29           = 0x5d,
+	HT_MCS_30           = 0x5e,
+	HT_MCS_31           = 0x5f,
+
+	VHT_MCS_0           = 0x80,
+	VHT_MCS_1           = 0x81,
+	VHT_MCS_2           = 0x82,
+	VHT_MCS_3           = 0x83,
+	VHT_MCS_4           = 0x84,
+	VHT_MCS_5           = 0x85,
+	VHT_MCS_6           = 0x86,
+	VHT_MCS_7           = 0x87,
+	VHT_MCS_8           = 0x88,
+	VHT_MCS_9           = 0x89,
+
+	HE_MCS_0            = 0xc0,
+	HE_MCS_1            = 0xc1,
+	HE_MCS_2            = 0xc2,
+	HE_MCS_3            = 0xc3,
+	HE_MCS_4            = 0xc4,
+	HE_MCS_5            = 0xc5,
+	HE_MCS_6            = 0xc6,
+	HE_MCS_7            = 0xc7,
+	HE_MCS_8            = 0xc8,
+	HE_MCS_9            = 0xc9,
+	HE_MCS_10            = 0xca,
+	HE_MCS_11            = 0xcb,
+
+	ER_NDCM_1SS_242TONE_MCS0          = 0xcc,
+	ER_NDCM_1SS_242TONE_MCS1          = 0xcd,
+	ER_NDCM_1SS_242TONE_MCS2          = 0xce,
+	ER_NDCM_1SS_106TONE_MCS0          = 0xcf,
+
+	ER_DCM_1SS_242TONE_MCS0          = 0xdc,
+	ER_DCM_1SS_242TONE_MCS1          = 0xdd,
+	ER_DCM_1SS_106TONE_MCS0          = 0xdf,
+
+	NER_DCM_1SS_MCS0          = 0xec,
+	NER_DCM_1SS_MCS1          = 0xed,
+	NER_DCM_1SS_MCS3          = 0xee,
+	NER_DCM_1SS_MCS4          = 0xef,
+
+	NER_DCM_2SS_MCS0          = 0xfc,
+	NER_DCM_2SS_MCS1          = 0xfd,
+	NER_DCM_2SS_MCS3          = 0xfe,
+	NER_DCM_2SS_MCS4          = 0xff,
+};
+
+struct skw_force_rts_rate {
+	u8 enable;
+	u8 rts_rate_24G;
+	u8 rts_rate_5G;
+} __packed;
+
+struct skw_force_rx_rsp_rate {
+	u8 enable;
+	u8 rx_rsp_rate_11b_long;
+	u8 rx_rsp_rate_11b_short;
+	u8 rx_rsp_rate_ofdm;
+} __packed;
+
+struct skw_set_scan_time {
+	u8 active_dwell_time;
+	u8 bypass_active_acan_auto_time;
+ //0 or 1
+} __packed;
+
+struct skw_set_tcpd_wakeup_host {
+	u8 enable;
+} __packed;
+
+struct skw_set_rate_control_min_rate {
+	u8 rstrict_min_rate;
+} __packed;
+
+struct skw_set_rate_control_rate_change {
+	u8 up_rate_class_num;
+	u8 down_rate_class_num;
+	u8 hw_rty_limit;
+	u8 per_rate_hw_rty_limit;
+	u8 per_rate_probe_hw_rty_limit;
+} __packed;
+
+struct skw_set_rate_control_special_rate {
+	u8 special_frm_rate;
+} __packed;
+
+struct skw_tlv_set_tx_lifetime {
+	u16 lifetime;
+} __packed;
+
+struct skw_tlv_set_retry_cnt {
+	u8 rtycnt;
+
+} __packed;
+struct skw_tlv_set_tx_rts_thrd {
+	u16 rts_thrd;
+} __packed;
+
+struct skw_tlv_set_rx_special_80211_frame {
+	u8 en;
+	u8 type;
+	u8 sub_type;
+} __packed;
+
+struct skw_tlv_set_rx_update_nav {
+	u8 intra_rssi;
+	u8 basic_rssi;
+	u8 nav_max_time;
+} __packed;
+
+struct skw_tlv_set_apgo_timap {
+	u8 dtimforce0;
+	u8 dtimforce1;
+	u8 timforce0;
+	u8 timforce1;
+} __packed;
+
+struct skw_tlv_set_dbdc_disable {
+	u8 disable;
+} __packed;
+
+struct skw_tlv_set_assign_addr_val {
+	u32 addr;
+	u32 val;
+} __packed;
+
+struct skw_tlv_get_assign_addr {
+	u32 addr;
+} __packed;
+
+struct skw_tlv_get_assign_addr_rsp {
+	u32 val;
+} __packed;
+
+struct skw_tlv_set_ageout_thrd {
+	u8 ageout_kick_thrd;
+	u8 ageout_keep_alive_thrd;
+} __packed;
+
+//TLV 42
+struct skw_tlv_set_report_cqm_rssi_low_itvl {
+	u16 report_cqm_low_intvl_min_dur;
+	u16 report_cqm_low_intvl_max_dur;
+} __packed;
+
+//TLV 54
+struct skw_tlv_set_ap_new_channel {
+	u8 chan;
+	u8 center_chan;
+	u8 center_two_chan;
+	u8 bw;
+	u8 band;
+} __packed;
+
+//TLV 55
+struct skw_tlv_set_tx_retry_limit_en {
+	u8 short_retry_check_en;
+	u8 long_retry_check_en;
+	u8 ampdu_retry_check_en;
+} __packed;
+
+//TLV 56
+struct skw_tlv_set_partial_twt_sched {
+	u8 en;
+	u32 start_time_l;
+	u32 start_time_h;
+	u32 interval;
+	u16 duration;
+	u8 duration_unit;
+	u8 sub_type;
+} __packed;
+
+//TLV 57
+struct skw_tlv_set_thm_thrd {
+	u16 thm_high_thrd_tx_suspend;
+	u16 thm_low_thrd_tx_resume;
+} __packed;
+
+//TLV 59
+struct skw_tlv_set_normal_scan_with_acs {
+	u8 en;
+} __packed;
+
+const void *skw_iw_handlers(void);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_log.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_log.c
new file mode 100755
index 0000000..c4ae298
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_log.c
@@ -0,0 +1,379 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/version.h>
+#include <linux/uaccess.h>
+#include <linux/ctype.h>
+#include <linux/if_arp.h>
+
+#include "skw_compat.h"
+#include "skw_log.h"
+#include "skw_dentry.h"
+#include "skw_cfg80211.h"
+#include "skw_iface.h"
+#include "skw_rx.h"
+
+#define SKW_LL_MASK 0xffff
+
+#if defined(CONFIG_SWT6621S_LOG_ERROR)
+#define SKW_LOG_LEVEL SKW_ERROR
+#elif defined(CONFIG_SWT6621S_LOG_WARN)
+#define SKW_LOG_LEVEL SKW_WARN
+#elif defined(CONFIG_SWT6621S_LOG_INFO)
+#define SKW_LOG_LEVEL SKW_INFO
+#elif defined(CONFIG_SWT6621S_LOG_DEBUG)
+#define SKW_LOG_LEVEL SKW_DEBUG
+#elif defined(CONFIG_SWT6621S_LOG_DETAIL)
+#define SKW_LOG_LEVEL SKW_DETAIL
+#else
+#define SKW_LOG_LEVEL SKW_INFO
+#endif
+
+static unsigned long skw_dbg_level;
+
+unsigned long skw_log_level(void)
+{
+	return skw_dbg_level;
+}
+
+static void skw_set_log_level(int level)
+{
+	unsigned long dbg_level;
+
+	dbg_level = skw_log_level() & (~SKW_LL_MASK);
+	dbg_level |= ((level << 1) - 1);
+
+	xchg(&skw_dbg_level, dbg_level);
+}
+
+static void skw_enable_func_log(int func, bool enable)
+{
+	unsigned long dbg_level = skw_log_level();
+
+	if (enable)
+		dbg_level |= func;
+	else
+		dbg_level &= (~func);
+
+	xchg(&skw_dbg_level, dbg_level);
+}
+
+
+static struct skw_monitor_dbg_iface *g_skw_dbg_iface;
+static DEFINE_MUTEX(skw_dbg_mutex);
+
+void skw_dump_frame(u8 *frame, u16 frame_len)
+{
+	struct skw_radiotap_desc *radio_desc;
+	struct sk_buff *skb;
+
+	mutex_lock(&skw_dbg_mutex);
+	if (g_skw_dbg_iface == NULL) {
+		mutex_unlock(&skw_dbg_mutex);
+		return;
+	}
+
+	skb = alloc_skb(frame_len + sizeof(struct skw_radiotap_desc), GFP_KERNEL);
+	if (skb == NULL) {
+		mutex_unlock(&skw_dbg_mutex);
+		return;
+	}
+
+	radio_desc = (struct skw_radiotap_desc *)skb_put(skb,
+								sizeof(struct skw_radiotap_desc));
+	radio_desc->radiotap_hdr.it_version = PKTHDR_RADIOTAP_VERSION;
+	radio_desc->radiotap_hdr.it_pad = 0;
+	radio_desc->radiotap_hdr.it_len = sizeof(struct skw_radiotap_desc);
+	radio_desc->radiotap_hdr.it_present = BIT(IEEE80211_RADIOTAP_FLAGS);
+	radio_desc->radiotap_flag = 0;
+
+	skw_put_skb_data(skb, frame, frame_len);
+
+	__skb_trim(skb, frame_len + radio_desc->radiotap_hdr.it_len);
+	skb_reset_mac_header(skb);
+
+	skb->dev = g_skw_dbg_iface->ndev;
+	skb->ip_summed = CHECKSUM_NONE;
+	skb->pkt_type = PACKET_OTHERHOST;
+	skb->protocol = htons(ETH_P_80211_RAW);
+
+	#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 18, 0)
+		netif_rx_ni(skb);
+	#else
+		netif_rx(skb);
+	#endif
+
+	mutex_unlock(&skw_dbg_mutex);
+}
+
+static netdev_tx_t skw_monitor_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+	dev_kfree_skb_any(skb);
+	return NETDEV_TX_OK;
+}
+
+static const struct net_device_ops skw_monitor_netdev_ops = {
+	.ndo_start_xmit = skw_monitor_xmit,
+};
+
+static void skw_add_dbg_iface(void)
+{
+	int priv_size, ret;
+	struct net_device *ndev = NULL;
+	struct skw_monitor_dbg_iface *iface;
+	u8 mac[6] = {0xFe, 0xfd, 0xfc, 0x12, 0x11, 0x88};
+
+	if (g_skw_dbg_iface) {
+		skw_err("already add dbg iface\n");
+		return;
+	}
+
+	priv_size = sizeof(struct skw_monitor_dbg_iface);
+	ndev = alloc_netdev_mqs(priv_size, "skw_dbg",
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
+					NET_NAME_ENUM,
+#endif
+					ether_setup, SKW_WMM_AC_MAX, 1);
+	if (!ndev) {
+		skw_err("alloc ndev fail\n");
+		return;
+	}
+
+	iface = netdev_priv(ndev);
+	iface->ndev = ndev;
+
+	skw_ether_copy(iface->addr, mac);
+	ndev->type = ARPHRD_IEEE80211_RADIOTAP;
+
+	ndev->netdev_ops = &skw_monitor_netdev_ops;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)
+	eth_hw_addr_set(ndev, mac);
+#else
+	skw_ether_copy(ndev->dev_addr, mac);
+#endif
+	rtnl_lock();
+	ret = skw_register_netdevice(ndev);
+	rtnl_unlock();
+	if (ret) {
+		skw_err("register netdev failed\n");
+		goto free_iface;
+	}
+
+	mutex_lock(&skw_dbg_mutex);
+	g_skw_dbg_iface = iface;
+	mutex_unlock(&skw_dbg_mutex);
+
+	return;
+
+free_iface:
+	if (ndev)
+		free_netdev(ndev);
+}
+
+void skw_del_dbg_iface(void)
+{
+	struct skw_monitor_dbg_iface *iface;
+
+	if (!g_skw_dbg_iface)
+		return;
+
+	mutex_lock(&skw_dbg_mutex);
+
+	iface = g_skw_dbg_iface;
+	g_skw_dbg_iface = NULL;
+
+	mutex_unlock(&skw_dbg_mutex);
+
+	rtnl_lock();
+	skw_unregister_netdevice(iface->ndev);
+	rtnl_unlock();
+}
+
+static void skw_set_dump_frame(bool enable)
+{
+	if (enable)
+		skw_add_dbg_iface();
+	else
+		skw_del_dbg_iface();
+}
+
+static char *skw_dump_frame_status(void)
+{
+	if (g_skw_dbg_iface)
+		return "enable";
+	else
+		return "disable";
+}
+
+static int skw_log_show(struct seq_file *seq, void *data)
+{
+	int i;
+	u32 level = skw_log_level();
+	const char *log_level;
+	static const char *log_name[] = {"NONE", "ERROR", "WARN", "INFO", "DEBUG", "DETAIL"};
+
+	i = ffs((level & SKW_LL_MASK) + 1) - 1;
+	if (i >= 0 && i < ARRAY_SIZE(log_name))
+		log_level = log_name[i];
+	else
+		log_level = "INVALID";
+
+	seq_puts(seq, "\n");
+	seq_printf(seq, "Log Level: %s    [ERROR|WARN|INFO|DEBUG|DETAIL]\n", log_name[i]);
+
+#define SKW_LOG_STATUS(s) (level & (s) ? "enable" : "disable")
+	seq_puts(seq, "\n");
+	seq_printf(seq, "command log: %s\n", SKW_LOG_STATUS(SKW_CMD));
+	seq_printf(seq, "event   log: %s\n", SKW_LOG_STATUS(SKW_EVENT));
+	seq_printf(seq, "dump    log: %s\n", SKW_LOG_STATUS(SKW_DUMP));
+	seq_printf(seq, "scan    log: %s\n", SKW_LOG_STATUS(SKW_SCAN));
+	seq_printf(seq, "timer   log: %s\n", SKW_LOG_STATUS(SKW_TIMER));
+	seq_printf(seq, "state   log: %s\n", SKW_LOG_STATUS(SKW_STATE));
+	seq_printf(seq, "work    log: %s\n", SKW_LOG_STATUS(SKW_WORK));
+	seq_printf(seq, "dump  frame: %s\n", skw_dump_frame_status());
+	seq_printf(seq, "DFS     log: %s\n", SKW_LOG_STATUS(SKW_DFS));
+#undef SKW_LOG_STATUS
+
+	return 0;
+}
+
+static int skw_log_open(struct inode *inode, struct file *file)
+{
+	// return single_open(file, &skw_log_show, inode->i_private);
+	return single_open(file, &skw_log_show, skw_pde_data(inode));
+}
+
+static int skw_log_control(const char *cmd, bool enable)
+{
+	if (!strcmp("command", cmd))
+		skw_enable_func_log(SKW_CMD, enable);
+	else if (!strcmp("event", cmd))
+		skw_enable_func_log(SKW_EVENT, enable);
+	else if (!strcmp("dump", cmd))
+		skw_enable_func_log(SKW_DUMP, enable);
+	else if (!strcmp("scan", cmd))
+		skw_enable_func_log(SKW_SCAN, enable);
+	else if (!strcmp("timer", cmd))
+		skw_enable_func_log(SKW_TIMER, enable);
+	else if (!strcmp("state", cmd))
+		skw_enable_func_log(SKW_STATE, enable);
+	else if (!strcmp("work", cmd))
+		skw_enable_func_log(SKW_WORK, enable);
+	else if (!strcmp("dfs", cmd))
+		skw_enable_func_log(SKW_DFS, enable);
+	else if (!strcmp("detail", cmd))
+		skw_set_log_level(SKW_DETAIL);
+	else if (!strcmp("debug", cmd))
+		skw_set_log_level(SKW_DEBUG);
+	else if (!strcmp("info", cmd))
+		skw_set_log_level(SKW_INFO);
+	else if (!strcmp("warn", cmd))
+		skw_set_log_level(SKW_WARN);
+	else if (!strcmp("error", cmd))
+		skw_set_log_level(SKW_ERROR);
+	else if (!strcmp("dumpframe", cmd))
+		skw_set_dump_frame(enable);
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static ssize_t skw_log_write(struct file *fp, const char __user *buffer,
+				size_t len, loff_t *offset)
+{
+	int i, idx;
+	char cmd[32];
+	bool enable = false;
+
+	for (idx = 0, i = 0; i < len; i++) {
+		char c;
+
+		if (get_user(c, buffer))
+			return -EFAULT;
+
+		switch (c) {
+		case ' ':
+			break;
+
+		case ':':
+			cmd[idx] = 0;
+			if (!strcmp("enable", cmd))
+				enable = true;
+			else
+				enable = false;
+
+			idx = 0;
+			break;
+
+		case '|':
+		case '\0':
+		case '\n':
+			cmd[idx] = 0;
+			skw_log_control(cmd, enable);
+			idx = 0;
+			break;
+
+		default:
+			cmd[idx++] = tolower(c);
+			idx %= 32;
+
+			break;
+		}
+
+		buffer++;
+	}
+
+	return len;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_log_fops = {
+	.proc_open = skw_log_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+	.proc_write = skw_log_write,
+};
+#else
+static const struct file_operations skw_log_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_log_open,
+	.read = seq_read,
+	.release = single_release,
+	.write = skw_log_write,
+};
+#endif
+
+void skw_log_level_init(void)
+{
+	skw_set_log_level(SKW_LOG_LEVEL);
+
+	skw_enable_func_log(SKW_CMD, false);
+	skw_enable_func_log(SKW_EVENT, false);
+	skw_enable_func_log(SKW_DUMP, false);
+	skw_enable_func_log(SKW_SCAN, false);
+	skw_enable_func_log(SKW_TIMER, false);
+	skw_enable_func_log(SKW_STATE, true);
+	skw_enable_func_log(SKW_WORK, false);
+	skw_enable_func_log(SKW_DFS, false);
+
+	skw_procfs_file(NULL, "log_level", 0666, &skw_log_fops, NULL);
+}
+
+void skw_log_level_deinit(void)
+{
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_log.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_log.h
new file mode 100755
index 0000000..caf6034
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_log.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_LOG_H__
+#define __SKW_LOG_H__
+
+#define SKW_ERROR              BIT(0)
+#define SKW_WARN               BIT(1)
+#define SKW_INFO               BIT(2)
+#define SKW_DEBUG              BIT(3)
+#define SKW_DETAIL             BIT(4)
+
+#define SKW_CMD                BIT(16)
+#define SKW_EVENT              BIT(17)
+#define SKW_SCAN               BIT(18)
+#define SKW_TIMER              BIT(19)
+#define SKW_STATE              BIT(20)
+#define SKW_WORK               BIT(21)
+#define SKW_DFS                BIT(22)
+
+#define SKW_DUMP               BIT(31)
+
+#define SKW_LOG_TAG            "SKWIFI6621S"
+#define SKW_TAG_NAME(name)     SKW_LOG_TAG " " #name
+
+#define SKW_TAG_ERROR          SKW_TAG_NAME(ERROR)
+#define SKW_TAG_WARN           SKW_TAG_NAME(WARN)
+#define SKW_TAG_INFO           SKW_TAG_NAME(INFO)
+#define SKW_TAG_DEBUG          SKW_TAG_NAME(DBG)
+#define SKW_TAG_DETAIL         SKW_TAG_NAME(DETAIL)
+
+#define SKW_TAG_CMD            SKW_TAG_NAME(CMD)
+#define SKW_TAG_DATA           SKW_TAG_NAME(DATA)
+#define SKW_TAG_EVENT          SKW_TAG_NAME(EVENT)
+#define SKW_TAG_SCAN           SKW_TAG_NAME(SCAN)
+#define SKW_TAG_TIMER          SKW_TAG_NAME(TIMER)
+#define SKW_TAG_STATE          SKW_TAG_NAME(STATE)
+#define SKW_TAG_WORK           SKW_TAG_NAME(WORK)
+#define SKW_TAG_DUMP           SKW_TAG_NAME(DUMP)
+#define SKW_TAG_LOCAL          SKW_TAG_NAME(LOCAL)
+
+unsigned long skw_log_level(void);
+
+#define skw_data_path_log(fmt, ...) \
+	do { \
+		if ((skw_log_level() & SKW_DEBUG)) \
+			printk_ratelimited("[%s] %s: "fmt, \
+				SKW_TAG_DATA, __func__, ##__VA_ARGS__); \
+	} while (0)
+
+#define skw_log(level, fmt, ...) \
+	do { \
+		if (skw_log_level() & level) \
+			pr_err(fmt,  ##__VA_ARGS__); \
+	} while (0)
+
+#define skw_err(fmt, ...) \
+	skw_log(SKW_ERROR, "[%s] %s: "fmt, SKW_TAG_ERROR, __func__, ##__VA_ARGS__)
+
+#define skw_warn(fmt, ...) \
+	skw_log(SKW_WARN, "[%s] %s: "fmt, SKW_TAG_WARN, __func__, ##__VA_ARGS__)
+
+#define skw_info(fmt, ...) \
+	skw_log(SKW_INFO, "[%s] %s: "fmt, SKW_TAG_INFO, __func__, ##__VA_ARGS__)
+
+#define skw_dbg(fmt, ...) \
+	skw_log(SKW_DEBUG, "[%s] %s: "fmt, SKW_TAG_DEBUG, __func__, ##__VA_ARGS__)
+
+#define skw_detail(fmt, ...) \
+	skw_log(SKW_DETAIL, "[%s] %s: "fmt, SKW_TAG_DETAIL, __func__, ##__VA_ARGS__)
+
+#define skw_hex_dump(prefix, buf, len, force)                                    \
+	do {                                                                     \
+		if ((skw_log_level() & SKW_DUMP) || force) {                     \
+			print_hex_dump(KERN_ERR, "["SKW_TAG_DUMP"] "prefix" - ", \
+				DUMP_PREFIX_OFFSET, 16, 1, buf, len, true);      \
+		}                                                                \
+	} while (0)
+
+void skw_dump_frame(u8 *frame, u16 frame_len);
+void skw_del_dbg_iface(void);
+
+void skw_log_level_init(void);
+void skw_log_level_deinit(void);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mbssid.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mbssid.c
new file mode 100755
index 0000000..5529ea2
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mbssid.c
@@ -0,0 +1,396 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/ieee80211.h>
+#include <net/cfg80211.h>
+
+#include "skw_core.h"
+#include "skw_log.h"
+#include "skw_mbssid.h"
+#include "skw_cfg80211.h"
+#include "skw_compat.h"
+
+#define SKW_GENMASK_ULL(h, l)   (((~0ULL) - (1ULL << (l)) + 1) & \
+				(~0ULL >> (BITS_PER_LONG_LONG - 1 - (h))))
+
+static __always_inline u16 skw_get_unaligned_le16(const void *p)
+{
+	return le16_to_cpup((__le16 *)p);
+}
+
+static struct skw_element *skw_find_elem(u8 eid, const u8 *ies,
+					int len, const u8 *match,
+					unsigned int match_len,
+					unsigned int match_offset)
+{
+	struct skw_element *elem;
+
+	skw_foreach_element_id(elem, eid, ies, len) {
+		if (elem->datalen >= match_offset - 2 + match_len &&
+		    !memcmp(elem->data + match_offset - 2, match, match_len))
+			return (void *)elem;
+	}
+
+	return NULL;
+}
+
+static const struct skw_element *
+skw_get_profile_continuation(const u8 *ie, size_t ie_len,
+			const struct skw_element *mbssid_elem,
+			const struct skw_element *sub_elem)
+{
+	const u8 *mbssid_end = mbssid_elem->data + mbssid_elem->datalen;
+	const struct skw_element *next_mbssid;
+	const struct skw_element *sub;
+
+	next_mbssid = skw_find_elem(WLAN_EID_MULTIPLE_BSSID,
+			mbssid_end, ie_len - (mbssid_end - ie),
+			NULL, 0, 0);
+
+	if (!next_mbssid ||
+	    (sub_elem->data + sub_elem->datalen < mbssid_end - 1))
+		return NULL;
+
+	if (next_mbssid->datalen < 4)
+		return NULL;
+
+	sub = (void *)&next_mbssid->data[1];
+
+	if (next_mbssid->data + next_mbssid->datalen < sub->data + sub->datalen)
+		return NULL;
+
+	if (sub->id != 0 || sub->datalen < 2)
+		return NULL;
+
+	return sub->data[0] == WLAN_EID_NON_TX_BSSID_CAP ?  NULL : next_mbssid;
+}
+
+static size_t skw_merge_profile(const u8 *ie, size_t ie_len,
+				const struct skw_element *mbssid_elem,
+				const struct skw_element *sub_elem,
+				u8 *merged_ie, size_t max_copy_len)
+{
+	size_t copied_len = sub_elem->datalen;
+	const struct skw_element *next_mbssid;
+
+	if (sub_elem->datalen > max_copy_len)
+		return 0;
+
+	memcpy(merged_ie, sub_elem->data, sub_elem->datalen);
+
+	while ((next_mbssid = skw_get_profile_continuation(ie, ie_len,
+					mbssid_elem, sub_elem))) {
+		const struct skw_element *next = (void *)&next_mbssid->data[1];
+
+		if (copied_len + next->datalen > max_copy_len)
+			break;
+
+		memcpy(merged_ie + copied_len, next->data, next->datalen);
+
+		copied_len += next->datalen;
+	}
+
+	return copied_len;
+}
+
+static inline void skw_gen_new_bssid(const u8 *bssid, u8 max_bssid,
+				u8 mbssid_index, u8 *new_bssid)
+{
+	u64 bssid_u64 = skw_mac_to_u64(bssid);
+	u64 mask = SKW_GENMASK_ULL(max_bssid - 1, 0);
+	u64 new_bssid_u64;
+
+	new_bssid_u64 = bssid_u64 & ~mask;
+
+	new_bssid_u64 |= ((bssid_u64 & mask) + mbssid_index) & mask;
+
+	skw_u64_to_mac(new_bssid_u64, new_bssid);
+}
+
+static bool is_skw_element_inherited(const struct skw_element *elem,
+		const struct skw_element *non_inherit_elem)
+{
+	u8 id_len, ext_id_len, i, loop_len, id;
+	const u8 *list;
+
+	if (elem->id == WLAN_EID_MULTIPLE_BSSID)
+		return false;
+
+	if (!non_inherit_elem || non_inherit_elem->datalen < 2)
+		return true;
+
+	id_len = non_inherit_elem->data[1];
+	if (non_inherit_elem->datalen < 3 + id_len)
+		return true;
+
+	ext_id_len = non_inherit_elem->data[2 + id_len];
+	if (non_inherit_elem->datalen < 3 + id_len + ext_id_len)
+		return true;
+
+	if (elem->id == SKW_WLAN_EID_EXTENSION) {
+		if (!ext_id_len)
+			return true;
+
+		loop_len = ext_id_len;
+		list = &non_inherit_elem->data[3 + id_len];
+		id = elem->data[0];
+	} else {
+		if (!id_len)
+			return true;
+
+		loop_len = id_len;
+		list = &non_inherit_elem->data[2];
+		id = elem->id;
+	}
+
+	for (i = 0; i < loop_len; i++) {
+		if (list[i] == id)
+			return false;
+	}
+
+	return true;
+}
+
+static size_t skw_gen_new_ie(const u8 *ie, size_t ielen,
+		const u8 *subelement, size_t subie_len,
+		u8 *new_ie, gfp_t gfp)
+{
+	u8 eid;
+	u8 *pos, *tmp;
+	const u8 *tmp_old, *tmp_new;
+	const struct skw_element *non_inherit;
+	u8 *sub_copy;
+
+	sub_copy = SKW_KMEMDUP(subelement, subie_len, gfp);
+	if (!sub_copy)
+		return 0;
+
+	pos = &new_ie[0];
+
+	/* set new ssid */
+	tmp_new = cfg80211_find_ie(WLAN_EID_SSID, sub_copy, subie_len);
+	if (tmp_new) {
+		memcpy(pos, tmp_new, tmp_new[1] + 2);
+		pos += (tmp_new[1] + 2);
+	}
+
+	/* get non inheritance list if exists */
+	eid = SKW_EID_EXT_NON_INHERITANCE;
+	non_inherit = skw_find_elem(SKW_WLAN_EID_EXTENSION, sub_copy,
+					subie_len, &eid, 1, 0);
+
+	tmp_old = cfg80211_find_ie(WLAN_EID_SSID, ie, ielen);
+	tmp_old = (tmp_old) ? tmp_old + tmp_old[1] + 2 : ie;
+
+	while (tmp_old + tmp_old[1] + 2 - ie <= ielen) {
+		if (tmp_old[0] == 0) {
+			tmp_old++;
+			continue;
+		}
+
+		if (tmp_old[0] == SKW_WLAN_EID_EXTENSION) {
+			tmp = (u8 *)skw_find_elem(SKW_WLAN_EID_EXTENSION,
+					sub_copy, subie_len, &tmp_old[2], 1, 2);
+		} else {
+			tmp = (u8 *)cfg80211_find_ie(tmp_old[0], sub_copy,
+					subie_len);
+		}
+
+		if (!tmp) {
+			const struct skw_element *old_elem = (void *)tmp_old;
+
+			if (is_skw_element_inherited(old_elem, non_inherit)) {
+				memcpy(pos, tmp_old, tmp_old[1] + 2);
+				pos += tmp_old[1] + 2;
+			}
+		} else {
+			if (tmp_old[0] == WLAN_EID_VENDOR_SPECIFIC) {
+				if (!memcmp(tmp_old + 2, tmp + 2, 5)) {
+					memcpy(pos, tmp, tmp[1] + 2);
+					pos += tmp[1] + 2;
+					tmp[0] = WLAN_EID_SSID;
+				} else {
+					memcpy(pos, tmp_old, tmp_old[1] + 2);
+					pos += tmp_old[1] + 2;
+				}
+			} else {
+				memcpy(pos, tmp, tmp[1] + 2);
+				pos += tmp[1] + 2;
+				tmp[0] = WLAN_EID_SSID;
+			}
+		}
+
+		if (tmp_old + tmp_old[1] + 2 - ie == ielen)
+			break;
+
+		tmp_old += tmp_old[1] + 2;
+	}
+
+	tmp_new = sub_copy;
+	while (tmp_new + tmp_new[1] + 2 - sub_copy <= subie_len) {
+		if (!(tmp_new[0] == WLAN_EID_NON_TX_BSSID_CAP ||
+		    tmp_new[0] == WLAN_EID_SSID)) {
+			memcpy(pos, tmp_new, tmp_new[1] + 2);
+			pos += tmp_new[1] + 2;
+		}
+
+		if (tmp_new + tmp_new[1] + 2 - sub_copy == subie_len)
+			break;
+
+		tmp_new += tmp_new[1] + 2;
+	}
+
+	SKW_KFREE(sub_copy);
+
+	return pos - new_ie;
+}
+
+static void skw_parse_mbssid_data(struct wiphy *wiphy,
+				struct ieee80211_channel *rx_channel,
+				s32 signal,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
+				enum cfg80211_bss_frame_type ftype,
+#endif
+				const u8 *bssid, u64 tsf,
+				u16 beacon_interval, const u8 *ie,
+				size_t ie_len, gfp_t gfp)
+{
+	const u8 *idx_ie;
+	const struct skw_element *elem, *sub;
+	size_t new_ie_len;
+	u8 bssid_index;
+	u8 max_indicator;
+	u8 new_bssid[ETH_ALEN];
+	u8 *new_ie, *profile;
+	u64 seen_indices = 0;
+	u16 capability;
+	struct cfg80211_bss *bss;
+
+	new_ie = SKW_ZALLOC(IEEE80211_MAX_DATA_LEN, gfp);
+	if (!new_ie)
+		return;
+
+	profile = SKW_ZALLOC(ie_len, gfp);
+	if (!profile)
+		goto out;
+
+	skw_foreach_element_id(elem, WLAN_EID_MULTIPLE_BSSID, ie, ie_len) {
+		if (elem->datalen < 4)
+			continue;
+
+		skw_foreach_element(sub, elem->data + 1, elem->datalen - 1) {
+			u8 profile_len;
+
+			if (sub->id != 0 || sub->datalen < 4)
+				continue;
+
+			if (sub->data[0] != WLAN_EID_NON_TX_BSSID_CAP ||
+			    sub->data[1] != 2) {
+				continue;
+			}
+
+			memset(profile, 0, ie_len);
+
+			profile_len = skw_merge_profile(ie, ie_len,
+					elem, sub, profile, ie_len);
+			idx_ie = cfg80211_find_ie(SKW_WLAN_EID_MULTI_BSSID_IDX,
+						  profile, profile_len);
+
+			if (!idx_ie || idx_ie[1] < 1 ||
+			    idx_ie[2] == 0 || idx_ie[2] > 46) {
+				/* No valid Multiple BSSID-Index element */
+				continue;
+			}
+
+			if (seen_indices & (1ULL << (idx_ie[2])))
+				net_dbg_ratelimited("Partial info for BSSID index %d\n",
+						idx_ie[2]);
+
+			seen_indices |= (1ULL << (idx_ie[2]));
+
+			bssid_index = idx_ie[2];
+			max_indicator = elem->data[0];
+
+			skw_gen_new_bssid(bssid, max_indicator,
+					bssid_index, new_bssid);
+
+			memset(new_ie, 0, IEEE80211_MAX_DATA_LEN);
+			new_ie_len = skw_gen_new_ie(ie, ie_len, profile,
+						profile_len, new_ie,
+						GFP_KERNEL);
+			if (!new_ie_len)
+				continue;
+
+			capability = skw_get_unaligned_le16(profile + 2);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
+			bss = cfg80211_inform_bss(wiphy, rx_channel, ftype,
+						new_bssid, tsf, capability,
+						beacon_interval, new_ie,
+						new_ie_len, signal, gfp);
+#else
+			bss = cfg80211_inform_bss(wiphy, rx_channel,
+						new_bssid, tsf, capability,
+						beacon_interval, new_ie,
+						new_ie_len, signal, gfp);
+#endif
+
+			if (!bss)
+				break;
+
+			skw_bss_priv(bss)->bssid_index = bssid_index;
+			skw_bss_priv(bss)->max_bssid_indicator = max_indicator;
+
+			cfg80211_put_bss(wiphy, bss);
+		}
+	}
+
+	SKW_KFREE(profile);
+out:
+	SKW_KFREE(new_ie);
+}
+
+void skw_mbssid_data_parser(struct wiphy *wiphy, bool beacon,
+		struct ieee80211_channel *chan, s32 signal,
+		struct ieee80211_mgmt *mgmt, int mgmt_len)
+{
+	const u8 *ie = mgmt->u.probe_resp.variable;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
+	enum cfg80211_bss_frame_type ftype = CFG80211_BSS_FTYPE_PRESP;
+#endif
+	size_t len = offsetof(struct ieee80211_mgmt, u.probe_resp.variable);
+
+	if (!cfg80211_find_ie(WLAN_EID_MULTIPLE_BSSID, ie, mgmt_len - len))
+		return;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
+	if (beacon)
+		ftype = CFG80211_BSS_FTYPE_BEACON;
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
+	skw_parse_mbssid_data(wiphy, chan, signal,  ftype, mgmt->bssid,
+			le64_to_cpu(mgmt->u.probe_resp.timestamp),
+			le16_to_cpu(mgmt->u.probe_resp.beacon_int),
+			ie, mgmt_len - len, GFP_KERNEL);
+#else
+	skw_parse_mbssid_data(wiphy, chan, signal, mgmt->bssid,
+			le64_to_cpu(mgmt->u.probe_resp.timestamp),
+			le16_to_cpu(mgmt->u.probe_resp.beacon_int),
+			ie, mgmt_len - len, GFP_KERNEL);
+#endif
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mbssid.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mbssid.h
new file mode 100755
index 0000000..5c3f130
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mbssid.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_MBSSID_H__
+#define __SKW_MBSSID_H__
+
+#define SKW_EID_EXT_NON_INHERITANCE   56
+
+void skw_mbssid_data_parser(struct wiphy *wiphy, bool beacon,
+		struct ieee80211_channel *chan, s32 signal,
+		struct ieee80211_mgmt *mgmt, int mgmt_len);
+
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mlme.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mlme.c
new file mode 100755
index 0000000..760fcf4
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mlme.c
@@ -0,0 +1,1208 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/workqueue.h>
+#include <linux/skbuff.h>
+#include <linux/ieee80211.h>
+#include <linux/etherdevice.h>
+#include <net/cfg80211.h>
+
+#include "skw_core.h"
+#include "skw_cfg80211.h"
+#include "skw_iface.h"
+#include "skw_timer.h"
+#include "skw_msg.h"
+#include "skw_mlme.h"
+#include "skw_work.h"
+
+#define SKW_AP_AUTH_TIMEOUT     5000
+#define SKW_IEEE80211_HDR_LEN   24
+
+static void skw_mlme_ap_del_client(struct skw_iface *iface,
+				struct skw_client *client)
+{
+	if (!client)
+		return;
+
+	skw_dbg("client: %pM\n", client->addr);
+
+	skw_del_timer_work(iface->skw, client);
+
+	skw_list_del(&iface->sap.mlme_client_list, &client->list);
+
+	if (client->aid)
+		clear_bit(client->aid, iface->sap.aid_map);
+
+	SKW_KFREE(client->assoc_req_ie);
+	SKW_KFREE(client->challenge);
+	SKW_KFREE(client);
+}
+
+static struct skw_client *
+skw_mlme_ap_get_client(struct skw_iface *iface, const u8 *addr)
+{
+	struct skw_client *client = NULL, *tmp;
+
+	if (!iface)
+		return NULL;
+
+	spin_lock_bh(&iface->sap.mlme_client_list.lock);
+
+	list_for_each_entry(tmp, &iface->sap.mlme_client_list.list, list) {
+		if (tmp && ether_addr_equal(addr, tmp->addr)) {
+			client = tmp;
+			break;
+		}
+	}
+
+	spin_unlock_bh(&iface->sap.mlme_client_list.lock);
+
+	return client;
+}
+
+static struct skw_client *
+skw_mlme_ap_add_client(struct skw_iface *iface, const u8 *addr)
+{
+	struct skw_client *client = NULL;
+
+	client = SKW_ZALLOC(sizeof(*client), GFP_KERNEL);
+	if (client) {
+		INIT_LIST_HEAD(&client->list);
+		client->iface = iface;
+		client->state = SKW_STATE_NONE;
+		client->last_seq_ctrl = 0xFFFF;
+		client->idle = jiffies;
+		client->challenge = NULL;
+		client->assoc_req_ie = NULL;
+		client->aid = 0;
+		skw_ether_copy(client->addr, addr);
+		skw_dbg("%pM\n", client->addr);
+
+		skw_list_add(&iface->sap.mlme_client_list, &client->list);
+	}
+
+	return client;
+}
+
+void skw_mlme_ap_remove_client(struct skw_iface *iface, const u8 *addr)
+{
+	struct skw_client *client;
+
+	if (!iface->sap.sme_external) {
+		client = skw_mlme_ap_get_client(iface, addr);
+		skw_mlme_ap_del_client(iface, client);
+	}
+}
+
+void skw_mlme_ap_del_sta(struct wiphy *wiphy, struct net_device *ndev,
+			 const u8 *addr, u8 force)
+{
+	int ret = -1;
+
+	skw_dbg("sta: %pM\n", addr);
+
+	ret = skw_del_station(wiphy, ndev, addr, 12, 3);
+	if (ret) {
+		skw_err("failed, ret: %d\n", ret);
+		return;
+	}
+
+}
+
+static void skw_mlme_ap_auth_timeout(void *data)
+{
+	unsigned long timeout;
+	struct skw_client *client = data;
+
+	if (!client)
+		return;
+
+	skw_dbg("client: %pM\n", client->addr);
+
+	if (client->state == SKW_STATE_ASSOCED)
+		return;
+
+	timeout = client->idle + msecs_to_jiffies(SKW_AP_AUTH_TIMEOUT);
+	if (time_after(jiffies, timeout)) {
+		skw_queue_local_event(priv_to_wiphy(client->iface->skw),
+				client->iface, SKW_EVENT_LOCAL_AP_AUTH_TIMEOUT,
+				client, sizeof(*client));
+		return;
+	}
+}
+
+#if 0
+void skw_flush_sta_info(struct skw_iface *iface)
+{
+	LIST_HEAD(flush_list);
+	struct skw_client *sta;
+
+	spin_lock_bh(&iface->sap.sta_lock);
+	list_replace_init(&iface->sap.mlme_client_list, &flush_list);
+	spin_unlock_bh(&iface->sap.sta_lock);
+
+	// fixme:
+	// deauth all sta
+	while (!list_empty(&flush_list)) {
+		sta = list_first_entry(&flush_list, struct skw_client, list);
+		list_del(&sta->list);
+		skw_dbg("sta: %pM, state: %d\n", sta->addr, sta->state);
+		// skw_mlle_ap_state_event(sta, SKW_STATE_NONE);
+		SKW_KFREE(sta);
+	}
+}
+#endif
+
+int skw_mgmt_frame_with_reason(struct skw_iface *iface, u8 *da, u64 *cookie, u8 *bssid,
+			struct ieee80211_channel *ch, u16 stype, u16 reason, bool switchover)
+{
+	struct wiphy *wiphy = priv_to_wiphy(iface->skw);
+	struct ieee80211_mgmt reply;
+
+	skw_dbg("stype: %d, reason: %d\n", stype, reason);
+
+	reply.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | stype);
+	reply.duration = 0;
+	reply.seq_ctrl = 0;
+	skw_ether_copy(reply.da, da);
+	skw_ether_copy(reply.sa, iface->addr);
+	skw_ether_copy(reply.bssid, bssid);
+
+	reply.u.deauth.reason_code = cpu_to_le16(reason);
+
+	return skw_mgmt_tx(wiphy, iface, ch, 0,
+			   cookie, false, &reply,
+			   SKW_DEAUTH_FRAME_LEN, SKW_DEAUTH_FRAME_LEN, &reply, switchover);
+}
+
+
+int skw_ap_simple_reply(struct skw_iface *iface, struct skw_client *client,
+			u16 stype, u16 reason)
+{
+	skw_dbg("stype: %d, reason: %d\n", stype, reason);
+
+	return skw_mgmt_frame_with_reason(iface, client->addr, &client->cookie,
+			iface->sap.cfg.bssid, iface->sap.cfg.channel, stype, reason, true);
+}
+
+static void skw_mlme_ap_auth_cb(struct skw_iface *iface,
+				struct skw_client *client,
+				struct ieee80211_mgmt *mgmt,
+				int mgmt_len, bool ack)
+{
+	u16 status;
+
+	skw_dbg("client: %pM, ack: %d\n", client->addr, ack);
+	if (!client)
+		return;
+
+	status = le16_to_cpu(mgmt->u.auth.status_code);
+
+	if (ack && status == WLAN_STATUS_SUCCESS) {
+		skw_del_timer_work(iface->skw, client);
+		skw_add_timer_work(iface->skw, "auth_timeout",
+				   skw_mlme_ap_auth_timeout,
+				   client, SKW_AP_AUTH_TIMEOUT,
+				   client, GFP_KERNEL);
+	} else {
+		skw_warn("failed\n");
+		client->state = SKW_STATE_NONE;
+		skw_mlme_ap_del_sta(iface->wdev.wiphy,
+				iface->ndev, client->addr, false);
+	}
+}
+
+static void skw_mlme_ap_assoc_cb(struct skw_iface *iface,
+				 struct skw_client *client,
+				 struct ieee80211_mgmt *mgmt,
+				 int frame_len, bool ack, int reassoc)
+{
+	u16 status_code;
+	struct station_info info;
+	struct station_parameters params;
+
+	if (!client)
+		return;
+
+	if (reassoc)
+		status_code = le16_to_cpu(mgmt->u.reassoc_resp.status_code);
+	else
+		status_code = le16_to_cpu(mgmt->u.assoc_resp.status_code);
+
+	skw_dbg("client: %pM, ack: %d, status code: %d\n",
+		client->addr, ack, status_code);
+
+	if (ack && status_code == WLAN_STATUS_SUCCESS) {
+		skw_del_timer_work(iface->skw, client);
+
+		params.sta_flags_set = 0;
+		params.sta_flags_set |= BIT(NL80211_STA_FLAG_ASSOCIATED);
+		skw_change_station(iface->wdev.wiphy, iface->ndev,
+				client->addr, &params);
+
+		memset(&info, 0x0, sizeof(info));
+		if (client->assoc_req_ie) {
+			info.assoc_req_ies = client->assoc_req_ie;
+			info.assoc_req_ies_len = client->assoc_req_ie_len;
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0))
+			info.filled |= STATION_INFO_ASSOC_REQ_IES;
+#endif
+		}
+
+		cfg80211_new_sta(iface->ndev, client->addr,
+				 &info, GFP_KERNEL);
+		SKW_KFREE(client->assoc_req_ie);
+		client->assoc_req_ie = NULL;
+		client->assoc_req_ie_len = 0;
+
+		client->state = SKW_STATE_ASSOCED;
+	} else {
+		skw_err("failed, ack: %d, status_code: %d\n", ack, status_code);
+
+		client->state = SKW_STATE_NONE;
+		skw_mlme_ap_del_sta(iface->wdev.wiphy,
+				iface->ndev, client->addr, false);
+	}
+}
+
+void skw_mlme_ap_tx_status(struct skw_iface *iface, u64 cookie,
+			   const u8 *frame, int frame_len, u16 ack)
+{
+	u16 fc;
+	int reassoc = 0;
+	struct skw_client *client;
+	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)frame;
+
+	skw_dbg("iface: %d, da: %pM, ack: %d, cookie: %lld\n",
+		iface->id, mgmt->da, ack, cookie);
+
+	client = skw_mlme_ap_get_client(iface, mgmt->da);
+	if (!client || client->cookie != cookie) {
+		skw_dbg("cfg80211 tx status, cookie: %lld\n", cookie);
+		goto report;
+	}
+
+	fc = SKW_MGMT_SFC(mgmt->frame_control);
+
+	switch (fc) {
+	case IEEE80211_STYPE_AUTH:
+		skw_mlme_ap_auth_cb(iface, client, mgmt, frame_len, !!ack);
+		break;
+
+	case IEEE80211_STYPE_REASSOC_RESP:
+		reassoc = 1;
+		/* fall through */
+		skw_fallthrough;
+	case IEEE80211_STYPE_ASSOC_RESP:
+		skw_mlme_ap_assoc_cb(iface, client, mgmt, frame_len,
+				     !!ack, reassoc);
+		break;
+
+	default:
+		break;
+	}
+
+	return;
+
+report:
+	cfg80211_mgmt_tx_status(&iface->wdev, cookie, frame, frame_len,
+				ack, GFP_KERNEL);
+}
+
+static int skw_mlme_ap_auth_reply(struct skw_iface *iface,
+		struct skw_client *client, const u8 *bssid,
+		u16 auth_type, u16 transaction, u16 status,
+		u8 *ie, int ie_len)
+{
+	int ret;
+	int frame_len;
+	struct wiphy *wiphy;
+	struct ieee80211_mgmt *reply;
+
+	skw_dbg("da: %pM, bssid: %pM, transaction: %d, status: %d, ie: %d\n",
+		client->addr, bssid, transaction, status, ie_len);
+
+	wiphy = priv_to_wiphy(iface->skw);
+	frame_len = SKW_IEEE80211_HDR_LEN +
+		    sizeof(reply->u.auth) +
+		    ie_len;
+
+	reply = SKW_ZALLOC(frame_len, GFP_KERNEL);
+
+	if (!reply)
+		return -ENOMEM;
+
+	reply->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+					   IEEE80211_STYPE_AUTH);
+	skw_ether_copy(reply->da, client->addr);
+	skw_ether_copy(reply->sa, iface->addr);
+	skw_ether_copy(reply->bssid, bssid);
+
+	reply->u.auth.auth_alg = cpu_to_le16(auth_type);
+	reply->u.auth.auth_transaction = cpu_to_le16(transaction);
+	reply->u.auth.status_code = cpu_to_le16(status);
+
+	if (ie && ie_len)
+		memcpy(reply->u.auth.variable, ie, ie_len);
+
+	// skw_hex_dump("auth_reply", reply, frame_len, false);
+	ret = skw_mgmt_tx(wiphy, iface, iface->sap.cfg.channel,
+			  0, &client->cookie, false, reply, frame_len,
+			  frame_len, reply, true);
+
+	SKW_KFREE(reply);
+
+	return ret;
+}
+
+#if 0
+static int skw_ap_auth_shared_key(struct skw_client *sta, u16 trans_action)
+{
+	u16 status;
+	u8 *challenge;
+
+	switch (trans_action) {
+	case 1:
+		sta->challenge = SKW_ZALLOC(WLAN_AUTH_CHALLENGE_LEN,
+				GFP_KERNEL);
+		if (IS_ERR(sta->challenge)) {
+			status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto reply;
+		}
+
+		/* Generate challenge text */
+		get_random_bytes(sta->challenge,
+				WLAN_AUTH_CHALLENGE_LEN);
+
+		status = WLAN_STATUS_SUCCESS;
+		break;
+
+	case 3:
+		challenge = &mgmt->u.auth.variable[2];
+
+		if (memcmp(sta->challenge, challenge,
+			   WLAN_AUTH_CHALLENGE_LEN) == 0) {
+			status = WLAN_STATUS_SUCCESS;
+			SKW_KFREE(sta->challenge);
+		} else {
+			status = WLAN_STATUS_CHALLENGE_FAIL;
+		}
+
+		break;
+
+	default:
+		status = WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION;
+		break;
+	}
+
+	return status;
+}
+#endif
+
+static int skw_mlme_ap_auth_handler(struct skw_iface *iface, int freq,
+				int signal, void *frame, int frame_len)
+{
+	u8 *ies = NULL, *challenge;
+	int ies_len = 0, ret = 0;
+	struct skw_client *client = NULL;
+	struct station_parameters sta_params;
+	u8 challenge_ies[WLAN_AUTH_CHALLENGE_LEN + 2];
+	struct wiphy *wiphy = priv_to_wiphy(iface->skw);
+	struct ieee80211_mgmt *mgmt = frame;
+	u16 auth_alg, status_code, trans_action;
+	u16 status = WLAN_STATUS_SUCCESS;
+	u16 seq_ctrl;
+
+	auth_alg = le16_to_cpu(mgmt->u.auth.auth_alg);
+	trans_action = le16_to_cpu(mgmt->u.auth.auth_transaction);
+	status_code = le16_to_cpu(mgmt->u.auth.status_code);
+	seq_ctrl = le16_to_cpu(mgmt->seq_ctrl);
+
+	skw_dbg("auth alg: %d, trans action: %d, status: %d, seq: %u\n",
+		auth_alg, trans_action, status_code, seq_ctrl);
+
+	client = skw_mlme_ap_get_client(iface, mgmt->sa);
+	if (client) {
+		skw_dbg("flush peer status\n");
+	} else {
+		client = skw_mlme_ap_add_client(iface, mgmt->sa);
+		if (!client) {
+			skw_err("add client: %pM failed\n", mgmt->sa);
+			return 0;
+		}
+
+		memset(&sta_params, 0x0, sizeof(sta_params));
+		skw_add_station(wiphy, iface->ndev, mgmt->sa, &sta_params);
+	}
+
+	if (ieee80211_has_retry(mgmt->frame_control) &&
+	    client->last_seq_ctrl == seq_ctrl) {
+		skw_dbg("drop repeated auth(seq: %d)\n", seq_ctrl);
+		return 0;
+	}
+
+	client->last_seq_ctrl = seq_ctrl;
+	client->state = SKW_STATE_AUTHED;
+
+	if (!ether_addr_equal(mgmt->bssid, iface->sap.cfg.bssid)) {
+		skw_warn("failed, ap bssid: %pM, rx bssid: %pM\n",
+			 iface->sap.cfg.bssid, mgmt->bssid);
+		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto reply;
+	}
+
+	// TODO:
+	// transation check
+
+#if 0
+	if (auth_alg != iface->sap.auth_type) {
+		skw_err("auth type not match (client: %d, ap: %d)\n",
+			auth_alg, iface->sap.auth_type);
+		status = WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG;
+		goto reply;
+	}
+
+	if (client->state !=  SKW_STATE_NONE) {
+		skw_warn("current state: %s\n", sm_str[client->state]);
+		return 0;
+	}
+
+#endif
+
+	switch (auth_alg) {
+	case WLAN_AUTH_OPEN:
+		if (trans_action != 1) {
+			status = WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION;
+			goto reply;
+		}
+
+		if (status_code != WLAN_STATUS_SUCCESS)
+			return 0;
+
+		status = WLAN_STATUS_SUCCESS;
+		client->last_seq_ctrl = seq_ctrl;
+
+		break;
+
+	case WLAN_AUTH_SAE:
+		if (!skw_compat_cfg80211_rx_mgmt(&iface->wdev, freq, signal,
+				      frame, frame_len, 0, GFP_ATOMIC)) {
+			skw_warn("cfg80211_rx_mgmt failed\n");
+		}
+
+		return 0;
+
+	case WLAN_AUTH_SHARED_KEY:
+		switch (trans_action) {
+		case 1:
+			client->challenge = SKW_ZALLOC(WLAN_AUTH_CHALLENGE_LEN, GFP_KERNEL);
+			if (!client->challenge) {
+				status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+				goto reply;
+			}
+
+			/* Generate challenge text */
+			get_random_bytes(client->challenge,
+					WLAN_AUTH_CHALLENGE_LEN);
+
+			challenge_ies[0] = WLAN_EID_CHALLENGE;
+			challenge_ies[1] = WLAN_AUTH_CHALLENGE_LEN;
+			memcpy(challenge_ies + 2, client->challenge,
+					WLAN_AUTH_CHALLENGE_LEN);
+			ies_len = 2 + WLAN_AUTH_CHALLENGE_LEN;
+
+			ies = challenge_ies;
+			status = WLAN_STATUS_SUCCESS;
+			break;
+
+		case 3:
+			challenge = &mgmt->u.auth.variable[2];
+
+			if (client->challenge &&
+				(memcmp(client->challenge, challenge,
+				   WLAN_AUTH_CHALLENGE_LEN) == 0))
+				status = WLAN_STATUS_SUCCESS;
+			else
+				status = WLAN_STATUS_CHALLENGE_FAIL;
+
+			break;
+
+		default:
+			status = WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION;
+			break;
+		}
+
+
+		break;
+
+	default:
+		status = WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG;
+		skw_warn("unsupport auth alg: %d\n", auth_alg);
+		break;
+	}
+
+reply:
+	ret = skw_mlme_ap_auth_reply(iface, client, mgmt->bssid, auth_alg,
+				trans_action + 1, status, ies, ies_len);
+	if (ret || status != WLAN_STATUS_SUCCESS) {
+		skw_warn("failed, ret = %d, status: %d\n", ret, status);
+		client->state = SKW_STATE_NONE;
+		skw_mlme_ap_del_sta(wiphy, iface->ndev, mgmt->sa, false);
+	}
+
+	return 0;
+}
+
+#if 0
+static int skw_ap_parse_element(struct skw_80211_element *element,
+				const u8 *ies, u32 ie_len)
+{
+	const struct element *elem;
+
+	return 0;
+
+	for_each_element(elem, ies, ie_len) {
+		switch (elem->id) {
+		case WLAN_EID_SSID:
+			// element->ssid = elem->data;
+			// element->ssid_len = elem->data;
+			break;
+
+		case WLAN_EID_SUPP_RATES:
+			break;
+		case WLAN_EID_EXT_SUPP_RATES:
+			break;
+		case WLAN_EID_RSN:
+			break;
+		case WLAN_EID_PWR_CAPABILITY:
+			break;
+		case WLAN_EID_SUPPORTED_CHANNELS:
+			break;
+		case WLAN_EID_HT_CAPABILITY:
+			break;
+		case WLAN_EID_HT_OPERATION:
+			break;
+		case WLAN_EID_VHT_CAPABILITY:
+			break;
+		case WLAN_EID_VHT_OPERATION:
+			break;
+		case WLAN_EID_EXT_CAPABILITY:
+			break;
+		case WLAN_EID_MIC:
+			break;
+		case WLAN_EID_SUPPORTED_REGULATORY_CLASSES:
+			break;
+		default:
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static u16 skw_ap_check_ssid(struct skw_iface *iface,
+			     const u8 *ssid, int ssid_len)
+{
+	if (!ssid ||
+	    ssid_len != iface->sap.ssid_len ||
+	    memcmp(ssid, iface->sap.ssid, iface->sap.ssid_len) != 0)
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+
+	return WLAN_STATUS_SUCCESS;
+}
+
+static u16 skw_ap_check_wmm(struct skw_client *sta, const u8 *wmm_ie, int len)
+{
+#define SKW_WMM_IE_LEN  24
+	struct skw_wmm_info {
+		u8 oui[3];
+		u8 oui_type;
+		u8 oui_subtype;
+		u8 version;
+		u8 qos_info;
+	} __packed;
+
+	struct skw_wmm_info *wmm = (struct skw_wmm_info *)wmm_ie;
+
+	if (len != SKW_WMM_IE_LEN ||
+	    wmm->oui_subtype != 0 ||
+	    wmm->version != 1)
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+
+	return WLAN_STATUS_SUCCESS;
+}
+#endif
+static u16 skw_mlme_ap_check_assoc_ie(struct skw_iface *iface,
+				 struct skw_client *client,
+				 const u8 *ie, int ie_len)
+{
+	// skw_hex_dump("rx assoc ie", ie, ie_len, false);
+	// struct skw_80211_element e;
+
+	//memset(&ie, 0x0, sizeof(e));
+	// skw_ap_parse_element(&e, ie, ie_len);
+
+#if 0
+	/* check ssid */
+	if (skw_ap_check_ssid(iface, e.ssid, e.ssid_len))
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+
+	/* check wmm */
+	if (skw_ap_check_wmm(sta, e.wmm, e.wmm_len))
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+
+	/* check ext capa */
+	/* check support rate */
+#endif
+	return 0;
+}
+
+static u16 skw_mlme_ap_new_aid(struct skw_iface *iface)
+{
+	u16 aid = 0;
+
+	for (aid = 1; aid < 64; aid++)
+		if (!test_and_set_bit(aid, iface->sap.aid_map))
+			break;
+
+	return aid;
+}
+
+/* add basic rate & ext support rate */
+static u8 *skw_mlme_ap_add_rate(struct wiphy *wiphy,
+			   struct skw_iface *iface, u8 *ies)
+{
+	int i, nr;
+	u8 *pos = ies, *ext_rate_count;
+	struct ieee80211_rate *rate;
+	struct ieee80211_supported_band *sband;
+
+	/* basic rate */
+	sband = wiphy->bands[iface->sap.cfg.channel->band];
+	rate = sband->bitrates;
+#if 0
+	enum ieee80211_rate_flags mandatory;
+
+	if (sband->band == NL80211_BAND_2GHZ) {
+		if (scan_width == NL80211_BSS_CHAN_WIDTH_5 ||
+		    scan_width == NL80211_BSS_CHAN_WIDTH_10)
+			mandatory = IEEE80211_RATE_MANDATORY_G;
+		else
+			mandatory = IEEE80211_RATE_MANDATORY_B;
+	} else {
+		mandatory = IEEE80211_RATE_MANDATORY_A;
+	}
+#endif
+	*pos++ = WLAN_EID_SUPP_RATES;
+	*pos++ = SKW_BASIC_RATE_COUNT;
+	for (i = 0; i < SKW_BASIC_RATE_COUNT; i++)
+		*pos++ = rate[i].bitrate / 5;
+
+	/* ext support rate */
+	*pos++ = WLAN_EID_EXT_SUPP_RATES;
+	ext_rate_count = pos++;
+
+	for (i = SKW_BASIC_RATE_COUNT; i < sband->n_bitrates; i++)
+		*pos++ = rate[i].bitrate / 5;
+
+	nr = sband->n_bitrates - SKW_BASIC_RATE_COUNT;
+	if (iface->sap.ht_required) {
+		*pos++ = 0x80 | SKW_BSS_MEMBERSHIP_SELECTOR_HT_PHY;
+		nr++;
+	}
+
+	if (iface->sap.vht_required) {
+		*pos++ = 0x80 | SKW_BSS_MEMBERSHIP_SELECTOR_VHT_PHY;
+		nr++;
+	}
+
+	*ext_rate_count = nr;
+
+	return pos;
+}
+
+static u8 *skw_mlme_ap_add_ht_cap(struct skw_iface *iface, u8 *ies)
+{
+	u8 *pos = ies;
+	int len = sizeof(struct ieee80211_ht_cap);
+
+	*pos++ = WLAN_EID_HT_CAPABILITY;
+	*pos++ = len;
+	memcpy(pos, &iface->sap.cfg.ht_cap, len);
+
+	return pos + len;
+}
+
+static u8 *skw_mlme_ap_add_ht_oper(struct skw_iface *iface, u8 *ies)
+{
+	u8 *pos = ies;
+	struct ieee80211_ht_operation *oper;
+
+	*pos++ = WLAN_EID_HT_OPERATION;
+	*pos++ = sizeof(*oper);
+
+	oper = (struct ieee80211_ht_operation *)pos;
+	memset(oper, 0x0, sizeof(*oper));
+
+	oper->primary_chan = iface->sap.cfg.channel->hw_value;
+
+	return pos + sizeof(*oper);
+}
+
+static void skw_mlme_ap_parse_ies(const u8 *beacon, int beacon_len,
+				 struct skw_element_info *e)
+{
+	const struct skw_element *element;
+
+	if (!beacon || beacon_len == 0)
+		return;
+
+	skw_foreach_element(element, beacon, beacon_len) {
+		switch (element->id) {
+		case WLAN_EID_SSID:
+			e->ssid.len = element->datalen;
+			memcpy(e->ssid.data, element->data, element->datalen);
+			break;
+
+		case WLAN_EID_SUPP_RATES:
+			e->support_rate = element;
+			break;
+
+		case WLAN_EID_EXT_SUPP_RATES:
+			e->ext_rate = element;
+			break;
+
+		case WLAN_EID_HT_CAPABILITY:
+			e->ht_capa = element;
+			break;
+
+		case WLAN_EID_HT_OPERATION:
+			e->ht_oper = element;
+			break;
+
+		case WLAN_EID_VHT_CAPABILITY:
+			e->vht_capa = element;
+			break;
+
+		case WLAN_EID_VHT_OPERATION:
+			e->vht_oper = element;
+			break;
+
+		case WLAN_EID_EXT_CAPABILITY:
+			e->ext_capa = element;
+			break;
+
+		case WLAN_EID_VENDOR_SPECIFIC:
+			e->vendor_vht = element;
+			break;
+
+		default:
+			skw_dbg("unused element: %d, len: %d\n",
+				element->id, element->datalen);
+			break;
+		}
+	}
+}
+
+static int skw_mlme_ap_assoc_reply(struct skw_iface *iface,
+		struct skw_client *client, u16 status, bool reassoc)
+{
+	u16 fc, elen;
+	u8 *ies, *pos;
+	int ret, frame_len, len;
+	struct wiphy *wiphy = priv_to_wiphy(iface->skw);
+	struct ieee80211_mgmt *reply;
+	struct skw_element_info e;
+
+	skw_dbg("client addr: %pM, reassoc: %d, aid: %d, status code: %d\n",
+		client->addr, reassoc, client->aid, status);
+
+	len = sizeof(struct ieee80211_mgmt) + 1024;
+
+	reply = SKW_ZALLOC(len, GFP_KERNEL);
+	if (!reply)
+		return -ENOMEM;
+
+	memset(&e, 0x0, sizeof(e));
+
+	fc = reassoc ? IEEE80211_STYPE_REASSOC_RESP :
+		IEEE80211_STYPE_ASSOC_RESP;
+
+	reply->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | fc);
+	reply->duration = 0;
+	skw_ether_copy(reply->da, client->addr);
+	skw_ether_copy(reply->sa, iface->addr);
+	skw_ether_copy(reply->bssid, iface->sap.cfg.bssid);
+	reply->seq_ctrl = 0;
+
+	reply->u.assoc_resp.capab_info = client->capa;
+	reply->u.assoc_resp.status_code = status;
+	reply->u.assoc_resp.aid = client->aid;
+
+	frame_len = SKW_IEEE80211_HDR_LEN +
+		    sizeof(reply->u.assoc_resp);
+
+	pos = ies = reply->u.assoc_resp.variable;
+
+	len = offsetof(struct ieee80211_mgmt, u.probe_resp.variable);
+	skw_mlme_ap_parse_ies(iface->sap.probe_resp + len,
+			      iface->sap.probe_resp_len - len, &e);
+
+	/* support rate & ext rate */
+
+	if (e.support_rate) {
+		elen = e.support_rate->datalen + 2;
+		memcpy(pos, e.support_rate, elen);
+		pos += elen;
+
+		if (e.ext_rate) {
+			elen = e.ext_rate->datalen + 2;
+			memcpy(pos, e.ext_rate, elen);
+			pos += elen;
+		}
+	} else {
+		pos = skw_mlme_ap_add_rate(wiphy, iface, pos);
+	}
+
+#if 1
+	/* 80211n capa & oper */
+	if (e.ht_capa && e.ht_oper) {
+		elen = e.ht_capa->datalen + 2;
+		memcpy(pos, e.ht_capa, elen);
+		pos += elen;
+
+		elen = e.ht_oper->datalen + 2;
+		memcpy(pos, e.ht_oper, elen);
+		pos += elen;
+	} else {
+		pos = skw_mlme_ap_add_ht_cap(iface, pos);
+		pos = skw_mlme_ap_add_ht_oper(iface, pos);
+	}
+
+	/* 11ac capa */
+
+	/* vendor vht */
+	if (e.vendor_vht) {
+		elen = e.vendor_vht->datalen + 2;
+		memcpy(pos, e.vendor_vht, elen);
+		pos += elen;
+	}
+#endif
+
+	frame_len += pos - ies;
+
+	ret = skw_mgmt_tx(wiphy, iface, iface->sap.cfg.channel,
+			  0, &client->cookie, false, reply, frame_len,
+			  frame_len, reply, true);
+
+	SKW_KFREE(reply);
+
+	return ret;
+}
+
+static int skw_mlme_ap_assoc_handler(struct skw_iface *iface, void *frame,
+				int frame_len, int reassoc)
+{
+	u8 *ie;
+	int ie_len = 0, ret;
+	u16 capab_info, status;
+	struct skw_client *client;
+	struct ieee80211_mgmt *mgmt = frame;
+	u16 seq_ctrl;
+
+	skw_dbg("iface: %d, sa: %pM, reassoc: %d\n",
+		iface->id, mgmt->sa, reassoc);
+
+	seq_ctrl = le16_to_cpu(mgmt->seq_ctrl);
+
+	client = skw_mlme_ap_get_client(iface, mgmt->sa);
+	if (!client) {
+		skw_warn("client: %pM not exist\n", mgmt->sa);
+		return 0;
+	}
+
+	if (client->state == SKW_STATE_NONE) {
+		status = WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA;
+		skw_dbg("client->state: %d\n", client->state);
+		skw_ap_send_disassoc(iface, client, status);
+		return 0;
+	}
+
+	if (ieee80211_has_retry(mgmt->frame_control) &&
+	    client->last_seq_ctrl == seq_ctrl) {
+		skw_dbg("drop repeated assoc frame(seq: %d)\n", seq_ctrl);
+		return 0;
+	}
+
+	client->last_seq_ctrl = seq_ctrl;
+
+	ie_len = frame_len - sizeof(struct ieee80211_hdr_3addr);
+	if (reassoc) {
+		capab_info = le16_to_cpu(mgmt->u.reassoc_req.capab_info);
+		ie = mgmt->u.reassoc_req.variable;
+		ie_len -= sizeof(mgmt->u.reassoc_req);
+	} else {
+		capab_info = le16_to_cpu(mgmt->u.assoc_req.capab_info);
+		ie = mgmt->u.assoc_req.variable;
+		ie_len -= sizeof(mgmt->u.assoc_req);
+	}
+
+	client->capa = capab_info;
+
+	// check assoc ies
+	status = skw_mlme_ap_check_assoc_ie(iface, client, ie, ie_len);
+	if (status != WLAN_STATUS_SUCCESS) {
+		skw_ap_send_disassoc(iface, client, status);
+		return 0;
+	}
+
+	// assign aid
+	client->aid = skw_mlme_ap_new_aid(iface);
+	if (!client->aid) {
+		status = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
+		goto reply;
+	}
+
+	// 11W
+reply:
+	ret = skw_mlme_ap_assoc_reply(iface, client, status, reassoc);
+	if (ret || status != WLAN_STATUS_SUCCESS) {
+		skw_err("ret: %d, status: %d\n", ret, status);
+		return ret;
+	}
+
+	if (!client->assoc_req_ie) {
+		client->assoc_req_ie = SKW_ZALLOC(ie_len, GFP_KERNEL);
+		if (client->assoc_req_ie) {
+			memcpy(client->assoc_req_ie, ie, ie_len);
+			client->assoc_req_ie_len = ie_len;
+		}
+	}
+
+	return ret;
+}
+
+int skw_mlme_ap_rx_mgmt(struct skw_iface *iface, u16 fc, int freq,
+			int signal, void *frame, int frame_len)
+{
+	int reassoc = 0;
+	struct skw_client *client;
+	struct ieee80211_mgmt *mgmt = frame;
+
+	// address check
+	switch (fc) {
+	case IEEE80211_STYPE_AUTH:
+		skw_mlme_ap_auth_handler(iface, freq, signal, frame, frame_len);
+		break;
+
+	case IEEE80211_STYPE_DEAUTH:
+	case IEEE80211_STYPE_DISASSOC:
+		client = skw_mlme_ap_get_client(iface, mgmt->sa);
+		if (!client) {
+			skw_warn("can't find sta:%pM\n", mgmt->sa);
+			return 0;
+		}
+
+		if (client->state >= SKW_STATE_ASSOCED) {
+			//notify hostapd to update state and delete sta
+			cfg80211_del_sta(iface->ndev, client->addr, GFP_KERNEL);
+		} else if (client->state >= SKW_STATE_AUTHED) {
+			//just delete local sta info
+			skw_mlme_ap_del_sta(iface->wdev.wiphy,
+					    iface->ndev, client->addr, false);
+		}
+#if 0
+		skw_add_timer_work("idle_release", skw_mlme_ap_auth_timeout,
+				   client, SKW_AP_IDLE_TIMEOUT,
+				   client, GFP_KERNEL);
+#endif
+		break;
+
+	case IEEE80211_STYPE_REASSOC_REQ:
+		reassoc = 1;
+		/* fall through */
+		skw_fallthrough;
+	case IEEE80211_STYPE_ASSOC_REQ:
+		skw_mlme_ap_assoc_handler(iface, frame, frame_len, reassoc);
+		break;
+
+	case IEEE80211_STYPE_PROBE_REQ:
+		skw_fallthrough;
+	case IEEE80211_STYPE_PROBE_RESP:
+		skw_fallthrough;
+	case IEEE80211_STYPE_ACTION:
+		if (!skw_compat_cfg80211_rx_mgmt(&iface->wdev, freq, signal,
+						frame, frame_len, 0, GFP_ATOMIC)) {
+			skw_warn("mlme_ap_rx failed\n");
+		}
+		break;
+
+	default:
+		skw_warn("unsupport fc type: 0x%x\n", fc);
+		break;
+	}
+
+	return 0;
+}
+
+#if 0
+int skw_send_deauth_frame(struct wiphy *wiphy, struct net_device *netdev,
+			int reason_code)
+{
+	int ret;
+	int size;
+	char *buff = NULL;
+	struct skw_core *skw;
+	struct skw_disconnect_param *deauth_param = NULL;
+
+	skw = wiphy_priv(wiphy);
+
+	size = sizeof(struct skw_disconnect_param);
+	buff = SKW_ZALLOC(size, GFP_KERNEL);
+	if (IS_ERR_OR_NULL(buff)) {
+		skw_err("Malloc disconnect param for deauth failed\n");
+		return -ENOMEM;
+	}
+
+	deauth_param = (struct skw_disconnect_param *)buff;
+	deauth_param->type = SKW_DISCONNECT_SEND_DEAUTH;
+	deauth_param->local_state_change = true;
+	deauth_param->reason_code = reason_code;
+	deauth_param->ie_len = 0;
+
+	ret = skw_send_msg(wiphy, netdev, SKW_CMD_DISCONNECT, buff,
+			size, NULL, 0);
+	if (ret)
+		skw_err("Deauth failed ret:%d\n", ret);
+
+	SKW_KFREE(buff);
+
+	return ret;
+}
+#endif
+
+static int skw_mlme_sta_ft_event(struct skw_iface *iface, void *buf, int len)
+{
+	int ie_len;
+	struct cfg80211_ft_event_params ft_event;
+	struct ieee80211_mgmt *mgmt = buf;
+
+	ie_len = len - offsetof(struct ieee80211_mgmt, u.auth.variable);
+
+	ft_event.ies = mgmt->u.auth.variable;
+	ft_event.ies_len = ie_len;
+	ft_event.target_ap = mgmt->bssid;
+	ft_event.ric_ies = NULL;
+	ft_event.ric_ies_len = 0;
+
+	cfg80211_ft_event(iface->ndev, &ft_event);
+
+	return 0;
+}
+
+int skw_mlme_sta_rx_auth(struct skw_iface *iface, int freq, int signal,
+			 void *buf, int len)
+{
+	u16 status_code;
+	struct ieee80211_mgmt *mgmt = buf;
+	struct wiphy *wiphy = iface->wdev.wiphy;
+	struct skw_connect_param *conn = iface->sta.conn;
+
+	skw_dbg("auth_type: %d, flags: 0x%x\n",
+		conn->auth_type, conn->flags);
+
+	conn->state = SKW_STATE_AUTHED;
+
+	if (conn->auth_type == NL80211_AUTHTYPE_SAE)
+		return skw_compat_cfg80211_rx_mgmt(&iface->wdev, freq, signal,
+						buf, len, 0, GFP_ATOMIC);
+
+	if (conn->auth_type == NL80211_AUTHTYPE_FT)
+		return skw_mlme_sta_ft_event(iface, mgmt, len);
+
+	status_code = le16_to_cpu(mgmt->u.auth.status_code);
+	if (status_code == WLAN_STATUS_SUCCESS)
+		return skw_connect_assoc(wiphy, iface->ndev, conn);
+
+	if (SKW_TEST(conn->flags, SKW_CONN_FLAG_AUTH_AUTO) &&
+	    status_code == WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG) {
+
+		mutex_lock(&iface->sta.conn->lock);
+
+		switch (conn->auth_type) {
+		case NL80211_AUTHTYPE_OPEN_SYSTEM:
+			if (conn->key_len)
+				conn->auth_type = NL80211_AUTHTYPE_SHARED_KEY;
+			else
+				conn->auth_type = NL80211_AUTHTYPE_FT;
+			break;
+
+		case NL80211_AUTHTYPE_SHARED_KEY:
+			conn->auth_type = NL80211_AUTHTYPE_FT;
+			break;
+
+		default:
+			SKW_CLEAR(conn->flags, SKW_CONN_FLAG_AUTH_AUTO);
+			break;
+		}
+
+		mutex_unlock(&iface->sta.conn->lock);
+
+		if (conn->flags & SKW_CONN_FLAG_AUTH_AUTO) {
+			return skw_queue_local_event(wiphy, iface,
+					SKW_EVENT_LOCAL_STA_CONNECT,
+					NULL, 0);
+		}
+	}
+
+	/* status code != WLAN_STATUS_SUCCESS */
+	conn->state = SKW_STATE_NONE;
+
+	return 0;
+}
+
+int skw_mlme_sta_rx_assoc(struct skw_iface *iface, struct cfg80211_bss *bss,
+			  void *frame, int len, void *req_ie, int req_ie_len)
+{
+	u16 status;
+	int resp_ie_len;
+	struct ieee80211_mgmt *mgmt = frame;
+	struct skw_connect_param *conn = iface->sta.conn;
+
+	skw_dbg("bssid: %pM\n", mgmt->bssid);
+
+	resp_ie_len = offsetof(struct ieee80211_mgmt, u.assoc_resp.variable);
+	resp_ie_len = len - resp_ie_len;
+
+	status = le16_to_cpu(mgmt->u.assoc_resp.status_code);
+	if (status == WLAN_STATUS_SUCCESS) {
+		mutex_lock(&conn->lock);
+		conn->state = SKW_STATE_ASSOCED;
+		skw_connected(iface->ndev, conn, req_ie, req_ie_len,
+			      mgmt->u.assoc_resp.variable, resp_ie_len,
+			      status, GFP_KERNEL);
+		mutex_unlock(&conn->lock);
+	} else {
+		conn->state = SKW_STATE_NONE;
+		skw_disconnected(iface->ndev, status,
+				mgmt->u.assoc_resp.variable, resp_ie_len,
+				false, GFP_KERNEL);
+	}
+
+	return 0;
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mlme.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mlme.h
new file mode 100755
index 0000000..00439bc
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_mlme.h
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_MLME_H__
+#define __SKW_MLME_H__
+
+#include "skw_iface.h"
+
+struct skw_client {
+	struct list_head list;
+	struct skw_iface *iface;
+	enum SKW_STATES state;
+	u32 capa;
+
+	u16 aid;
+	u8 addr[ETH_ALEN];
+
+	u8 *challenge;
+	u64 cookie;
+	unsigned long idle;
+	u8 *assoc_req_ie;
+	u16 assoc_req_ie_len;
+	u16 last_seq_ctrl;
+};
+
+struct skw_element_info {
+	struct {
+		int len;
+		u8 data[32];
+	} ssid;
+	const struct skw_element *support_rate;
+	const struct skw_element *ext_rate;
+	const struct skw_element *ht_capa;
+	const struct skw_element *ht_oper;
+	const struct skw_element *vht_capa;
+	const struct skw_element *vht_oper;
+	const struct skw_element *ext_capa;
+	const struct skw_element *vendor_vht;
+};
+
+int skw_mgmt_frame_with_reason(struct skw_iface *iface, u8 *da, u64 *cookie, u8 *bssid,
+			struct ieee80211_channel *ch, u16 stype, u16 reason, bool switchover);
+
+int skw_ap_simple_reply(struct skw_iface *iface, struct skw_client *client,
+			u16 stype, u16 reason);
+
+static inline int skw_ap_send_deauth(struct skw_iface *iface,
+				     struct skw_client *client, u16 reason)
+{
+	return skw_ap_simple_reply(iface, client,
+					IEEE80211_STYPE_DEAUTH, reason);
+}
+
+
+static inline int skw_ap_send_disassoc(struct skw_iface *iface,
+				       struct skw_client *client, u16 code)
+{
+	return skw_ap_simple_reply(iface, client,
+					IEEE80211_STYPE_DISASSOC, code);
+}
+
+void skw_mlme_sta_tx_status(struct skw_iface *iface, u64 cookie,
+			   const u8 *frame, int frame_len, u16 ack);
+int skw_mlme_sta_rx_mgmt(struct skw_iface *iface, int freq, int signal,
+			void *frame, int frame_len);
+int skw_process_auth_response(struct skw_iface *iface, int freq,
+			int signal, void *frame, int frame_len);
+int skw_ap_mgmt_handler(struct skw_iface *iface, void *frame, int frame_len);
+void skw_mlme_ap_del_sta(struct wiphy *wiphy, struct net_device *ndev,
+			 const u8 *addr, u8 force);
+int skw_mlme_ap_rx_mgmt(struct skw_iface *iface, u16 fc, int freq,
+			int signal, void *frame, int frame_len);
+void skw_mlme_ap_remove_client(struct skw_iface *iface, const u8 *addr);
+void skw_mlme_ap_tx_status(struct skw_iface *iface, u64 cookie,
+			   const u8 *frame, int frame_len, u16 ack);
+int skw_mlme_sta_rx_auth(struct skw_iface *iface, int freq, int signal,
+			 void *buf, int len);
+
+int skw_mlme_sta_rx_assoc(struct skw_iface *iface, struct cfg80211_bss *bss,
+			  void *frame, int len, void *req_ie, int req_ie_len);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_msg.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_msg.c
new file mode 100755
index 0000000..060e681
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_msg.c
@@ -0,0 +1,2145 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/skbuff.h>
+#include <net/netlink.h>
+
+#include "skw_core.h"
+#include "skw_iface.h"
+#include "skw_msg.h"
+#include "skw_vendor.h"
+#include "skw_mlme.h"
+#include "skw_mbssid.h"
+#include "skw_cfg80211.h"
+#include "skw_timer.h"
+#include "skw_rx.h"
+#include "skw_work.h"
+#include "skw_calib.h"
+#include "trace.h"
+#include "skw_dfs.h"
+
+static int skw_event_scan_complete(struct skw_core *skw,
+			struct skw_iface *iface, void *buf, int len,
+			struct skw_skb_rxcb *cb)
+{
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return -EINVAL;
+	}
+
+	skw_scan_done(skw, iface, false);
+
+	return 0;
+}
+
+static int skw_event_sched_scan_done(struct skw_core *skw,
+			struct skw_iface *iface, void *buf, int len,
+			struct skw_skb_rxcb *cb)
+{
+	struct wiphy *wiphy = priv_to_wiphy(skw);
+
+	skw_dbg("actived: %d\n", !!skw->sched_scan_req);
+
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return -EINVAL;
+	}
+
+	if (!skw->sched_scan_req)
+		return 0;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
+	cfg80211_sched_scan_results(wiphy, skw->sched_scan_req->reqid);
+#else
+	cfg80211_sched_scan_results(wiphy);
+#endif
+
+	return 0;
+}
+
+static int skw_event_disconnect(struct skw_core *skw, struct skw_iface *iface,
+				void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	int ret = 0;
+	struct wiphy *wiphy = priv_to_wiphy(skw);
+	struct skw_discon_event_params *param = buf;
+
+	skw_info("bssid: %pM, reason: %u\n", param->bssid, param->reason);
+
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return -EINVAL;
+	}
+
+	skw_sta_leave(wiphy, iface->ndev, param->bssid,
+			param->reason, false);
+
+	if (iface->sta.sme_external) {
+		if (iface->sta.core.cbss) {
+			skw_compat_assoc_failure(iface->ndev,
+				iface->sta.core.cbss, false);
+
+			iface->sta.core.cbss = NULL;
+		} else {
+			skw_tx_mlme_mgmt(iface->ndev, IEEE80211_STYPE_DEAUTH,
+					param->bssid, param->bssid, param->reason);
+		}
+	} else {
+		skw_disconnected(iface->ndev, param->reason, NULL, 0, true,
+				GFP_KERNEL);
+	}
+
+	return ret;
+}
+
+static int skw_sta_rx_deauth(struct wiphy *wiphy, struct skw_iface *iface,
+			void *buf, int len)
+{
+	u16 reason;
+	struct ieee80211_mgmt *mgmt = buf;
+	struct skw_peer_ctx *ctx;
+
+	skw_wdev_assert_lock(iface);
+
+	if (!ether_addr_equal(mgmt->bssid, mgmt->sa)) {
+		cfg80211_tdls_oper_request(iface->ndev, mgmt->sa,
+				NL80211_TDLS_TEARDOWN,
+				SKW_WLAN_REASON_TDLS_TEARDOWN_UNREACHABLE,
+				GFP_KERNEL);
+		return 0;
+	}
+
+	ctx = skw_peer_ctx(iface, mgmt->bssid);
+	if (!ctx) {
+		skw_dbg("recv deauth twice\n");
+		return -ENOENT;
+	}
+
+	reason = le16_to_cpu(mgmt->u.deauth.reason_code);
+
+	skw_sta_leave(wiphy, iface->ndev, mgmt->bssid, reason, false);
+
+	if (iface->sta.sme_external)
+		skw_compat_rx_mlme_mgmt(iface->ndev, buf, len);
+	else
+		skw_disconnected(iface->ndev, reason, NULL, 0, true,
+			GFP_KERNEL);
+
+	return 0;
+}
+
+static int skw_sta_rx_auth(struct wiphy *wiphy, struct skw_iface *iface,
+			   int freq, int signal, void *buf, int len)
+{
+	u16 status_code, auth_alg;
+	struct ieee80211_mgmt *mgmt = buf;
+	struct skw_bss_cfg *bss = &iface->sta.core.bss;
+
+	skw_wdev_assert_lock(iface);
+
+	if (!ether_addr_equal(bss->bssid, mgmt->bssid)) {
+		skw_warn("bssid unmatch, current: %pM, mgmt: %pM\n",
+			 bss->bssid, mgmt->bssid);
+
+		return 0;
+	}
+
+	skw_set_state(&iface->sta.core.sm, SKW_STATE_AUTHED);
+
+	iface->sta.core.pending.step_start = jiffies;
+	iface->sta.core.pending.retry = 0;
+
+	auth_alg = le16_to_cpu(mgmt->u.auth.auth_alg);
+
+	/* SAE confirm frame received */
+	if (auth_alg == WLAN_AUTH_SAE &&
+	    le16_to_cpu(mgmt->u.auth.auth_transaction) == 2)
+		SKW_SET(iface->sta.core.sm.flags, SKW_SM_FLAG_SAE_RX_CONFIRM);
+
+	status_code = le16_to_cpu(mgmt->u.auth.status_code);
+	switch (status_code) {
+	case WLAN_STATUS_SUCCESS:
+	case SKW_WLAN_STATUS_SAE_HASH_TO_ELEMENT:
+		break;
+
+	case WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG:
+		if (iface->sta.is_wep) {
+			struct skw_auth_param *params;
+
+			params = (struct skw_auth_param *)iface->sta.core.pending.auth_cmd;
+			if (auth_alg != params->auth_algorithm)
+				mgmt->u.auth.auth_alg = cpu_to_le16(params->auth_algorithm);
+		}
+
+		skw_fallthrough;
+
+	default:
+		skw_info("auth failed, status code: %d\n", status_code);
+		iface->sta.report_deauth = false;
+		skw_sta_leave(wiphy, iface->ndev, mgmt->bssid,
+				WLAN_REASON_UNSPECIFIED, false);
+		break;
+	}
+
+	if (iface->sta.core.sm.rty_state == SKW_RETRY_ASSOC) {
+		skw_dbg("local retry auth, not report\n");
+		skw_set_state(&iface->sta.core.sm, SKW_STATE_ASSOCING);
+		return 0;
+	}
+
+	if (iface->sta.sme_external)
+		skw_compat_rx_mlme_mgmt(iface->ndev, buf, len);
+	else
+		skw_mlme_sta_rx_auth(iface, freq, signal, buf, len);
+
+	return 0;
+}
+
+static int skw_sta_rx_assoc(struct skw_iface *iface, int freq,
+			int signal, void *buf, int len)
+{
+	u16 status_code;
+	struct skw_peer_ctx *ctx;
+	u8 *assoc_req_ie = NULL;
+	struct ieee80211_mgmt *mgmt = buf;
+	struct skw_sta_core *core = &iface->sta.core;
+
+	skw_dump_frame((u8 *)buf, len);
+	skw_wdev_assert_lock(iface);
+
+	ctx = skw_get_ctx(iface->skw, iface->lmac_id, core->bss.ctx_idx);
+	if (!ctx) {
+		skw_err("invalid pidx: %d\n", core->bss.ctx_idx);
+		return 0;
+	}
+
+	skw_peer_ctx_lock(ctx);
+
+	if (!ctx->peer ||
+	    !ether_addr_equal(ctx->peer->addr, mgmt->bssid)) {
+		skw_peer_ctx_unlock(ctx);
+		return 0;
+	}
+
+	skw_set_state(&core->sm, SKW_STATE_ASSOCED);
+
+	//TBD: Intial the rx free channel and enable the ability to refill it.
+	if (iface->skw->hw.bus == SKW_BUS_PCIE) {
+		if (skw_edma_get_refill((void *)iface->skw, iface->lmac_id) == 0)
+			skw_edma_init_data_chan((void *)iface->skw, iface->lmac_id);
+		else
+			skw_edma_inc_refill((void *)iface->skw, iface->lmac_id);
+	}
+
+	status_code = le16_to_cpu(mgmt->u.assoc_resp.status_code);
+	if (status_code == WLAN_STATUS_SUCCESS) {
+		u8 *ies = mgmt->u.assoc_resp.variable;
+		int ies_len = len - (ies - (u8 *)mgmt);
+
+		skw_iface_set_wmm_capa(iface, ies, ies_len);
+
+		atomic_set(&ctx->peer->rx_filter, SKW_RX_FILTER_SET);
+		__skw_peer_ctx_transmit(ctx, true);
+
+		netif_carrier_on(iface->ndev);
+
+	} else {
+		skw_info("failed, status code: %d\n", status_code);
+
+		iface->sta.report_deauth = false;
+		skw_set_state(&core->sm, SKW_STATE_NONE);
+	}
+
+	skw_peer_ctx_unlock(ctx);
+
+	if (core->assoc_req_ie_len)
+		assoc_req_ie = core->assoc_req_ie;
+
+	if (iface->sta.sme_external)
+		skw_compat_rx_assoc_resp(iface->ndev, core->cbss, buf, len, 0,
+				assoc_req_ie, core->assoc_req_ie_len);
+	else
+		skw_mlme_sta_rx_assoc(iface, NULL, buf, len, assoc_req_ie,
+				core->assoc_req_ie_len);
+
+	core->cbss = NULL;
+
+	return 0;
+}
+
+static int skw_sta_rx_mgmt(struct skw_core *skw, struct skw_iface *iface,
+		u16 fc, int freq, int signal, void *buf, int len)
+{
+	u16 seq_ctrl;
+	int ret = 0;
+	struct ieee80211_mgmt *mgmt = buf;
+	struct wiphy *wiphy = priv_to_wiphy(skw);
+
+	skw_dump_frame((u8 *)buf, len);
+	seq_ctrl = le16_to_cpu(mgmt->seq_ctrl);
+	if (ieee80211_has_retry(mgmt->frame_control) &&
+	    iface->sta.last_seq_ctrl == seq_ctrl) {
+		skw_dbg("drop retry frame (seq: %d)\n", seq_ctrl);
+
+		return 0;
+	}
+
+	iface->sta.last_seq_ctrl = seq_ctrl;
+
+	skw_wdev_lock(&iface->wdev);
+
+	switch (fc) {
+	case IEEE80211_STYPE_DISASSOC:
+		mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+			  IEEE80211_STYPE_DEAUTH);
+
+		skw_fallthrough;
+
+	case IEEE80211_STYPE_DEAUTH:
+		ret = skw_sta_rx_deauth(wiphy, iface, buf, len);
+		break;
+
+	case IEEE80211_STYPE_AUTH:
+		ret = skw_sta_rx_auth(wiphy, iface, freq, signal, buf, len);
+		break;
+
+	case IEEE80211_STYPE_ASSOC_RESP:
+	case IEEE80211_STYPE_REASSOC_RESP:
+		ret = skw_sta_rx_assoc(iface, freq, signal, buf, len);
+		break;
+
+	default:
+		skw_compat_cfg80211_rx_mgmt(&iface->wdev, freq, signal, buf,
+					len, 0, GFP_ATOMIC);
+		break;
+	}
+
+	skw_wdev_unlock(&iface->wdev);
+
+	return ret;
+}
+
+static void skw_ibss_add_sta(struct skw_iface *iface, void *frame,
+					int frame_len)
+{
+	int ret;
+	struct station_parameters params;
+	struct ieee80211_mgmt *mgmt = frame;
+
+	if (!ether_addr_equal(mgmt->bssid, iface->ibss.bssid))
+		return;
+
+	if (skw_peer_ctx(iface, mgmt->sa))
+		return;
+
+	memset(&params, 0x0, sizeof(params));
+	ret = skw_add_station(iface->wdev.wiphy, iface->ndev,
+			mgmt->sa, &params);
+	if (ret < 0)
+		return;
+
+	params.sta_flags_set |= BIT(NL80211_STA_FLAG_ASSOCIATED);
+	skw_change_station(iface->wdev.wiphy, iface->ndev,
+			mgmt->sa, &params);
+}
+
+static void skw_ibss_del_sta(struct skw_iface *iface, void *frame,
+					int frame_len)
+{
+	struct skw_peer_ctx *ctx;
+	struct ieee80211_mgmt *mgmt = frame;
+	u16 reason = le16_to_cpu(mgmt->u.deauth.reason_code);
+
+	skw_dbg("iface: %d, bssid: %pM, sa: %pM, da: %pM, reason: %d\n",
+		iface->id, mgmt->bssid, mgmt->sa, mgmt->bssid, reason);
+
+	ctx = skw_peer_ctx(iface, mgmt->sa);
+	if (!ctx)
+		return;
+
+	skw_peer_ctx_transmit(ctx, false);
+	skw_peer_ctx_bind(iface, ctx, NULL);
+}
+
+static void skw_ibss_rx_mgmt(struct skw_iface *iface, void *frame,
+					int frame_len)
+{
+	u16 fc;
+	struct ieee80211_mgmt *mgmt = frame;
+
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return;
+	}
+
+	fc = SKW_MGMT_SFC(mgmt->frame_control);
+	switch (fc) {
+	case IEEE80211_STYPE_BEACON:
+	case IEEE80211_STYPE_PROBE_RESP:
+		skw_ibss_add_sta(iface, frame, frame_len);
+		break;
+
+	case IEEE80211_STYPE_DEAUTH:
+		skw_ibss_del_sta(iface, frame, frame_len);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static bool skw_sta_access_allowed(struct skw_iface *iface, u8 *mac)
+{
+	int idx;
+	struct skw_peer_ctx *ctx;
+	int nr_allowed = iface->sap.max_sta_allowed;
+	int bitmap = atomic_read(&iface->peer_map);
+
+	while (bitmap && nr_allowed) {
+		idx = ffs(bitmap) - 1;
+		SKW_CLEAR(bitmap, BIT(idx));
+
+		ctx = &iface->skw->hw.lmac[iface->lmac_id].peer_ctx[idx];
+
+		mutex_lock(&ctx->lock);
+
+		if (ctx->peer && ether_addr_equal(ctx->peer->addr, mac)) {
+			mutex_unlock(&ctx->lock);
+			break;
+		}
+
+		mutex_unlock(&ctx->lock);
+
+		nr_allowed--;
+	}
+
+	return (nr_allowed && skw_acl_allowed(iface, mac));
+}
+
+static int skw_sap_rx_mgmt(struct skw_core *skw, struct skw_iface *iface,
+		u16 fc, int freq, int signal, void *buf, int len)
+{
+	int ret;
+	struct skw_peer_ctx *ctx;
+	bool force_deauth = false;
+	struct ieee80211_mgmt *mgmt = buf;
+
+	if (fc == IEEE80211_STYPE_AUTH) {
+		if (!skw_sta_access_allowed(iface, mgmt->sa)) {
+			skw_info("deny: sta: %pM\n", mgmt->sa);
+
+			skw_cmd_del_sta(priv_to_wiphy(skw), iface->ndev,
+					mgmt->sa,
+					12, /* Deauthentication */
+					5,  /* WLAN_REASON_DISASSOC_AP_BUSY */
+					true);
+			return 0;
+		}
+
+		ctx = skw_peer_ctx(iface, mgmt->sa);
+		if (ctx) {
+			skw_peer_ctx_lock(ctx);
+
+			if (ctx->peer && ctx->peer->sm.state >= SKW_STATE_ASSOCED) {
+				ctx->peer->flags |= SKW_PEER_FLAG_DEAUTHED;
+				force_deauth = true;
+			}
+
+			skw_peer_ctx_unlock(ctx);
+		}
+
+	} else if (fc == IEEE80211_STYPE_DISASSOC) {
+		mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+						  IEEE80211_STYPE_DEAUTH);
+	}
+
+	if (iface->sap.sme_external) {
+		if (force_deauth) {
+			struct ieee80211_mgmt reply;
+
+			skw_info("force deauth with: %pM\n", mgmt->sa);
+
+			reply.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+							  IEEE80211_STYPE_DEAUTH);
+			reply.duration = 0;
+			reply.seq_ctrl = 0;
+			skw_ether_copy(reply.da, mgmt->da);
+			skw_ether_copy(reply.sa, mgmt->sa);
+			skw_ether_copy(reply.bssid, mgmt->bssid);
+
+			reply.u.deauth.reason_code = cpu_to_le16(3); // WLAN_REASON_DEAUTH_LEAVING
+
+			ret = !skw_compat_cfg80211_rx_mgmt(&iface->wdev, freq,
+						signal, (const u8 *)&reply,
+						SKW_DEAUTH_FRAME_LEN, 0, GFP_ATOMIC);
+			if (ret)
+				skw_warn("deauth with %pM failed\n", mgmt->sa);
+		}
+
+		ret = !skw_compat_cfg80211_rx_mgmt(&iface->wdev, freq, signal,
+						buf, len, 0, GFP_ATOMIC);
+	} else {
+		ret = skw_mlme_ap_rx_mgmt(iface, fc, freq, signal, buf, len);
+	}
+
+	if (ret)
+		skw_warn("frame %s rx failed\n", skw_mgmt_name(fc));
+
+	return ret;
+}
+
+static int skw_event_rx_mgmt(struct skw_core *skw, struct skw_iface *iface,
+			     void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	u16 fc;
+	int freq, signal;
+	struct skw_peer_ctx *ctx;
+	struct skw_mgmt_hdr *hdr = buf;
+
+	if (!iface || !hdr) {
+		skw_err("iface: 0x%p, buf: 0x%p\n", iface, hdr);
+		return -EINVAL;
+	}
+	skw_dump_frame((u8 *)hdr->mgmt, hdr->mgmt_len);
+	freq = ieee80211_channel_to_frequency(hdr->chan, to_nl80211_band(hdr->band));
+	signal = DBM_TO_MBM(hdr->signal);
+	fc = SKW_MGMT_SFC(hdr->mgmt->frame_control);
+
+	skw_dbg("%s(inst: %d), sa: %pM, chn: %d, band: %d, signal: %d\n",
+		skw_mgmt_name(fc), iface->id, hdr->mgmt->sa, hdr->chan,
+		hdr->band, signal);
+
+	skw_hex_dump("mgmt rx", buf, len, false);
+
+	if (fc == IEEE80211_STYPE_DEAUTH || fc == IEEE80211_STYPE_DISASSOC) {
+		skw_info("iface: %d, sa: %pM, da: %pM, %s(reason: %d)\n",
+			 iface->id, hdr->mgmt->sa, hdr->mgmt->da,
+			 skw_mgmt_name(fc), hdr->mgmt->u.deauth.reason_code);
+
+		if (time_before(cb->rx_time, iface->sta.core.auth_start)) {
+			skw_dbg("drop deauth frame:%ld before auth:%ld\n",
+				cb->rx_time, iface->sta.core.auth_start);
+			return 0;
+		}
+
+		ctx = skw_peer_ctx(iface, hdr->mgmt->sa);
+		if (ctx) {
+			skw_peer_ctx_lock(ctx);
+
+			if (ctx->peer)
+				SKW_SET(ctx->peer->flags,
+						SKW_PEER_FLAG_DEAUTHED);
+
+			skw_peer_ctx_unlock(ctx);
+		}
+	}
+
+	switch (iface->wdev.iftype) {
+	case NL80211_IFTYPE_STATION:
+		if (iface->flags & SKW_IFACE_FLAG_LEGACY_P2P_DEV) {
+			skw_compat_cfg80211_rx_mgmt(&iface->wdev, freq, signal,
+					(void *)hdr->mgmt, hdr->mgmt_len,
+					0, GFP_ATOMIC);
+
+			break;
+		}
+		skw_fallthrough;
+	case NL80211_IFTYPE_P2P_CLIENT:
+		skw_sta_rx_mgmt(skw, iface, fc, freq, signal, hdr->mgmt,
+				hdr->mgmt_len);
+		break;
+
+	case NL80211_IFTYPE_AP:
+	case NL80211_IFTYPE_P2P_GO:
+		skw_sap_rx_mgmt(skw, iface, fc, freq, signal, hdr->mgmt,
+				hdr->mgmt_len);
+		break;
+
+	case NL80211_IFTYPE_ADHOC:
+		skw_ibss_rx_mgmt(iface, hdr->mgmt, hdr->mgmt_len);
+		break;
+
+	default:
+		skw_compat_cfg80211_rx_mgmt(&iface->wdev, freq, signal,
+					(void *)hdr->mgmt, hdr->mgmt_len,
+					0, GFP_ATOMIC);
+		break;
+	}
+
+
+	return 0;
+}
+
+static int skw_event_acs_report(struct skw_core *skw, struct skw_iface *iface,
+				void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	struct skw_survey_info *sinfo = NULL;
+
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return -EINVAL;
+	}
+
+	sinfo = SKW_ZALLOC(sizeof(*sinfo), GFP_KERNEL);
+	if (!sinfo)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&sinfo->list);
+	memcpy(&sinfo->data, buf, sizeof(struct skw_survey_data));
+
+	list_add(&sinfo->list, &iface->survey_list);
+
+	return 0;
+}
+
+void skw_del_sta_event(struct skw_iface *iface, const u8 *addr, u16 reason)
+{
+	struct ieee80211_mgmt mgmt;
+
+	if (iface->wdev.iftype == NL80211_IFTYPE_STATION) {
+		cfg80211_tdls_oper_request(iface->ndev, addr,
+					   NL80211_TDLS_TEARDOWN,
+					   reason, GFP_KERNEL);
+
+		return;
+	}
+
+	if (iface->sap.sme_external) {
+		mgmt.duration = 0;
+		mgmt.seq_ctrl = 0;
+		memcpy(mgmt.da, iface->addr, ETH_ALEN);
+		memcpy(mgmt.sa, addr, ETH_ALEN);
+		memcpy(mgmt.bssid, iface->sap.cfg.bssid, ETH_ALEN);
+		mgmt.u.deauth.reason_code = cpu_to_le16(reason);
+		mgmt.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+						 IEEE80211_STYPE_DISASSOC);
+
+		skw_compat_cfg80211_rx_mgmt(&iface->wdev,
+				 iface->sap.cfg.channel->center_freq,
+				 -5400, (void *)&mgmt,
+				 SKW_DEAUTH_FRAME_LEN, 0, GFP_ATOMIC);
+	} else {
+		cfg80211_del_sta(iface->ndev, addr, GFP_KERNEL);
+	}
+}
+
+static int skw_event_del_sta(struct skw_core *skw, struct skw_iface *iface,
+			     void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	struct skw_del_sta *del_sta = buf;
+	struct skw_peer_ctx *ctx = NULL;
+
+	skw_info("mac: %pM, reason: %d\n", del_sta->mac, del_sta->reason_code);
+
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return -EINVAL;
+	}
+
+	ctx = skw_peer_ctx(iface, del_sta->mac);
+	if (!ctx) {
+		skw_err("sta: %pM not exist\n", del_sta->mac);
+		return -EINVAL;
+	}
+
+	skw_peer_ctx_lock(ctx);
+
+	skw_del_sta_event(iface, del_sta->mac, del_sta->reason_code);
+
+	skw_peer_ctx_unlock(ctx);
+
+	return 0;
+}
+
+static int skw_event_rrm_report(struct skw_core *skw, struct skw_iface *iface,
+				void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	return 0;
+}
+
+static int skw_get_bss_channel(struct ieee80211_mgmt *mgmt, int len)
+{
+	const u8 *tmp;
+	int chn = -1;
+	const u8 *ie = mgmt->u.beacon.variable;
+	size_t ielen = len - offsetof(struct ieee80211_mgmt,
+				u.probe_resp.variable);
+
+	tmp = cfg80211_find_ie(WLAN_EID_DS_PARAMS, ie, ielen);
+	if (tmp && tmp[1] == 1) {
+		chn = tmp[2];
+	} else {
+		tmp = cfg80211_find_ie(WLAN_EID_HT_OPERATION, ie, ielen);
+		if (tmp && tmp[1] >= sizeof(struct ieee80211_ht_operation)) {
+			struct ieee80211_ht_operation *htop = (void *)(tmp + 2);
+
+			chn = htop->primary_chan;
+		}
+	}
+
+	return chn;
+}
+
+static int skw_event_scan_report(struct skw_core *skw, struct skw_iface *iface,
+				 void *buf, int len, struct skw_skb_rxcb *cb)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0)
+	struct timespec64 ts;
+#endif
+	struct cfg80211_bss *bss = NULL;
+	struct ieee80211_channel *rx_channel = NULL;
+	struct skw_mgmt_hdr *hdr = buf;
+	int freq = ieee80211_channel_to_frequency(hdr->chan,
+				to_nl80211_band(hdr->band));
+	s32 signal = DBM_TO_MBM(hdr->signal);
+	bool is_beacon = ieee80211_is_beacon(hdr->mgmt->frame_control);
+
+	skw_log(SKW_SCAN, "[%s] bssid: %pM, chn: %d, signal: %d, %s\n",
+		SKW_TAG_SCAN, hdr->mgmt->sa, hdr->chan, hdr->signal,
+		is_beacon ? "beacon" : "probe resp");
+
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return -EINVAL;
+	}
+
+	skw_dump_frame((u8 *)hdr->mgmt, hdr->mgmt_len);
+
+	rx_channel = ieee80211_get_channel(iface->wdev.wiphy, freq);
+	if (!rx_channel) {
+		skw_err("invalid, freq: %d, channel: %d\n", freq, hdr->chan);
+		return -EINVAL;
+	}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0)
+	ts = ktime_to_timespec64(ktime_get_boottime());
+	hdr->mgmt->u.probe_resp.timestamp = ((u64)ts.tv_sec*1000000)
+						+ ts.tv_nsec / 1000;
+#else
+	hdr->mgmt->u.probe_resp.timestamp = ktime_get_boottime().tv64;
+	do_div(hdr->mgmt->u.probe_resp.timestamp,  1000);
+#endif
+
+	bss = cfg80211_inform_bss_frame(iface->wdev.wiphy, rx_channel,
+					hdr->mgmt, hdr->mgmt_len,
+					signal, GFP_KERNEL);
+	if (unlikely(!bss)) {
+		int bss_chn = skw_get_bss_channel(hdr->mgmt, hdr->mgmt_len);
+
+		skw_dbg("failed, bssid: %pM, chn: %d, rx chn: %d, flags: %d\n",
+			hdr->mgmt->bssid, bss_chn, hdr->chan, rx_channel->flags);
+
+		return 0;
+	}
+
+	skw->nr_scan_results++;
+
+	if (test_bit(SKW_FLAG_MBSSID_PRIV, &skw->flags) && bss) {
+
+		skw_bss_priv(bss)->bssid_index = 0;
+		skw_bss_priv(bss)->max_bssid_indicator = 0;
+
+		skw_mbssid_data_parser(iface->wdev.wiphy, is_beacon,
+			rx_channel, signal, hdr->mgmt, hdr->mgmt_len);
+	}
+
+	cfg80211_put_bss(iface->wdev.wiphy, bss);
+
+	return 0;
+}
+
+int skw_mgmt_tx_status(struct skw_iface *iface, u64 cookie,
+			   const u8 *frame, int frame_len, u16 ack)
+{
+	// fixme:
+	// check this tx status is for driver or for apps
+	switch (iface->wdev.iftype) {
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_P2P_CLIENT:
+	case NL80211_IFTYPE_P2P_DEVICE:
+		cfg80211_mgmt_tx_status(&iface->wdev, cookie,
+					frame, frame_len,
+					ack, GFP_KERNEL);
+
+		break;
+
+	case NL80211_IFTYPE_AP:
+	case NL80211_IFTYPE_P2P_GO:
+		skw_mlme_ap_tx_status(iface, cookie, frame,
+					frame_len, ack);
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int skw_event_mgmt_tx_status(struct skw_core *skw,
+				struct skw_iface *iface, void *buf,
+				int len, struct skw_skb_rxcb *cb)
+{
+	struct skw_tx_mgmt_status *status = buf;
+
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return -EINVAL;
+	}
+
+	return skw_mgmt_tx_status(iface, status->cookie, &status->mgmt,
+					status->payload_len, status->ack);
+}
+
+
+static int skw_event_ba_action(struct skw_core *skw, struct skw_iface *iface,
+			       void *data, int len, struct skw_skb_rxcb *cb)
+{
+	struct skw_peer_ctx *ctx;
+	int ret;
+	struct skw_ba_action *ba = (struct skw_ba_action *)data;
+	const u8 *action_str[] = {"ADD_TX_BA", "DEL_TX_BA",
+				  "ADD_RX_BA", "DEL_RX_BA",
+				  "REQ_RX_BA"};
+
+	skw_dbg("%s, lmac_id :%d peer: %d, tid: %d, status: %d, win start: %d, win size: %d\n",
+		action_str[ba->action], ba->lmac_id, ba->peer_idx,
+		ba->tid, ba->status_code, ba->ssn, ba->win_size);
+
+	if (!iface) {
+		skw_warn("iface is none\n");
+		return 0;
+	}
+
+	if (unlikely(iface->lmac_id != ba->lmac_id)) {
+		skw_err("diferent lmac id iface lmac id:%d ba lmac id:%d\n",
+			iface->lmac_id, ba->lmac_id);
+	}
+
+	if (ba->tid >= SKW_NR_TID ||
+	    ba->peer_idx >= SKW_MAX_PEER_SUPPORT) {
+		skw_warn("iface: 0x%p, peer idx: %d, tid: %d\n",
+			 iface, ba->peer_idx, ba->tid);
+
+		SKW_BUG_ON(1);
+
+		return 0;
+	}
+
+	ctx = &skw->hw.lmac[ba->lmac_id].peer_ctx[ba->peer_idx];
+
+	skw_peer_ctx_lock(ctx);
+
+	if (!ctx->peer)
+		goto unlock;
+
+	switch (ba->action) {
+	case SKW_ADD_TX_BA:
+		if (ba->status_code) {
+			if (++ctx->peer->txba.tx_try[ba->tid] > 5)
+				ctx->peer->txba.blacklist |= BIT(ba->tid);
+
+			SKW_CLEAR(ctx->peer->txba.bitmap, BIT(ba->tid));
+		}
+
+		break;
+
+	case SKW_DEL_TX_BA:
+		if (ba->tid != SKW_INVALID_ID) {
+			SKW_CLEAR(ctx->peer->txba.bitmap, BIT(ba->tid));
+			ctx->peer->txba.tx_try[ba->tid] = 0;
+		} else {
+			memset(&ctx->peer->txba, 0x0, sizeof(ctx->peer->txba));
+		}
+
+		break;
+
+	case SKW_REQ_RX_BA:
+		skw_update_tid_rx(ctx->peer, ba->tid, ba->ssn, ba->win_size);
+		break;
+
+	case SKW_ADD_RX_BA:
+		ret = skw_add_tid_rx(ctx->peer, ba->tid, ba->ssn, ba->win_size);
+		if (ret < 0)
+			skw_warn("add tid rx fail, ret: %d\n", ret);
+		else
+			SKW_SET(ctx->peer->rx_tid_map, BIT(ba->tid));
+
+		break;
+
+	case SKW_DEL_RX_BA:
+		skw_del_tid_rx(ctx->peer, ba->tid);
+		SKW_CLEAR(ctx->peer->rx_tid_map, BIT(ba->tid));
+		break;
+
+	default:
+		WARN_ON(1);
+		break;
+	}
+
+unlock:
+	skw_peer_ctx_unlock(ctx);
+
+	return 0;
+}
+
+static int skw_event_enter_roc(struct skw_core *skw, struct skw_iface *iface,
+				void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	struct skw_enter_roc *roc = buf;
+	struct ieee80211_channel *chan = NULL;
+	u32 freq;
+
+	skw_dbg("cookie: %llu chn: %u duration:%u\n",
+		roc->cookie, roc->chn, roc->duration);
+
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return -EINVAL;
+	}
+
+	freq = ieee80211_channel_to_frequency(roc->chn, to_nl80211_band(roc->band));
+	chan = ieee80211_get_channel(iface->wdev.wiphy, freq);
+	if (unlikely(!chan)) {
+		skw_err("can't get channel:%d\n", roc->chn);
+		return -EINVAL;
+	}
+
+	cfg80211_ready_on_channel(&iface->wdev, roc->cookie, chan,
+					  roc->duration, GFP_ATOMIC);
+
+	return 0;
+}
+
+
+static int skw_event_cancel_roc(struct skw_core *skw, struct skw_iface *iface,
+				void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	struct skw_cancel_roc *roc = buf;
+	struct ieee80211_channel *chan = NULL;
+	u32 freq;
+
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return -EINVAL;
+	}
+
+	skw_dbg("cookie: %llu chn: %u band: %u\n",
+		roc->cookie, roc->chn, roc->band);
+
+	freq = ieee80211_channel_to_frequency(roc->chn, to_nl80211_band(roc->band));
+	chan = ieee80211_get_channel(iface->wdev.wiphy, freq);
+	if (unlikely(!chan)) {
+		skw_err("can't get channel:%d\n", roc->chn);
+		return -EINVAL;
+	}
+
+	cfg80211_remain_on_channel_expired(&iface->wdev, roc->cookie,
+				chan, GFP_KERNEL);
+
+	return 0;
+}
+
+static int skw_event_tdls(struct skw_core *skw, struct skw_iface *iface,
+			  void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	unsigned int length = 0;
+	struct sk_buff *skb = NULL;
+	struct net_device *ndev = NULL;
+	int ret = 0;
+
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return -EINVAL;
+	}
+
+	length = (unsigned int) len;
+	skb = dev_alloc_skb(length);
+	if (!skb)
+		return -ENOMEM;
+
+	skb_push(skb, length);
+	memcpy(skb->data, buf, length);
+	ndev = iface->ndev;
+
+	skb->dev = ndev;
+	skb->protocol = eth_type_trans(skb, ndev);
+
+	if (!(ndev->flags & IFF_UP)) {
+		dev_kfree_skb(skb);
+		return -ENETDOWN;
+	}
+
+	ret = netif_receive_skb(skb);
+	if (ret == NET_RX_SUCCESS)
+		ndev->stats.rx_packets++;
+	else
+		ndev->stats.rx_dropped++;
+
+	return 0;
+}
+
+#if 0
+static int skw_event_credit_update(struct skw_core *skw,
+			struct skw_iface *iface, void *cred, int len)
+{
+	if (!cred && len != sizeof(u16))
+		return -EINVAL;
+
+	skw_add_credit(skw, 0, *(u16 *)cred);
+
+	return 0;
+}
+#endif
+
+static int skw_event_mic_failure(struct skw_core *skw, struct skw_iface *iface,
+				 void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	struct skw_mic_failure *mic_failure = buf;
+
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return -EINVAL;
+	}
+
+	cfg80211_michael_mic_failure(iface->ndev, mic_failure->mac,
+			 (mic_failure->is_mcbc ? NL80211_KEYTYPE_GROUP :
+			  NL80211_KEYTYPE_PAIRWISE), mic_failure->key_id,
+			NULL, GFP_KERNEL);
+
+	return 0;
+}
+
+static int skw_event_thermal_warn(struct skw_core *skw, struct skw_iface *iface,
+				  void *buf, int len, struct skw_skb_rxcb *cb)
+{
+#define SKW_FW_THERMAL_TRIP      0
+
+	u8 event = *(u8 *)buf;
+	struct skw_iface *tmp_iface = NULL;
+	int i;
+
+	/*
+	 * 0: stop transmit
+	 * 1: resume transmit
+	 */
+
+	skw_warn("active: %u\n", !event);
+
+	if (event == SKW_FW_THERMAL_TRIP)
+		set_bit(SKW_FLAG_FW_THERMAL, &skw->flags);
+	else
+		clear_bit(SKW_FLAG_FW_THERMAL, &skw->flags);
+
+	for (i = 0; i < SKW_NR_IFACE; i++) {
+		tmp_iface = skw->vif.iface[i];
+		if (!tmp_iface)
+			continue;
+
+		if (tmp_iface->wdev.iftype == NL80211_IFTYPE_P2P_DEVICE)
+			continue;
+
+		if (event == SKW_FW_THERMAL_TRIP)
+			netif_tx_stop_all_queues(tmp_iface->ndev);
+		else
+			netif_tx_start_all_queues(tmp_iface->ndev);
+	}
+
+	return 0;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
+static int skw_event_rssi_monitor(struct skw_core *skw, struct skw_iface *iface,
+				  void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	struct skw_rssi_mointor *rssi_mointor = buf;
+	struct sk_buff *skb = NULL;
+
+	if (!iface || !buf || len != sizeof(struct skw_rssi_mointor))
+		return -EINVAL;
+
+	skb = skw_compat_vendor_event_alloc(priv_to_wiphy(skw),
+			NULL, EXT_VENDOR_EVENT_BUF_SIZE + NLMSG_HDRLEN,
+			SKW_NL80211_VENDOR_SUBCMD_MONITOR_RSSI, GFP_KERNEL);
+
+	if (!skb) {
+		skw_err("Alloc skb for rssi monitor event failed\n");
+		return -ENOMEM;
+	}
+
+	if (nla_put_u32(skb, SKW_WLAN_VENDOR_ATTR_RSSI_MONITORING_REQUEST_ID,
+		rssi_mointor->req_id) ||
+		nla_put(skb, SKW_WLAN_VENDOR_ATTR_RSSI_MONITORING_CUR_BSSID,
+		ETH_ALEN, rssi_mointor->curr_bssid) ||
+		nla_put_s8(skb, SKW_WLAN_VENDOR_ATTR_RSSI_MONITORING_CUR_RSSI,
+		rssi_mointor->curr_rssi)) {
+		skw_err("nla put for rssi monitor event failed\n");
+		goto fail;
+	}
+
+	cfg80211_vendor_event(skb, GFP_KERNEL);
+
+fail:
+	kfree_skb(skb);
+	return 0;
+}
+#endif
+
+
+void skw_cqm_scan_timeout(void *data)
+{
+	struct skw_iface *iface = data;
+
+	skw_dbg(" enter\n");
+	if (unlikely(!iface)) {
+		skw_warn("iface is NULL\n");
+		return;
+	}
+
+	spin_lock_bh(&iface->sta.roam_data.lock);
+	iface->sta.roam_data.flags &= ~SKW_IFACE_STA_ROAM_FLAG_CQM_LOW;
+	spin_unlock_bh(&iface->sta.roam_data.lock);
+}
+
+static int skw_event_cqm(struct skw_core *skw, struct skw_iface *iface,
+			 void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	struct skw_cqm_info *cqm_info = buf;
+
+	skw_dbg("cqm_status:%d cqm_rssi:%d chan:%d band:%d %pM\n",
+		cqm_info->cqm_status, cqm_info->cqm_rssi,
+		cqm_info->chan, cqm_info->band, cqm_info->bssid);
+
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return -EINVAL;
+	}
+
+	switch (cqm_info->cqm_status) {
+	case CQM_STATUS_RSSI_LOW:
+		if (iface->sta.sme_external) {
+			if (is_valid_ether_addr(cqm_info->bssid)) {
+				spin_lock_bh(&iface->sta.roam_data.lock);
+				if (!(iface->sta.roam_data.flags & SKW_IFACE_STA_ROAM_FLAG_CQM_LOW)) {
+					skw_dbg("recv cqm low event bssid:%pM\n", cqm_info->bssid);
+					memcpy(iface->sta.roam_data.target_bssid, cqm_info->bssid, ETH_ALEN);
+					iface->sta.roam_data.target_chn = cqm_info->chan;
+					skw_add_timer_work(skw, "cqm_scan_timeout", skw_cqm_scan_timeout,
+							iface, SKW_CQM_SCAN_TIMEOUT,
+							skw_cqm_scan_timeout, GFP_KERNEL);
+					iface->sta.roam_data.flags |= SKW_IFACE_STA_ROAM_FLAG_CQM_LOW;
+				}
+				spin_unlock_bh(&iface->sta.roam_data.lock);
+			}
+
+			skw_compat_cqm_rssi_notify(iface->ndev,
+					NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW,
+					cqm_info->cqm_rssi, GFP_KERNEL);
+
+		} else {
+			skw_roam_connect(iface, cqm_info->bssid, cqm_info->chan,
+					 to_nl80211_band(cqm_info->band));
+		}
+
+		break;
+
+	case CQM_STATUS_RSSI_HIGH:
+		if (iface->sta.sme_external) {
+			skw_compat_cqm_rssi_notify(iface->ndev,
+					NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH,
+					cqm_info->cqm_rssi, GFP_KERNEL);
+		}
+
+		break;
+
+	case CQM_STATUS_BEACON_LOSS:
+		if (is_valid_ether_addr(cqm_info->bssid)) {
+			// FW use beacon loss event to trigger roaming
+			if (iface->sta.sme_external) {
+				skw_dbg("beacon loss trigger roaming");
+				skw_compat_cqm_rssi_notify(iface->ndev,
+					NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW,
+					cqm_info->cqm_rssi, GFP_KERNEL);
+			} else
+				skw_roam_connect(iface, cqm_info->bssid, cqm_info->chan,
+						 to_nl80211_band(cqm_info->band));
+		} else {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+			cfg80211_cqm_beacon_loss_notify(iface->ndev, GFP_KERNEL);
+#endif
+		}
+
+		break;
+
+	case CQM_STATUS_TDLS_LOSS:
+		cfg80211_cqm_pktloss_notify(iface->ndev, cqm_info->bssid,
+					    0, GFP_KERNEL);
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int skw_event_rx_unprotect_frame(struct skw_core *skw,
+				struct skw_iface *iface, void *buf,
+				int len, struct skw_skb_rxcb *cb)
+{
+	struct ieee80211_hdr *hdr = buf;
+
+	skw_dump_frame((u8 *)buf, len);
+
+	skw_dbg("frame control: %02x, len: %d\n",
+		SKW_MGMT_SFC(hdr->frame_control), len);
+
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return -EINVAL;
+	}
+
+	if (ieee80211_is_data(hdr->frame_control)) {
+		struct sk_buff *skb;
+		struct ethhdr *eth_hdr = NULL;
+
+		skb = dev_alloc_skb(len);
+		if (!skb) {
+			skw_warn("alloc skb failed, length: %d\n", len);
+			return 0;
+		}
+
+		skw_put_skb_data(skb, buf, len);
+
+		if (ieee80211_data_to_8023(skb, iface->addr, iface->wdev.iftype)) {
+			skw_warn("build 8023 failed\n");
+
+			dev_kfree_skb_any(skb);
+
+			return 0;
+		}
+
+		eth_hdr = (struct ethhdr *)skb->data;
+
+		if (htons(SKW_ETH_P_WAPI) == eth_hdr->h_proto) {
+			skb->dev = iface->ndev;
+			skb->protocol = eth_type_trans(skb, iface->ndev);
+			skb->csum = 0;
+			skb->ip_summed = CHECKSUM_NONE;
+
+			if (netif_receive_skb(skb) == NET_RX_SUCCESS) {
+				iface->ndev->stats.rx_packets++;
+				iface->ndev->stats.rx_bytes += skb->len;
+			} else {
+				iface->ndev->stats.rx_dropped++;
+			}
+
+		} else {
+			skw_warn("data frame, sa: %pM\n", eth_hdr->h_source);
+
+			dev_kfree_skb_any(skb);
+		}
+	} else if (ieee80211_is_mgmt(hdr->frame_control)) {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
+		cfg80211_rx_unprot_mlme_mgmt(iface->ndev, buf, len);
+#else
+		if (ieee80211_is_deauth(hdr->frame_control))
+			cfg80211_send_unprot_deauth(iface->ndev, buf, len);
+		else
+			cfg80211_send_unprot_disassoc(iface->ndev, buf, len);
+#endif
+	} else {
+		skw_err("Unsupported frames\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int skw_chbw_to_cfg80211_chan_def(struct wiphy *wiphy,
+					 struct cfg80211_chan_def *chdef,
+					 struct skw_event_csa_param *csa)
+{
+	int freq;
+	struct ieee80211_channel *chan = NULL;
+	enum nl80211_band band = to_nl80211_band(csa->band);
+
+	skw_dbg("chn: %d, band: %d, bw: %d, center1: %d, center2: %d, bss type: 0x%x\n",
+		csa->chan, csa->band, csa->band_width, csa->center_chan1,
+		csa->center_chan2, csa->bss_type);
+
+	memset(chdef, 0, sizeof(struct cfg80211_chan_def));
+
+	freq = ieee80211_channel_to_frequency(csa->chan, band);
+	if (!freq) {
+		skw_err("invalid channel: %d\n", csa->chan);
+		return -EINVAL;
+	}
+
+	chan = ieee80211_get_channel(wiphy, freq);
+	if (!chan || chan->flags & IEEE80211_CHAN_DISABLED) {
+		skw_err("invalid freq: %d\n", freq);
+		return -EINVAL;
+	}
+
+	chdef->chan = chan;
+	chdef->center_freq1 = ieee80211_channel_to_frequency(csa->center_chan1, band);
+	chdef->center_freq2 = 0;
+
+	switch (csa->band_width) {
+	case SKW_CHAN_WIDTH_20:
+		if (csa->bss_type & SKW_CAPA_HT)
+			chdef->width = NL80211_CHAN_WIDTH_20;
+		else
+			chdef->width = NL80211_CHAN_WIDTH_20_NOHT;
+		break;
+
+	case SKW_CHAN_WIDTH_40:
+		chdef->width = NL80211_CHAN_WIDTH_40;
+		break;
+
+	case SKW_CHAN_WIDTH_80:
+		chdef->width = NL80211_CHAN_WIDTH_80;
+		break;
+
+	case SKW_CHAN_WIDTH_80P80:
+		chdef->width = NL80211_CHAN_WIDTH_80P80;
+		chdef->center_freq2 = ieee80211_channel_to_frequency(csa->center_chan2, band);
+		break;
+
+	case SKW_CHAN_WIDTH_160:
+		chdef->width = NL80211_CHAN_WIDTH_160;
+		break;
+
+	default:
+		skw_err("invalid band width: %d\n", csa->band_width);
+		return -EINVAL;
+	}
+
+	if (!cfg80211_chandef_valid(chdef)) {
+		skw_err("chandef invalid\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int skw_event_chan_switch(struct skw_core *skw, struct skw_iface *iface,
+				 void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	struct skw_event_csa_param *csa_param = buf;
+	struct cfg80211_chan_def chan_def;
+
+	skw_dbg("mode: %d\n", csa_param->mode);
+
+	if (unlikely(!iface)) {
+		skw_err("iface invalid\n");
+		return -EINVAL;
+	}
+
+	skw_hex_dump("csa_event", csa_param, sizeof(*csa_param), false);
+
+	if (!skw_chbw_to_cfg80211_chan_def(iface->wdev.wiphy, &chan_def, csa_param))
+		skw_dbg("mode:%d, switch to chan: %d\n", csa_param->mode, csa_param->chan);
+	else {
+		skw_err("failed convert  to cfg80211_chan_def\n");
+		return -EINVAL;
+	}
+
+	if (csa_param->mode == SKW_CSA_START) {
+		netif_carrier_off(iface->ndev);
+
+		skw_ch_switch_started_notify(iface->ndev, &chan_def, 10, true);
+	} else {
+		netif_carrier_on(iface->ndev);
+
+		skw_ch_switch_notify(iface->ndev, &chan_def);
+
+		if (is_skw_sta_mode(iface)) {
+			iface->sta.core.bss.channel = chan_def.chan;
+			iface->sta.core.bss.width = csa_param->band_width;
+		} else {
+			iface->sap.cfg.channel = chan_def.chan;
+			iface->sap.cfg.width = csa_param->band_width;
+		}
+	}
+
+	return 0;
+}
+
+static int skw_event_tx_frame(struct skw_core *skw, struct skw_iface *iface,
+			      void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	u16 fc;
+	u8 *ie;
+	int ie_len;
+	struct skw_frame_tx_status *tx = buf;
+	struct skw_sta_core *core;
+	const u8 *ht_cap_ie;
+
+	skw_dump_frame((u8 *)buf, (u16)len);
+
+	if (unlikely(!iface)) {
+		skw_warn("iface invalid\n");
+		return -EINVAL;
+	}
+
+	if (iface->wdev.iftype != NL80211_IFTYPE_STATION &&
+	    iface->wdev.iftype != NL80211_IFTYPE_P2P_CLIENT)
+		return 0;
+
+	skw_hex_dump("tx frame", buf, len, false);
+
+	fc = SKW_MGMT_SFC(tx->mgmt->frame_control);
+
+	skw_dbg("iface: %d, fc: 0x%x, len: %d\n", iface->id, fc, len);
+
+	if (fc == IEEE80211_STYPE_ASSOC_REQ ||
+	    fc == IEEE80211_STYPE_REASSOC_REQ) {
+		if (fc == IEEE80211_STYPE_ASSOC_REQ) {
+			ie = tx->mgmt->u.assoc_req.variable;
+			ie_len = tx->mgmt_len - offsetof(struct ieee80211_mgmt,
+				u.assoc_req.variable);
+		} else {
+			ie = tx->mgmt->u.reassoc_req.variable;
+			ie_len = tx->mgmt_len - offsetof(struct ieee80211_mgmt,
+				u.reassoc_req.variable);
+		}
+
+		skw_wdev_lock(&iface->wdev);
+		core = &iface->sta.core;
+
+		if (ie_len <= SKW_2K_SIZE) {
+			memcpy(core->assoc_req_ie, ie, ie_len);
+			core->assoc_req_ie_len = ie_len;
+		}
+
+		skw_wdev_unlock(&iface->wdev);
+
+		if (is_skw_sta_mode(iface)) {
+			ht_cap_ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, core->assoc_req_ie, core->assoc_req_ie_len);
+
+			if (ht_cap_ie)
+				skw_dbg("ht_cap: %2x  %2x  %2x  %2x\n", ht_cap_ie[0], ht_cap_ie[1], ht_cap_ie[2], ht_cap_ie[3]);
+			if (ht_cap_ie && (ht_cap_ie[2] & 0x2))
+				iface->sta.core.bss.ht_cap_chwidth = SKW_CHAN_WIDTH_40;
+			else if (ht_cap_ie && ht_cap_ie[2])
+				iface->sta.core.bss.ht_cap_chwidth = SKW_CHAN_WIDTH_20;
+		}
+	}
+
+	return 0;
+}
+
+static int skw_event_dpd_coeff_result(struct skw_core *skw,
+			struct skw_iface *iface, void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	return 0;
+
+}
+
+static int skw_event_dpd_gear_cmpl(struct skw_core *skw,
+			struct skw_iface *iface, void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	return 0;
+}
+
+static int skw_event_fw_recovery(struct skw_core *skw,
+			struct skw_iface *iface, void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	u8 done = *(u8 *)buf;
+
+	skw_dbg("done: %d\n", done);
+
+	/* Frimware start recovery */
+	if (done)
+		clear_bit(SKW_FLAG_FW_MAC_RECOVERY, &skw->flags);
+	else
+		set_bit(SKW_FLAG_FW_MAC_RECOVERY, &skw->flags);
+
+	return 0;
+}
+
+static int skw_event_mp_mode_handler(struct skw_core *skw,
+			struct skw_iface *iface, void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	u8 state = *(u8 *)buf;
+
+	skw_dbg("state: %d\n", state);
+
+	if (state) {
+		set_bit(SKW_FLAG_MP_MODE, &skw->flags);
+		skw_abort_cmd(skw);
+
+	} else {
+		clear_bit(SKW_FLAG_MP_MODE, &skw->flags);
+	}
+
+	return 0;
+}
+
+static int skw_event_dfs_radar_pulse(struct skw_core *skw,
+			struct skw_iface *iface, void *buff, int len, struct skw_skb_rxcb *cb)
+{
+	int i;
+	struct skw_pulse_info info;
+	struct skw_radar_pulse *radar = buff;
+
+#define SKW_RADAR_RSSI(r) ((r) < 0x10 ? (r) - 60 : (r) - 92)
+#define SKW_RADAR_TS(t)   ((jiffies_to_usecs(jiffies) & (~0xffffff)) | t)
+
+	if (!skw->dfs.info || !skw->dfs.fw_enabled || !skw->dfs.flags)
+		return 0;
+
+	for (i = 0; i < radar->nr_pulse; i++) {
+		info.chirp = radar->data[i].chirp;
+		info.width = radar->data[i].width >> 1;
+		info.rssi = SKW_RADAR_RSSI(radar->data[i].rssi);
+		info.ts = SKW_RADAR_TS(radar->data[i].ts);
+
+		skw_log(SKW_DFS, "[SKWIFI DFS] %s: [%d] inst: %d, width: %d, rssi: %d, chirp: %d, ts: 0x%llx\n",
+			__func__, i, iface->id, info.width, info.rssi, info.chirp, info.ts);
+
+		if (!info.width)
+			continue;
+
+		skw_dfs_add_pulse(priv_to_wiphy(skw), iface->ndev, &info);
+	}
+
+	return 0;
+}
+
+static int skw_event_dpd_result_handler(struct skw_core *skw,
+		struct skw_iface *iface, void *buf, int len, struct skw_skb_rxcb *cb)
+{
+	skw_dpd_result_handler(skw, buf, len);
+
+	return 0;
+}
+
+static int skw_local_ap_auth_timeout(struct skw_core *skw,
+			struct skw_iface *iface, void *buf,
+			int len, struct skw_skb_rxcb *cb)
+{
+	struct skw_client *client = buf;
+
+	skw_warn("client: %pM\n", client->addr);
+
+	skw_mlme_ap_del_sta(priv_to_wiphy(skw), iface->ndev,
+					client->addr, false);
+
+	return 0;
+}
+
+static int skw_local_ibss_connect(struct skw_core *skw,
+			struct skw_iface *iface, void *buf,
+			int len, struct skw_skb_rxcb *cb)
+{
+	u16 chn;
+	int ret = 0;
+	struct skw_ibss_params params;
+
+	memcpy(params.ssid, iface->ibss.ssid, iface->ibss.ssid_len);
+	params.ssid_len = iface->ibss.ssid_len;
+
+	memcpy(params.bssid, iface->ibss.bssid, ETH_ALEN);
+
+	params.type = 0;
+	params.chan = iface->ibss.channel;
+	params.band = iface->ibss.band;
+	params.bw = iface->ibss.bw;
+	params.beacon_int = iface->ibss.beacon_int;
+
+	chn = skw_freq_to_chn(iface->ibss.center_freq1);
+	params.center_chan1 = chn;
+
+	chn = skw_freq_to_chn(iface->ibss.center_freq2);
+	params.center_chan2 = chn;
+
+	ret = skw_send_msg(iface->wdev.wiphy, iface->ndev, SKW_CMD_IBSS_JOIN,
+			&params, sizeof(params), NULL, 0);
+	if (!ret) {
+		netif_carrier_on(iface->ndev);
+
+		cfg80211_ibss_joined(iface->ndev, iface->ibss.bssid,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0)
+				iface->ibss.chandef.chan,
+#endif
+				GFP_KERNEL);
+	} else {
+		skw_err("failed, ret: %d, ssid: %s, bssid: %pM\n",
+			ret, iface->ibss.ssid, iface->ibss.bssid);
+	}
+
+	return ret;
+}
+
+static int skw_local_sta_connect(struct skw_core *skw,
+			struct skw_iface *iface, void *buf,
+			int len, struct skw_skb_rxcb *cb)
+{
+	int ret = 0;
+	struct cfg80211_bss *bss;
+	struct wiphy *wiphy = iface->wdev.wiphy;
+	struct skw_connect_param *conn = iface->sta.conn;
+
+	if (!iface->sta.conn)
+		return 0;
+
+	bss = cfg80211_get_bss(wiphy, conn->channel, conn->bssid,
+			conn->ssid, conn->ssid_len,
+			SKW_BSS_TYPE_ESS, SKW_PRIVACY_ESS_ANY);
+
+	if (conn->auth_type == NL80211_AUTHTYPE_SAE)
+		ret = skw_connect_sae_auth(wiphy, iface->ndev, bss);
+	else
+		ret = skw_connect_auth(wiphy, iface->ndev, conn, bss);
+
+	cfg80211_put_bss(wiphy, bss);
+
+	return ret;
+}
+
+#define FUNC_INIT(e, f)         \
+	[e] = {                 \
+		.id = e,        \
+		.name = #e,     \
+		.func = f       \
+	}
+
+static const struct skw_event_func g_event_fn[] = {
+	FUNC_INIT(SKW_EVENT_NORMAL_SCAN_CMPL, skw_event_scan_complete),
+	FUNC_INIT(SKW_EVENT_SCHED_SCAN_CMPL, skw_event_sched_scan_done),
+	FUNC_INIT(SKW_EVENT_DISCONNECT, skw_event_disconnect),
+	FUNC_INIT(SKW_EVNET_RX_MGMT, skw_event_rx_mgmt),
+	FUNC_INIT(SKW_EVENT_ACS_REPORT, skw_event_acs_report),
+	FUNC_INIT(SKW_EVENT_DEL_STA, skw_event_del_sta),
+	FUNC_INIT(SKW_EVENT_RRM_REPORT, skw_event_rrm_report),
+	FUNC_INIT(SKW_EVENT_SCAN_REPORT, skw_event_scan_report),
+	FUNC_INIT(SKW_EVENT_MGMT_TX_STATUS, skw_event_mgmt_tx_status),
+	FUNC_INIT(SKW_EVENT_BA_ACTION, skw_event_ba_action),
+	FUNC_INIT(SKW_EVENT_ENTER_ROC, skw_event_enter_roc),
+	FUNC_INIT(SKW_EVENT_CANCEL_ROC, skw_event_cancel_roc),
+	FUNC_INIT(SKW_EVENT_TDLS, skw_event_tdls),
+	// FUNC_INIT(SKW_EVENT_CREDIT_UPDATE, skw_event_credit_update),
+	FUNC_INIT(SKW_EVENT_MIC_FAILURE, skw_event_mic_failure),
+	FUNC_INIT(SKW_EVENT_THERMAL_WARN, skw_event_thermal_warn),
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
+	FUNC_INIT(SKW_EVENT_RSSI_MONITOR, skw_event_rssi_monitor),
+#endif
+	FUNC_INIT(SKW_EVENT_CQM, skw_event_cqm),
+	FUNC_INIT(SKW_EVENT_RX_UNPROTECT_FRAME, skw_event_rx_unprotect_frame),
+	FUNC_INIT(SKW_EVENT_CHAN_SWITCH, skw_event_chan_switch),
+	FUNC_INIT(SKW_EVENT_TX_FRAME, skw_event_tx_frame),
+	FUNC_INIT(SKW_EVENT_DPD_ILC_COEFF_REPORT, skw_event_dpd_coeff_result),
+	FUNC_INIT(SKW_EVENT_DPD_ILC_GEAR_CMPL, skw_event_dpd_gear_cmpl),
+	FUNC_INIT(SKW_EVENT_FW_RECOVERY, skw_event_fw_recovery),
+	FUNC_INIT(SKW_EVENT_NPI_MP_MODE, skw_event_mp_mode_handler),
+	FUNC_INIT(SKW_EVENT_RADAR_PULSE, skw_event_dfs_radar_pulse),
+	FUNC_INIT(SKW_EVENT_DPD_RESULT, skw_event_dpd_result_handler),
+	FUNC_INIT(SKW_EVENT_MAX, NULL),
+};
+
+static const struct skw_event_func g_local_event_fn[] = {
+	// FUNC_INIT(SKW_EVENT_LOCAL_STA_AUTH_ASSOC_TIMEOUT, skw_local_sta_auth_assoc_timeout),
+	FUNC_INIT(SKW_EVENT_LOCAL_AP_AUTH_TIMEOUT, skw_local_ap_auth_timeout),
+	FUNC_INIT(SKW_EVENT_LOCAL_STA_CONNECT, skw_local_sta_connect),
+	FUNC_INIT(SKW_EVENT_LOCAL_IBSS_CONNECT, skw_local_ibss_connect),
+	FUNC_INIT(SKW_EVENT_LOCAL_MAX, NULL),
+};
+
+#undef FUNC_INIT
+
+static inline void skw_cmd_lock(struct skw_core *skw, unsigned long flags)
+{
+	down(&skw->cmd.lock);
+
+	if (test_bit(SKW_CMD_FLAG_NO_WAKELOCK, &flags) ||
+	    test_bit(SKW_FLAG_SUSPEND_PREPARE, &skw->flags))
+		return;
+
+	__pm_stay_awake(skw->cmd.ws);
+}
+
+static inline void skw_cmd_unlock(struct skw_core *skw, unsigned long flags)
+{
+	if (!(flags & BIT(SKW_CMD_FLAG_NO_WAKELOCK)))
+		__pm_relax(skw->cmd.ws);
+
+	up(&skw->cmd.lock);
+}
+
+static bool skw_cmd_allowed(struct skw_core *skw, int inst, int cmd, unsigned long extra_flags)
+{
+	struct skw_iface *iface;
+	unsigned long flags = READ_ONCE(skw->flags);
+	bool ret;
+
+	if (inst < 0) {
+		skw_warn("invalid inst: %d\n", inst);
+		SKW_BUG_ON(1);
+
+		return false;
+	}
+
+	if (extra_flags & BIT(SKW_CMD_FLAG_IGNORE_BLOCK_TX))
+		clear_bit(SKW_FLAG_BLOCK_TX, &flags);
+
+	if (!skw_cmd_tx_allowed(flags)) {
+		skw_warn("skw->flags: 0x%lx, extra_flags: 0x%lx\n",
+			 skw->flags, extra_flags);
+
+		return false;
+	}
+
+	iface = to_skw_iface(skw, inst);
+	if (iface && iface->ndev &&
+	    iface->ndev->ieee80211_ptr->iftype == NL80211_IFTYPE_MONITOR) {
+		if (cmd == SKW_CMD_SET_MONITOR_PARAM ||
+		    cmd == SKW_CMD_CLOSE_DEV ||
+		    cmd == SKW_CMD_OPEN_DEV)
+			return true;
+
+		skw_warn("monitor cmd not allowed: %d\n", cmd);
+		return false;
+	}
+
+	if (cmd == SKW_CMD_GET_INFO ||
+		cmd == SKW_CMD_SYN_VERSION ||
+		cmd == SKW_CMD_OPEN_DEV ||
+		cmd == SKW_CMD_PHY_BB_CFG ||
+		cmd == SKW_CMD_SET_REGD ||
+		cmd == SKW_CMD_SET_MIB ||
+		cmd == SKW_CMD_DPD_ILC_GEAR_PARAM ||
+		cmd == SKW_CMD_DPD_ILC_MARTIX_PARAM)
+		return true;
+
+	ret = (likely(iface && (iface->flags & SKW_IFACE_FLAG_OPENED)));
+	if (ret != true) {
+		if (iface)
+			skw_warn("iface flags:%x, cmd:%d\n", iface->flags, cmd);
+		else
+			skw_warn("iface NULL, cmd:%d\n", cmd);
+	}
+
+	return ret;
+}
+
+
+static bool skw_cmd_len_in_limit(struct skw_core *skw, int total_len)
+{
+	return !((skw->hw.bus == SKW_BUS_USB && total_len > SKW_USB_CMD_MAX_LEN) ||
+		(skw->hw.bus == SKW_BUS_SDIO && total_len > SKW_SDIO_CMD_MAX_LEN) ||
+		(skw->hw.bus == SKW_BUS_PCIE && total_len > SKW_PCIE_CMD_MAX_LEN));
+}
+
+/* data_len not include struct skw_msg */
+bool skw_cmd_data_len_in_limit(struct skw_core *skw, int data_len)
+{
+	int total_len;
+	bool ret;
+
+	total_len = data_len + sizeof(struct skw_msg);
+
+	if (skw_need_extra_hdr(skw)) {
+		if (skw->hw.bus == SKW_BUS_USB)
+			total_len = total_len + skw->hw.extra.hdr_len;
+		else
+			total_len = round_up(total_len + skw->hw.extra.hdr_len,
+				skw->hw.align);
+	}
+
+	ret = skw_cmd_len_in_limit(skw, total_len);
+
+	if (!ret)
+		skw_warn("data_len: %d, total_len: %d\n", data_len, total_len);
+
+	return ret;
+}
+
+static int skw_set_cmd(struct skw_core *skw, int dev_id, int cmd,
+		       void *data, int data_len, void *arg,
+		       int arg_size, char *name, u64 start,
+		       unsigned long extra_flags)
+{
+	struct skw_msg *msg_hdr;
+	int total_len, msg_len;
+	void *pos;
+
+	// lockdep_assert_held(&skw->cmd.lock.lock);
+	pos = skw->cmd.data;
+	total_len = msg_len = data_len + sizeof(*msg_hdr);
+
+	if (skw_need_extra_hdr(skw)) {
+		if (skw->hw.bus == SKW_BUS_USB)
+			total_len = total_len + skw->hw.extra.hdr_len;
+		else
+			total_len = round_up(total_len + skw->hw.extra.hdr_len,
+				skw->hw.align);
+
+		skw_set_extra_hdr(skw, pos, skw->hw.cmd_port, total_len, 0, 0);
+
+		pos += skw->hw.extra.hdr_len;
+	}
+
+	if (!skw_cmd_len_in_limit(skw, total_len)) {
+		skw_warn("total_len: %d\n", total_len);
+		SKW_BUG_ON(1);
+
+		return -E2BIG;
+	}
+
+	skw->cmd.id = cmd;
+	skw->cmd.name = name;
+	skw->cmd.seq++;
+	skw->cmd.start_time = jiffies;
+	skw->cmd.arg = arg;
+	skw->cmd.arg_size = arg_size;
+	skw->cmd.status = 0;
+	skw->cmd.data_len = total_len;
+	WRITE_ONCE(skw->cmd.flags, extra_flags);
+
+	msg_hdr = pos;
+	msg_hdr->inst_id = dev_id;
+	msg_hdr->type = SKW_MSG_CMD;
+	msg_hdr->id = cmd;
+	msg_hdr->total_len = msg_len;
+	msg_hdr->seq = skw->cmd.seq;
+
+	pos += sizeof(*msg_hdr);
+	if (data_len)
+		memcpy(pos, data, data_len);
+
+	skw->dbg.cmd_idx = (skw->dbg.cmd_idx + 1) % skw->dbg.nr_cmd;
+	skw->dbg.cmd[skw->dbg.cmd_idx].trigger = start;
+	skw->dbg.cmd[skw->dbg.cmd_idx].build = skw_local_clock();
+	skw->dbg.cmd[skw->dbg.cmd_idx].id = cmd;
+	skw->dbg.cmd[skw->dbg.cmd_idx].seq = skw->cmd.seq;
+	skw->dbg.cmd[skw->dbg.cmd_idx].flags = skw->cmd.flags;
+	skw->dbg.cmd[skw->dbg.cmd_idx].xmit = 0;
+	skw->dbg.cmd[skw->dbg.cmd_idx].ack = 0;
+	skw->dbg.cmd[skw->dbg.cmd_idx].assert = 0;
+	skw->dbg.cmd[skw->dbg.cmd_idx].loop = 0;
+	atomic_set(&skw->dbg.loop, 0);
+
+	skw_log(SKW_CMD, "[%s] TX %s[%d], iface: %d, seq: %d, flags: 0x%lx,len = %d\n",
+		SKW_TAG_CMD, name, cmd, dev_id, skw->cmd.seq, skw->cmd.flags, data_len);
+	skw_hex_dump("cmd data", skw->cmd.data, skw->cmd.data_len, false);
+
+	return 0;
+}
+
+static void skw_cmd_timeout_fn(void *data)
+{
+	struct skw_core *skw = data;
+
+	skw_err("<%s> %s[%d], seq: %d, flags: 0x%lx, timeout:%d(ms)\n",
+		skw_bus_name(skw->hw.bus), skw->cmd.name, skw->cmd.id,
+		skw->cmd.seq, skw->flags, jiffies_to_msecs(SKW_CMD_TIMEOUT));
+
+	set_bit(SKW_FLAG_BLOCK_TX, &skw->flags);
+	skw->dbg.cmd[skw->dbg.cmd_idx].assert = skw_local_clock();
+
+	if (!skw->dbg.cmd[skw->dbg.cmd_idx].loop)
+		skw->dbg.cmd[skw->dbg.cmd_idx].loop = atomic_read(&skw->dbg.loop);
+
+	skw_cmd_unlock(skw, 0);
+
+	skw_dbg_dump(skw);
+	skw_assert_schedule(priv_to_wiphy(skw));
+}
+
+static void skw_msg_try_send_cb(struct skw_core *skw)
+{
+	skw_unlock_schedule(priv_to_wiphy(skw));
+	// skw_del_timer_work(skw, skw->cmd.data);
+	// skw_cmd_unlock(skw);
+}
+
+int skw_msg_try_send(struct skw_core *skw, int inst, int cmd, void *data,
+		     int data_len, void *arg, int arg_size, char *name)
+{
+	int ret;
+
+	if (down_trylock(&skw->cmd.lock))
+		return -EBUSY;
+
+	__pm_stay_awake(skw->cmd.ws);
+
+	if (!skw_cmd_allowed(skw, inst, cmd, 0)) {
+		skw_cmd_unlock(skw, 0);
+		return -EIO;
+	}
+
+	ret = skw_set_cmd(skw, inst, cmd, data, data_len,
+			  arg, arg_size, name,
+			  skw_local_clock(), 0);
+	if (ret) {
+		skw_cmd_unlock(skw, 0);
+		return ret;
+	}
+
+	skw_add_timer_work(skw, name, skw_cmd_timeout_fn, skw, SKW_CMD_TIMEOUT,
+			   skw->cmd.data, GFP_ATOMIC);
+
+	skw->cmd.callback = skw_msg_try_send_cb;
+
+	set_bit(SKW_CMD_FLAG_XMIT, &skw->cmd.flags);
+	skw_wakeup_tx(skw, 0);
+
+	return 0;
+}
+
+static void skw_msg_xmit_timeout_cb(struct skw_core *skw)
+{
+	set_bit(SKW_CMD_FLAG_DONE, &skw->cmd.flags);
+
+	smp_mb();
+
+	wake_up(&skw->cmd.wq);
+}
+
+/* SDIO BUS
+ *             +--------------------- MSG_HDR->total_len ---------------------+
+ *             |                                                              |
+ * +-----------+----------+------------+------------+-----------+-------------+
+ * | EXTRA_HDR |  MSG_HDR | IE_OFFSET  |  PARAM ... |   IE ...  |  OTHERS ... |
+ * +-----------+----------+------------+------------+-----------+-------------+
+ *                        |                         |
+ *                        +-------- IE_OFFSET ------+
+ */
+static int skw_cmd_xmit_timeout(struct wiphy *wiphy, int dev_id, int cmd,
+			 void *buf, int buf_len, void *arg, int arg_size,
+			 char *name, unsigned long timeout, u64 start,
+			 unsigned long extra_flags)
+{
+	int ret;
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	ret = skw_set_cmd(skw, dev_id, cmd, buf, buf_len,
+			  arg, arg_size, name, start, extra_flags);
+	if (ret)
+		return ret;
+
+	skw->cmd.callback = skw_msg_xmit_timeout_cb;
+	set_bit(SKW_CMD_FLAG_XMIT, &skw->cmd.flags);
+
+	skw_wakeup_tx(skw, 0);
+
+	ret = wait_event_timeout(skw->cmd.wq,
+			test_bit(SKW_CMD_FLAG_DONE, &skw->cmd.flags),
+			msecs_to_jiffies(100));
+	if (ret)
+		goto out;
+
+	if (test_bit(SKW_CMD_FLAG_XMIT, &skw->cmd.flags))
+		skw_wakeup_tx(skw, 0);
+
+	skw_dbg("skw_hw_request_ack, cmd: %s(%d)\n", name, cmd);
+	skw_hw_request_ack(skw);
+
+	ret = wait_event_timeout(skw->cmd.wq,
+			test_bit(SKW_CMD_FLAG_DONE, &skw->cmd.flags), timeout);
+	if (unlikely(!ret)) {
+		skw_err("<%s> %s[%d], seq: %d, flags: 0x%lx, timeout:%d(ms)\n",
+			skw_bus_name(skw->hw.bus), name, cmd, skw->cmd.seq,
+			skw->flags, jiffies_to_msecs(timeout));
+
+		skw->dbg.cmd[skw->dbg.cmd_idx].assert = skw_local_clock();
+
+		if (!skw->dbg.cmd[skw->dbg.cmd_idx].loop)
+			skw->dbg.cmd[skw->dbg.cmd_idx].loop = atomic_read(&skw->dbg.loop);
+
+		set_bit(SKW_CMD_FLAG_ACKED, &skw->cmd.flags);
+		skw_hw_assert(skw, true);
+
+		return -ETIMEDOUT;
+	}
+
+out:
+	return ret > 0 ? 0 - skw->cmd.status : ret;
+}
+
+int skw_msg_xmit_timeout(struct wiphy *wiphy, int dev_id, int cmd,
+			 void *buf, int buf_len, void *arg, int arg_size,
+			 char *name, unsigned long timeout,
+			 unsigned long extra_flags)
+{
+	int ret;
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	if (!skw_cmd_allowed(skw, dev_id, cmd, extra_flags))
+		return -EIO;
+
+	BUG_ON(in_interrupt());
+
+	skw_cmd_lock(skw, extra_flags);
+
+	ret = skw_cmd_xmit_timeout(wiphy, dev_id, cmd, buf, buf_len,
+				arg, arg_size, name, timeout,
+				skw_local_clock(), extra_flags);
+
+	skw_cmd_unlock(skw, extra_flags);
+
+	return ret;
+}
+
+/*
+ *        +--------------+-----------------+------------------+
+ *        |   msg_hdr    |  status_code    |     payload      |
+ *        +--------------+-----------------+------------------+
+ * octets:        8               2              variable
+ *
+ */
+int skw_cmd_ack_handler(struct skw_core *skw, void *data, int data_len)
+{
+	struct skw_msg *msg_ack = data;
+
+	if (msg_ack->id != skw->cmd.id || msg_ack->seq != skw->cmd.seq ||
+	    test_and_set_bit(SKW_CMD_FLAG_ACKED, &skw->cmd.flags)) {
+		skw_err("ack id: %d, ack seq: %d, cmd id: %d, cmd seq: %d, flags: 0x%lx\n",
+			msg_ack->id, msg_ack->seq, skw->cmd.id,
+			skw->cmd.seq, skw->cmd.flags);
+
+		return -EINVAL;
+	}
+
+	skw->cmd.status = msg_ack->data[0];
+	skw->dbg.cmd[skw->dbg.cmd_idx].ack = skw_local_clock();
+
+	skw_log(SKW_CMD, "[%s] RX %s[%d], status = %d, used %d msec\n",
+		SKW_TAG_CMD, skw->cmd.name, skw->cmd.id, skw->cmd.status,
+		jiffies_to_msecs(jiffies - skw->cmd.start_time));
+
+	if (skw->cmd.arg) {
+		u16 hdr_len = sizeof(struct skw_msg) + sizeof(u16);
+		u16 len = msg_ack->total_len - hdr_len;
+
+		// WARN_ON(msg_ack->total_len - hdr_len != skw->cmd.arg_size);
+		if (len != skw->cmd.arg_size)
+			skw_warn("%s expect len: %d, recv len: %d\n",
+				 skw->cmd.name, skw->cmd.arg_size, len);
+
+		memcpy(skw->cmd.arg, data + hdr_len,
+		       min(data_len - hdr_len, (int)skw->cmd.arg_size));
+	}
+
+	skw->cmd.callback(skw);
+
+	return 0;
+}
+
+void skw_event_handler(struct skw_core *skw, struct skw_iface *iface,
+			struct skw_msg *msg_hdr, void *data,
+			size_t data_len, struct skw_skb_rxcb *cb)
+{
+	int inst, max_event_id;
+	const struct skw_event_func *handler, *func;
+
+	if (msg_hdr->type == SKW_MSG_EVENT_LOCAL) {
+		inst = iface->id;
+		func = g_local_event_fn;
+		max_event_id = SKW_EVENT_LOCAL_MAX;
+	} else {
+		inst = 0;
+		func = g_event_fn;
+		max_event_id = SKW_EVENT_MAX;
+	}
+
+	if (msg_hdr->id >= max_event_id) {
+		skw_err("invalid event id, type: %d, id: %d(max: %d)\n",
+			 msg_hdr->type, msg_hdr->id, max_event_id);
+		return;
+	}
+
+	handler = &func[msg_hdr->id];
+	if (!handler->func) {
+		skw_err("function not implement, type: %d, id: %d\n",
+			 msg_hdr->type, msg_hdr->id);
+		return;
+	}
+
+	skw_log(SKW_EVENT, "[%s] iface: %d, %s[%d], seq: %d, len: %ld\n",
+		msg_hdr->type == SKW_MSG_EVENT_LOCAL ? SKW_TAG_LOCAL : SKW_TAG_EVENT,
+		inst, handler->name, msg_hdr->id, msg_hdr->seq, (long)data_len);
+
+	handler->func(skw, iface, data, data_len, cb);
+}
+
+void skw_default_event_work(struct work_struct *work)
+{
+	struct skw_core *skw;
+	struct sk_buff *skb;
+	struct skw_msg *msg_hdr;
+
+	skw = container_of(work, struct skw_core, event_work.work);
+
+	while ((skb = skb_dequeue(&skw->event_work.qlist))) {
+
+		msg_hdr = (struct skw_msg *)skb->data;
+		skb_pull(skb, sizeof(struct skw_msg));
+
+		if (msg_hdr)
+			skw_event_handler(skw, NULL, msg_hdr, skb->data,
+				skb->len, SKW_SKB_RXCB(skb));
+
+		kfree_skb(skb);
+	}
+}
+
+int skw_queue_local_event(struct wiphy *wiphy, struct skw_iface *iface,
+			  int event_id, void *data, size_t data_len)
+{
+	int ret;
+	struct skw_msg msg = {0};
+	struct sk_buff *skb;
+	struct skw_event_work *work;
+	static u16 local_event_sn;
+
+	skw_dbg("iface: %d, msg: %s, msg len: %ld\n", iface ? iface->id : 0,
+		g_local_event_fn[event_id].name, (long)data_len);
+
+	msg.total_len = data_len + sizeof(msg);
+
+	skb = netdev_alloc_skb(NULL, msg.total_len);
+	if (!skb) {
+		skw_err("alloc skb failed, len: %d\n", msg.total_len);
+		return -ENOMEM;
+	}
+
+	msg.inst_id = iface ? iface->id : 0;
+	msg.type = SKW_MSG_EVENT_LOCAL;
+	msg.id = event_id;
+	msg.seq = ++local_event_sn;
+
+	skw_put_skb_data(skb, &msg, sizeof(msg));
+	skw_put_skb_data(skb, data, data_len);
+
+	if (iface) {
+		work = &iface->event_work;
+	} else {
+		struct skw_core *skw = wiphy_priv(wiphy);
+
+		work = &skw->event_work;
+	}
+
+	ret = skw_queue_event_work(wiphy, work, skb);
+	if (ret < 0)
+		kfree_skb(skb);
+
+	return ret;
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_msg.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_msg.h
new file mode 100755
index 0000000..015dc96
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_msg.h
@@ -0,0 +1,721 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_MSG_H__
+#define __SKW_MSG_H__
+
+#include <net/cfg80211.h>
+#include "skw_core.h"
+#include "skw_rx.h"
+
+#define SKW_USB_CMD_MAX_LEN                  1536
+#define SKW_PCIE_CMD_MAX_LEN                 1600
+#define SKW_SDIO_CMD_MAX_LEN                 1588
+#define SKW_CMD_TIMEOUT                      2000
+
+#define SKW_TXBA_COUNT_MASK                  0x3
+#define SKW_TXBA_DONE                        BIT(4)
+#define SKW_TXBA_WAIT_EVENT                  BIT(5)
+#define SKW_TXBA_DISABLED                    BIT(6)
+
+#define SKW_CMD_FLAG_NO_WAKELOCK             0
+#define SKW_CMD_FLAG_NO_ACK                  1
+#define SKW_CMD_FLAG_IGNORE_BLOCK_TX         2
+#define SKW_CMD_FLAG_DONE                    3
+#define SKW_CMD_FLAG_XMIT                    4
+#define SKW_CMD_FLAG_ACKED                   5
+#define SKW_CMD_FLAG_DISABLE_IRQ             6
+
+#define SKW_WLAN_STATUS_SAE_HASH_TO_ELEMENT 126
+
+typedef int (*skw_event_fn)(struct skw_core *, struct skw_iface *, void *, int, struct skw_skb_rxcb *);
+
+enum SKW_BA_ACTION_CMD {
+	SKW_ADD_TX_BA,
+	SKW_DEL_TX_BA,
+	SKW_ADD_RX_BA,
+	SKW_DEL_RX_BA,
+	SKW_REQ_RX_BA,
+};
+
+enum SKW_TX_BA_STATUS_CODE {
+	SKW_TX_BA_SETUP_SUCC,
+	SKW_TX_BA_REFUSED,
+	SKW_TX_BA_TIMEOUT,
+	SKW_TX_BA_DEL_EVENT,
+	SKW_TX_BA_DEL_SUCC,
+};
+
+struct skw_ba_action {
+	u8 action;
+	u8 lmac_id;
+	u8 peer_idx;
+	u8 tid;
+	u8 status_code;
+	u8 resv[3];
+	u16 ssn;
+	u16 win_size;
+} __packed;
+
+struct skw_event_func {
+	int id;
+	char *name;
+	skw_event_fn func;
+};
+
+enum SKW_CSA_COMMAND_MODE {
+	SKW_CSA_DONE,
+	SKW_CSA_START,
+};
+
+struct skw_event_csa_param {
+	u8 mode;
+	u8 bss_type;  /* reference SKW_CAPA_ */
+	u8 chan;
+	u8 center_chan1;
+	u8 center_chan2;
+	u8 band_width;
+	u8 band;
+	u8 oper_class;		/* Only valid for csa start */
+	u8 switch_mode;		/* Only valid for csa start */
+	u8 switch_count;	/* Only valid for csa start */
+	u8 hw_id;
+};
+
+enum SKW_CMD_ID {
+	SKW_CMD_DOWNLOAD_INI = 0,
+	SKW_CMD_GET_INFO = 1,
+	SKW_CMD_SYN_VERSION = 2,
+	SKW_CMD_OPEN_DEV = 3,
+	SKW_CMD_CLOSE_DEV = 4,
+	SKW_CMD_START_SCAN = 5,
+	SKW_CMD_STOP_SCAN = 6,
+	SKW_CMD_START_SCHED_SCAN = 7,
+	SKW_CMD_STOP_SCHED_SCAN = 8,
+	SKW_CMD_JOIN = 9,
+	SKW_CMD_AUTH = 10,
+	SKW_CMD_ASSOC = 11,
+	SKW_CMD_ADD_KEY = 12,
+	SKW_CMD_DEL_KEY = 13,
+	SKW_CMD_TX_MGMT = 14,
+	SKW_CMD_TX_DATA_FRAME = 15,
+	SKW_CMD_SET_IP = 16,
+	SKW_CMD_DISCONNECT = 17,
+	SKW_CMD_RPM_REQ = 18,
+	SKW_CMD_START_AP = 19,
+	SKW_CMD_STOP_AP = 20,
+	SKW_CMD_ADD_STA = 21,
+	SKW_CMD_DEL_STA = 22,
+	SKW_CMD_GET_STA = 23,
+	SKW_CMD_RANDOM_MAC = 24,
+	SKW_CMD_GET_LLSTAT = 25,
+	SKW_CMD_SET_MC_ADDR = 26,
+	SKW_CMD_RESUME = 27,
+	SKW_CMD_SUSPEND = 28,
+	SKW_CMD_REMAIN_ON_CHANNEL = 29,
+	SKW_CMD_BA_ACTION = 30,
+	SKW_CMD_TDLS_MGMT = 31,
+	SKW_CMD_TDLS_OPER = 32,
+	SKW_CMD_TDLS_CHANNEL_SWITCH = 33,
+	SKW_CMD_SET_CQM_RSSI = 34,
+	SKW_CMD_NPI_MSG = 35,
+	SKW_CMD_IBSS_JOIN = 36,
+	SKW_CMD_SET_IBSS_ATTR = 37,
+	SKW_CMD_RSSI_MONITOR = 38,
+	SKW_CMD_SET_IE = 39,
+	SKW_CMD_SET_MIB = 40,
+	SKW_CMD_REGISTER_FRAME = 41,
+	SKW_CMD_ADD_TX_TS = 42,
+	SKW_CMD_DEL_TX_TS = 43,
+	SKW_CMD_REQ_CHAN_SWITCH = 44,
+	SKW_CMD_CHANGE_BEACON = 45,
+	SKW_CMD_DPD_ILC_GEAR_PARAM = 46,
+	SKW_CMD_DPD_ILC_MARTIX_PARAM = 47,
+	SKW_CMD_DPD_ILC_COEFF_PARAM = 48,
+	SKW_CMD_WIFI_RECOVER = 49,
+	SKW_CMD_PHY_BB_CFG = 50,
+	SKW_CMD_SET_REGD = 51,
+	SKW_CMD_SET_EFUSE = 52,
+	SKW_CMD_SET_PROBEREQ_FILTER = 53,
+	SKW_CMD_CFG_ANT = 54,
+	SKW_CMD_RTT = 55,
+	SKW_CMD_GSCAN = 56,
+	SKW_CMD_DFS = 57,
+	SKW_CMD_SET_SPD_ACTION = 58,
+	SKW_CMD_SET_DPD_RESULT = 59,
+	SKW_CMD_SET_MONITOR_PARAM = 60,
+	SKW_CMD_GET_DEBUG_INFO = 61,
+
+	SKW_CMD_NUM,
+};
+
+enum SKW_EVENT_ID {
+	SKW_EVENT_NORMAL_SCAN_CMPL = 0,
+	SKW_EVENT_SCHED_SCAN_CMPL = 1,
+	SKW_EVENT_DISCONNECT = 2,
+	SKW_EVENT_ASOCC = 3,
+	SKW_EVNET_RX_MGMT = 4,
+	SKW_EVENT_DEAUTH = 5,
+	SKW_EVENT_DISASOC = 6,
+	SKW_EVENT_JOIN_CMPL = 7,
+	SKW_EVENT_ACS_REPORT = 8,
+	SKW_EVENT_DEL_STA = 9,
+
+	SKW_EVENT_RRM_REPORT = 10,
+	SKW_EVENT_SCAN_REPORT  = 11,
+	SKW_EVENT_MGMT_TX_STATUS = 12,
+	SKW_EVENT_BA_ACTION = 13,
+	SKW_EVENT_CANCEL_ROC = 14,
+	SKW_EVENT_TDLS = 15,
+	SKW_EVENT_CREDIT_UPDATE = 16,
+	SKW_EVENT_MIC_FAILURE = 17,
+	SKW_EVENT_THERMAL_WARN = 18,
+	SKW_EVENT_RSSI_MONITOR = 19,
+
+	SKW_EVENT_CQM = 20,
+	SKW_EVENT_RX_UNPROTECT_FRAME = 21,
+	SKW_EVENT_CHAN_SWITCH = 22,
+	SKW_EVENT_CHN_SCH_DONE = 23,
+	SKW_EVENT_DOWNLOAD_FW = 24,
+	SKW_EVENT_TX_FRAME = 25,
+	SKW_EVENT_NPI_MP_MODE = 26,
+	SKW_EVENT_DPD_ILC_COEFF_REPORT = 27,
+	SKW_EVENT_DPD_ILC_GEAR_CMPL = 28,
+	SKW_EVENT_FW_RECOVERY = 29,
+
+	SKW_EVENT_TDLS_CHAN_SWITCH_RESULT = 30,
+	SKW_EVENT_THM_FW_STATE = 31,
+	SKW_EVENT_ENTER_ROC = 32,
+	SKW_EVENT_RADAR_PULSE = 33,
+	SKW_EVENT_RTT = 34,
+	SKW_EVENT_GSCAN = 35,
+	SKW_EVENT_GSCAN_FRAME = 36,
+	SKW_EVENT_DPD_RESULT = 37,
+
+	SKW_EVENT_MAX
+};
+
+enum SKW_EVENT_LOCAL_ID {
+	SKW_EVENT_LOCAL_STA_AUTH_ASSOC_TIMEOUT,
+	SKW_EVENT_LOCAL_AP_AUTH_TIMEOUT,
+	SKW_EVENT_LOCAL_STA_CONNECT,
+	SKW_EVENT_LOCAL_IBSS_CONNECT,
+
+	SKW_EVENT_LOCAL_MAX
+};
+
+enum SKW_MSG_TYPE {
+	SKW_MSG_CMD,
+	SKW_MSG_CMD_ACK,
+	SKW_MSG_EVENT,
+	SKW_MSG_EVENT_LOCAL
+};
+
+struct skw_msg {
+	/* for a global message, inst_id should be 0 */
+	u8 inst_id:4;
+
+	/* reference SKW_MSG_TYPE */
+	u8 type:4;
+	u8 id;
+	u16 seq;
+	u16 total_len;
+	u8 resv[2];
+	u16 data[0];
+};
+
+struct skw_mgmt_hdr {
+	u8 chan;
+	u8 band;
+	s16 signal;
+	u16 mgmt_len;
+	u16 resv;
+	struct ieee80211_mgmt mgmt[0];
+} __packed;
+
+struct skw_tx_mgmt_status {
+	u64 cookie;
+	u16 ack;
+	u16 payload_len;
+	const u8 mgmt;
+} __packed;
+
+struct skw_enter_roc {
+	u8 inst;
+	u8 chn;
+	u8 band;
+	u64 cookie;
+	u32 duration;
+} __packed;
+
+struct skw_cancel_roc {
+	u8 inst;
+	u8 chn;
+	u8 band;
+	u64 cookie;
+} __packed;
+
+struct skw_mc_list {
+	u16 count;
+	struct mac_address mac[];
+} __packed;
+
+struct skw_discon_event_params {
+	u16 reason;
+	u8 bssid[ETH_ALEN];
+} __packed;
+
+/*
+ * IEEE 802.2 Link Level Control headers, for use in conjunction with
+ * 802.{3,4,5} media access control methods.
+ *
+ * Headers here do not use bit fields due to shortcommings in many
+ * compilers.
+ */
+
+struct llc {
+	u_int8_t llc_dsap;
+	u_int8_t llc_ssap;
+	union {
+	    struct {
+		u_int8_t control;
+		u_int8_t format_id;
+		u_int8_t class;
+		u_int8_t window_x2;
+	    } __packed type_u;
+	    struct {
+		u_int8_t num_snd_x2;
+		u_int8_t num_rcv_x2;
+	    } __packed type_i;
+	    struct {
+		u_int8_t control;
+		u_int8_t num_rcv_x2;
+	    } __packed type_s;
+	    struct {
+		u_int8_t control;
+		/*
+		 * We cannot put the following fields in a structure because
+		 * the structure rounding might cause padding.
+		 */
+		u_int8_t frmr_rej_pdu0;
+		u_int8_t frmr_rej_pdu1;
+		u_int8_t frmr_control;
+		u_int8_t frmr_control_ext;
+		u_int8_t frmr_cause;
+	    } __packed type_frmr;
+	    struct {
+		u_int8_t  control;
+		u_int8_t  org_code[3];
+		u_int16_t ether_type;
+	    } __packed type_snap;
+	    struct {
+		u_int8_t control;
+		u_int8_t control_ext;
+	    } __packed type_raw;
+	} llc_un /* XXX __packed ??? */;
+} __packed;
+
+struct skw_frame_tx_status {
+	u8 status;
+	u8 resv;
+	u16 mgmt_len;
+	struct ieee80211_mgmt mgmt[0];
+} __packed;
+
+
+enum SKW_OFFLOAD_PAT_OP_TYPE {
+	PAT_OP_TYPE_SAME = 0,
+	PAT_OP_TYPE_DIFF = 1,
+	PAT_OP_TYPE_CONTINUE_MATCH = 2,
+};
+
+enum SKW_OFFLOAD_PAT_OFFSET {
+	PAT_TYPE_ETH = 0,
+	PAT_TYPE_IPV4 = 1,
+	PAT_TYPE_UDP = 2,
+	PAT_TYPE_TCP = 3,
+};
+
+struct skw_pkt_pattern {
+	u8 op;
+	u8 type_offset;
+	u16 offset;
+	u8 len;
+	u8 val[0];
+} __packed;
+
+#define SKW_MAX_WOW_USER_RULE_LEN      64
+#define SKW_MAX_WOW_RULE_NUM           64
+#define SKW_MAX_WOW_RULE_NUM_ONE_TIME  20
+#define SKW_WOW_RULE_CMD_BUF_MAX       1400
+struct skw_wow_rule {
+	u16 len;
+	u8 rule[64];
+} __packed;
+
+struct skw_wow_input_param {
+	u32 wow_flags;
+	u8 rule_num;
+	struct skw_wow_rule rules[0];
+} __packed;
+
+struct skw_wow_rules_set {
+	u64 en_bitmap;
+	u32 wow_flags;
+	u8 rule_num;
+	struct skw_wow_rule rules[SKW_MAX_WOW_RULE_NUM];
+};
+
+struct skw_wow_user_rules {
+	u64 en_bitmap;
+	char rules[SKW_MAX_WOW_RULE_NUM][SKW_MAX_WOW_USER_RULE_LEN];
+};
+
+enum SKW_SPD_ACTION_SUBCMD {
+	ACTION_DIS_ALL = 0,
+	ACTION_DIS_WOW = 1,
+	ACTION_DIS_KEEPALIVE = 2,
+	ACTION_EN_WOW = 3,
+	ACTION_EN_KEEPALIVE = 4,
+	ACTION_EN_ALWAYS_KEEPALIVE = 5,
+	ACTION_DIS_ALWAYS_KEEPALIVE = 6,
+	ACTION_DIS_ALL_KEEPALIVE = 7,
+	ACTION_EN_KEEPALIVE_APPEND = 8,
+	ACTION_WOW_KEEPALIVE = 16,
+	ACTION_EN_WOW_APPEND = 17,
+};
+
+struct skw_spd_action_param {
+	u16 sub_cmd;
+	u16 len;
+} __packed;
+
+struct skw_set_monitor_param {
+	u8 mode;
+	u8 chan_num;
+	u8 bandwidth;
+	u8 center_chn1;
+	u8 center_chn2;
+} __packed;
+
+struct skw_rssi_mointor {
+	u32 req_id;
+	s8 curr_rssi;
+	u8 curr_bssid[ETH_ALEN];
+} __packed;
+
+static inline int skw_cmd_locked(struct semaphore *sem)
+{
+	unsigned long flags;
+	int count;
+
+	raw_spin_lock_irqsave(&sem->lock, flags);
+	count = sem->count - 1;
+	raw_spin_unlock_irqrestore(&sem->lock, flags);
+
+	return (count < 0);
+}
+
+#define SKW_MAX_INST_NUM                        4
+struct skw_inst_nss_info {
+	u8 valid_id;
+	u8 inst_rx_nss:4;
+	u8 inst_tx_nss:4;
+} __packed;
+
+struct skw_mac_nss_info {
+	u8 mac_nss[SKW_MAX_LMAC_SUPPORT];
+	u8 is_dbdc_mode;
+	u8 max_inst_num;
+	struct skw_inst_nss_info inst_nss[SKW_MAX_INST_NUM];
+} __packed;
+
+#define SKW_MAX_W_LEVEL                        5
+struct skw_hw_w_debug {
+	u16 w_total_cnt;
+	u16 w_cnt[SKW_MAX_W_LEVEL];
+	u16 w_ctrl_thresh[SKW_MAX_W_LEVEL - 1];
+} __packed;
+
+struct skw_debug_info_w {
+	struct skw_hw_w_debug hw_w[SKW_MAX_LMAC_SUPPORT];
+} __packed;
+
+struct skw_debug_info_s {
+	u16 tlv;
+	u8 val[1];
+} __packed;
+
+/* TLV: SKW_MIB_GET_HW_TX_INFO_E */
+/* No val data for debug info cmd */
+/** Rsp of this TLV **/
+struct skw_hw_tx_info_rsp {
+	u16 status;
+	u16 tlv;
+	u16 len;
+	u8 val[0];
+} __packed;
+
+/*** val[0] start***/
+struct tx_hmac_per_hw_rpt_info {
+	u32 hmac_tx_stat[7];
+} __packed;
+
+struct tx_mib_acc_info {
+	u32 acc_tx_psdu_cnt;
+	u16 acc_tx_wi_ack_to_cnt;
+	u16 acc_tx_wi_ack_fail_cnt;
+	u32 acc_rts_wo_cts_cnt;
+	u16 acc_tx_post_busy_cnt;
+	u8 tx_en_perct;
+} __packed;
+
+struct tx_mib_tb_acc_info {
+	/*from tx reg*/
+	u32 rx_basic_trig_cnt;
+	u32 rx_basic_trig_succ_cnt;
+
+	u32 tb_gen_invoke_cnt;
+	u32 tb_gen_abort_cnt;
+
+	u32 tb_cs_fail_acc_cnt;
+	u32 all_tb_pre_tx_cnt;
+
+	u32 tb_tx_acc_cnt;
+	u32 tb_tx_acc_scucc_cnt;
+	u32 tb_tx_suc_ratio;
+
+	/*from rx req*/
+	u32 rx_trig_reg_cnt;
+	u32 rx_trig_start_tx_cnt;
+	u32 rx_trig_suc_ratio;
+} __packed;
+
+/* val definition */
+struct skw_get_hw_tx_info_s {
+	u32 hif_tx_all_cnt;
+	u32 hif_rpt_all_credit_cnt;
+	u32 cur_free_buf_cnt;
+	u32 pendding_tx_pkt_cnt;
+	u32 lst_cmd_seq;
+	u32 lst_evt_seq;
+	u32 lmac_tx_bc_cnt;
+	u32 lmac_tx_uc_cnt[5];
+	struct tx_hmac_per_hw_rpt_info hmac_tx_stat;
+	struct tx_mib_acc_info tx_mib_acc_info;
+	struct tx_mib_tb_acc_info tx_mib_tb_acc_info;
+} __packed;
+/*** val[0] end***/
+
+/* TLV: SKW_MIB_GET_INST_INFO_E */
+/* No val data for debug info cmd */
+/** Rsp of this TLV **/
+struct skw_inst_info_rsp {
+	u16 status;
+	u16 tlv;
+	u16 len;
+	u8 val[0];
+} __packed;
+
+/*** val[0] start***/
+struct skw_cs_monitor_rpt {
+	u8 p20_lst_busy_perctage;
+	u8 p40_lst_busy_perctage;
+	u8 p80_lst_busy_perctage;
+	u8 p20_lst_nav_perctage;
+	u8 p40_lst_nav_perctage;
+	u8 p80_lst_nav_perctage;
+} __packed;
+
+#define RPI_MAX_LEVEL 6
+struct skw_rpi_monitor_rpt {
+	int8_t no_uc_direct_rpi_level;
+	int8_t uc_direct_rpi_level;
+	int8_t rx_ba_rssi;
+	u8 rx_idle_percent_in_rpi;
+	u8 rx_percent_in_rpi;
+	/* measure dur*/
+	u32 rpi_class_dur[RPI_MAX_LEVEL];
+} __packed;
+
+#define NOISE_MAX_LEVEL 6
+struct skw_noise_monitor_rpt {
+	u32 noise_class_dur[RPI_MAX_LEVEL];
+} __packed;
+
+/*val[0]*/
+struct skw_get_inst_info_s {
+	u8 inst_mode;
+	u8 hw_id;
+	u8 enable;
+	u8 mac_id;
+	u32 asoc_peer_map;
+	u32 peer_ps_state;
+	struct skw_cs_monitor_rpt cs_info;
+	struct skw_rpi_monitor_rpt rpi_info;
+	struct skw_noise_monitor_rpt noise_info;
+} __packed;
+/*** val[0] end***/
+
+/* TLV: SKW_MIB_GET_PEER_INFO_E */
+/* No val data for debug info cmd */
+/** Rsp of this TLV **/
+struct skw_peer_info_rsp {
+	u16 status;
+	u16 tlv;
+	u16 len;
+	u8 val[0];
+} __packed;
+
+/*** val[0] start***/
+
+struct skw_tx_hmac_per_peer_rpt_info {
+	u32 hmac_tx_stat[5];
+	u32 txc_isr_read_dscr_fifo_cnt;
+} __packed;
+
+struct skw_rate_struct {
+	u8 flags;
+	u8 mcs_idx;
+	u16 legacy_rate;
+	u8 nss;
+	u8 bw;
+	u8 gi;
+	u8 ru;
+	u8 dcm;
+} __packed;
+
+struct skw_rx_msdu_info_rpt {
+	u8 ppdu_mode;
+	u8 data_rate;
+	u8 gi_type;
+	u8 sbw;
+	u8 dcm;
+	u8 nss;
+} __packed;
+
+struct skw_tx_dbg_stats_per_peer {
+	u32 rts_fail_cnt;
+	u32 psdu_ack_timeout_cnt;
+	u32 tx_mpdu_cnt;
+	u32 tx_wait_time;
+} __packed;
+
+/*val*/
+struct skw_get_peer_info {
+	u8 is_valid;
+	u8 inst_id;
+	u8 hw_id;
+	int8_t rx_rsp_rssi;
+	u16 avg_add_delay_in_ms;
+	u32 tx_max_delay_time;
+	struct skw_tx_hmac_per_peer_rpt_info hmac_tx_stat;
+	struct skw_rate_struct tx_rate_info;
+	struct skw_rx_msdu_info_rpt rx_msdu_info_rpt;
+	struct skw_tx_dbg_stats_per_peer tx_stats_info;
+	u16 tx_pending_pkt_cnt[4];
+} __packed;
+
+/*** val[0] end***/
+
+
+/* TLV: SKW_MIB_GET_HW_RX_INFO_E */
+/* No val data for debug info cmd */
+/** Rsp of this TLV **/
+struct skw_hw_rx_info_rsp {
+	u16 status;
+	u16 tlv;
+	u16 len;
+	u8 val[0];
+} __packed;
+
+/*** val[0] start***/
+
+/*val*/
+struct skw_get_hw_rx_info {
+	u16 phy_rx_all_cnt;
+	u16 phy_rx_11b_cnt;
+	u16 phy_rx_11g_cnt;
+	u16 phy_rx_11n_cnt;
+	u16 phy_rx_11ac_cnt;
+	u16 phy_rx_11ax_cnt;
+	u16 mac_rx_mpdu_cnt;
+	u16 mac_rx_mpdu_fcs_pass_cnt;
+	u16 mac_rx_mpdu_fcs_pass_dir_cnt;
+} __packed;
+
+/*** val[0] end***/
+
+/* TLV: SKW_MIB_GET_INST_TSF_E */
+/* No val data for debug info cmd */
+/** Rsp of this TLV **/
+struct skw_inst_tsf {
+	u32 tsf_l;
+	u32 tsf_h;
+} __packed;
+
+/*** val[0] start***/
+
+/*val*/
+struct skw_get_inst_tsf_rsp {
+	u16 status;
+	u16 tlv;
+	u16 len;
+	u8 val[0];
+} __packed;
+
+static inline void skw_abort_cmd(struct skw_core *skw)
+{
+	if (skw_cmd_locked(&skw->cmd.lock) &&
+	    !test_and_set_bit(SKW_CMD_FLAG_ACKED, &skw->cmd.flags))
+		skw->cmd.callback(skw);
+}
+
+int skw_msg_xmit_timeout(struct wiphy *wiphy, int dev_id, int cmd,
+			 void *buf, int len, void *arg, int size,
+			 char *name, unsigned long timeout,
+			 unsigned long extra_flags);
+
+#define skw_msg_xmit(wiphy, inst, cmd, buf, len, arg, size)                     \
+	skw_msg_xmit_timeout(wiphy, inst, cmd, buf, len,                        \
+			arg, size, #cmd,                                        \
+			msecs_to_jiffies(SKW_CMD_TIMEOUT), 0)
+
+#define SKW_NDEV_ID(d) (((struct skw_iface *)netdev_priv(d))->id)
+
+#define skw_send_msg(wiphy, dev, cmd, buf, len, arg, size)                      \
+	skw_msg_xmit_timeout(wiphy, dev ? SKW_NDEV_ID(dev) : -1,                \
+			cmd, buf, len, arg, size, #cmd,                         \
+			msecs_to_jiffies(SKW_CMD_TIMEOUT), 0)
+
+
+int skw_msg_try_send(struct skw_core *skw, int dev_id,
+		     int cmd, void *data, int data_len,
+		     void *arg, int arg_size, char *name);
+
+void skw_default_event_work(struct work_struct *work);
+int skw_cmd_ack_handler(struct skw_core *skw, void *data, int data_len);
+void skw_event_handler(struct skw_core *skw, struct skw_iface *iface,
+		       struct skw_msg *msg_hdr, void *data, size_t data_len, struct skw_skb_rxcb *cb);
+int skw_queue_local_event(struct wiphy *wiphy, struct skw_iface *iface,
+			  int event_id, void *data, size_t data_len);
+void skw_del_sta_event(struct skw_iface *iface, const u8 *addr, u16 reason);
+bool skw_cmd_data_len_in_limit(struct skw_core *skw, int data_len);
+void skw_cqm_scan_timeout(void *data);
+int skw_mgmt_tx_status(struct skw_iface *iface, u64 cookie,
+			   const u8 *frame, int frame_len, u16 ack);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_recovery.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_recovery.c
new file mode 100755
index 0000000..888ace7
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_recovery.c
@@ -0,0 +1,561 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/skbuff.h>
+
+#include "skw_core.h"
+#include "skw_cfg80211.h"
+#include "skw_iface.h"
+#include "skw_msg.h"
+#include "skw_iw.h"
+#include "skw_calib.h"
+#include "skw_recovery.h"
+#include "skw_mlme.h"
+#include "skw_rx.h"
+#include "skw_tx.h"
+
+static inline void
+skw_recovery_sta_disconnect(struct net_device *ndev, u8 *addr)
+{
+	struct skw_iface *iface = netdev_priv(ndev);
+
+	if (iface->sta.sme_external)
+		skw_tx_mlme_mgmt(ndev, IEEE80211_STYPE_DEAUTH, addr, addr, 3);
+	else
+		skw_disconnected(ndev, 3, NULL, 0, true, GFP_KERNEL);
+}
+
+static int skw_recovery_sta(struct wiphy *wiphy, struct skw_recovery_data *rd,
+				struct skw_iface *iface)
+{
+	int ret;
+	u32 peer_map = rd->iface[iface->id].peer_map;
+	struct skw_sta_core *core = &iface->sta.core;
+	struct net_device *dev = iface->ndev;
+
+#ifdef SKW_STATE_RECOVERY
+	// TODO:
+	// recovery peer state
+
+	struct cfg80211_bss *cbss;
+
+	cbss = cfg80211_get_bss(wiphy, core->bss.channel, core->bss.bssid,
+				core->bss.ssid, core->bss.ssid_len,
+				IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY);
+	if (!cbss) {
+		if (!skw_cmd_unjoin(wiphy, dev, peer->addr, 3, true))
+			peer->flags |= SKW_PEER_FLAG_DEAUTHED;
+
+		skw_recovery_sta_disconnect(dev, core->bss.bssid);
+		return 0;
+	}
+
+	ret = skw_join(wiphy, dev, cbss, false);
+	if (ret) {
+		if (!skw_cmd_unjoin(wiphy, dev, peer->addr, 3, true))
+			peer->flags |= SKW_PEER_FLAG_DEAUTHED;
+
+		skw_recovery_sta_disconnect(dev, core->bss.bssid);
+		return 0;
+	}
+
+	// set key
+	// set ip
+
+	cfg80211_put_bss(wiphy, cbss);
+#else
+	while (peer_map) {
+		u8 idx = ffs(peer_map) - 1;
+		struct skw_peer *peer = rd->peer[iface->lmac_id][idx];
+
+		SKW_CLEAR(peer_map, BIT(idx));
+
+		if (!peer || peer->flags & SKW_PEER_FLAG_DEAUTHED)
+			continue;
+
+		if (ether_addr_equal(peer->addr, core->bss.bssid)) {
+			del_timer_sync(&core->timer);
+			cancel_work_sync(&iface->sta.work);
+
+			ret = skw_cmd_unjoin(wiphy, dev, peer->addr,
+					SKW_LEAVE, true);
+			if (ret)
+				skw_warn("failed, sta: %pM, ret: %d\n",
+					 peer->addr, ret);
+
+			skw_set_state(&core->sm, SKW_STATE_NONE);
+			memset(&core->bss, 0, sizeof(struct skw_bss_cfg));
+			core->bss.ctx_idx = SKW_INVALID_ID;
+
+			skw_recovery_sta_disconnect(dev, peer->addr);
+			peer->flags |= SKW_PEER_FLAG_DEAUTHED;
+
+		} else {
+			/* TDLS */
+			cfg80211_tdls_oper_request(dev, peer->addr,
+					NL80211_TDLS_TEARDOWN,
+					SKW_WLAN_REASON_TDLS_TEARDOWN_UNREACHABLE,
+					GFP_KERNEL);
+
+			peer->flags |= SKW_PEER_FLAG_DEAUTHED;
+		}
+	}
+
+	atomic_set(&iface->actived_ctx, 0);
+#endif
+
+	return 0;
+}
+
+static void
+skw_recovery_sap_flush_sta(struct wiphy *wiphy, struct skw_recovery_data *rd,
+			struct skw_iface *iface, u8 subtype, u16 reason)
+{
+	int idx, ret;
+	u8 addr[ETH_ALEN];
+	struct skw_peer *peer;
+	struct skw_core *skw = wiphy_priv(wiphy);
+	u32 peer_map = rd->iface[iface->id].peer_map;
+	u8 lmac_id = iface->lmac_id;
+
+	while (peer_map) {
+
+		if (test_bit(SKW_FLAG_FW_ASSERT, &skw->flags))
+			break;
+
+		idx = ffs(peer_map) - 1;
+		SKW_CLEAR(peer_map, BIT(idx));
+
+		peer = rd->peer[lmac_id][idx];
+		if (!peer || peer->flags & SKW_PEER_FLAG_DEAUTHED)
+			continue;
+
+		peer->flags |= SKW_PEER_FLAG_DEAUTHED;
+		skw_mlme_ap_remove_client(iface, peer->addr);
+		skw_del_sta_event(iface, peer->addr, SKW_LEAVE);
+	}
+
+	memset(addr, 0xff, ETH_ALEN);
+	ret = skw_cmd_del_sta(wiphy, iface->ndev, addr, subtype, reason, true);
+	if (ret)
+		skw_warn("failed, sta: %pM, ret: %d\n", addr, ret);
+}
+
+static int skw_recovery_sap(struct wiphy *wiphy, struct skw_recovery_data *rd,
+			struct skw_iface *iface)
+{
+	int ret, size;
+	struct skw_startap_param *param;
+	struct net_device *ndev = iface->ndev;
+	struct skw_startap_resp resp = {};
+
+	param = rd->iface[iface->id].param;
+	if (!param) {
+		skw_err("invalid param\n");
+		return -EINVAL;
+	}
+
+	ret = skw_sap_set_mib(wiphy, iface->ndev,
+			(u8 *)param + param->beacon_tail_offset,
+			param->beacon_tail_len);
+	if (ret) {
+		skw_err("set tlv failed, ret: %d\n", ret);
+		return ret;
+	}
+
+	size = rd->iface[iface->id].size;
+
+	ret = skw_send_msg(wiphy, ndev, SKW_CMD_START_AP, param, size, &resp, sizeof(resp));
+	if (ret) {
+		skw_err("failed, ret: %d\n", ret);
+		return ret;
+	}
+
+	skw_startap_resp_handler(iface->skw, iface, &resp);
+
+	skw_dpd_set_coeff_params(wiphy, ndev, param->chan, param->center_chn1,
+				 param->center_chn2, param->chan_width);
+
+	skw_recovery_sap_flush_sta(wiphy, rd, iface, 12, SKW_LEAVE);
+
+	return 0;
+}
+
+static int skw_recovery_ibss(struct wiphy *wiphy, struct skw_iface *iface)
+{
+	return 0;
+}
+
+static int skw_recovery_p2p_dev(struct wiphy *wiphy, struct skw_iface *iface)
+{
+	skw_dbg("done\n");
+
+	return 0;
+}
+
+static void
+skw_recovery_prepare(struct skw_core *skw, struct skw_recovery_data *rd)
+{
+	int i, j;
+	struct skw_peer_ctx *ctx;
+	struct skw_iface *iface;
+
+	skw->cmd.seq = 0;
+	skw->skw_event_sn = 0;
+
+	for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
+		atomic_set(&skw->hw.lmac[i].fw_credit, 0);
+
+		WRITE_ONCE(skw->hw.lmac[i].iface_bitmap, 0);
+	}
+
+	if (test_and_set_bit(SKW_FLAG_FW_CHIP_RECOVERY, &skw->flags)) {
+		skw_info("received recovery event during recovery");
+
+
+		for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
+			for (j = 0; j < SKW_MAX_PEER_SUPPORT; j++) {
+				ctx = &skw->hw.lmac[i].peer_ctx[j];
+
+				skw_peer_ctx_lock(ctx);
+
+				rcu_assign_pointer(ctx->entry, NULL);
+				ctx->peer = NULL;
+
+				skw_peer_ctx_unlock(ctx);
+			}
+		}
+
+		spin_lock_bh(&skw->vif.lock);
+
+		for (i = 0; i < SKW_NR_IFACE; i++) {
+			iface = skw->vif.iface[i];
+			if (!iface)
+				continue;
+
+			for (j = 0; j <= SKW_WMM_AC_MAX; j++) {
+				skb_queue_purge(&iface->txq[j]);
+				skb_queue_purge(&iface->tx_cache[j]);
+			}
+
+			atomic_set(&iface->peer_map, 0);
+		}
+
+		spin_unlock_bh(&skw->vif.lock);
+
+		return;
+	}
+
+	mutex_lock(&rd->lock);
+	for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
+		for (j = 0; j < SKW_MAX_PEER_SUPPORT; j++) {
+			ctx = &skw->hw.lmac[i].peer_ctx[j];
+
+			skw_peer_ctx_lock(ctx);
+
+			rcu_assign_pointer(ctx->entry, NULL);
+			rd->peer[i][j] = ctx->peer;
+			ctx->peer = NULL;
+
+			skw_peer_ctx_unlock(ctx);
+		}
+	}
+
+	spin_lock_bh(&skw->vif.lock);
+
+	for (i = 0; i < SKW_NR_IFACE; i++) {
+		iface = skw->vif.iface[i];
+		if (!iface)
+			continue;
+
+		for (j = 0; j <= SKW_WMM_AC_MAX; j++) {
+			skb_queue_purge(&iface->txq[j]);
+			skb_queue_purge(&iface->tx_cache[j]);
+		}
+
+		rd->iface[i].peer_map = atomic_read(&iface->peer_map);
+		atomic_set(&iface->peer_map, 0);
+	}
+
+	spin_unlock_bh(&skw->vif.lock);
+
+	mutex_unlock(&rd->lock);
+
+	skw_dpd_zero(&skw->dpd);
+}
+
+static int skw_recovery_iface(struct work_struct *wk, u8 iface_idx)
+{
+	int ret = 0;
+	struct skw_core *skw = container_of(wk, struct skw_core, recovery_work);
+	struct wiphy *wiphy = priv_to_wiphy(skw);
+	struct skw_recovery_data *rd = &skw->recovery_data;
+	struct skw_iface *iface = skw->vif.iface[iface_idx];
+
+	if (!iface)
+		goto ret;
+
+	if (test_bit(SKW_FLAG_FW_ASSERT, &skw->flags)) {
+		ret = -EAGAIN;
+		goto ret;
+	}
+
+	skw_info("%s: inst: %d\n",
+			skw_iftype_name(iface->wdev.iftype), iface_idx);
+
+	ret = skw_cmd_open_dev(wiphy, iface->id, iface->addr,
+			iface->wdev.iftype, 0);
+	if (ret) {
+		skw_err("open %s failed, inst: %d, ret: %d\n",
+			skw_iftype_name(iface->wdev.iftype),
+			iface->id, ret);
+
+		skw_hw_assert(skw, false);
+
+		goto ret;
+	}
+
+	spin_lock_bh(&skw->vif.lock);
+	SKW_SET(iface->flags, SKW_IFACE_FLAG_OPENED);
+	skw->vif.opened_dev++;
+	spin_unlock_bh(&skw->vif.lock);
+
+	mutex_lock(&rd->lock);
+
+	switch (iface->wdev.iftype) {
+	case NL80211_IFTYPE_STATION:
+		if (iface->flags & SKW_IFACE_FLAG_LEGACY_P2P_DEV) {
+			skw_recovery_p2p_dev(wiphy, iface);
+			break;
+		}
+
+		skw_fallthrough;
+
+	case NL80211_IFTYPE_P2P_CLIENT:
+		skw_recovery_sta(wiphy, rd, iface);
+		break;
+
+	case NL80211_IFTYPE_AP:
+	case NL80211_IFTYPE_P2P_GO:
+		skw_recovery_sap(wiphy, rd, iface);
+		break;
+
+	case NL80211_IFTYPE_ADHOC:
+		skw_recovery_ibss(wiphy, iface);
+		break;
+
+	case NL80211_IFTYPE_P2P_DEVICE:
+		skw_recovery_p2p_dev(wiphy, iface);
+		break;
+
+	default:
+		break;
+	}
+
+	mutex_unlock(&rd->lock);
+
+ret:
+	return ret;
+}
+
+static void skw_recovery_work(struct work_struct *wk)
+{
+	int i, j, ret;
+	struct skw_chip_info chip;
+	struct skw_core *skw = container_of(wk, struct skw_core, recovery_work);
+	struct wiphy *wiphy = priv_to_wiphy(skw);
+	struct skw_recovery_data *rd = &skw->recovery_data;
+	struct skw_iface *iface;
+
+	skw_info("start\n");
+
+	skw_recovery_prepare(skw, rd);
+
+	spin_lock_bh(&skw->vif.lock);
+	skw->vif.opened_dev = 0;
+	for (i = 0; i < SKW_NR_IFACE; i++) {
+		iface = skw->vif.iface[i];
+
+		if (iface)
+			SKW_CLEAR(iface->flags, SKW_IFACE_FLAG_OPENED);
+	}
+	spin_unlock_bh(&skw->vif.lock);
+
+	skw_wifi_enable(skw->hw_pdata);
+
+	ret = skw_register_rx_callback(skw, skw_rx_cb, skw, skw_rx_cb, skw);
+	if (ret < 0)
+		skw_err("register rx callback failed, ret: %d\n", ret);
+
+	skw_hw_xmit_init(skw, skw->hw.dma);
+
+	clear_bit(SKW_FLAG_FW_ASSERT, &skw->flags);
+	clear_bit(SKW_FLAG_BLOCK_TX, &skw->flags);
+	clear_bit(SKW_FLAG_FW_MAC_RECOVERY, &skw->flags);
+	clear_bit(SKW_FLAG_FW_THERMAL, &skw->flags);
+
+	skw_sync_cmd_event_version(wiphy);
+
+	ret = skw_sync_chip_info(wiphy, &chip);
+	if (ret) {
+		skw_err("sync chip info failed, ret: %d\n", ret);
+		goto ret;
+	}
+
+	ret = skw_calib_download(wiphy, skw->fw.calib_file);
+	if (ret) {
+		skw_err("calib download failed, ret: %d\n", ret);
+		goto ret;
+	}
+
+	for (i = 0; i < SKW_NR_IFACE; i++) {
+		struct skw_iface *iface = skw->vif.iface[i];
+
+		if (!iface)
+			continue;
+
+		if (iface->wdev.iftype == NL80211_IFTYPE_STATION ||
+			iface->wdev.iftype == NL80211_IFTYPE_P2P_DEVICE) {
+			ret = skw_recovery_iface(wk, i);
+			if (ret)
+				goto ret;
+		}
+	}
+
+	for (i = 0; i < SKW_NR_IFACE; i++) {
+		struct skw_iface *iface = skw->vif.iface[i];
+
+		if (!iface)
+			continue;
+
+		if (!(iface->wdev.iftype == NL80211_IFTYPE_STATION ||
+			iface->wdev.iftype == NL80211_IFTYPE_P2P_DEVICE)) {
+			ret = skw_recovery_iface(wk, i);
+			if (ret)
+				goto ret;
+		}
+	}
+
+	if (!ret) {
+		for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
+			for (j = 0; j < SKW_MAX_PEER_SUPPORT; j++) {
+				skw_peer_free(rd->peer[i][j]);
+				rd->peer[i][j] = NULL;
+			}
+		}
+
+		clear_bit(SKW_FLAG_FW_CHIP_RECOVERY, &skw->flags);
+
+#ifdef CONFIG_SWT6621S_USB3_WORKAROUND
+		if (test_bit(SKW_FLAG_SWITCHING_USB_MODE, &skw->flags)) {
+			skw_dbg("usb switch done");
+			complete(&skw->usb_switch_done);
+		}
+#endif
+	}
+
+ret:
+	skw_dbg("end %d\n", ret);
+}
+
+void skw_recovery_del_peer(struct skw_iface *iface, u8 peer_idx)
+{
+	struct skw_recovery_data *rd = &iface->skw->recovery_data;
+	u8 lmac_id = iface->lmac_id;
+
+	if (!test_bit(SKW_FLAG_FW_CHIP_RECOVERY, &iface->skw->flags))
+		return;
+
+	mutex_lock(&rd->lock);
+
+	if (rd->peer[lmac_id])
+		rd->peer[lmac_id][peer_idx]->flags |= SKW_PEER_FLAG_DEAUTHED;
+
+	mutex_unlock(&rd->lock);
+}
+
+int skw_recovery_data_update(struct skw_iface *iface, void *param, int len)
+{
+	void *data;
+	struct skw_recovery_data *rd = &iface->skw->recovery_data;
+
+	if (!param)
+		return 0;
+
+	data = SKW_ZALLOC(SKW_2K_SIZE, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	memcpy(data, param, len);
+
+	mutex_lock(&rd->lock);
+
+	SKW_KFREE(rd->iface[iface->id].param);
+
+	rd->iface[iface->id].param = data;
+	rd->iface[iface->id].size = len;
+
+	mutex_unlock(&rd->lock);
+
+	return 0;
+}
+
+void skw_recovery_data_clear(struct skw_iface *iface)
+{
+	struct skw_recovery_data *rd = &iface->skw->recovery_data;
+
+	mutex_lock(&rd->lock);
+
+	rd->iface[iface->id].size = 0;
+	rd->iface[iface->id].peer_map = 0;
+	SKW_KFREE(rd->iface[iface->id].param);
+
+	mutex_unlock(&rd->lock);
+}
+
+int skw_recovery_init(struct skw_core *skw)
+{
+	mutex_init(&skw->recovery_data.lock);
+	INIT_WORK(&skw->recovery_work, skw_recovery_work);
+#ifdef CONFIG_SWT6621S_USB3_WORKAROUND
+	init_completion(&skw->usb_switch_done);
+#endif
+
+	return 0;
+}
+
+void skw_recovery_deinit(struct skw_core *skw)
+{
+	int i, j;
+	struct skw_recovery_data *rd = &skw->recovery_data;
+
+	mutex_lock(&rd->lock);
+
+	cancel_work_sync(&skw->recovery_work);
+
+	for (i = 0; i < SKW_NR_IFACE; i++)
+		SKW_KFREE(rd->iface[i].param);
+
+	for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
+		for (j = 0; j < SKW_MAX_PEER_SUPPORT; j++) {
+			skw_peer_free(rd->peer[i][j]);
+			rd->peer[i][j] = NULL;
+		}
+	}
+
+	mutex_unlock(&rd->lock);
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_recovery.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_recovery.h
new file mode 100755
index 0000000..a2c0a4d
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_recovery.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_RECOVERY_H__
+#define __SKW_RECOVERY_H__
+
+#define SKW_RECOVERY_TIMEOUT                    (msecs_to_jiffies(5000))
+
+struct skw_recovery_ifdata {
+	void *param;
+	int size;
+	u32 peer_map;
+};
+
+int skw_recovery_data_update(struct skw_iface *iface, void *param, int len);
+void skw_recovery_data_clear(struct skw_iface *iface);
+int skw_recovery_init(struct skw_core *skw);
+void skw_recovery_deinit(struct skw_core *skw);
+
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_regd.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_regd.c
new file mode 100755
index 0000000..a066d65
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_regd.c
@@ -0,0 +1,370 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/nl80211.h>
+#include <net/cfg80211.h>
+
+#include "skw_core.h"
+#include "skw_regd.h"
+#include "skw_msg.h"
+#include "skw_log.h"
+#include "skw_db.h"
+
+static int skw_regd_show(struct seq_file *seq, void *data)
+{
+	struct wiphy *wiphy = seq->private;
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	seq_puts(seq, "\n");
+
+	seq_printf(seq, "country: %c%c\n", skw->country[0], skw->country[1]);
+
+	seq_puts(seq, "\n");
+
+	return 0;
+}
+
+static int skw_regd_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, skw_regd_show, inode->i_private);
+}
+
+static ssize_t skw_regd_write(struct file *fp, const char __user *buf,
+				size_t size, loff_t *off)
+{
+	u8 country[3] = {0};
+	struct wiphy *wiphy = fp->f_inode->i_private;
+	int ret = 0;
+
+	if (size != 3) {
+		skw_err("invalid len: %zd\n", size);
+		return size;
+	}
+
+	if (copy_from_user(&country, buf, size)) {
+		skw_err("copy failed\n");
+		return size;
+	}
+
+	ret = skw_set_regdom(wiphy, (char *)country);
+	if (ret)
+		skw_err("set country failed, ret: %d\n", ret);
+
+	return size;
+}
+
+static const struct file_operations skw_regd_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_regd_open,
+	.read = seq_read,
+	.write = skw_regd_write,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static bool skw_alpha2_equal(const char *alpha2_x, const char *alpha2_y)
+{
+	if (!alpha2_x || !alpha2_y)
+		return false;
+
+	return alpha2_x[0] == alpha2_y[0] && alpha2_x[1] == alpha2_y[1];
+}
+
+static bool skw_freq_in_rule_band(const struct ieee80211_freq_range *freq_range,
+			      u32 freq_khz)
+{
+#define ONE_GHZ_IN_KHZ	1000000
+	u32 limit = freq_khz > 45 * ONE_GHZ_IN_KHZ ?
+			20 * ONE_GHZ_IN_KHZ : 2 * ONE_GHZ_IN_KHZ;
+
+	if (abs(freq_khz - freq_range->start_freq_khz) <= limit)
+		return true;
+
+	if (abs(freq_khz - freq_range->end_freq_khz) <= limit)
+		return true;
+
+	return false;
+
+#undef ONE_GHZ_IN_KHZ
+}
+
+static bool skw_does_bw_fit_range(const struct ieee80211_freq_range *freq_range,
+				u32 center_freq_khz, u32 bw_khz)
+{
+	u32 start_freq_khz, end_freq_khz;
+
+	start_freq_khz = center_freq_khz - (bw_khz / 2);
+	end_freq_khz = center_freq_khz + (bw_khz / 2);
+
+	if (start_freq_khz >= freq_range->start_freq_khz &&
+	    end_freq_khz <= freq_range->end_freq_khz)
+		return true;
+
+	return false;
+}
+
+static const struct ieee80211_regdomain *skw_get_regd(const char *alpha2)
+{
+	int i;
+	const struct ieee80211_regdomain *regdom;
+
+	if (!is_skw_valid_reg_code(alpha2)) {
+		skw_err("Invalid alpha\n");
+		return NULL;
+	}
+
+	for (i = 0; i < skw_regdb_size; i++) {
+		regdom = skw_regdb[i];
+
+		if (skw_alpha2_equal(alpha2, regdom->alpha2))
+			return regdom;
+	}
+
+	skw_warn("country: %c%c not support\n", alpha2[0], alpha2[1]);
+
+	return NULL;
+}
+
+static bool is_skw_valid_reg_rule(const struct ieee80211_reg_rule *rule)
+{
+	u32 freq_diff;
+	const struct ieee80211_freq_range *freq_range = &rule->freq_range;
+
+	if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0) {
+		skw_dbg("invalid, start: %d, end: %d\n",
+			freq_range->start_freq_khz, freq_range->end_freq_khz);
+
+		return false;
+	}
+
+	if (freq_range->start_freq_khz > freq_range->end_freq_khz) {
+		skw_dbg("invalid, start: %d > end: %d\n",
+			freq_range->start_freq_khz, freq_range->end_freq_khz);
+		return false;
+	}
+
+	freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz;
+
+	if (freq_range->end_freq_khz <= freq_range->start_freq_khz ||
+	    freq_range->max_bandwidth_khz > freq_diff) {
+		skw_dbg("invalid, start: %d, end: %d, max band: %d, diff: %d\n",
+			freq_range->start_freq_khz, freq_range->end_freq_khz,
+			freq_range->max_bandwidth_khz, freq_diff);
+		return false;
+	}
+
+	return true;
+}
+
+static bool is_skw_valid_rd(const struct ieee80211_regdomain *rd)
+{
+	int i;
+	const struct ieee80211_reg_rule *reg_rule = NULL;
+
+	for (i = 0; i < rd->n_reg_rules; i++) {
+		reg_rule = &rd->reg_rules[i];
+
+		if (!is_skw_valid_reg_rule(reg_rule))
+			return false;
+	}
+
+	return true;
+}
+
+static const struct ieee80211_reg_rule *
+skw_freq_reg_info(const struct ieee80211_regdomain *regd, u32 freq)
+{
+	int i;
+	bool band_rule_found = false;
+	bool bw_fits = false;
+
+	if (!regd)
+		return ERR_PTR(-EINVAL);
+
+	for (i = 0; i < regd->n_reg_rules; i++) {
+		const struct ieee80211_reg_rule *rr;
+		const struct ieee80211_freq_range *fr = NULL;
+
+		rr = &regd->reg_rules[i];
+		fr = &rr->freq_range;
+
+		if (!band_rule_found)
+			band_rule_found = skw_freq_in_rule_band(fr, freq);
+
+		bw_fits = skw_does_bw_fit_range(fr, freq, MHZ_TO_KHZ(20));
+
+		if (band_rule_found && bw_fits)
+			return rr;
+	}
+
+	if (!band_rule_found)
+		return ERR_PTR(-ERANGE);
+
+	return ERR_PTR(-EINVAL);
+}
+
+static const struct ieee80211_reg_rule *skw_regd_rule(struct wiphy *wiphy, u32 freq)
+{
+	u32 freq_khz = MHZ_TO_KHZ(freq);
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	if (skw->regd || skw_regd_self_mamaged(wiphy))
+		return skw_freq_reg_info(skw->regd, freq_khz);
+
+	return freq_reg_info(wiphy, freq_khz);
+}
+
+int skw_cmd_set_regdom(struct wiphy *wiphy, const char *alpha2)
+{
+	int ret;
+	int i, idx, band;
+	struct ieee80211_supported_band *sband;
+	struct skw_regdom regd = {};
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct skw_reg_rule *rule = &regd.rules[0];
+	const struct ieee80211_reg_rule *rr = NULL, *_rr = NULL;
+
+#define SKW_MAX_POWER(rr)  (MBM_TO_DBM(rr->power_rule.max_eirp))
+#define SKW_MAX_GAIN(rr)   (MBI_TO_DBI(rr->power_rule.max_antenna_gain))
+
+	regd.country[0] = alpha2[0];
+	regd.country[1] = alpha2[1];
+	regd.country[2] = 0;
+
+	for (idx = 0, band = 0; band < NUM_NL80211_BANDS; band++) {
+		sband = wiphy->bands[band];
+		if (!sband)
+			continue;
+
+		for (i = 0; i < sband->n_channels; i++) {
+			struct ieee80211_channel *chn = &sband->channels[i];
+
+			rr = skw_regd_rule(wiphy, chn->center_freq);
+			if (IS_ERR(rr) || rr->flags & SKW_RRF_NO_IR)
+				continue;
+
+			if (rr != _rr) {
+				regd.nr_reg_rules++;
+
+				rule = &regd.rules[idx++];
+
+				rule->nr_channel = 0;
+				rule->start_channel = chn->hw_value;
+				rule->max_power = SKW_MAX_POWER(rr);
+				rule->max_gain = SKW_MAX_GAIN(rr);
+				rule->flags = rr->flags;
+
+				_rr = rr;
+			}
+
+			rule->nr_channel = chn->hw_value - rule->start_channel + 1;
+		}
+	}
+
+	if (!regd.nr_reg_rules)
+		return 0;
+
+	for (i = 0; i < regd.nr_reg_rules; i++) {
+		skw_dbg("%d @ %d, power: %d, gain: %d, flags: 0x%x\n",
+			regd.rules[i].start_channel, regd.rules[i].nr_channel,
+			regd.rules[i].max_power, regd.rules[i].max_gain,
+			regd.rules[i].flags);
+	}
+
+	ret = skw_msg_xmit(wiphy, 0, SKW_CMD_SET_REGD, &regd,
+			   sizeof(regd), NULL, 0);
+	if (!ret) {
+		skw->country[0] = alpha2[0];
+		skw->country[1] = alpha2[1];
+	} else {
+		skw_warn("failed, country: %c%c, rules: %d, ret: %d\n",
+			 alpha2[0], alpha2[1], regd.nr_reg_rules, ret);
+	}
+
+	return ret;
+}
+
+static int __skw_set_wiphy_regd(struct wiphy *wiphy, struct ieee80211_regdomain *rd)
+{
+	int ret = 0;
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	skw->regd = rd;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
+	if (rtnl_is_locked())
+		ret = skw_set_wiphy_regd_sync(wiphy, rd);
+	else
+		ret = regulatory_set_wiphy_regd(wiphy, rd);
+#else
+	wiphy_apply_custom_regulatory(wiphy, rd);
+#endif
+
+	return ret;
+}
+
+int skw_set_wiphy_regd(struct wiphy *wiphy, const char *country)
+{
+	const struct ieee80211_regdomain *regd;
+
+	if (!skw_regd_self_mamaged(wiphy))
+		return 0;
+
+	regd = skw_get_regd(country);
+	if (!regd)
+		return -EINVAL;
+
+	if (!is_skw_valid_rd(regd))
+		return -EINVAL;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
+	if (country[0] == '0' && country[1] == '0')
+		wiphy->regulatory_flags &= ~REGULATORY_DISABLE_BEACON_HINTS;
+	else
+		wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS;
+#endif
+
+	return __skw_set_wiphy_regd(wiphy, (void *)regd);
+}
+
+int skw_set_regdom(struct wiphy *wiphy, char *country)
+{
+	int ret;
+
+	skw_dbg("country: %c%c\n", country[0], country[1]);
+
+	if (!is_skw_valid_reg_code(country)) {
+		skw_err("Invalid country code: %c%c\n",
+			country[0], country[1]);
+
+		return -EINVAL;
+	}
+
+	if (skw_regd_self_mamaged(wiphy)) {
+		ret = skw_set_wiphy_regd(wiphy, country);
+		if (!ret)
+			ret = skw_cmd_set_regdom(wiphy, country);
+
+		return ret;
+	}
+
+	return regulatory_hint(wiphy, country);
+}
+
+void skw_regd_init(struct wiphy *wiphy)
+{
+	skw_debugfs_file(SKW_WIPHY_DENTRY(wiphy), "regdom", 0666, &skw_regd_fops, wiphy);
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_regd.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_regd.h
new file mode 100755
index 0000000..a7dacc1
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_regd.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_REGD_H__
+#define __SKW_REGD_H__
+
+#include <linux/ctype.h>
+#include "skw_core.h"
+
+struct skw_reg_rule {
+	u8 start_channel;
+	u8 nr_channel;
+	s8 max_power;
+	s8 max_gain;
+	u32 flags;
+} __packed;
+
+struct skw_regdom {
+	u8 country[3];
+	u8 nr_reg_rules;
+	struct skw_reg_rule rules[8];
+} __packed;
+
+static inline bool skw_regd_self_mamaged(struct wiphy *wiphy)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	return test_bit(SKW_FLAG_PRIV_REGD, &skw->flags);
+}
+
+static inline bool is_skw_valid_reg_code(const char *alpha2)
+{
+	if (!alpha2)
+		return false;
+
+	if (alpha2[0] == '0' && alpha2[1] == '0')
+		return true;
+
+	return isalpha(alpha2[0]) && isalpha(alpha2[1]);
+}
+
+void skw_regd_init(struct wiphy *wiphy);
+int skw_set_regdom(struct wiphy *wiphy, char *country);
+int skw_set_wiphy_regd(struct wiphy *wiphy, const char *country);
+int skw_cmd_set_regdom(struct wiphy *wiphy, const char *alpha2);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_rx.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_rx.c
new file mode 100755
index 0000000..ccd9d27
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_rx.c
@@ -0,0 +1,2102 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/timer.h>
+#include <linux/skbuff.h>
+#include <linux/kthread.h>
+#include <linux/cpumask.h>
+#include <linux/ctype.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <net/ip6_checksum.h>
+#include <net/tcp.h>
+
+#include "skw_core.h"
+#include "skw_msg.h"
+#include "skw_cfg80211.h"
+#include "skw_rx.h"
+#include "skw_work.h"
+#include "skw_tx.h"
+#include "trace.h"
+
+#define SKW_PN_U48(x)          (*(u64 *)(x) & 0xffffffffffff)
+#define SKW_PN_U32(x)          (*(u64 *)(x) & 0xffffffff)
+#define SKW_MSDU_HDR_LEN       6 /* ETH_HLEN - SKW_SNAP_HDR_LEN */
+
+static u8 rx_reorder_flag;
+
+static inline void skw_wake_lock_timeout(struct skw_core *skw, long timeout)
+{
+#ifdef CONFIG_HAS_WAKELOCK
+	if (!wake_lock_active(&skw->rx_wlock))
+		wake_lock_timeout(&skw->rx_wlock, msecs_to_jiffies(timeout));
+#endif
+}
+
+static inline void skw_wake_lock_init(struct skw_core *skw, int type, const char *name)
+{
+#ifdef CONFIG_HAS_WAKELOCK
+	wake_lock_init(&skw->rx_wlock, type, name);
+#endif
+}
+
+static inline void skw_wake_lock_deinit(struct skw_core *skw)
+{
+#ifdef CONFIG_HAS_WAKELOCK
+	wake_lock_destroy(&skw->rx_wlock);
+#endif
+}
+
+static int skw_rx_reorder_show(struct seq_file *seq, void *data)
+{
+	if (rx_reorder_flag)
+		seq_puts(seq, "enable\n");
+	else
+		seq_puts(seq, "disable\n");
+
+	return 0;
+}
+
+static int skw_rx_reorder_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, skw_rx_reorder_show, inode->i_private);
+}
+
+static ssize_t skw_rx_reorder_write(struct file *fp, const char __user *buf,
+				size_t len, loff_t *offset)
+{
+	int i;
+	char cmd[32] = {0};
+
+	for (i = 0; i < len; i++) {
+		char c;
+
+		if (get_user(c, buf))
+			return -EFAULT;
+
+		if (c == '\n' || c == '\0')
+			break;
+
+		cmd[i] = tolower(c);
+		buf++;
+	}
+
+	if (strcmp(cmd, "enable") == 0)
+		rx_reorder_flag = true;
+	else if (strcmp(cmd, "disable") == 0)
+		rx_reorder_flag = false;
+	else
+		skw_warn("rx_reorder support setting values of \"enable\" or \"disable\"\n");
+
+	return len;
+}
+
+static const struct file_operations skw_rx_reorder_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_rx_reorder_open,
+	.read = seq_read,
+	.release = single_release,
+	.write = skw_rx_reorder_write,
+};
+
+static int skw_pn_reuse_show(struct seq_file *seq, void *data)
+{
+	struct skw_core *skw = seq->private;
+
+	if (test_bit(SKW_FLAG_FW_PN_REUSE, &skw->flags))
+		seq_puts(seq, "enable\n");
+	else
+		seq_puts(seq, "disable\n");
+
+	return 0;
+}
+
+static int skw_pn_reuse_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, skw_pn_reuse_show, inode->i_private);
+}
+
+static ssize_t skw_pn_reuse_write(struct file *fp, const char __user *buf,
+				size_t len, loff_t *offset)
+{
+	int i;
+	struct skw_core *skw = fp->f_inode->i_private;
+	char cmd[32] = {0};
+
+	for (i = 0; i < len; i++) {
+		char c;
+
+		if (get_user(c, buf))
+			return -EFAULT;
+
+		if (c == '\n' || c == '\0')
+			break;
+
+		cmd[i] = tolower(c);
+		buf++;
+	}
+
+	if (strcmp(cmd, "enable") == 0)
+		set_bit(SKW_FLAG_FW_PN_REUSE, &skw->flags);
+	else if (strcmp(cmd, "disable") == 0)
+		clear_bit(SKW_FLAG_FW_PN_REUSE, &skw->flags);
+	else
+		skw_warn("pn_reuse_flag support setting values of \"enable\" or \"disable\"\n");
+
+	return len;
+}
+
+static const struct file_operations skw_pn_reuse_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_pn_reuse_open,
+	.read = seq_read,
+	.release = single_release,
+	.write = skw_pn_reuse_write,
+};
+
+static inline struct skw_rx_desc *skw_rx_desc_hdr(struct sk_buff *skb)
+{
+	return (struct skw_rx_desc *)(skb->data - SKW_SKB_RXCB(skb)->rx_desc_offset);
+}
+
+void skw_tcpopt_window_handle(struct sk_buff *skb)
+{
+	unsigned int tcphoff, tcp_hdrlen, length;
+	u8 *ptr;
+	__sum16	check;
+	struct tcphdr *tcph;
+	struct ethhdr *eth = eth_hdr(skb);
+	struct iphdr *iph = (struct iphdr *)(skb->data);
+
+	if (eth->h_proto == htons(ETH_P_IP) && iph->protocol == IPPROTO_TCP) {
+
+		if (skb->len < ntohs(iph->tot_len))
+			return;
+
+		tcphoff = iph->ihl * 4;
+		tcph = (struct tcphdr *)(skb->data + tcphoff);
+
+		if (!(tcp_flag_word(tcph)&TCP_FLAG_SYN))
+			return;
+
+		//skw_dbg("skb->len:%d tot_len:%d\n", skb->len, ntohs(iph->tot_len));
+
+		tcp_hdrlen = tcph->doff*4;
+		length = (tcph->doff-5)*4;
+		ptr = (u8 *)tcph + tcp_hdrlen - length;
+		while (length > 0) {
+			int opcode = *ptr++;
+			int opsize;
+
+			switch (opcode) {
+			case TCPOPT_EOL:
+				return;
+			case TCPOPT_NOP:	/* Ref: RFC 793 section 3.1 */
+				length--;
+				continue;
+			default:
+				if (length < 2)
+					return;
+				opsize = *ptr++;
+				if (opsize < 2) /* "silly options" */
+					return;
+				if (opsize > length)
+					return;	/* don't parse partial options */
+
+				if (opcode == TCPOPT_WINDOW
+					&& opsize == TCPOLEN_WINDOW) {
+					//skw_dbg("val:%d\n", *ptr);
+					if (*ptr < 6) {
+						*ptr = 6;
+						tcph->check = 0;
+						check = csum_partial(tcph, tcp_hdrlen, 0);
+						tcph->check = csum_tcpudp_magic(iph->saddr,
+							iph->daddr, tcp_hdrlen, IPPROTO_TCP, check);
+					}
+				}
+
+				ptr += opsize - 2;
+				length -= opsize;
+			}
+		}
+	}
+}
+
+/*
+ * To verify HW checksum for ipv6
+ */
+static void skw_csum_verify(struct skw_rx_desc *desc, struct sk_buff *skb)
+{
+	u16 data_len;
+	__sum16 csum;
+	unsigned int tcphoff;
+	struct iphdr *iph;
+	struct ipv6hdr *ip6h;
+	struct ethhdr *eth = eth_hdr(skb);
+
+	if (!skb->csum)
+		return;
+
+	switch (eth->h_proto) {
+	case htons(ETH_P_IPV6):
+		ip6h = (struct ipv6hdr *)(skb->data);
+		tcphoff = sizeof(struct ipv6hdr);
+		// tcph = (struct tcphdr *)(skb->data + tcphoff);
+
+		// fixme:
+		// minus the length of any extension headers present between the IPv6
+		// header and the upper-layer header
+		data_len = ntohs(ip6h->payload_len);
+
+		if (skb->len != data_len + tcphoff) {
+			skw_detail("ipv6 dummy pending: rx len: %d, tot_len: %d",
+				   skb->len, data_len);
+
+			skb->csum = csum_partial(skb->data + tcphoff,
+						data_len, 0);
+
+			skb_trim(skb, data_len + tcphoff);
+		}
+
+		csum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, data_len,
+					ip6h->nexthdr, skb->csum);
+		if (csum) {
+			skw_detail("sa: %pI6, da: %pI6, proto: 0x%x, seq: %d, csum: 0x%x, result: 0x%x\n",
+				&ip6h->saddr, &ip6h->daddr, ip6h->nexthdr,
+				desc->sn, skb->csum, csum);
+
+			skw_hex_dump("csum failed", skb->data, skb->len, false);
+		} else {
+			skb->ip_summed = CHECKSUM_UNNECESSARY;
+		}
+
+		break;
+
+	case htons(ETH_P_IP):
+		iph = (struct iphdr *)(skb->data);
+		tcphoff = iph->ihl * 4;
+		// tcph = (struct tcphdr *)(skb->data + tcphoff);
+
+		data_len = ntohs(iph->tot_len);
+
+		if (skb->len != data_len) {
+			skw_detail("ipv4 dummy pending: rx len: %d, tot_len: %d",
+				   skb->len, data_len);
+
+			skb->csum = csum_partial(skb->data + tcphoff,
+					data_len - tcphoff, 0);
+
+			skb_trim(skb, data_len);
+		}
+
+		csum = csum_tcpudp_magic(iph->saddr, iph->daddr,
+					data_len - tcphoff,
+					iph->protocol, skb->csum);
+		if (csum) {
+			skw_detail("sa: %pI4, da: %pI4, proto: 0x%x, seq: %d, csum: 0x%x, result: 0x%x\n",
+				&iph->saddr, &iph->daddr, iph->protocol,
+				desc->sn, skb->csum, csum);
+
+			skw_hex_dump("csum failed", skb->data, skb->len, false);
+		}
+
+		break;
+
+	default:
+		break;
+	}
+}
+
+static void skw_rptr_hdl(struct skw_iface *iface, struct sk_buff *skb)
+{
+	int mcast;
+	struct iphdr *iph;
+	struct ipv6hdr *ip6h;
+	struct ethhdr *eth = (struct ethhdr *)skb->data;
+
+	if (unlikely(test_bit(SKW_FLAG_REPEATER, &iface->skw->flags))) {
+		if (eth->h_proto == ntohs(ETH_P_ARP) && is_skw_sta_mode(iface)
+			&& iface->ndev->priv_flags & IFF_BRIDGE_PORT) {
+
+			struct skw_arphdr *arp = skw_arp_hdr(skb);
+
+			if (arp->ar_op == ntohs(ARPOP_REPLY)) {
+				int i;
+				struct skw_ctx_entry *e = NULL;
+
+				rcu_read_lock();
+				for (i = 0; i < SKW_MAX_PEER_SUPPORT; i++) {
+					e = rcu_dereference(iface->skw->hw.lmac[iface->lmac_id].peer_ctx[i].entry);
+					if (e && e->peer && e->peer->ip_addr == arp->ar_tip) {
+						skw_ether_copy(eth->h_dest, e->peer->addr);
+						skw_ether_copy(arp->ar_tha, e->peer->addr);
+						break;
+					}
+				}
+				rcu_read_unlock();
+			}
+		}
+
+		mcast = is_multicast_ether_addr(eth->h_dest);
+		if (ntohs(eth->h_proto) == ETH_P_IP) {
+			iph = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
+
+			if (ipv4_is_multicast(iph->daddr) && !mcast) {
+				ip_eth_mc_map(iph->daddr, eth->h_dest);
+			}
+		} else if (ntohs(eth->h_proto) == ETH_P_IPV6) {
+			ip6h = (struct ipv6hdr *)(skb->data + sizeof(struct ethhdr));
+
+			if (ipv6_addr_is_multicast(&ip6h->daddr) && !mcast) {
+				ipv6_eth_mc_map(&ip6h->daddr, eth->h_dest);
+			}
+		}
+	}
+}
+
+static void skw_deliver_skb(struct skw_iface *iface, struct sk_buff *skb)
+{
+	struct sk_buff *tx_skb = NULL;
+	struct ethhdr *eth = (struct ethhdr *)skb->data;
+	struct skw_rx_desc *desc = skw_rx_desc_hdr(skb);
+	int mcast;
+	unsigned int len = skb->len;
+	int ret = NET_RX_DROP;
+
+	if (ether_addr_equal(eth->h_source, iface->addr))
+		goto stats;
+
+	if (unlikely(!desc->snap_match)) {
+		skw_detail("snap unmatch, sn: %d\n", desc->sn);
+
+		skw_snap_unmatch_handler(skb);
+	}
+
+	/* forward for ap mode */
+	mcast = is_multicast_ether_addr(skb->data);
+	if (desc->need_forward && is_skw_ap_mode(iface) && !iface->sap.ap_isolate) {
+		if (mcast) {
+			tx_skb = skb_copy(skb, GFP_ATOMIC);
+		} else {
+			int i;
+			struct skw_ctx_entry *e = NULL;
+
+			rcu_read_lock();
+			for (i = 0; i < SKW_MAX_PEER_SUPPORT; i++) {
+				e = rcu_dereference(iface->skw->hw.lmac[iface->lmac_id].peer_ctx[i].entry);
+				if (e && ether_addr_equal(e->addr, skb->data)) {
+					tx_skb = skb;
+					skb = NULL;
+					break;
+				}
+			}
+			rcu_read_unlock();
+		}
+
+		if (tx_skb) {
+			tx_skb->priority += 256;
+			tx_skb->protocol = htons(ETH_P_802_3);
+			skb_reset_network_header(tx_skb);
+			skb_reset_mac_header(tx_skb);
+			dev_queue_xmit(tx_skb);
+		}
+
+		if (!skb) {
+			ret = NET_RX_SUCCESS;
+			goto stats;
+			//return;
+		}
+	}
+
+	skw_rptr_hdl(iface, skb);
+	skb->protocol = eth_type_trans(skb, iface->ndev);
+	// TODO:
+	// ipv6 csum check
+	if (desc->csum_valid) {
+		skb->csum = desc->csum;
+		skb->ip_summed = CHECKSUM_COMPLETE;
+		skw_csum_verify(desc, skb);
+	} else {
+		skb->csum = 0;
+		skb->ip_summed = CHECKSUM_NONE;
+	}
+
+	ret = NET_RX_SUCCESS;
+
+	skw_detail("skb recv delta %lld us\n", ktime_to_us(net_timedelta(skb->tstamp)));
+
+	#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 18, 0)
+		netif_rx_ni(skb);
+	#else
+		netif_rx(skb);
+	#endif
+stats:
+	if (unlikely(ret == NET_RX_DROP)) {
+		iface->ndev->stats.rx_dropped++;
+		dev_kfree_skb(skb);
+	} else {
+		iface->ndev->stats.rx_packets++;
+		iface->ndev->stats.rx_bytes += len;
+		if (mcast)
+			iface->ndev->stats.multicast++;
+	}
+}
+
+/*
+ * get fragment entry
+ * @tid & @sn as fragment entry match id
+ * @active, if false, check duplicat first, then get an inactive fragment,
+ *          else return the oldest active entry
+ */
+static struct skw_frag_entry *
+skw_frag_get_entry(struct skw_iface *iface, u8 tid, u16 sn, bool active)
+{
+	int i;
+	struct skw_frag_entry *entry = NULL, *oldest = NULL;
+	struct skw_frag_entry *inactive = NULL;
+
+	for (i = 0; i < SKW_MAX_DEFRAG_ENTRY; i++) {
+		struct skw_frag_entry *e = &iface->frag[i];
+
+		if (e->sn == sn && e->tid == tid) {
+			entry = e;
+			break;
+		}
+
+		if (!active) {
+
+			// skw_dbg("i: %d,entry tid: %d, sn: %d, status: %d\n",
+			//        i, e->tid, e->sn, e->status);
+
+			if (!(e->status & SKW_FRAG_STATUS_ACTIVE)) {
+				inactive = e;
+				continue;
+			}
+
+			if (!oldest) {
+				oldest = e;
+				continue;
+			}
+
+			if (time_after(oldest->start, e->start))
+				oldest = e;
+		}
+	}
+
+	if (!active && !entry)
+		entry = inactive ? inactive : oldest;
+
+	return entry;
+}
+// Firmware will cover the exception that receiving a fragment
+// frame while in a ba session
+// Firmware will split A-MSDU frame to MSDU to Wi-Fi driver
+
+static void skw_frag_init_entry(struct skw_iface *iface, struct sk_buff *skb)
+{
+	struct skw_frag_entry *entry = NULL;
+	struct skw_rx_desc *desc = skw_rx_desc_hdr(skb);
+
+	entry = skw_frag_get_entry(iface, desc->tid, desc->sn, false);
+	if (entry->status & SKW_FRAG_STATUS_ACTIVE) {
+		skw_warn("overwrite, entry: %d, tid: %d, sn: %d, time: %d ms\n",
+			 entry->id, entry->tid, entry->sn,
+			 jiffies_to_msecs(jiffies - entry->start));
+	}
+
+	if (!skb_queue_empty(&entry->skb_list))
+		__skb_queue_purge(&entry->skb_list);
+
+	entry->status = SKW_FRAG_STATUS_ACTIVE;
+	entry->pending_len = 0;
+	entry->start = jiffies;
+	entry->tid = desc->tid;
+	entry->sn = desc->sn;
+	entry->frag_num = 0;
+
+	if (iface->key_conf.skw_cipher == SKW_CIPHER_TYPE_CCMP ||
+	    iface->key_conf.skw_cipher == SKW_CIPHER_TYPE_CCMP_256 ||
+	    iface->key_conf.skw_cipher == SKW_CIPHER_TYPE_GCMP ||
+	    iface->key_conf.skw_cipher == SKW_CIPHER_TYPE_GCMP_256) {
+		memcpy(entry->last_pn, desc->pn, IEEE80211_CCMP_PN_LEN);
+		SKW_SET(entry->status, SKW_FRAG_STATUS_CHK_PN);
+	}
+
+	__skb_queue_tail(&entry->skb_list, skb);
+}
+
+/*
+ * if @skb is a fragment frame, start to defragment.
+ * return skb buffer if all fragment frames have received, else return NULL
+ */
+static struct sk_buff *
+skw_rx_defragment(struct skw_core *skw, struct skw_iface *iface,
+				struct sk_buff *skb)
+{
+	struct sk_buff *pskb;
+	struct skw_frag_entry *entry;
+	struct skw_rx_desc *desc = skw_rx_desc_hdr(skb);
+
+	if (likely(!desc->more_frag && !desc->frag_num))
+		return skb;
+
+	//skw_dbg("peer: %d, tid: %d, sn: %d, more frag: %d, frag num: %d\n",
+	//	desc->peer_idx, desc->tid, desc->sn,
+	//	desc->more_frag, desc->frag_num);
+
+
+	if (desc->frag_num == 0) {
+		desc->csum_valid = 0;
+		desc->csum = 0;
+		skw_frag_init_entry(iface, skb);
+
+		return NULL;
+	}
+
+	entry = skw_frag_get_entry(iface, desc->tid, desc->sn, true);
+	if (!entry || (entry->frag_num + 1 != desc->frag_num)) {
+		//TBD: the frag num increased by 2 when it is WAPI
+		skw_dbg("drop, entry: %d, tid: %d, sn: %d, frag num: %d\n",
+			entry ? entry->id : -1, desc->tid,
+			desc->sn, desc->frag_num);
+
+		dev_kfree_skb(skb);
+		return NULL;
+	}
+
+	/* check fragment frame PN if cipher is CCMP
+	 * The PN shall be incremented in steps of 1 for constituent
+	 * MPDUs of fragmented MSDUs and MMPDUs
+	 */
+	if (entry->status & SKW_FRAG_STATUS_CHK_PN) {
+		u64 last_pn, pn;
+
+		if (skw->hw.bus == SKW_BUS_SDIO && test_bit(SKW_FLAG_FW_PN_REUSE, &skw->flags)) {
+			last_pn = SKW_PN_U32(entry->last_pn);
+			pn = SKW_PN_U32(desc->pn);
+		} else {
+			last_pn = SKW_PN_U48(entry->last_pn);
+			pn = SKW_PN_U48(desc->pn);
+		}
+		if (last_pn + 1 != pn) {
+			skw_dbg("drop frame last pn:%llu desc_pn:%llu\n",
+				last_pn, pn);
+			dev_kfree_skb(skb);
+			return NULL;
+		}
+
+		memcpy(entry->last_pn, desc->pn, IEEE80211_CCMP_PN_LEN);
+	}
+
+	entry->frag_num++;
+
+	/* remove mac address header -- SA & DA */
+	skb_pull(skb, 12);
+
+	entry->pending_len += skb->len;
+
+	__skb_queue_tail(&entry->skb_list, skb);
+
+	if (desc->more_frag)
+		return NULL;
+
+	pskb = __skb_dequeue(&entry->skb_list);
+	if (!pskb)
+		return NULL;
+
+	if (skb_tailroom(pskb) < entry->pending_len) {
+		if (unlikely(pskb_expand_head(pskb, 0, entry->pending_len,
+						GFP_ATOMIC))) {
+
+			skw_warn("drop: tailroom: %d, needed: %d\n",
+				 skb_tailroom(pskb), entry->pending_len);
+
+			__skb_queue_purge(&entry->skb_list);
+			dev_kfree_skb(pskb);
+			entry->status = 0;
+			entry->tid = SKW_INVALID_ID;
+
+			return NULL;
+		}
+	}
+
+	while ((skb = __skb_dequeue(&entry->skb_list))) {
+		/* snap unmatch */
+		skw_put_skb_data(pskb, skb->data, skb->len);
+		dev_kfree_skb(skb);
+	}
+
+	entry->status = 0;
+	entry->tid = SKW_INVALID_ID;
+
+	// Remove the mic value in the final fragment when encryption is TKIP
+	if (iface->key_conf.skw_cipher == SKW_CIPHER_TYPE_TKIP)
+		skb_trim(pskb, pskb->len - 8);
+
+	return pskb;
+}
+
+static int skw_pn_allowed(struct skw_core *skw, struct skw_key *key, struct skw_rx_desc *desc, int queue)
+{
+	int ret;
+	u64 pn, rx_pn;
+
+	if (!key)
+		return -EINVAL;
+
+	if (skw->hw.bus == SKW_BUS_SDIO && test_bit(SKW_FLAG_FW_PN_REUSE, &skw->flags)) {
+		pn = SKW_PN_U32(desc->pn);
+		rx_pn = SKW_PN_U32(key->rx_pn[queue]);
+	} else {
+		pn = SKW_PN_U48(desc->pn);
+		rx_pn = SKW_PN_U48(key->rx_pn[queue]);
+	}
+
+	if (pn > rx_pn)
+		ret = 1;
+	else if (pn == rx_pn)
+		ret = 0;
+	else
+		ret = -1;
+
+	if (ret < 0 || (!ret && !desc->is_amsdu && pn != 0)) {
+		/* SKW_PN_U48(desc->pn) = 0 allow workaround some devices pn=0*/
+
+		/* failed that PN less than or equal to rx_pn */
+		skw_warn("seq: %d, rx_pn: %llu, pn: %llu\n",
+			desc->sn, rx_pn, pn);
+
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int skw_replay_detect(struct skw_core *skw, struct skw_iface *iface,
+			struct sk_buff *skb)
+{
+	int64_t ret = 0;
+	int key_idx, queue = -1;
+	struct skw_key *key;
+	struct skw_key_conf *conf;
+	struct skw_peer_ctx *ctx;
+	struct skw_rx_desc *desc = skw_rx_desc_hdr(skb);
+
+	if (SKW_SKB_RXCB(skb)->skip_replay_detect)
+		return 0;
+
+	// fixme:
+	ctx = &skw->hw.lmac[iface->lmac_id].peer_ctx[desc->peer_idx];
+	if (!ctx->peer)
+		return -EINVAL;
+
+	if (desc->is_mc_addr) {
+		return 0;
+#if 0
+		conf = &iface->key_conf;
+		if (!conf->installed_bitmap)
+			conf = &ctx->peer->gtk_conf;
+#endif
+	} else {
+		conf = &ctx->peer->ptk_conf;
+	}
+
+	key_idx = skw_key_idx(conf->installed_bitmap);
+	if (key_idx == SKW_INVALID_ID)
+		return 0;
+
+	switch (conf->skw_cipher) {
+	case SKW_CIPHER_TYPE_CCMP:
+	case SKW_CIPHER_TYPE_CCMP_256:
+	case SKW_CIPHER_TYPE_GCMP:
+	case SKW_CIPHER_TYPE_GCMP_256:
+	case SKW_CIPHER_TYPE_TKIP:
+		queue = desc->tid;
+		break;
+
+	case SKW_CIPHER_TYPE_AES_CMAC:
+	case SKW_CIPHER_TYPE_BIP_CMAC_256:
+	case SKW_CIPHER_TYPE_BIP_GMAC_128:
+	case SKW_CIPHER_TYPE_BIP_GMAC_256:
+		queue = -1;
+		break;
+
+	default:
+		queue = -1;
+		break;
+	}
+
+	if (queue < 0)
+		return 0;
+
+	rcu_read_lock();
+
+	key = rcu_dereference(conf->key[key_idx]);
+	if (key) {
+#if 0
+		skw_detail("inst: %d, peer: %d, tid: %d, key_idx: %d, queue: %d, pn: 0x%llx, rx pn: 0x%llx\n",
+			desc->inst_id, desc->peer_idx, desc->tid, key_idx, queue,
+			SKW_PN_U48(key->rx_pn[queue]), SKW_PN_U48(desc->pn));
+#endif
+
+		ret = skw_pn_allowed(skw, key, desc, queue);
+		if (!ret)
+			memcpy(key->rx_pn[queue], desc->pn, SKW_PN_LEN);
+	}
+
+	rcu_read_unlock();
+
+	return ret;
+}
+
+static void skw_rx_handler(struct skw_core *skw, struct sk_buff_head *list)
+{
+	struct sk_buff *skb;
+	struct skw_iface *iface;
+	struct skw_rx_desc *desc;
+	struct sk_buff_head deliver_list;
+
+	__skb_queue_head_init(&deliver_list);
+
+	spin_lock_bh(&skw->rx_lock);
+
+	while ((skb = __skb_dequeue(list))) {
+		if (SKW_SKB_RXCB(skb)->skw_created) {
+			dev_kfree_skb(skb);
+			continue;
+		}
+
+		desc = skw_rx_desc_hdr(skb);
+
+		trace_skw_rx_handler_seq(desc->sn, desc->msdu_filter);
+
+		iface = to_skw_iface(skw, desc->inst_id);
+		if (iface == NULL) {
+			dev_kfree_skb(skb);
+			continue;
+		}
+
+		if (skw_replay_detect(skw, iface, skb) < 0) {
+			dev_kfree_skb(skb);
+			continue;
+		}
+
+		skb = skw_rx_defragment(skw, iface, skb);
+		if (!skb)
+			continue;
+
+		skb->dev = iface->ndev;
+		__skb_queue_tail(&deliver_list, skb);
+	}
+
+	spin_unlock_bh(&skw->rx_lock);
+
+	while ((skb = __skb_dequeue(&deliver_list)))
+		skw_deliver_skb(netdev_priv(skb->dev), skb);
+}
+
+static void skw_set_reorder_timer(struct skw_tid_rx *tid_rx, u16 sn)
+{
+	u16 index;
+	struct sk_buff_head *list;
+	unsigned long timeout = 0;
+	struct skw_reorder_rx *reorder = tid_rx->reorder;
+
+	smp_rmb();
+
+	if (timer_pending(&reorder->timer) ||
+	    atomic_read(&reorder->ref_cnt) != tid_rx->ref_cnt)
+		return;
+
+	index = sn % tid_rx->win_size;
+	list = &tid_rx->reorder_buf[index];
+	if (!list || skb_queue_empty(list)) {
+		//skw_warn("invalid rx list, sn: %d\n", sn);
+		return;
+	}
+
+	timeout = SKW_SKB_RXCB(skb_peek(list))->rx_time +
+		  msecs_to_jiffies(reorder->skw->config.global.reorder_timeout);
+
+	trace_skw_rx_set_reorder_timer(reorder->inst, reorder->peer_idx,
+				reorder->tid, sn, jiffies, timeout);
+
+	if (time_before(jiffies, timeout)) {
+		reorder->expired.sn = sn;
+		reorder->expired.ref_cnt = tid_rx->ref_cnt;
+		mod_timer(&reorder->timer, timeout);
+	} else {
+
+		spin_lock_bh(&reorder->todo.lock);
+
+		if (!reorder->todo.actived) {
+			reorder->todo.seq = sn;
+			reorder->todo.actived = true;
+			reorder->todo.reason = SKW_RELEASE_EXPIRED;
+			if (reorder->skw->hw.bus == SKW_BUS_PCIE) {
+				u8 lmac_id;
+
+				lmac_id = reorder->peer->iface->lmac_id;
+				skw_list_add(&reorder->skw->hw.lmac[lmac_id].rx_todo_list,
+					&reorder->todo.list);
+			} else
+				skw_list_add(&reorder->skw->rx_todo_list, &reorder->todo.list);
+		}
+
+		spin_unlock_bh(&reorder->todo.lock);
+
+		skw_wakeup_rx(reorder->skw);
+	}
+}
+
+static inline bool is_skw_release_ready(struct sk_buff_head *list)
+{
+	struct sk_buff *skb = skb_peek(list);
+	struct skw_skb_rxcb *cb = NULL;
+
+	if (!skb)
+		return false;
+
+	cb = SKW_SKB_RXCB(skb);
+	if ((cb->amsdu_flags & SKW_AMSDU_FLAG_VALID) &&
+	    (cb->amsdu_bitmap != cb->amsdu_mask))
+		return false;
+
+	return true;
+}
+
+static inline bool is_skw_msdu_timeout(struct skw_core *skw, struct sk_buff_head *list)
+{
+	struct sk_buff *skb;
+	unsigned long timeout = 0;
+
+	skb = skb_peek(list);
+	if (skb) {
+		timeout = SKW_SKB_RXCB(skb)->rx_time + msecs_to_jiffies(skw->config.global.reorder_timeout);
+		if (time_after(jiffies, timeout))
+			return true;
+	}
+
+	return false;
+}
+
+/* Force release frame in reorder buffer to to_sn*/
+static void skw_reorder_force_release(struct skw_tid_rx *tid_rx,
+		u16 to_sn, struct sk_buff_head *release_list, int reason)
+{
+	u16 index, target;
+	struct sk_buff *skb, *pskb;
+
+	if (!tid_rx)
+		return;
+
+	target = ieee80211_sn_inc(to_sn);
+
+	smp_rmb();
+
+	if (timer_pending(&tid_rx->reorder->timer) &&
+	    atomic_read(&tid_rx->reorder->ref_cnt) == tid_rx->ref_cnt &&
+	    (ieee80211_sn_less(tid_rx->reorder->expired.sn, to_sn) ||
+	     ieee80211_sn_less(to_sn, tid_rx->win_start)))
+		del_timer(&tid_rx->reorder->timer);
+
+	while (ieee80211_sn_less(tid_rx->win_start, target)) {
+		struct sk_buff_head *list;
+
+		index = tid_rx->win_start % tid_rx->win_size;
+		list = &tid_rx->reorder_buf[index];
+
+		if (!tid_rx->stored_num) {
+			tid_rx->win_start = to_sn;
+			break;
+		}
+
+		skb = skb_peek(list);
+		if (skb) {
+			if (!is_skw_release_ready(list)) {
+				skw_dbg("warn, seq: %d, amsdu bitmap: 0x%x\n",
+					skw_rx_desc_hdr(skb)->sn,
+					SKW_SKB_RXCB(skb)->amsdu_bitmap);
+			}
+
+			if (SKW_SKB_RXCB(skb)->amsdu_flags
+					& SKW_AMSDU_FLAG_TAINT) {
+				__skb_queue_purge(list);
+			} else {
+				while ((pskb = __skb_dequeue(list)))
+					__skb_queue_tail(release_list, pskb);
+			}
+
+			tid_rx->stored_num--;
+		}
+
+		WARN_ON(!skb_queue_empty(list));
+
+		tid_rx->win_start = ieee80211_sn_inc(tid_rx->win_start);
+
+		trace_skw_rx_force_release(tid_rx->reorder->inst,
+					tid_rx->reorder->peer_idx,
+					tid_rx->reorder->tid,
+					index, tid_rx->win_start, target,
+					tid_rx->stored_num, reason);
+	}
+}
+
+/*
+ * release all ready skb in reorder buffer until a gap
+ * if first ready skb is timeout, release all skb in reorder buffer,
+ * else reset timer
+ */
+static void skw_reorder_release(struct skw_reorder_rx *reorder,
+			struct sk_buff_head *release_list)
+{
+	bool release = true;
+
+	u16 i, index;
+	u16 win_start;
+	struct sk_buff *skb;
+	struct sk_buff_head *list;
+	struct skw_tid_rx *tid_rx;
+
+	tid_rx = rcu_dereference(reorder->tid_rx);
+	if (!tid_rx)
+		return;
+
+	win_start = tid_rx->win_start;
+
+	for (i = 0; i < tid_rx->win_size; i++) {
+		if (tid_rx->stored_num == 0) {
+			if (timer_pending(&reorder->timer))
+				del_timer(&reorder->timer);
+
+			break;
+		}
+
+		index = (win_start + i) % tid_rx->win_size;
+		list = &tid_rx->reorder_buf[index];
+
+		if (!skb_queue_len(list)) {
+			if (timer_pending(&reorder->timer))
+				break;
+
+			release = false;
+			continue;
+		}
+
+		/* release timeout skb and reset reorder timer */
+		if (!release) {
+			if (!is_skw_msdu_timeout(reorder->skw, list)) {
+				skw_set_reorder_timer(tid_rx, win_start + i);
+				break;
+			}
+
+			skw_reorder_force_release(tid_rx, win_start + i,
+					release_list, SKW_RELEASE_EXPIRED);
+			release = true;
+			continue;
+		}
+
+		if (release) {
+			skb = skb_peek(list);
+
+			if (timer_pending(&reorder->timer) &&
+			    reorder->expired.sn == tid_rx->win_start)
+				del_timer(&reorder->timer);
+
+			if (SKW_SKB_RXCB(skb)->amsdu_flags & SKW_AMSDU_FLAG_TAINT) {
+				__skb_queue_purge(list);
+				release = false;
+				continue;
+			}
+
+			if (is_skw_release_ready(list) ||
+			    is_skw_msdu_timeout(reorder->skw, list)) {
+				struct sk_buff *pskb;
+
+				while ((pskb = __skb_dequeue(list)))
+					__skb_queue_tail(release_list, pskb);
+
+				tid_rx->win_start = ieee80211_sn_inc(tid_rx->win_start);
+				tid_rx->stored_num--;
+
+				trace_skw_rx_reorder_release(reorder->inst,
+						reorder->peer_idx, reorder->tid,
+						win_start, win_start + i,
+						index, tid_rx->win_start,
+						tid_rx->stored_num);
+
+			} else {
+				/* AMSDU not ready and expired */
+				if (!timer_pending(&reorder->timer))
+					skw_set_reorder_timer(tid_rx, win_start + i);
+
+				break;
+			}
+		}
+	}
+}
+
+static void skw_ampdu_reorder(struct skw_core *skw, struct skw_rx_desc *desc,
+			struct sk_buff *skb, struct sk_buff_head *release_list)
+{
+	u32 filter;
+	u16 win_start, win_size;
+	struct skw_ctx_entry *entry;
+	struct skw_tid_rx *tid_rx;
+	struct sk_buff_head *list = NULL;
+	struct sk_buff *pskb;
+	struct skw_peer *peer;
+	struct skw_reorder_rx *reorder;
+	bool release = false, drop = false;
+	const u8 snap_hdr[] = {0xAA, 0xAA, 0x03, 0x0, 0x0, 0x0};
+	struct skw_skb_rxcb *cb = NULL;
+	struct skw_lmac *lmac = NULL;
+	struct ethhdr *eth_hdr = NULL;
+
+#define SKW_RXCB_AMSDU_LAST    BIT(0)
+
+	if (!rx_reorder_flag) {
+		__skb_queue_tail(release_list, skb);
+		return;
+	}
+
+	cb = SKW_SKB_RXCB(skb);
+	lmac = &skw->hw.lmac[cb->lmac_id];
+	entry = rcu_dereference(lmac->peer_ctx[desc->peer_idx].entry);
+	if (!entry) {
+		__skb_queue_tail(release_list, skb);
+		return;
+	}
+
+	peer = entry->peer;
+	filter = atomic_read(&peer->rx_filter);
+	eth_hdr = (struct ethhdr *)skb->data;
+	if (filter &&
+	    !(filter & BIT(desc->msdu_filter & 0x1F)) &&
+	    !((htons(ETH_P_PAE) == eth_hdr->h_proto) || (htons(SKW_ETH_P_WAPI) == eth_hdr->h_proto))) {
+		skw_dbg("warn: rx filter: 0x%x, msdu filter: 0x%x\n",
+			filter, desc->msdu_filter);
+
+		kfree_skb(skb);
+		return;
+	}
+
+	entry->peer->rx.bytes += skb->len;
+	entry->peer->rx.pkts++;
+
+	if (!desc->is_qos_data || desc->is_mc_addr) {
+		__skb_queue_tail(release_list, skb);
+		return;
+	}
+
+	/* if this mpdu is fragmented, skip reorder */
+	if (desc->more_frag || desc->frag_num) {
+		__skb_queue_tail(release_list, skb);
+		return;
+	}
+
+	reorder = &peer->reorder[desc->tid];
+	reorder->peer = peer;
+	tid_rx = rcu_dereference(reorder->tid_rx);
+	if (!tid_rx) {
+		__skb_queue_tail(release_list, skb);
+		return;
+	}
+
+	win_start = tid_rx->win_start;
+	win_size = tid_rx->win_size;
+	/* case:
+	 * frame seqence number less than window start
+	 */
+	if (ieee80211_sn_less(desc->sn, win_start)) {
+		if (SKW_RX_FILTER_EXCL & BIT(desc->msdu_filter & 0x1F)) {
+			SKW_SKB_RXCB(skb)->skip_replay_detect = 1;
+			__skb_queue_tail(release_list, skb);
+			return;
+		}
+
+		skw_detail("drop: peer: %d, tid: %d, ssn: %d, seq: %d, amsdu idx: %d, filter: %d\n",
+			   desc->peer_idx, desc->tid, win_start,
+			   desc->sn, desc->amsdu_idx, desc->msdu_filter);
+
+		drop = true;
+		goto out;
+	}
+
+	/* case:
+	 * frame sequence number exceeds window size
+	 */
+	if (!ieee80211_sn_less(desc->sn, win_start + win_size)) {
+		win_start = ieee80211_sn_sub(desc->sn, win_size);
+
+		skw_reorder_force_release(tid_rx, win_start, release_list,
+						SKW_RELEASE_OOB);
+		release = true;
+		win_start = tid_rx->win_start;
+	}
+
+	/* dup check
+	 */
+	// index = desc->sn % win_size;
+	list = &tid_rx->reorder_buf[desc->sn % win_size];
+	pskb = skb_peek(list);
+
+	if (desc->is_amsdu) {
+		struct skw_skb_rxcb *cb;
+
+		if (!pskb) {
+			pskb = skb;
+			tid_rx->stored_num++;
+		}
+
+		cb = SKW_SKB_RXCB(pskb);
+		if (cb->amsdu_bitmap & BIT(desc->amsdu_idx)) {
+			drop = true;
+			goto out;
+		}
+
+		cb->amsdu_bitmap |= BIT(desc->amsdu_idx);
+		cb->amsdu_flags |= SKW_AMSDU_FLAG_VALID;
+		__skb_queue_tail(list, skb);
+
+		if (desc->amsdu_first_idx &&
+		    ether_addr_equal(skb->data, snap_hdr)) {
+			cb->amsdu_flags |= SKW_AMSDU_FLAG_TAINT;
+			skw_hex_dump("attack", skb->data, 14, true);
+		}
+
+		if (desc->amsdu_last_idx) {
+			cb->amsdu_mask = BIT(desc->amsdu_idx + 1) - 1;
+			cb->amsdu_bitmap |= SKW_RXCB_AMSDU_LAST;
+		}
+
+		if (cb->amsdu_bitmap != cb->amsdu_mask)
+			goto out;
+
+		/* amsdu ready to release */
+		tid_rx->stored_num--;
+
+		if (cb->amsdu_flags & SKW_AMSDU_FLAG_TAINT) {
+			__skb_queue_purge(list);
+			tid_rx->win_start = ieee80211_sn_inc(tid_rx->win_start);
+			drop = true;
+			skb = NULL;
+
+			goto out;
+		}
+
+	} else {
+		if (pskb) {
+			drop = true;
+			goto out;
+		}
+
+		__skb_queue_tail(list, skb);
+	}
+
+	if (desc->sn == win_start) {
+		while ((pskb = __skb_dequeue(list)))
+			__skb_queue_tail(release_list, pskb);
+
+		if (timer_pending(&reorder->timer) &&
+			reorder->expired.sn == tid_rx->win_start)
+			del_timer(&reorder->timer);
+
+		tid_rx->win_start = ieee80211_sn_inc(tid_rx->win_start);
+
+		release = true;
+
+	} else {
+		tid_rx->stored_num++;
+	}
+
+out:
+	trace_skw_rx_reorder(desc->inst_id, desc->peer_idx, desc->tid,
+			     desc->sn, desc->is_amsdu, desc->amsdu_idx,
+			     tid_rx->win_size, tid_rx->win_start,
+			     tid_rx->stored_num, release, drop);
+
+	if (drop && skb) {
+		dev_kfree_skb(skb);
+		skb = NULL;
+	}
+
+	if (tid_rx->stored_num) {
+		if (release)
+			skw_reorder_release(reorder, release_list);
+		else if (skb)
+			skw_set_reorder_timer(tid_rx, desc->sn);
+	} else {
+		if (timer_pending(&reorder->timer))
+			del_timer(&reorder->timer);
+	}
+}
+
+void skw_rx_todo(struct skw_list *todo_list)
+{
+	// u16 target;
+	LIST_HEAD(list);
+	struct sk_buff_head release;
+	struct skw_reorder_rx *reorder;
+	struct skw_tid_rx *tid_rx;
+
+	if (likely(!todo_list->count))
+		return;
+
+	INIT_LIST_HEAD(&list);
+	__skb_queue_head_init(&release);
+
+	spin_lock_bh(&todo_list->lock);
+
+	list_splice_init(&todo_list->list, &list);
+	todo_list->count = 0;
+
+	spin_unlock_bh(&todo_list->lock);
+
+
+	while (!list_empty(&list)) {
+		reorder = list_first_entry(&list, struct skw_reorder_rx,
+					   todo.list);
+
+		spin_lock_bh(&reorder->todo.lock);
+
+		list_del(&reorder->todo.list);
+		INIT_LIST_HEAD(&reorder->todo.list);
+
+		rcu_read_lock();
+		tid_rx = rcu_dereference(reorder->tid_rx);
+		skw_reorder_force_release(tid_rx, reorder->todo.seq,
+					&release, reorder->todo.reason);
+		rcu_read_unlock();
+
+		reorder->todo.actived = false;
+
+		spin_unlock_bh(&reorder->todo.lock);
+
+		skw_reorder_release(reorder, &release);
+
+		skw_rx_handler(reorder->skw, &release);
+
+		trace_skw_rx_expired_release(reorder->inst, reorder->peer_idx,
+					reorder->tid, reorder->todo.seq);
+	}
+
+}
+
+static void skw_rx_handler_drop_info(struct skw_core *skw, struct sk_buff *pskb,
+			int offset, struct sk_buff_head *release_list)
+{
+	int i, buff_len;
+	int total_drop_sn;
+	struct sk_buff *skb;
+	struct skw_rx_desc *new_desc;
+	struct skw_drop_sn_info *sn_info;
+	static unsigned long j;
+
+	total_drop_sn = *(int *)(pskb->data + offset);
+	buff_len = pskb->len - offset;
+
+	if (total_drop_sn > buff_len / sizeof(*sn_info)) {
+		struct skw_rx_desc *desc;
+		int msdu_offset;
+
+		desc = skw_rx_desc_hdr(pskb);
+		msdu_offset = desc->msdu_offset -
+				skw->hw.rx_desc.msdu_offset -
+				skw->hw.rx_desc.hdr_offset;
+		if (printk_timed_ratelimit(&j, 5000))
+			skw_hex_dump("dump", pskb->data - msdu_offset, pskb->len+msdu_offset, true);
+		// skw_hw_assert(skw, false);
+		return;
+	}
+
+	sn_info = (struct skw_drop_sn_info *)(pskb->data + offset + 4);
+	for (i = 0; i < total_drop_sn; i++) {
+		trace_skw_rx_data(sn_info[i].inst, sn_info[i].peer_idx,
+				  sn_info[i].tid, 0,
+				  sn_info[i].sn, sn_info[i].qos,
+				  0, sn_info[i].is_amsdu,
+				  sn_info[i].amsdu_idx, sn_info[i].amsdu_first,
+				  sn_info[i].amsdu_last, true);
+
+		if (!sn_info[i].qos)
+			continue;
+
+		skb = dev_alloc_skb(sizeof(struct skw_rx_desc));
+		if (skb) {
+			SKW_SKB_RXCB(skb)->skw_created = 1;
+			SKW_SKB_RXCB(skb)->rx_time = jiffies;
+
+			new_desc = skw_put_skb_zero(skb, sizeof(struct skw_rx_desc));
+			new_desc->inst_id = sn_info[i].inst;
+			new_desc->peer_idx = sn_info[i].peer_idx;
+			new_desc->tid = sn_info[i].tid;
+			new_desc->is_qos_data = sn_info[i].qos;
+			new_desc->sn = sn_info[i].sn;
+			new_desc->is_amsdu = sn_info[i].is_amsdu;
+			new_desc->amsdu_idx = sn_info[i].amsdu_idx;
+			new_desc->amsdu_first_idx = sn_info[i].amsdu_first;
+			new_desc->amsdu_last_idx = sn_info[i].amsdu_last;
+
+			rcu_read_lock();
+			skw_ampdu_reorder(skw, new_desc, skb, release_list);
+			rcu_read_unlock();
+
+			skw_rx_handler(skw, release_list);
+		}
+	}
+}
+
+static void skw_netif_monitor_rx(struct skw_core *skw, struct sk_buff *skb)
+{
+	struct skw_iface *iface;
+	struct skw_sniffer_desc *desc;
+	struct skw_radiotap_desc *skw_radio_desc;
+	u16 msdu_len;
+	int i;
+
+	if (skw->hw.bus == SKW_BUS_USB)
+		skb_pull(skb, 12);	//offset word0 ~ word2
+
+	desc = (struct skw_sniffer_desc *) skb->data;
+
+	msdu_len = desc->mpdu_len;
+	skw_detail("sniffer mode, len = %d\n", msdu_len);
+	if (unlikely(!msdu_len)) {
+		skw_detail("strip invalid pakcet\n");
+		kfree_skb(skb);
+		return;
+	}
+
+	skw_radio_desc = (struct skw_radiotap_desc *)skb_pull(skb,
+				sizeof(struct skw_sniffer_desc) - sizeof(struct skw_radiotap_desc));
+
+	skw_radio_desc->radiotap_hdr.it_version = PKTHDR_RADIOTAP_VERSION;
+	skw_radio_desc->radiotap_hdr.it_pad = 0;
+	skw_radio_desc->radiotap_hdr.it_len = sizeof(struct skw_radiotap_desc);
+	skw_radio_desc->radiotap_hdr.it_present = BIT(IEEE80211_RADIOTAP_FLAGS);
+
+	if (skw_radio_desc->radiotap_hdr.it_present & BIT(IEEE80211_RADIOTAP_FLAGS))
+		skw_radio_desc->radiotap_flag = IEEE80211_RADIOTAP_F_FCS;
+
+	for (i = 0; i < SKW_NR_IFACE; i++) {
+		iface = skw->vif.iface[i];
+		if (!iface || !iface->ndev)
+			continue;
+		if (iface->ndev->ieee80211_ptr->iftype == NL80211_IFTYPE_MONITOR)
+			break;
+	}
+
+	if (unlikely(!iface || !iface->ndev || iface->ndev->ieee80211_ptr->iftype != NL80211_IFTYPE_MONITOR)) {
+		skw_err("iface not valid\n");
+		kfree_skb(skb);
+		return;
+	}
+
+	__skb_trim(skb, msdu_len + skw_radio_desc->radiotap_hdr.it_len);
+
+	skb->dev = iface->ndev;
+	skb_reset_mac_header(skb);
+	skb->ip_summed = CHECKSUM_NONE;
+	skb->pkt_type = PACKET_OTHERHOST;
+	skb->protocol = htons(ETH_P_80211_RAW);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 18, 0)
+	netif_rx_ni(skb);
+#else
+	netif_rx(skb);
+#endif
+}
+
+static void skw_rx_data_handler(struct skw_core *skw,
+				struct sk_buff_head *rx_list, struct skw_list *rx_todo_list)
+{
+	struct sk_buff_head release_list;
+	struct skw_rx_desc *desc;
+	struct sk_buff *skb;
+
+	__skb_queue_head_init(&release_list);
+
+	while ((skb = __skb_dequeue(rx_list))) {
+		int msdu_offset = 0, msdu_len = 0;
+
+		if (is_skw_monitor_data(skw, skb->data)) {
+			skw_netif_monitor_rx(skw, skb);
+			continue;
+		}
+
+		desc = (struct skw_rx_desc *)skb_pull(skb, skw->hw.rx_desc.hdr_offset);
+
+		trace_skw_rx_data(desc->inst_id, desc->peer_idx, desc->tid,
+				  desc->msdu_filter, desc->sn, desc->is_qos_data,
+				  desc->retry_frame, desc->is_amsdu,
+				  desc->amsdu_idx, desc->amsdu_first_idx,
+				  desc->amsdu_last_idx, false);
+
+		if (desc->tid >= SKW_NR_TID) {
+			skw_warn("invlid peer: %d, tid: %d\n",
+				desc->peer_idx, desc->tid);
+
+			kfree_skb(skb);
+			continue;
+		}
+
+		msdu_offset = desc->msdu_offset -
+			      skw->hw.rx_desc.msdu_offset -
+			      skw->hw.rx_desc.hdr_offset;
+
+		skb_pull(skb, msdu_offset);
+		SKW_SKB_RXCB(skb)->rx_desc_offset = msdu_offset;
+		SKW_SKB_RXCB(skb)->rx_time = jiffies;
+
+		if (BIT(desc->msdu_filter & 0x1f) & SKW_RX_FILTER_DBG)
+			skw_dbg("filter: %d, sn: %d, sa: %pM\n",
+				desc->msdu_filter, desc->sn,
+				skw_eth_hdr(skb)->h_source);
+
+		if (skw->hw.bus == SKW_BUS_SDIO && test_bit(SKW_FLAG_FW_PN_REUSE, &skw->flags))
+			msdu_len = *((u16 *)&desc->pn[4]) + SKW_MSDU_HDR_LEN;
+		else
+			msdu_len = desc->msdu_len + SKW_MSDU_HDR_LEN;
+
+		if (desc->mac_drop_frag) {
+			int offset = round_up(msdu_len + desc->msdu_offset, 4);
+
+			offset -= desc->msdu_offset;
+			skw_rx_handler_drop_info(skw, skb, offset, &release_list);
+		}
+
+		skb_trim(skb, msdu_len);
+
+		rcu_read_lock();
+
+		skw_ampdu_reorder(skw, desc, skb, &release_list);
+
+		rcu_read_unlock();
+
+		skw_rx_handler(skw, &release_list);
+
+		skw_rx_todo(rx_todo_list);
+	}
+}
+
+static void skw_free_tid_rx(struct rcu_head *head)
+{
+	u16 win_end;
+	struct skw_tid_rx *tid_rx;
+	struct sk_buff_head release_list;
+
+	tid_rx = container_of(head, struct skw_tid_rx, rcu_head);
+
+	__skb_queue_head_init(&release_list);
+	win_end = ieee80211_sn_add(tid_rx->win_start, tid_rx->win_size - 1);
+
+	rcu_read_lock();
+
+	skw_reorder_force_release(tid_rx, win_end, &release_list,
+					SKW_RELEASE_FREE);
+
+	rcu_read_unlock();
+
+	skw_rx_handler(tid_rx->reorder->skw, &release_list);
+
+	SKW_KFREE(tid_rx->reorder_buf);
+	SKW_KFREE(tid_rx);
+}
+
+int skw_update_tid_rx(struct skw_peer *peer, u16 tid, u16 ssn, u16 win_size)
+{
+	struct skw_tid_rx *tid_rx;
+	struct skw_reorder_rx *reorder;
+
+	skw_dbg("inst: %d, peer: %d, tid: %d, ssn: %d, win size: %d\n",
+		peer->iface->id, peer->idx, tid, ssn, win_size);
+
+	trace_skw_rx_update_ba(peer->iface->id, peer->idx, tid, ssn);
+
+	rcu_read_lock();
+
+	reorder = &peer->reorder[tid];
+	tid_rx = rcu_dereference(reorder->tid_rx);
+	if (!tid_rx)
+		goto unlock;
+
+	spin_lock_bh(&reorder->todo.lock);
+
+	/* force to update rx todo list */
+	reorder->todo.seq = ssn;
+	reorder->todo.reason = SKW_RELEASE_BAR;
+
+	if (!reorder->todo.actived) {
+		reorder->todo.actived = true;
+		INIT_LIST_HEAD(&reorder->todo.list);
+		if (reorder->skw->hw.bus == SKW_BUS_PCIE)
+			skw_list_add(&reorder->skw->hw.lmac[peer->iface->lmac_id].rx_todo_list,
+				&reorder->todo.list);
+		else
+			skw_list_add(&reorder->skw->rx_todo_list, &reorder->todo.list);
+	}
+
+	spin_unlock_bh(&reorder->todo.lock);
+
+	skw_wakeup_rx(reorder->skw);
+
+unlock:
+	rcu_read_unlock();
+
+	return 0;
+}
+
+int skw_add_tid_rx(struct skw_peer *peer, u16 tid, u16 ssn, u16 buf_size)
+{
+	int i;
+	u32 win_sz;
+	struct skw_tid_rx *tid_rx;
+	struct skw_reorder_rx *reorder;
+
+	skw_dbg("peer: %d, tid: %d, ssn: %d, win size: %d\n",
+		peer->idx, tid, ssn, buf_size);
+
+	reorder = &peer->reorder[tid];
+
+	tid_rx = rcu_dereference(reorder->tid_rx);
+	if (tid_rx)
+		return skw_update_tid_rx(peer, tid, ssn, buf_size);
+
+	win_sz = buf_size > 64 ? buf_size : 64;
+	win_sz <<= 1;
+
+	trace_skw_rx_add_ba(peer->iface->id, peer->idx, tid, ssn, win_sz);
+
+	tid_rx = SKW_ZALLOC(sizeof(*tid_rx), GFP_KERNEL);
+	if (!tid_rx) {
+		skw_err("alloc failed, len: %ld\n", (long)(sizeof(*tid_rx)));
+		return -ENOMEM;
+	}
+
+	tid_rx->reorder_buf = kcalloc(win_sz, sizeof(struct sk_buff_head),
+				      GFP_KERNEL);
+	if (!tid_rx->reorder_buf) {
+		SKW_KFREE(tid_rx);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < win_sz; i++)
+		__skb_queue_head_init(&tid_rx->reorder_buf[i]);
+
+	tid_rx->win_start = ssn;
+	tid_rx->win_size = win_sz;
+	tid_rx->stored_num = 0;
+	tid_rx->reorder = reorder;
+	tid_rx->ref_cnt = atomic_read(&reorder->ref_cnt);
+
+	reorder->inst = peer->iface->id;
+	reorder->peer_idx = peer->idx;
+	reorder->tid = tid;
+
+	reorder->todo.seq = 0;
+	reorder->todo.actived = false;
+	reorder->todo.reason = SKW_RELEASE_INVALID;
+
+	reorder->skw = peer->iface->skw;
+	reorder->peer = peer;
+
+	rcu_assign_pointer(reorder->tid_rx, tid_rx);
+
+	return 0;
+}
+
+int skw_del_tid_rx(struct skw_peer *peer, u16 tid)
+{
+	struct skw_tid_rx *tid_rx;
+	struct skw_reorder_rx *reorder;
+	struct sk_buff_head release_list;
+	struct skw_list *todo_list;
+
+	reorder = &peer->reorder[tid];
+
+	__skb_queue_head_init(&release_list);
+
+	trace_skw_rx_del_ba(tid);
+
+	spin_lock_bh(&reorder->lock);
+	tid_rx = rcu_dereference_protected(reorder->tid_rx,
+			lockdep_is_held(&reorder->lock));
+
+	if (tid_rx) {
+		if (reorder->skw->hw.bus == SKW_BUS_PCIE)
+			todo_list = &reorder->skw->hw.lmac[peer->iface->lmac_id].rx_todo_list;
+		else
+			todo_list = &reorder->skw->rx_todo_list;
+
+		if (!list_empty(&reorder->todo.list))
+			skw_list_del(todo_list, &reorder->todo.list);
+	}
+
+	RCU_INIT_POINTER(reorder->tid_rx, NULL);
+
+	atomic_inc(&reorder->ref_cnt);
+
+	smp_wmb();
+
+	del_timer_sync(&reorder->timer);
+
+	if (tid_rx) {
+#ifdef CONFIG_SWT6621S_GKI_DRV
+		skw_call_rcu(peer->iface->skw, &tid_rx->rcu_head, skw_free_tid_rx);
+#else
+		call_rcu(&tid_rx->rcu_head, skw_free_tid_rx);
+#endif
+	}
+
+	spin_unlock_bh(&reorder->lock);
+
+	return 0;
+}
+
+#ifdef SKW_RX_WORKQUEUE
+
+void skw_rx_worker(struct work_struct *work)
+{
+	unsigned long flags;
+	struct skw_core *skw;
+	struct sk_buff_head qlist;
+
+	skw = container_of(work, struct skw_core, rx_worker);
+	__skb_queue_head_init(&qlist);
+
+	while (skw->rx_todo_list.count || skb_queue_len(&skw->rx_dat_q)) {
+
+		skw_rx_todo(&skw->rx_todo_list);
+
+		if (skb_queue_empty(&skw->rx_dat_q))
+			return;
+
+		/*
+		 * data frame format:
+		 * RX_DESC_HEADER + ETHERNET
+		 */
+		spin_lock_irqsave(&skw->rx_dat_q.lock, flags);
+		skb_queue_splice_tail_init(&skw->rx_dat_q, &qlist);
+		spin_unlock_irqrestore(&skw->rx_dat_q.lock, flags);
+
+		skw_rx_data_handler(skw, &qlist);
+	}
+}
+
+static int __skw_rx_init(struct skw_core *skw)
+{
+	int cpu;
+	struct workqueue_attrs wq_attrs;
+
+	skw->rx_wq = alloc_workqueue("skw_rxwq.%d", WQ_UNBOUND | __WQ_ORDERED, 1, skw->idx);
+	if (!skw->rx_wq) {
+		skw_err("alloc skwrx_workqueue failed\n");
+		return -EFAULT;
+	}
+
+	memset(&wq_attrs, 0, sizeof(wq_attrs));
+	wq_attrs.nice = MIN_NICE;
+
+	apply_workqueue_attrs(skw->rx_wq, &wq_attrs);
+
+	INIT_WORK(&skw->rx_worker, skw_rx_worker);
+
+	queue_work(skw->rx_wq, &skw->rx_worker);
+
+	return 0;
+}
+
+static void __skw_rx_deinit(struct skw_core *skw)
+{
+	atomic_set(&skw->exit, 1);
+	cancel_work_sync(&skw->rx_worker);
+	destroy_workqueue(skw->rx_wq);
+}
+
+#else
+
+int skw_rx_process(struct skw_core *skw, struct sk_buff_head *rx_dat_q, struct skw_list *rx_todo_list)
+{
+	unsigned long flags;
+	struct sk_buff_head qlist;
+
+	__skb_queue_head_init(&qlist);
+	while (!skb_queue_empty(rx_dat_q) || rx_todo_list->count) {
+		skw_rx_todo(rx_todo_list);
+
+		//skw_dbg("enter\n");
+
+		/*
+		 * data frame format:
+		 * RX_DESC_HEADER + ETHERNET
+		 */
+		spin_lock_irqsave(&rx_dat_q->lock, flags);
+		skb_queue_splice_tail_init(rx_dat_q, &qlist);
+		spin_unlock_irqrestore(&rx_dat_q->lock, flags);
+
+		skw_rx_data_handler(skw, &qlist, rx_todo_list);
+	}
+
+	return 0;
+}
+
+int skw_rx_poll_rx(struct napi_struct *napi, int budget)
+{
+	struct skw_core *skw = container_of(napi, struct skw_core, napi_rx);
+
+	skw_rx_process(skw, &skw->rx_dat_q, &skw->rx_todo_list);
+
+	if (skw->rx_todo_list.count)
+		return budget;
+
+	napi_complete(napi);
+
+	return 0;
+}
+
+static int skw_rx_thread(void *data)
+{
+	struct skw_core *skw;
+
+	skw = (struct skw_core *)data;
+	while (!kthread_should_stop()) {
+		skw_rx_process(skw, &skw->rx_dat_q, &skw->rx_todo_list);
+
+		if (skb_queue_empty(&skw->rx_dat_q) && !skw->rx_todo_list.count) {
+			set_current_state(TASK_IDLE);
+			schedule_timeout(msecs_to_jiffies(1));
+		}
+	}
+
+	skw_info("exit\n");
+
+	return 0;
+}
+
+static void *skw_thread_thread(int (*threadfn)(void *data),
+		void *data, const char * namefmt, u8 id)
+{
+	void *thread = NULL;
+
+	thread = kthread_create(threadfn, data, namefmt, id);
+	if (IS_ERR(thread)) {
+		skw_err("create rx thread failed\n");
+
+		return NULL;
+	}
+
+	skw_set_thread_priority(thread, SCHED_FIFO, 1);
+	set_user_nice(thread, MIN_NICE);
+	wake_up_process(thread);
+
+	return thread;
+}
+
+static int __skw_rx_init(struct skw_core *skw)
+{
+	skw->rx_thread = skw_thread_thread(skw_rx_thread, skw, "skw_rx.%d", skw->idx);
+	if (IS_ERR(skw->rx_thread)) {
+		return PTR_ERR(skw->rx_thread);
+	}
+
+#if 0
+	init_dummy_netdev(&skw->dummy_dev);
+	skw_compat_netif_napi_add_weight(&skw->dummy_dev, &skw->napi_rx, skw_rx_poll_rx, 64);
+	napi_enable(&skw->napi_rx);
+#endif
+
+	return 0;
+}
+
+static void __skw_rx_deinit(struct skw_core *skw)
+{
+#if 0
+	napi_disable(&skw->napi_rx);
+	netif_napi_del(&skw->napi_rx);
+#endif
+	if (skw->rx_thread) {
+		atomic_set(&skw->exit, 1);
+		kthread_stop(skw->rx_thread);
+		skw->rx_thread = NULL;
+	}
+}
+
+#endif
+
+static inline u8
+skw_port_to_lmacid(struct skw_core *skw, int port, bool multi_dport)
+{
+	int i;
+
+	for (i = 0; i < skw->hw.nr_lmac; i++) {
+		if (multi_dport) {
+			if (skw->hw.lmac[i].dport == port)
+				return i;
+		} else {
+			if (skw->hw.lmac[i].lport == port)
+				return i;
+		}
+	}
+
+	// BUG_ON(1);
+	skw_warn("invalid port: %d\n", port);
+
+	return SKW_INVALID_ID;
+}
+
+/*
+ * callback function, invoked by bsp
+ */
+int skw_rx_cb(int port, struct scatterlist *sglist,
+		     int nents, void *priv)
+{
+	int ret;
+	bool rx_sdma;
+	void *sg_addr;
+	int idx, total_len;
+	struct sk_buff *skb;
+	struct scatterlist *sg;
+	struct skw_msg *msg;
+	struct skw_iface *iface;
+	struct skw_event_work *work;
+	struct skw_core *skw = (struct skw_core *)priv;
+	u32 *data = NULL;
+	u8 usb_data_port = 0;
+	struct skw_skb_rxcb *cb = NULL;
+
+	rx_sdma = skw->hw_pdata->bus_type & RX_SDMA;
+
+	for_each_sg(sglist, sg, nents, idx) {
+		if (sg == NULL || !sg->length) {
+			skw_warn("sg: 0x%p, nents: %d, idx: %d, len: %d\n",
+				sg, nents, idx, sg ? sg->length : 0);
+			break;
+		}
+
+		sg_addr = sg_virt(sg);
+
+		//Port info only stored in the first sg for USB platform
+		if (skw->hw.bus == SKW_BUS_USB && idx == 0) {
+			data = (u32 *) sg_addr;
+			usb_data_port = data[2] & 0x1;
+			//skw_dbg("usb_data_port:%d\n", usb_data_port);
+		}
+
+		if (rx_sdma) {
+			skb = dev_alloc_skb(sg->length);
+			if (!skb) {
+				skw_err("alloc skb failed, len: %d\n", sg->length);
+				continue;
+			}
+
+			skw_put_skb_data(skb, sg_addr, sg->length);
+		} else {
+			total_len = SKB_DATA_ALIGN(sg->length) + skw->skb_share_len;
+			if (unlikely(total_len > SKW_ADMA_BUFF_LEN)) {
+				skw_warn("sg->length: %d, rx buff: %lu, share info: %d\n",
+					 sg->length, (long)SKW_ADMA_BUFF_LEN, skw->skb_share_len);
+
+				skw_compat_page_frag_free(sg_addr);
+				continue;
+			}
+
+			skb = build_skb(sg_addr, total_len);
+			if (!skb) {
+				skw_err("build skb failed, len: %d\n", total_len);
+
+				skw_compat_page_frag_free(sg_addr);
+				continue;
+			}
+
+			skb_put(skb, sg->length);
+		}
+		__net_timestamp(skb);
+		cb = SKW_SKB_RXCB(skb);
+		cb->rx_time = jiffies;
+
+		trace_skw_rx_irq(nents, idx, port, sg->length);
+
+		if (port == skw->hw_pdata->cmd_port) {
+			if (skw->hw.bus == SKW_BUS_SDIO)
+				skb_pull(skb, 4);
+			msg = (struct skw_msg *)skb_pull(skb, 12);
+			if (!msg) {
+				dev_kfree_skb(skb);
+				continue;
+			}
+
+			trace_skw_msg_rx(msg->inst_id, msg->type, msg->id,
+					msg->seq, msg->total_len);
+
+			switch (msg->type) {
+			case SKW_MSG_CMD_ACK:
+				skw->isr_cpu_id = raw_smp_processor_id();
+				skw_cmd_ack_handler(skw, skb->data, skb->len);
+				kfree_skb(skb);
+
+				break;
+
+			case SKW_MSG_EVENT:
+				if (++skw->skw_event_sn != msg->seq) {
+					skw_warn("invalid event seq: %d, expect: %d\n",
+						 msg->seq, skw->skw_event_sn);
+
+					skw_hw_assert(skw, false);
+					kfree_skb(skb);
+					continue;
+				}
+
+				if (msg->id == SKW_EVENT_CREDIT_UPDATE) {
+					skw_event_add_credit(skw, msg + 1);
+					smp_wmb();
+					kfree_skb(skb);
+
+					continue;
+				}
+
+				iface = to_skw_iface(skw, msg->inst_id);
+				if (iface)
+					work = &iface->event_work;
+				else
+					work = &skw->event_work;
+
+				ret = skw_queue_event_work(priv_to_wiphy(skw),
+							work, skb);
+				if (ret < 0) {
+					skw_err("inst: %d, drop event %d\n",
+						msg->inst_id, msg->id);
+					kfree_skb(skb);
+				}
+
+				break;
+
+			default:
+				skw_warn("invalid: type: %d, id: %d, seq: %d\n",
+					msg->type, msg->id, msg->seq);
+
+				kfree_skb(skb);
+				break;
+			}
+
+		} else {
+			if (skw->hw.bus == SKW_BUS_SDIO && !test_bit(SKW_FLAG_FW_PN_REUSE, &skw->flags))
+				skb_pull(skb, 4);
+
+			skw_data_add_credit(skw, skb->data);
+
+			if (skw->hw.bus == SKW_BUS_USB)
+				cb->lmac_id = skw_port_to_lmacid(skw, usb_data_port, false);
+			else
+				cb->lmac_id = skw_port_to_lmacid(skw, port, true);
+
+			if (cb->lmac_id == SKW_INVALID_ID) {
+				dev_kfree_skb(skb);
+				continue;
+			}
+
+			//skw_dbg("lmac_id:%d\n", cb->lmac_id);
+			skb_queue_tail(&skw->rx_dat_q, skb);
+
+			skw->rx_packets++;
+			if (skw->hw.bus == SKW_BUS_SDIO || skw->hw.bus == SKW_BUS_SDIO2)
+				set_cpus_allowed_ptr(skw->rx_thread, cpumask_of(task_cpu(current)));
+			skw->isr_cpu_id = raw_smp_processor_id();
+			skw_wakeup_rx(skw);
+
+			skw_wake_lock_timeout(skw, 400);
+		}
+	}
+
+	return 0;
+}
+
+int skw_register_rx_callback(struct skw_core *skw, void *cmd_cb, void *cmd_ctx,
+			void *dat_cb, void *dat_ctx)
+{
+	int i, map, ret = 0;
+
+	if (skw->hw.bus == SKW_BUS_PCIE)
+		return 0;
+
+	ret = skw_register_rx_cb(skw, skw->hw.cmd_port, cmd_cb, cmd_ctx);
+	if (ret < 0) {
+		skw_err("failed, command port: %d, ret: %d\n",
+			skw->hw.cmd_port, ret);
+
+		return ret;
+	}
+
+	for (map = 0, i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
+		int port = skw->hw.lmac[i].dport;
+
+		if (!(skw->hw.lmac[i].flags & SKW_LMAC_FLAG_RXCB))
+			continue;
+
+		ret = skw_register_rx_cb(skw, port, dat_cb, dat_ctx);
+		if (ret < 0) {
+			skw_err("failed, data port: %d, ret: %d\n", port, ret);
+
+			break;
+		}
+
+		map |= BIT(port);
+	}
+
+	skw_dbg("%s cmd port: %d, data port bitmap: 0x%x\n",
+		cmd_cb ? "register" : "unregister", skw->hw.cmd_port, map);
+
+	return ret;
+}
+
+int skw_rx_init(struct skw_core *skw)
+{
+	int ret;
+
+	skw_list_init(&skw->rx_todo_list);
+	spin_lock_init(&skw->rx_lock);
+	skw_wake_lock_init(skw, 0, "skw_rx_wlock");
+
+	ret = skw_register_rx_callback(skw, skw_rx_cb, skw, skw_rx_cb, skw);
+	if (ret < 0) {
+		skw_err("register rx callback failed, ret: %d\n", ret);
+		return ret;
+	}
+
+	ret = __skw_rx_init(skw);
+	if (ret < 0)
+		skw_register_rx_callback(skw, NULL, NULL, NULL, NULL);
+
+	rx_reorder_flag = true;
+	skw_debugfs_file(skw->dentry, "rx_reorder", 0666, &skw_rx_reorder_fops, NULL);
+
+	skw_debugfs_file(skw->dentry, "pn_reuse", 0666, &skw_pn_reuse_fops, skw);
+
+	return 0;
+}
+
+int skw_rx_deinit(struct skw_core *skw)
+{
+	skw_register_rx_callback(skw, NULL, NULL, NULL, NULL);
+
+	__skw_rx_deinit(skw);
+	skw_rx_todo(&skw->rx_todo_list);
+
+	skw_wake_lock_deinit(skw);
+
+	return 0;
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_rx.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_rx.h
new file mode 100755
index 0000000..1fb0721
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_rx.h
@@ -0,0 +1,314 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_RX_H__
+#define __SKW_RX_H__
+
+#include <net/ieee80211_radiotap.h>
+
+#include "skw_platform_data.h"
+#include "skw_iface.h"
+#include "skw_core.h"
+#include "skw_tx.h"
+
+#define SKW_MAX_AMPDU_BUF_SIZE            0x100 /* 256 */
+
+#define SKW_AMSDU_FLAG_TAINT              BIT(0)
+#define SKW_AMSDU_FLAG_VALID              BIT(1)
+
+#define SKW_SDIO_RX_DESC_HDR_OFFSET       0
+#define SKW_SDIO_RX_DESC_MSDU_OFFSET      52
+#define SKW_USB_RX_DESC_HDR_OFFSET        52
+#define SKW_USB_RX_DESC_MSDU_OFFSET       0
+#define SKW_PCIE_RX_DESC_HDR_OFFSET       44
+#define SKW_PCIE_RX_DESC_MSDU_OFFSET      8
+
+#ifndef ETH_P_80211_RAW
+#define ETH_P_80211_RAW (ETH_P_ECONET + 1)
+#endif
+
+enum SKW_RELEASE_REASON {
+	SKW_RELEASE_INVALID,
+	SKW_RELEASE_EXPIRED,
+	SKW_RELEASE_OOB,
+	SKW_RELEASE_BAR,
+	SKW_RELEASE_FREE,
+};
+
+struct skw_skb_rxcb {
+	unsigned long rx_time;
+	u16 rx_desc_offset;
+	u8 amsdu_bitmap;
+	u8 amsdu_mask;
+	u16 amsdu_flags;
+	u8 skw_created;
+	u8 lmac_id;
+	u8 skip_replay_detect;
+};
+
+struct skw_drop_sn_info {
+	u16 sn;
+	u8 amsdu_idx;
+	u8 amsdu_first: 1;
+	u8 amsdu_last: 1;
+	u8 is_amsdu: 1;
+	u8 qos: 1;
+	u8 tid: 4;
+	u32 peer_idx: 5;
+	u32 inst: 2;
+	u32 resved: 25;
+} __packed;
+
+struct skw_rx_desc {
+	/* word 13 */
+	u16 eosp:1;
+	u16 more_data:1;
+	u16 pm:1;
+	u16 retry_frame:1;
+	u16 is_eof:1; //mpdu_eof_flag
+	u16 ba_session:1;
+	u16 resv1:1;
+	u16 resv:1;
+	u16 cipher:4;
+	u16 snap_type:1;
+	u16 vlan:1;
+	u16 eapol:1;
+	u16 rcv_in_ps_mode:1;
+	u16 msdu_len;
+
+	/* word 14 */
+	u8 csum_valid:1;
+	u8 is_ampdu:1;
+	u8 snap_match:1;
+	u8 is_amsdu:1;
+	u8 is_qos_data:1;
+	u8 amsdu_first_idx:1;
+	u8 amsdu_last_idx:1;
+	u8 mpdu_sniff:1;
+	u16 csum;
+	u8 msdu_filter;
+
+	/* word 15 */
+	u16 sn:12; /* seq number */
+	u16 frag_num:4;
+	u16 inst_id:2; //mpdu_ra_index
+	u16 inst_id_valid:1;
+	u16 more_frag:1;
+	u16 peer_idx:5;
+	u16 peer_idx_valid:1;
+	u16 is_mc_addr:1; //bc_mc_flag
+	u16 first_msdu_in_buff:1;
+	u16 tid:4;
+
+	/* word 16 & word17*/
+	u8 pn[6];   //u16 msdu_len; //32:47
+	u8 msdu_offset;
+	u8 amsdu_idx:6;
+	u16 need_forward:1;//da_ra_diff
+	u16 mac_drop_frag:1;
+} __packed;
+
+struct skw_phy_rx_desc {
+	/* word 8 */
+	u32 lgacy_len:12;
+	u32 psdu_len:20;
+
+	/* word 9 */
+	u32 flock_rssi0:11;
+	u32 flock_rssi1:11;
+	u32 lp_snr0:7;
+	u32 nss:2;
+	u32 sigb_dcm:1;
+
+	/* word 10 */
+	u32 full_rssi0:11;
+	u32 full_rssi1:11;
+	u32 lp_snr1:7;
+	u32 sbw:3;
+
+	/* word 11 */
+	u8 agc_gain0;
+	u8 agc_gain1;
+	u8 ppdu_mode:4;
+	u8 dcm:1;
+	u8 gi_type:2;
+	u8 fec_coding:1;
+	u8 data_rate:6;
+	u8 ess_n_est_ss:2;
+
+	/* word 12 */
+	u16 sta_id:11;
+	u16 ru_size:3;
+	u16 he_sigb_comp:1;
+	u16 doppler:1;
+	u16 sr4:4;
+	u16 sr3:4;
+	u16 sr2:4;
+	u16 sr1:4;
+
+	/* word 13 */
+	u16 grp_id:6;
+	u16 partial_aid:9;
+	u16 befmed:1;
+	u16 top_dura:14;
+	u16 ltf_type:2;
+
+	/* word 14 */
+	u8 ch1_agc_gain0;
+	u8 ch1_agc_gain1;
+	u16 serv_field;
+
+	/* word 15 */
+	u32 sfo_ppm_init:24;
+	u32 plcp_delay:8;
+
+	/* word 16 */
+	u32 slock_rssi0:11;
+	u32 slock_rssi2:11;
+	u32 nsts:2;
+	u32 bss_color:6;
+	u32 tgnf_flag0:1;
+	u32 tgnf_flag1:1;
+
+	/* word 17 */
+	u8 ofdma:1;
+	u8 mimo:1;
+	u8 sifb_mcs:3;
+	u8 pe_dur:3;
+	u8 user_num:7;
+	u8 stbc:1;
+	u16 mu3_nsts:3;
+	u16 mu2_nsts:3;
+	u16 mu1_nsts:3;
+	u16 mu0_nsts:3;
+	u16 ltf_num:2;
+	u16 mimo_ofdma:1;
+	u16 resv8:1;
+
+};
+
+struct skw_sniffer_desc {
+	u32 resv1;
+	/* word 4 */
+	u8 mac_hdr_proc:7;
+	u8 sniff_flag:1;
+	u8 mpdu_proc_status;
+	u8 buf_num_mpdu;
+	u8 mac_hdr_len:6;
+	u8 dir_data_sniff:1;
+	u8 resv2:1;
+
+	/* word 5 */
+	u16 mpdu_len:14;
+	u16 resv3:2;
+	u16 psdu_cnt;
+
+	/* word 6 */
+	u8 sniff_rsv_num;
+	u8 resv4:1;
+	u8 is_ampdu:1;
+	u8 is_amsdu:1;
+	u8 mpdu2host:1;
+	u8 mpdu_defrag:1;
+	u8 mpdu_uc:1;
+	u8 mpdu_bc:1;
+	u8 resv5:1;
+	u8 peer_lut_idx:5;
+	u8 peer_lut_idx_vaild:1;
+	u8 resv6:2;
+	u8 cipher:4;
+	u8 inst_id:2; //mpdu_ra_index
+	u8 inst_id_vaild:1;
+	u8 resv7:1;
+
+	/* word 7 */
+	u16 sniff_status:12;
+	u16 pad_len:4;
+	u16 sn:12;
+	u16 frag_num:4;
+
+	/* word 8 - 17 */
+	struct skw_phy_rx_desc phy_desc;
+} __packed;
+
+struct skw_radiotap_desc {
+	struct ieee80211_radiotap_header radiotap_hdr;
+	u8 radiotap_flag;
+} __packed;
+
+static inline void skw_snap_unmatch_handler(struct sk_buff *skb)
+{
+	skb_reset_mac_header(skb);
+	eth_hdr(skb)->h_proto = htons(skb->len & 0xffff);
+}
+
+static inline void skw_event_add_credit(struct skw_core *skw, void *data)
+{
+	u16 *credit = data;
+
+	skw_add_credit(skw, 0, *credit);
+	skw_add_credit(skw, 1, *(credit + 1));
+}
+
+static inline void skw_data_add_credit(struct skw_core *skw, void *data)
+{
+}
+
+static inline bool is_skw_monitor_data(struct skw_core *skw, void *data)
+{
+	struct skw_sniffer_desc *desc = NULL;
+
+	if (skw->hw.bus == SKW_BUS_USB)
+		desc = (struct skw_sniffer_desc *)((u8 *)(data + 12));	//offset word0 ~ word2
+	else if (skw->hw.bus == SKW_BUS_SDIO)
+		desc = (struct skw_sniffer_desc *)((u8 *)(data));
+	else if (skw->hw.bus == SKW_BUS_PCIE) //TODO
+		return false;
+
+	if (!desc)
+		return -EINVAL;
+
+	skw_detail("sniffer flag:%d\n", desc->sniff_flag);
+
+	if (desc->sniff_flag) {
+		skw_detail("recv sniffer data, desc len:%ld\n", sizeof(struct skw_sniffer_desc));
+		return true;
+	}
+
+	return false;
+}
+
+static inline struct skw_skb_rxcb *SKW_SKB_RXCB(struct sk_buff *skb)
+{
+	return (struct skw_skb_rxcb *)skb->cb;
+}
+
+int skw_add_tid_rx(struct skw_peer *peer, u16 tid, u16 ssn, u16 buf_size);
+int skw_update_tid_rx(struct skw_peer *peer, u16 tid, u16 ssn, u16 win_size);
+int skw_del_tid_rx(struct skw_peer *peer, u16 tid);
+
+int skw_rx_process(struct skw_core *skw,
+	struct sk_buff_head *rx_dat_q, struct skw_list *rx_todo_list);
+void skw_rx_todo(struct skw_list *todo_list);
+
+int skw_rx_init(struct skw_core *skw);
+int skw_rx_deinit(struct skw_core *skw);
+int skw_rx_cb(int port, struct scatterlist *sglist, int nents, void *priv);
+int skw_register_rx_callback(struct skw_core *skw, void *cmd_cb, void *cmd_ctx,
+			void *dat_cb, void *dat_ctx);
+
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tdls.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tdls.c
new file mode 100755
index 0000000..28f4d64
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tdls.c
@@ -0,0 +1,1088 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/etherdevice.h>
+#include <linux/ieee80211.h>
+
+#include "skw_core.h"
+#include "skw_cfg80211.h"
+#include "skw_iface.h"
+#include "skw_work.h"
+#include "skw_log.h"
+#include "skw_tx.h"
+#include "skw_compat.h"
+#include "skw_msg.h"
+#include "skw_tdls.h"
+
+static size_t skw_skip_ie(const u8 *ies, size_t ielen, size_t pos)
+{
+	/* we assume a validly formed IEs buffer */
+	u8 len = ies[pos + 1];
+
+	pos += 2 + len;
+
+	/* the IE itself must have 255 bytes for fragments to follow */
+	if (len < 255)
+		return pos;
+
+	while (pos < ielen && ies[pos] == SKW_WLAN_EID_FRAGMENT) {
+		len = ies[pos + 1];
+		pos += 2 + len;
+	}
+
+	return pos;
+}
+
+static bool skw_id_in_list(const u8 *ids, int n_ids, u8 id, bool id_ext)
+{
+	int i = 0;
+
+	/* Make sure array values are legal */
+	if (WARN_ON(ids[n_ids - 1] == SKW_WLAN_EID_EXTENSION))
+		return false;
+
+	while (i < n_ids) {
+		if (ids[i] == SKW_WLAN_EID_EXTENSION) {
+			if (id_ext && (ids[i + 1] == id))
+				return true;
+
+			i += 2;
+			continue;
+		}
+
+		if (ids[i] == id && !id_ext)
+			return true;
+
+		i++;
+	}
+
+	return false;
+}
+
+static size_t skw_ie_split_ric(const u8 *ies, size_t ielen,
+			const u8 *ids, int n_ids,
+			const u8 *after_ric, int n_after_ric,
+			size_t offset)
+{
+	size_t pos = offset;
+
+	while (pos < ielen) {
+		u8 ext = 0;
+
+		if (ies[pos] == SKW_WLAN_EID_EXTENSION)
+			ext = 2;
+		if ((pos + ext) >= ielen)
+			break;
+
+		if (!skw_id_in_list(ids, n_ids, ies[pos + ext],
+					  ies[pos] == SKW_WLAN_EID_EXTENSION))
+			break;
+
+		if (ies[pos] == WLAN_EID_RIC_DATA && n_after_ric) {
+			pos = skw_skip_ie(ies, ielen, pos);
+
+			while (pos < ielen) {
+				if (ies[pos] == SKW_WLAN_EID_EXTENSION)
+					ext = 2;
+				else
+					ext = 0;
+
+				if ((pos + ext) >= ielen)
+					break;
+
+				if (!skw_id_in_list(after_ric,
+							  n_after_ric,
+							  ies[pos + ext],
+							  ext == 2))
+					pos = skw_skip_ie(ies, ielen, pos);
+				else
+					break;
+			}
+		} else {
+			pos = skw_skip_ie(ies, ielen, pos);
+		}
+	}
+
+	return pos;
+}
+
+static bool skw_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) {
+		if (chandef->width > NL80211_CHAN_WIDTH_40)
+			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;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
+	case NL80211_CHAN_WIDTH_10:
+	case NL80211_CHAN_WIDTH_5:
+		return false; /* unsupported for now */
+#endif
+	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 * 4) {
+		if (chandef->width >= NL80211_CHAN_WIDTH_40)
+			return false;
+
+		*op_class = 180;
+		return true;
+	}
+
+	/* not supported yet */
+	return false;
+}
+
+static void skw_tdls_add_link_ie(struct net_device *ndev, struct sk_buff *skb,
+		const u8 *peer, bool initiator)
+{
+	struct skw_iface *iface = netdev_priv(ndev);
+	struct ieee80211_tdls_lnkie *lnk;
+	const u8 *src_addr, *dst_addr;
+
+	if (initiator) {
+		src_addr = ndev->dev_addr;
+		dst_addr = peer;
+	} else {
+		src_addr = peer;
+		dst_addr = ndev->dev_addr;
+	}
+
+	lnk = (struct ieee80211_tdls_lnkie *)skb_put(skb, sizeof(*lnk));
+
+	lnk->ie_type = WLAN_EID_LINK_ID;
+	lnk->ie_len = sizeof(struct ieee80211_tdls_lnkie) - 2;
+
+	memcpy(lnk->bssid, iface->sta.core.bss.bssid, ETH_ALEN);
+	memcpy(lnk->init_sta, src_addr, ETH_ALEN);
+	memcpy(lnk->resp_sta, dst_addr, ETH_ALEN);
+}
+
+static int skw_add_srates_ie(struct net_device *ndev, struct sk_buff *skb,
+		bool need_basic, enum nl80211_band band)
+{
+	struct ieee80211_supported_band *sband;
+	struct skw_iface *iface = netdev_priv(ndev);
+	int rate, shift = 0;
+	u8 i, rates, *pos;
+	//u32 basic_rates = sdata->vif.bss_conf.basic_rates;
+	u32 basic_rates = 0xFFFF;
+	u32 rate_flags = 0;
+
+	//shift = ieee80211_vif_get_shift(&sdata->vif);
+	//shift = ieee80211_vif_get_shift(&sdata->vif);
+	//rate_flags = ieee80211_chandef_rate_flags(&sdata->vif.bss_conf.chandef);
+	sband = iface->wdev.wiphy->bands[band];
+	rates = 0;
+	for (i = 0; i < sband->n_bitrates; i++) {
+		if ((rate_flags & sband->bitrates[i].flags) != rate_flags)
+			continue;
+		rates++;
+	}
+	if (rates > 8)
+		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++) {
+		u8 basic = 0;
+
+		if ((rate_flags & sband->bitrates[i].flags) != rate_flags)
+			continue;
+
+		if (need_basic && basic_rates & BIT(i))
+			basic = 0x80;
+		rate = DIV_ROUND_UP(sband->bitrates[i].bitrate,
+				    5 * (1 << shift));
+		*pos++ = basic | (u8) rate;
+	}
+
+	return 0;
+}
+
+static int skw_add_ext_srates_ie(struct net_device *ndev,
+				struct sk_buff *skb, bool need_basic,
+				enum nl80211_band band)
+{
+	struct ieee80211_supported_band *sband;
+	int rate, shift = 0;
+	u8 i, exrates, *pos;
+	//u32 basic_rates = sdata->vif.bss_conf.basic_rates;
+	u32 basic_rates = 0xFFFF;
+	u32 rate_flags = 0;
+	struct skw_iface *iface = netdev_priv(ndev);
+
+	//rate_flags = ieee80211_chandef_rate_flags(&sdata->vif.bss_conf.chandef);
+	//shift = ieee80211_vif_get_shift(&sdata->vif);
+
+	sband = iface->wdev.wiphy->bands[band];
+	exrates = 0;
+	for (i = 0; i < sband->n_bitrates; i++) {
+		if ((rate_flags & sband->bitrates[i].flags) != rate_flags)
+			continue;
+		exrates++;
+	}
+
+	if (exrates > 8)
+		exrates -= 8;
+	else
+		exrates = 0;
+
+	if (skb_tailroom(skb) < exrates + 2)
+		return -ENOMEM;
+
+	if (exrates) {
+		pos = skb_put(skb, exrates + 2);
+		*pos++ = WLAN_EID_EXT_SUPP_RATES;
+		*pos++ = exrates;
+		for (i = 8; i < sband->n_bitrates; i++) {
+			u8 basic = 0;
+
+			if ((rate_flags & sband->bitrates[i].flags)
+			    != rate_flags)
+				continue;
+			if (need_basic && basic_rates & BIT(i))
+				basic = 0x80;
+			rate = DIV_ROUND_UP(sband->bitrates[i].bitrate,
+					    5 * (1 << shift));
+			*pos++ = basic | (u8) rate;
+		}
+	}
+
+	return 0;
+}
+
+static u8
+skw_tdls_add_subband(struct net_device *ndev, struct sk_buff *skb,
+		u16 start, u16 end, u16 spacing)
+{
+	u8 subband_cnt = 0, ch_cnt = 0;
+	struct ieee80211_channel *ch;
+	struct cfg80211_chan_def chandef;
+	int i, subband_start;
+	struct skw_iface *iface = netdev_priv(ndev);
+	struct wiphy *wiphy = iface->wdev.wiphy;
+
+	for (i = start; i <= end; i += spacing) {
+		if (!ch_cnt)
+			subband_start = i;
+
+		ch = ieee80211_get_channel(iface->wdev.wiphy, i);
+		if (ch) {
+			/* we will be active on the channel */
+			cfg80211_chandef_create(&chandef, ch,
+						NL80211_CHAN_NO_HT);
+			if (skw_compat_reg_can_beacon(wiphy, &chandef,
+						      iface->wdev.iftype)) {
+				ch_cnt++;
+				/*
+				 * check if the next channel is also part of
+				 * this allowed range
+				 */
+				continue;
+			}
+		}
+
+		/*
+		 * we've reached the end of a range, with allowed channels
+		 * found
+		 */
+		if (ch_cnt) {
+			u8 *pos = skb_put(skb, 2);
+			*pos++ = skw_freq_to_chn(subband_start);
+			*pos++ = ch_cnt;
+
+			subband_cnt++;
+			ch_cnt = 0;
+		}
+	}
+
+	/* all channels in the requested range are allowed - add them here */
+	if (ch_cnt) {
+		u8 *pos = skb_put(skb, 2);
+		*pos++ = skw_freq_to_chn(subband_start);
+		*pos++ = ch_cnt;
+
+		subband_cnt++;
+	}
+
+	return subband_cnt;
+}
+
+static void
+skw_tdls_add_supp_channels(struct net_device *ndev, struct sk_buff *skb)
+{
+	/*
+	 * Add possible channels for TDLS. These are channels that are allowed
+	 * to be active.
+	 */
+	u8 subband_cnt;
+	u8 *pos = skb_put(skb, 2);
+
+	*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 */
+	subband_cnt = skw_tdls_add_subband(ndev, skb, 2412, 2472, 5);
+
+	/* 5GHz, with 20MHz spacing */
+	subband_cnt += skw_tdls_add_subband(ndev, skb, 5000, 5825, 20);
+
+	/* length */
+	*pos = 2 * subband_cnt;
+}
+
+static void skw_tdls_add_ext_capab(struct net_device *ndev,
+				struct sk_buff *skb)
+{
+	u8 cap;
+	//struct ieee80211_supported_band *sband;
+	//struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+
+	//bool wider_band = ieee80211_hw_check(&local->hw, TDLS_WIDER_BW) &&
+			  //!ifmgd->tdls_wider_bw_prohibited;
+	//bool buffer_sta = ieee80211_hw_check(&local->hw,
+	//				     SUPPORTS_TDLS_BUFFER_STA);
+#ifdef WLAN_EXT_CAPA8_TDLS_WIDE_BW_ENABLED
+	struct skw_iface *iface = netdev_priv(ndev);
+	enum nl80211_band band = iface->sta.core.bss.channel->band;
+	struct ieee80211_supported_band *sband = iface->wdev.wiphy->bands[band];
+	bool vht = sband && sband->vht_cap.vht_supported;
+	bool wider_band = false;
+#endif
+	u8 *pos = skb_put(skb, 10);
+
+	*pos++ = WLAN_EID_EXT_CAPABILITY;
+	*pos++ = 8; /* len */
+	*pos++ = 0x0;
+	*pos++ = 0x0;
+	*pos++ = 0x0;
+
+	cap = 0;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+	cap |= WLAN_EXT_CAPA4_TDLS_BUFFER_STA;
+
+	if (iface->wdev.wiphy->features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH)
+		cap |= WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH;
+#endif
+	*pos++ = cap;
+	*pos++ = WLAN_EXT_CAPA5_TDLS_ENABLED;
+	*pos++ = 0;
+	*pos++ = 0;
+#ifdef WLAN_EXT_CAPA8_TDLS_WIDE_BW_ENABLED
+	*pos++ = (vht && wider_band) ? WLAN_EXT_CAPA8_TDLS_WIDE_BW_ENABLED : 0;
+#else
+	*pos++ = 0;
+#endif
+}
+
+/**
+ * @brief append wmm ie
+ *
+ * @param skb              A pointer to sk_buff structure
+ * @param wmm_type         SKW_WMM_TYPE_INFO/SKW_WMM_TYPE_PARAMETER
+ * @param pQosInfo         A pointer to qos info
+ *
+ * @return                      N/A
+ */
+static void
+skw_add_wmm_ie(struct skw_iface *iface, struct sk_buff *skb,
+		u8 wmm_type, u8 *pQosInfo)
+{
+	u8 wmmInfoElement[] = { 0x00, 0x50, 0xf2, 0x02, 0x00, 0x01 };
+	u8 wmmParamElement[] = { 0x00, 0x50, 0xf2, 0x02, 0x01, 0x01};
+
+	u8 qosInfo = 0x0;
+	u8 reserved = 0;
+	u8 wmmParamIe_len = 24;
+	u8 wmmInfoIe_len = 7;
+	u8 len = 0;
+	u8 *pos;
+
+	if (skb_tailroom(skb) < wmmParamIe_len + 2)
+		return;
+
+	qosInfo = (pQosInfo == NULL) ? 0xf : (*pQosInfo);
+
+	/*wmm parameter */
+	if (wmm_type == SKW_WMM_TYPE_PARAMETER) {
+		pos = skb_put(skb, wmmParamIe_len + 2);
+		len = wmmParamIe_len;
+	} else {
+		pos = skb_put(skb, wmmInfoIe_len + 2);
+		len = wmmInfoIe_len;
+	}
+
+	*pos++ = WLAN_EID_VENDOR_SPECIFIC;
+	*pos++ = len;
+
+	/*wmm parameter */
+	if (wmm_type == SKW_WMM_TYPE_PARAMETER) {
+		memcpy(pos, wmmParamElement, sizeof(wmmParamElement));
+		pos += sizeof(wmmParamElement);
+	} else {
+		memcpy(pos, wmmInfoElement, sizeof(wmmInfoElement));
+		pos += sizeof(wmmInfoElement);
+	}
+	*pos++ = qosInfo;
+
+	/* wmm parameter */
+	if (wmm_type == SKW_WMM_TYPE_PARAMETER) {
+		*pos++ = reserved;
+		/* Use the same WMM AC parameters as STA for TDLS link */
+		memcpy(pos, &iface->wmm.ac[0], sizeof(struct skw_ac_param));
+		pos += sizeof(struct skw_ac_param);
+		memcpy(pos, &iface->wmm.ac[1], sizeof(struct skw_ac_param));
+		pos += sizeof(struct skw_ac_param);
+		memcpy(pos, &iface->wmm.ac[2], sizeof(struct skw_ac_param));
+		pos += sizeof(struct skw_ac_param);
+		memcpy(pos, &iface->wmm.ac[3], sizeof(struct skw_ac_param));
+	}
+}
+
+static void
+skw_tdls_add_oper_classes(struct net_device *ndev, struct sk_buff *skb)
+{
+	u8 *pos;
+	u8 op_class;
+	int freq;
+	struct skw_iface *iface = netdev_priv(ndev);
+	struct cfg80211_chan_def chandef;
+	struct ieee80211_channel *channel, *bss_chn;
+
+	bss_chn = iface->sta.core.bss.channel;
+	freq = ieee80211_channel_to_frequency(bss_chn->hw_value, bss_chn->band);
+	channel = ieee80211_get_channel(ndev->ieee80211_ptr->wiphy, freq);
+
+	cfg80211_chandef_create(&chandef, channel, NL80211_CHAN_NO_HT);
+
+	if (!skw_chandef_to_operating_class(&chandef, &op_class))
+		return;
+
+	pos = skb_put(skb, 4);
+	*pos++ = WLAN_EID_SUPPORTED_REGULATORY_CLASSES;
+	*pos++ = 2; /* len */
+
+	*pos++ = op_class;
+	*pos++ = op_class; /* give current operating class as alternate too */
+}
+
+#if 0
+u8 *skw_ie_build_ht_cap(u8 *pos, struct ieee80211_sta_ht_cap *ht_cap,
+			      u16 cap)
+{
+	__le16 tmp;
+
+	*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);
+
+	return pos;
+}
+
+u8 *ieee80211_ie_build_vht_cap(u8 *pos, struct ieee80211_sta_vht_cap *vht_cap,
+			       u32 cap)
+{
+	__le32 tmp;
+
+	*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);
+
+	return pos;
+}
+#endif
+
+static void
+skw_tdls_add_setup_start_ies(struct net_device *ndev, struct sk_buff *skb,
+		const u8 *peer, u32 peer_cap, u8 action_code, bool initiator,
+		const u8 *ies, size_t ies_len)
+{
+	struct ieee80211_supported_band *sband;
+	//struct ieee80211_sta_ht_cap ht_cap;
+	//struct ieee80211_sta_vht_cap vht_cap;
+	size_t offset = 0, noffset;
+	struct skw_iface *iface = netdev_priv(ndev);
+	//u8 *pos;
+	enum nl80211_band band;
+
+	if (iface->sta.core.bss.channel) {
+		band = iface->sta.core.bss.channel->band;
+	} else {
+		skw_err("bss is null\n");
+		return;
+	}
+
+	sband = iface->wdev.wiphy->bands[band];
+	if (!sband)
+		return;
+
+	skw_add_srates_ie(ndev, skb, false, band);
+	skw_add_ext_srates_ie(ndev, skb, false, band);
+	skw_tdls_add_supp_channels(ndev, skb);
+
+	/* Add any custom IEs that go before Extended Capabilities */
+	if (ies_len) {
+		static const u8 before_ext_cap[] = {
+			WLAN_EID_SUPP_RATES,
+			WLAN_EID_COUNTRY,
+			WLAN_EID_EXT_SUPP_RATES,
+			WLAN_EID_SUPPORTED_CHANNELS,
+			WLAN_EID_RSN,
+		};
+		noffset = skw_ie_split_ric(ies, ies_len, before_ext_cap,
+				ARRAY_SIZE(before_ext_cap), NULL, 0, offset);
+		skw_put_skb_data(skb, ies + offset, noffset - offset);
+		offset = noffset;
+	}
+
+	skw_tdls_add_ext_capab(ndev, skb);
+
+	/* add the QoS element if we support it */
+	if (action_code != WLAN_PUB_ACTION_TDLS_DISCOVER_RES)
+		skw_add_wmm_ie(iface, skb, SKW_WMM_TYPE_INFO, NULL);
+
+	/* add any custom IEs that go before HT capabilities */
+	if (ies_len) {
+		static const u8 before_ht_cap[] = {
+			WLAN_EID_SUPP_RATES,
+			WLAN_EID_COUNTRY,
+			WLAN_EID_EXT_SUPP_RATES,
+			WLAN_EID_SUPPORTED_CHANNELS,
+			WLAN_EID_RSN,
+			WLAN_EID_EXT_CAPABILITY,
+			WLAN_EID_QOS_CAPA,
+			WLAN_EID_FAST_BSS_TRANSITION,
+			WLAN_EID_TIMEOUT_INTERVAL,
+			WLAN_EID_SUPPORTED_REGULATORY_CLASSES,
+		};
+		noffset = skw_ie_split_ric(ies, ies_len, before_ht_cap,
+				ARRAY_SIZE(before_ht_cap), NULL, 0,  offset);
+		skw_put_skb_data(skb, ies + offset, noffset - offset);
+		offset = noffset;
+	}
+
+	skw_tdls_add_oper_classes(ndev, skb);
+	skw_tdls_add_link_ie(ndev, skb, peer, initiator);
+
+	/* add any remaining IEs */
+	if (ies_len) {
+		noffset = ies_len;
+		skw_put_skb_data(skb, ies + offset, noffset - offset);
+	}
+}
+
+static void
+skw_tdls_add_setup_cfm_ies(struct net_device *ndev,
+			struct sk_buff *skb, const u8 *peer,
+			u32 peer_cap, bool initiator,
+			const u8 *extra_ies, size_t extra_ies_len)
+{
+	struct skw_iface *iface = netdev_priv(ndev);
+	size_t offset = 0, noffset;
+	struct ieee80211_supported_band *sband = NULL;
+	enum nl80211_band band;
+
+	band = iface->sta.core.bss.channel->band;
+	sband = iface->wdev.wiphy->bands[band];
+
+	if (!sband)
+		return;
+
+	/* add any custom IEs that go before the QoS IE */
+	if (extra_ies_len) {
+		static const u8 before_qos[] = {
+			WLAN_EID_RSN,
+		};
+		noffset = skw_ie_split_ric(extra_ies, extra_ies_len,
+					     before_qos,
+					     ARRAY_SIZE(before_qos),
+					     NULL, 0,
+					     offset);
+		skw_put_skb_data(skb, extra_ies + offset, noffset - offset);
+		offset = noffset;
+	}
+	/* add the QoS param IE if both the peer and we support it */
+	if (peer_cap & SKW_TDLS_PEER_WMM)
+		skw_add_wmm_ie(iface, skb, SKW_WMM_TYPE_PARAMETER, NULL);
+
+	/* add any custom IEs that go before HT operation */
+	if (extra_ies_len) {
+		static const u8 before_ht_op[] = {
+			WLAN_EID_RSN,
+			WLAN_EID_QOS_CAPA,
+			WLAN_EID_FAST_BSS_TRANSITION,
+			WLAN_EID_TIMEOUT_INTERVAL,
+		};
+		noffset = skw_ie_split_ric(extra_ies, extra_ies_len,
+					     before_ht_op,
+					     ARRAY_SIZE(before_ht_op),
+					     NULL, 0,
+					     offset);
+		skw_put_skb_data(skb, extra_ies + offset, noffset - offset);
+		offset = noffset;
+	}
+
+	skw_tdls_add_link_ie(ndev, skb, peer, initiator);
+
+	/* add any remaining IEs */
+	if (extra_ies_len) {
+		noffset = extra_ies_len;
+		skw_put_skb_data(skb, extra_ies + offset, noffset - offset);
+	}
+}
+
+static void skw_tdls_add_chan_switch_req_ies(struct net_device *ndev,
+				       struct sk_buff *skb, const u8 *peer,
+				       bool initiator, const u8 *extra_ies,
+				       size_t extra_ies_len, u8 oper_class,
+				       struct cfg80211_chan_def *chandef)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+	struct ieee80211_tdls_data *tf;
+	size_t offset = 0, noffset;
+
+	if (WARN_ON_ONCE(!chandef))
+		return;
+
+	tf = (void *)skb->data;
+	tf->u.chan_switch_req.target_channel =
+		skw_freq_to_chn(chandef->chan->center_freq);
+	tf->u.chan_switch_req.oper_class = oper_class;
+
+	if (extra_ies_len) {
+		static const u8 before_lnkie[] = {
+			WLAN_EID_SECONDARY_CHANNEL_OFFSET,
+		};
+		noffset = skw_ie_split_ric(extra_ies, extra_ies_len,
+					     before_lnkie,
+					     ARRAY_SIZE(before_lnkie),
+					     NULL, 0,
+					     offset);
+		skw_put_skb_data(skb, extra_ies + offset, noffset - offset);
+		offset = noffset;
+	}
+
+	skw_tdls_add_link_ie(ndev, skb, peer, initiator);
+
+	/* add any remaining IEs */
+	if (extra_ies_len) {
+		noffset = extra_ies_len;
+		skw_put_skb_data(skb, extra_ies + offset, noffset - offset);
+	}
+#endif
+}
+
+static void skw_tdls_add_ies(struct net_device *ndev, struct sk_buff *skb,
+		const u8 *peer, u8 action, u16 status_code, u32 peer_cap,
+		bool initiator, const u8 *ies, size_t ies_len)
+{
+
+	switch (action) {
+	case WLAN_TDLS_SETUP_REQUEST:
+	case WLAN_TDLS_SETUP_RESPONSE:
+	case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
+		if (status_code == 0)
+			skw_tdls_add_setup_start_ies(ndev, skb, peer, peer_cap,
+				action, initiator, ies, ies_len);
+		break;
+	case WLAN_TDLS_SETUP_CONFIRM:
+		if (status_code == 0)
+			skw_tdls_add_setup_cfm_ies(ndev, skb, peer, peer_cap,
+				initiator, ies, ies_len);
+		break;
+	case WLAN_TDLS_TEARDOWN:
+	case WLAN_TDLS_DISCOVERY_REQUEST:
+		if (ies_len)
+			skw_put_skb_data(skb, ies, ies_len);
+
+		if (status_code == 0 || action == WLAN_TDLS_TEARDOWN)
+			skw_tdls_add_link_ie(ndev, skb, peer, initiator);
+		break;
+	case WLAN_TDLS_CHANNEL_SWITCH_REQUEST:
+		skw_tdls_add_chan_switch_req_ies(ndev, skb, peer,
+			initiator, ies, ies_len, 0, NULL);
+		break;
+	default:
+		break;
+	}
+}
+
+static int
+skw_tdls_build_send_encap_data(struct net_device *ndev,
+		const u8 *peer, u8 action_code, u8 dialog_token,
+		u16 status_code, u32 peer_cap, struct sk_buff *skb,
+		bool initiator, const u8 *ies, size_t ies_len)
+{
+	int offset;
+	struct ieee80211_tdls_data *td = NULL;
+
+	offset = offsetof(struct ieee80211_tdls_data, u);
+	td = (struct ieee80211_tdls_data *)skb_put(skb, offset);
+
+	memcpy(td->da, peer, ETH_ALEN);
+	memcpy(td->sa, ndev->dev_addr, ETH_ALEN);
+	td->ether_type = cpu_to_be16(ETH_P_TDLS);
+	td->payload_type = WLAN_TDLS_SNAP_RFTYPE;
+
+	skb_set_network_header(skb, ETH_HLEN);
+
+	switch (action_code) {
+	case WLAN_TDLS_SETUP_REQUEST:
+		td->category = WLAN_CATEGORY_TDLS;
+		td->action_code = WLAN_TDLS_SETUP_REQUEST;
+
+		skb_put(skb, sizeof(td->u.setup_req));
+		td->u.setup_req.dialog_token = dialog_token;
+		td->u.setup_req.capability = 0;
+		break;
+
+	case WLAN_TDLS_SETUP_RESPONSE:
+		td->category = WLAN_CATEGORY_TDLS;
+		td->action_code = WLAN_TDLS_SETUP_RESPONSE;
+
+		skb_put(skb, sizeof(td->u.setup_resp));
+		td->u.setup_resp.status_code = cpu_to_le16(status_code);
+		td->u.setup_resp.dialog_token = dialog_token;
+
+		td->u.setup_resp.capability = 0;
+		break;
+
+	case WLAN_TDLS_SETUP_CONFIRM:
+		td->category = WLAN_CATEGORY_TDLS;
+		td->action_code = WLAN_TDLS_SETUP_CONFIRM;
+
+		skb_put(skb, sizeof(td->u.setup_cfm));
+		td->u.setup_cfm.status_code = cpu_to_le16(status_code);
+		td->u.setup_cfm.dialog_token = dialog_token;
+		break;
+
+	case WLAN_TDLS_TEARDOWN:
+		td->category = WLAN_CATEGORY_TDLS;
+		td->action_code = WLAN_TDLS_TEARDOWN;
+
+		skb_put(skb, sizeof(td->u.teardown));
+		td->u.teardown.reason_code = cpu_to_le16(status_code);
+		break;
+
+	case WLAN_TDLS_DISCOVERY_REQUEST:
+		td->category = WLAN_CATEGORY_TDLS;
+		td->action_code = WLAN_TDLS_DISCOVERY_REQUEST;
+
+		skb_put(skb, sizeof(td->u.discover_req));
+		td->u.discover_req.dialog_token = dialog_token;
+		break;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+	case WLAN_TDLS_CHANNEL_SWITCH_REQUEST:
+		td->category = WLAN_CATEGORY_TDLS;
+		td->action_code = WLAN_TDLS_CHANNEL_SWITCH_REQUEST;
+
+		skb_put(skb, sizeof(td->u.chan_switch_req));
+		break;
+
+	case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE:
+		td->category = WLAN_CATEGORY_TDLS;
+		td->action_code = WLAN_TDLS_CHANNEL_SWITCH_RESPONSE;
+
+		skb_put(skb, sizeof(td->u.chan_switch_resp));
+		td->u.chan_switch_resp.status_code = cpu_to_le16(status_code);
+		break;
+#endif
+
+	default:
+		return -EINVAL;
+	}
+
+	skw_tdls_add_ies(ndev, skb, peer, action_code, status_code, peer_cap,
+		initiator, ies, ies_len);
+
+	return dev_queue_xmit(skb);
+}
+
+static int
+skw_tdls_build_send_direct(struct net_device *dev,
+		const u8 *peer, u8 action_code, u8 dialog_token,
+		u16 status_code, struct sk_buff *skb, bool initiator,
+		const u8 *ies, size_t ies_len)
+{
+	struct skw_iface *iface = netdev_priv(dev);
+	struct skw_core *skw = iface->skw;
+	struct wiphy *wiphy = priv_to_wiphy(skw);
+	struct ieee80211_mgmt *mgmt;
+	int ret, total_len;
+	struct skw_mgmt_tx_param *param;
+
+	skw_dbg("Enter\n");
+	mgmt = skw_put_skb_zero(skb, 24);
+	memcpy(mgmt->da, peer, ETH_ALEN);
+	skw_ether_copy(mgmt->sa, iface->addr);
+	skw_ether_copy(mgmt->bssid, iface->sta.core.bss.bssid);
+	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.u.tdls_discover_resp));
+		mgmt->u.action.category = WLAN_CATEGORY_PUBLIC;
+		mgmt->u.action.u.tdls_discover_resp.action_code =
+			WLAN_PUB_ACTION_TDLS_DISCOVER_RES;
+		mgmt->u.action.u.tdls_discover_resp.dialog_token =
+			dialog_token;
+		mgmt->u.action.u.tdls_discover_resp.capability =
+			status_code ? 0 : (WLAN_CAPABILITY_SHORT_SLOT_TIME |
+			 WLAN_CAPABILITY_SHORT_PREAMBLE);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	skw_tdls_add_ies(dev, skb, peer, action_code, status_code, 0,
+		initiator, ies, ies_len);
+
+	skw_dbg("sending tdls discover response\n");
+
+	total_len = sizeof(*param) + skb->len;
+	param = SKW_ZALLOC(total_len, GFP_KERNEL);
+	if (!param)
+		return -ENOMEM;
+
+	param->channel = 0xFF;
+	param->wait = 0;
+	param->dont_wait_for_ack = 0;
+	param->cookie = 0;
+
+	memcpy(param->mgmt, skb->data, skb->len);
+	param->mgmt_frame_len = skb->len;
+
+	skw_hex_dump("mgmt tx", skb->data, skb->len, false);
+
+	down(&skw->cmd.mgmt_cmd_lock);
+	ret = skw_msg_xmit(wiphy, iface->id, SKW_CMD_TX_MGMT,
+			param, total_len, NULL, 0);
+	up(&skw->cmd.mgmt_cmd_lock);
+
+	SKW_KFREE(param);
+	return ret;
+}
+
+int skw_tdls_build_send_mgmt(struct skw_core *skw, struct net_device *ndev,
+			const u8 *peer, u8 action_code, u8 dialog_token,
+			u16 status_code, u32 peer_cap, bool initiator,
+			const u8 *ies, size_t ies_len)
+{
+	struct sk_buff *skb;
+	unsigned int skb_len;
+	int ret;
+
+	skb_len = skw->skb_headroom +
+		  max(sizeof(struct ieee80211_mgmt),
+		      sizeof(struct ieee80211_tdls_data)) +
+		  50 + /* supported rates */
+		  10 + /* ext capab */
+		  26 + /* WMM */
+		  2 + max(sizeof(struct ieee80211_ht_cap),
+			  sizeof(struct ieee80211_ht_operation)) +
+		  2 + max(sizeof(struct ieee80211_vht_cap),
+			  sizeof(struct ieee80211_vht_operation)) +
+		  50 + /* supported channels */
+		  3 + /* 40/20 BSS coex */
+		  4 + /* AID */
+		  4 + /* oper classes */
+		  ies_len +
+		  sizeof(struct ieee80211_tdls_lnkie);
+
+	skw_dbg("skb_headroom: %u skb_len: %u ies_len: %lu\n",
+		skw->skb_headroom, skb_len, (long)ies_len);
+
+	skb = netdev_alloc_skb(ndev, skb_len);
+	if (!skb)
+		return -ENOMEM;
+
+	skb_reserve(skb, skw->skb_headroom);
+
+	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:
+	case WLAN_TDLS_CHANNEL_SWITCH_REQUEST:
+	case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE:
+		ret = skw_tdls_build_send_encap_data(ndev, peer,
+				action_code, dialog_token, status_code,
+				peer_cap, skb, initiator, ies, ies_len);
+		break;
+
+	case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
+		ret = skw_tdls_build_send_direct(ndev, peer, action_code,
+				dialog_token, status_code, skb, initiator,
+				ies, ies_len);
+		break;
+
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	return ret;
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tdls.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tdls.h
new file mode 100755
index 0000000..8e42bc5
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tdls.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_TDLS_H__
+#define __SKW_TDLS_H__
+
+enum SKW_WMM_TYPE {
+	SKW_WMM_TYPE_INFO,
+	SKW_WMM_TYPE_PARAMETER,
+};
+
+enum SKW_TDLS_PEER_CAPA {
+	SKW_TDLS_PEER_HT  = BIT(0),
+	SKW_TDLS_PEER_VHT = BIT(1),
+	SKW_TDLS_PEER_WMM = BIT(2),
+};
+
+#ifdef CONFIG_SWT6621S_TDLS
+int skw_tdls_build_send_mgmt(struct skw_core *skw, struct net_device *ndev,
+			const u8 *peer, u8 action_code, u8 dialog_token,
+			u16 status_code, u32 peer_cap, bool initiator,
+			const u8 *ies, size_t ies_len);
+#else
+static inline int skw_tdls_build_send_mgmt(struct skw_core *skw,
+			struct net_device *ndev, const u8 *peer, u8 action,
+			u8 token, u16 status, u32 peer_capa, bool initiator,
+			const u8 *ies, size_t ies_len)
+{
+	return 0;
+}
+
+#endif
+
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_timer.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_timer.c
new file mode 100755
index 0000000..11421ad
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_timer.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/skbuff.h>
+#include <net/cfg80211.h>
+
+#include "skw_core.h"
+#include "skw_timer.h"
+#include "skw_msg.h"
+#include "skw_mlme.h"
+
+static int skw_timer_show(struct seq_file *seq, void *data)
+{
+	struct skw_timer *timer;
+	struct skw_core *skw = seq->private;
+
+	seq_printf(seq, "count: %d\n", skw->timer_data.count);
+
+	if (!skw->timer_data.count)
+		return 0;
+
+	spin_lock_bh(&skw->timer_data.lock);
+	list_for_each_entry(timer, &skw->timer_data.list, list) {
+		seq_puts(seq, "\n");
+
+		seq_printf(seq, "name: %s\n"
+				"id: 0x%p\n"
+				"time left: %u ms\n",
+				timer->name,
+				timer->id,
+				jiffies_to_msecs(timer->timeout - jiffies));
+	}
+
+	spin_unlock_bh(&skw->timer_data.lock);
+
+	return 0;
+}
+
+static int skw_timer_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, skw_timer_show, inode->i_private);
+}
+
+static const struct file_operations skw_timer_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_timer_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0)
+static void skw_timer_work(struct timer_list *data)
+#else
+static void skw_timer_work(unsigned long data)
+#endif
+{
+	struct skw_core *skw;
+	struct skw_timer *timer, *next;
+	LIST_HEAD(timeout_list);
+
+	skw = container_of((void *)data, struct skw_core, timer_data.timer);
+
+	spin_lock_bh(&skw->timer_data.lock);
+	list_for_each_entry_safe(timer, next, &skw->timer_data.list, list) {
+		if (time_before(jiffies, timer->timeout))
+			break;
+
+		list_move(&timer->list, &timeout_list);
+	}
+
+	spin_unlock_bh(&skw->timer_data.lock);
+
+	while (!list_empty(&timeout_list)) {
+		timer = list_first_entry(&timeout_list, struct skw_timer, list);
+		skw_log(SKW_TIMER, "[%s] %s: %s(id: 0x%p)\n",
+			SKW_TAG_TIMER, __func__, timer->name, timer->id);
+		list_del(&timer->list);
+
+		timer->cb(timer->data);
+		skw->timer_data.count--;
+
+		SKW_KFREE(timer);
+	}
+
+	spin_lock_bh(&skw->timer_data.lock);
+	timer = list_first_entry_or_null(&skw->timer_data.list, struct skw_timer, list);
+	if (timer)
+		mod_timer(&skw->timer_data.timer, timer->timeout);
+
+	spin_unlock_bh(&skw->timer_data.lock);
+}
+
+static bool skw_timer_id_exist(struct skw_core *skw, void *id)
+{
+	bool result = false;
+	struct skw_timer *timer;
+
+	spin_lock_bh(&skw->timer_data.lock);
+
+	list_for_each_entry(timer, &skw->timer_data.list, list) {
+		if (id == timer->id) {
+			result = true;
+			break;
+		}
+	}
+
+	spin_unlock_bh(&skw->timer_data.lock);
+
+	return result;
+}
+
+int skw_add_timer_work(struct skw_core *skw, const char *name,
+		       void (*cb)(void *dat), void *data,
+		       unsigned long timeout, void *timer_id, gfp_t flags)
+{
+	struct skw_timer *timer, *node;
+	struct list_head *head;
+
+	if (!timer_id || !cb || !name)
+		return -EINVAL;
+
+	skw_log(SKW_TIMER, "[%s] %s: %s(id: 0x%p), time out = %ld\n",
+		SKW_TAG_TIMER, __func__, name, timer_id, timeout);
+
+	if (skw_timer_id_exist(skw, timer_id)) {
+		skw_warn("id: 0x%p exist\n", timer_id);
+		SKW_BUG_ON(1);
+
+		return -EINVAL;
+	}
+
+	timer = SKW_ZALLOC(sizeof(*timer), flags);
+	if (!timer)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&timer->list);
+
+	timer->name = name;
+	timer->cb = cb;
+	timer->data = data;
+	timer->id = timer_id;
+	timer->timeout = msecs_to_jiffies(timeout) + jiffies + 1;
+
+	spin_lock_bh(&skw->timer_data.lock);
+	head = &skw->timer_data.list;
+
+	list_for_each_entry(node, &skw->timer_data.list, list) {
+		if (time_before_eq(timer->timeout, node->timeout)) {
+			head = &node->list;
+			break;
+		}
+	}
+
+	list_add(&timer->list, head);
+
+	skw->timer_data.count++;
+	node = list_first_entry(&skw->timer_data.list, struct skw_timer, list);
+
+	mod_timer(&skw->timer_data.timer, node->timeout);
+	spin_unlock_bh(&skw->timer_data.lock);
+
+	return 0;
+}
+
+void skw_del_timer_work(struct skw_core *skw, void *timer_id)
+{
+	struct skw_timer *timer;
+
+	skw_log(SKW_TIMER, "[%s] %s: id: 0x%p\n",
+		SKW_TAG_TIMER, __func__, timer_id);
+
+	spin_lock_bh(&skw->timer_data.lock);
+	list_for_each_entry(timer, &skw->timer_data.list, list) {
+		if (timer->id == timer_id) {
+			list_del(&timer->list);
+			skw->timer_data.count--;
+			SKW_KFREE(timer);
+			break;
+		}
+	}
+
+	timer = list_first_entry_or_null(&skw->timer_data.list, struct skw_timer, list);
+	if (timer)
+		mod_timer(&skw->timer_data.timer, timer->timeout);
+
+	spin_unlock_bh(&skw->timer_data.lock);
+}
+
+void skw_timer_init(struct skw_core *skw)
+{
+	// skw->timer_work.timeout = LONG_MAX;
+	skw->timer_data.count = 0;
+
+	INIT_LIST_HEAD(&skw->timer_data.list);
+	spin_lock_init(&skw->timer_data.lock);
+
+	// fixme:
+	// timer_setup(&skw->timer_data.timer, skw->timer_data.timer_work, 0);
+	skw_compat_setup_timer(&skw->timer_data.timer, skw_timer_work);
+
+	skw_debugfs_file(skw->dentry, "timer", 04444, &skw_timer_fops, skw);
+}
+
+void skw_timer_deinit(struct skw_core *skw)
+{
+	LIST_HEAD(flush_list);
+
+	del_timer(&skw->timer_data.timer);
+
+	spin_lock_bh(&skw->timer_data.lock);
+	list_replace_init(&skw->timer_data.list, &flush_list);
+	spin_unlock_bh(&skw->timer_data.lock);
+
+	while (!list_empty(&flush_list)) {
+		struct skw_timer *timer = list_first_entry(&flush_list,
+			struct skw_timer, list);
+
+		list_del(&timer->list);
+
+		skw_log(SKW_TIMER, "[%s] %s: name: %s\n",
+			SKW_TAG_TIMER, __func__, timer->name);
+
+		SKW_KFREE(timer);
+	}
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_timer.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_timer.h
new file mode 100755
index 0000000..2f985eb
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_timer.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_WORK_H__
+#define __SKW_WORK_H__
+
+struct skw_timer {
+	struct list_head list;
+	unsigned long timeout;
+	void (*cb)(void *data);
+	void *id;
+	void *data;
+	const char *name;
+};
+
+int skw_add_timer_work(struct skw_core *skw, const char *name,
+		       void (*cb)(void *dat), void *data,
+		       unsigned long timeout, void *timer_id, gfp_t flags);
+void skw_del_timer_work(struct skw_core *skw, void *timer_id);
+void skw_timer_init(struct skw_core *skw);
+void skw_timer_deinit(struct skw_core *skw);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tx.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tx.c
new file mode 100755
index 0000000..a47d09d
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tx.c
@@ -0,0 +1,2020 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/kthread.h>
+#include <linux/ip.h>
+#include <linux/ctype.h>
+
+#include "skw_core.h"
+#include "skw_tx.h"
+#include "skw_msg.h"
+#include "skw_iface.h"
+#include "skw_edma.h"
+#include "trace.h"
+
+#define SKW_BASE_VO                    16
+#define SKW_BASE_VI                    24
+#define SKW_TX_TIMEOUT                 200
+#define SKW_TX_RUNING_TIMES            20
+
+struct skw_tx_info {
+	int quota;
+	bool reset;
+	struct sk_buff_head *list;
+};
+
+struct skw_tx_lmac {
+	bool reset;
+	int cred;
+	u16 txq_map;
+	u16 nr_txq;
+	int bk_tx_limit;
+	int current_qlen;
+	int ac_reset;
+	int tx_count_limit;
+	int pending_qlen;
+
+	struct sk_buff_head tx_list;
+	struct skw_tx_info tx[SKW_NR_IFACE];
+};
+
+unsigned int tx_wait_time;
+
+static int skw_tx_time_show(struct seq_file *seq, void *data)
+{
+	seq_printf(seq, "current tx_wait_time = %dus\n", tx_wait_time);
+	return 0;
+}
+
+static int skw_tx_time_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, skw_tx_time_show, inode->i_private);
+}
+
+static ssize_t skw_tx_time_write(struct file *fp, const char __user *buf,
+				size_t len, loff_t *offset)
+{
+	int i;
+	char cmd[32] = {0};
+	unsigned int res = 0;
+
+	for (i = 0; i < 32; i++) {
+		char c;
+
+		if (get_user(c, buf))
+			return -EFAULT;
+
+		if (c == '\n' || c == '\0')
+			break;
+
+		if (isdigit(c) != 0)
+			cmd[i] = c;
+		else {
+			skw_warn("set fail, not number\n");
+			return -EFAULT;
+		}
+		buf++;
+	}
+
+	if (kstrtouint(cmd, 10, &res))
+		return -EFAULT;
+
+	skw_info("set tx_wait_time = %dus\n", res);
+	tx_wait_time = res;
+
+	return len;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
+static const struct proc_ops skw_tx_time_fops = {
+	.proc_open = skw_tx_time_open,
+	.proc_read = seq_read,
+	.proc_release = single_release,
+	.proc_write = skw_tx_time_write,
+};
+#else
+static const struct file_operations skw_tx_time_fops = {
+	.owner = THIS_MODULE,
+	.open = skw_tx_time_open,
+	.read = seq_read,
+	.release = single_release,
+	.write = skw_tx_time_write,
+};
+#endif
+
+#ifdef CONFIG_SWT6621S_SKB_RECYCLE
+void skw_recycle_skb_free(struct skw_core *skw, struct sk_buff *skb)
+{
+	if (!skb)
+		return;
+
+	skb->data = skb->head;
+	skb_reset_tail_pointer(skb);
+
+	skb->mac_header = (typeof(skb->mac_header))~0U;
+	skb->transport_header = (typeof(skb->transport_header))~0U;
+	skb->network_header = 0;
+
+	skb->len = 0;
+
+	skb_reserve(skb, NET_SKB_PAD);
+
+	skb_queue_tail(&skw->skb_recycle_qlist, skb);
+}
+
+int skw_recycle_skb_copy(struct skw_core *skw, struct sk_buff *skb_recycle, struct sk_buff *skb)
+{
+	if (!skw || !skb_recycle || !skb)
+		return -1;
+
+	skb_recycle->dev = skb->dev;
+
+	skb_reserve(skb_recycle, skw->skb_headroom);
+	skb_put(skb_recycle, skb->len);
+	memcpy(skb_recycle->data, skb->data, skb->len);
+
+	skb_set_mac_header(skb_recycle, 0);
+	skb_set_network_header(skb_recycle, ETH_HLEN);
+	skb_set_transport_header(skb_recycle, skb->transport_header - skb->mac_header);
+	if (skb->ip_summed == CHECKSUM_PARTIAL) {
+		skb_recycle->ip_summed = skb->ip_summed;
+		skb_recycle->csum_start = skb_headroom(skb_recycle) + skb_checksum_start_offset(skb);
+	}
+
+	return 0;
+}
+
+struct sk_buff *skw_recycle_skb_get(struct skw_core *skw)
+{
+	unsigned long flags;
+	struct sk_buff *skb = NULL;
+
+	spin_lock_irqsave(&skw->skb_recycle_qlist.lock, flags);
+	if (skw->skb_recycle_qlist.qlen > 0) {
+		skb = skb_peek(&skw->skb_recycle_qlist);
+		if (skb)
+			__skb_unlink(skb, &skw->skb_recycle_qlist);
+	}
+	spin_unlock_irqrestore(&skw->skb_recycle_qlist.lock, flags);
+
+	return skb;
+}
+#endif
+
+void skw_skb_kfree(struct skw_core *skw, struct sk_buff *skb)
+{
+#ifdef CONFIG_SWT6621S_SKB_RECYCLE
+	if (1 == SKW_SKB_TXCB(skb)->recycle)
+		skw_recycle_skb_free(skw, skb);
+	else
+		dev_kfree_skb_any(skb);
+#else
+	dev_kfree_skb_any(skb);
+#endif
+}
+
+int skw_pcie_cmd_xmit(struct skw_core *skw, void *data, int data_len)
+{
+	skw->edma.cmd_chn.hdr[0].data_len = data_len;
+	skw_edma_set_data(priv_to_wiphy(skw), &skw->edma.cmd_chn, data, data_len);
+
+	return skw_edma_tx(priv_to_wiphy(skw), &skw->edma.cmd_chn, data_len);
+}
+
+int skw_sdio_cmd_xmit(struct skw_core *skw, void *data, int data_len)
+{
+	int nr = 0, total_len;
+
+	sg_init_table(skw->sgl_cmd, SKW_NR_SGL_CMD);
+	sg_set_buf(&skw->sgl_cmd[nr++], data, data_len);
+	total_len = data_len;
+
+	skw_set_extra_hdr(skw, skw->eof_blk, skw->hw.cmd_port, skw->hw.align, 0, 1);
+	sg_set_buf(&skw->sgl_cmd[nr++], skw->eof_blk, skw->hw.align);
+	total_len += skw->hw.align;
+
+	if (test_bit(SKW_CMD_FLAG_DISABLE_IRQ, &skw->cmd.flags))
+		return skw->hw.cmd_disable_irq_xmit(skw, NULL, -1,
+				skw->hw.cmd_port, skw->sgl_cmd, nr, total_len);
+	else
+		return skw->hw.cmd_xmit(skw, NULL, -1, skw->hw.cmd_port,
+					skw->sgl_cmd, nr, total_len);
+}
+
+int skw_usb_cmd_xmit(struct skw_core *skw, void *data, int data_len)
+{
+	int nr = 0, total_len;
+
+	sg_init_table(skw->sgl_cmd, SKW_NR_SGL_CMD);
+	sg_set_buf(&skw->sgl_cmd[nr++], data, data_len);
+	total_len = data_len;
+
+	return skw->hw.cmd_xmit(skw, NULL, -1, skw->hw.cmd_port,
+			skw->sgl_cmd, nr, total_len);
+}
+
+static int skw_sync_sdma_tx(struct skw_core *skw, struct sk_buff_head *list,
+			int lmac_id, int port, struct scatterlist *sgl,
+			int nents, int tx_len)
+{
+	int total_len;
+	int ret;
+	struct sk_buff *skb, *tmp;
+
+	if (!skw->hw_pdata || !skw->hw_pdata->hw_sdma_tx)
+		return -EINVAL;
+
+	if (!skw->sdma_buff) {
+		skw_err("invalid buff\n");
+		return -ENOMEM;
+	}
+
+	total_len = sg_copy_to_buffer(sgl, nents, skw->sdma_buff,
+				skw->hw_pdata->max_buffer_size);
+
+	ret = skw->hw_pdata->hw_sdma_tx(port, skw->sdma_buff, total_len);
+	if (ret < 0)
+		skw_err("failed, ret: %d nents:%d\n", ret, nents);
+
+	if (list) {
+		if (likely(0 == ret))
+			skw_sub_credit(skw, lmac_id, skb_queue_len(list));
+
+		skb_queue_walk_safe(list, skb, tmp) {
+			if (likely(0 == ret)) {
+				skb->dev->stats.tx_packets++;
+				skb->dev->stats.tx_bytes += SKW_SKB_TXCB(skb)->skb_native_len;
+			} else
+				skb->dev->stats.tx_errors++;
+
+			__skb_unlink(skb, list);
+			//kfree_skb(skb);
+			skw_skb_kfree(skw, skb);
+		}
+		//skw_sub_credit(skw, lmac_id, skb_queue_len(list));
+		//__skb_queue_purge(list);
+	}
+
+	return ret;
+}
+
+static int skw_sync_sdma_cmd_disable_irq_tx(struct skw_core *skw,
+		struct sk_buff_head *list, int lmac_id, int port,
+		struct scatterlist *sgl, int nents, int tx_len)
+{
+	int total_len;
+
+	if (!skw->hw_pdata || !skw->hw_pdata->suspend_sdma_cmd)
+		return -EINVAL;
+
+	if (!skw->sdma_buff) {
+		skw_err("invalid buff\n");
+		return -ENOMEM;
+	}
+
+	total_len = sg_copy_to_buffer(sgl, nents, skw->sdma_buff,
+				skw->hw_pdata->max_buffer_size);
+
+	return skw->hw_pdata->suspend_sdma_cmd(port, skw->sdma_buff, total_len);
+}
+
+
+static int skw_async_sdma_tx(struct skw_core *skw, struct sk_buff_head *list,
+			int lmac_id, int port, struct scatterlist *sgl,
+			int nents, int tx_len)
+{
+	void *buff;
+	int ret, total_len;
+	struct sk_buff *skb, *tmp;
+	struct skw_sg_node *node;
+
+	if (!skw->hw_pdata || !skw->hw_pdata->hw_sdma_tx_async)
+		return -EINVAL;
+
+	tx_len += sizeof(struct skw_sg_node);
+
+	buff = SKW_ZALLOC(tx_len, GFP_KERNEL);
+	if (!buff) {
+		skw_err("invalid buffer\n");
+		return -ENOMEM;
+	}
+
+	node = (struct skw_sg_node *)buff;
+	buff = (u8 *)buff + sizeof(struct skw_sg_node);
+
+	total_len = sg_copy_to_buffer(sgl, nents, buff, tx_len);
+	node->lmac_id = lmac_id;
+	node->data = (void *)skw;
+	node->nents = nents;
+	ret = skw->hw_pdata->hw_sdma_tx_async(port, buff, total_len);
+	if (ret < 0) {
+		skw_err("failed, ret: %d nents:%d\n", ret, nents);
+		SKW_KFREE(buff);
+	}
+
+	if (list) {
+		if (likely(0 == ret))
+			skw_sub_credit(skw, lmac_id, skb_queue_len(list));
+
+		skb_queue_walk_safe(list, skb, tmp) {
+			if (likely(0 == ret)) {
+				skb->dev->stats.tx_packets++;
+				skb->dev->stats.tx_bytes += SKW_SKB_TXCB(skb)->skb_native_len;
+			} else
+				skb->dev->stats.tx_errors++;
+
+			__skb_unlink(skb, list);
+			//kfree_skb(skb);
+			skw_skb_kfree(skw, skb);
+		}
+
+		//skw_sub_credit(skw, lmac_id, skb_queue_len(list));
+		//__skb_queue_purge(list);
+	}
+
+	return ret;
+}
+
+static int skw_sync_adma_tx(struct skw_core *skw, struct sk_buff_head *list,
+			int lmac_id, int port, struct scatterlist *sgl,
+			int nents, int tx_len)
+{
+	struct sk_buff *skb, *tmp;
+	int ret;
+	unsigned long flags;
+
+	if (!skw->hw_pdata || !skw->hw_pdata->hw_adma_tx)
+		return -EINVAL;
+
+	ret = skw->hw_pdata->hw_adma_tx(port, sgl, nents, tx_len);
+	trace_skw_hw_adma_tx_done(nents);
+	if (ret < 0)
+		skw_err("failed, ret: %d nents:%d\n", ret, nents);
+
+	if (list) {
+		if (likely(0 == ret))
+			skw_sub_credit(skw, lmac_id, skb_queue_len(list));
+		else
+			skb_queue_walk_safe(list, skb, tmp)
+				SKW_SKB_TXCB(skb)->ret = 1;
+
+		spin_lock_irqsave(&skw->kfree_skb_qlist.lock, flags);
+		skb_queue_splice_tail_init(list, &skw->kfree_skb_qlist);
+		spin_unlock_irqrestore(&skw->kfree_skb_qlist.lock, flags);
+		schedule_work(&skw->kfree_skb_task);
+	}
+
+	return ret;
+}
+
+static int skw_sync_adma_cmd_disable_irq_tx(struct skw_core *skw,
+		struct sk_buff_head *list, int lmac_id, int port,
+		struct scatterlist *sgl, int nents, int tx_len)
+{
+	if (!skw->hw_pdata || !skw->hw_pdata->suspend_adma_cmd)
+		return -EINVAL;
+
+	return skw->hw_pdata->suspend_adma_cmd(port, sgl, nents, tx_len);
+}
+
+static int skw_async_adma_tx(struct skw_core *skw, struct sk_buff_head *list,
+			int lmac_id, int port, struct scatterlist *sgl,
+			int nents, int tx_len)
+{
+	int ret, idx;
+	struct sk_buff *skb;
+	struct scatterlist *sg_list, *sg;
+	unsigned long *skb_addr, *sg_addr;
+
+	if (!skw->hw_pdata || !skw->hw_pdata->hw_adma_tx_async)
+		return -EINVAL;
+
+	if (!sgl) {
+		ret = -ENOMEM;
+		skw_err("sgl is NULL\n");
+		goto out;
+	}
+
+	sg_list = kcalloc(SKW_NR_SGL_DAT, sizeof(*sg_list), GFP_KERNEL);
+
+	ret = skw->hw_pdata->hw_adma_tx_async(port, sgl, nents, tx_len);
+	if (unlikely(ret < 0)) {
+		skw_err("failed, ret: %d nents:%d\n", ret, nents);
+
+		for_each_sg(sgl, sg, nents, idx) {
+			sg_addr = (unsigned long *)sg_virt(sg);
+
+			skb_addr = sg_addr - 1;
+			skb = (struct sk_buff *)*skb_addr;
+
+			skb->dev->stats.tx_errors++;
+			//kfree_skb(skb);
+			skw_skb_kfree(skw, skb);
+		}
+
+		SKW_KFREE(sgl);
+	} else {
+		atomic_add(nents, &skw->txqlen_pending);
+		skw_sub_credit(skw, lmac_id, nents);
+	}
+
+	skw->sgl_dat = sg_list;
+out:
+	return ret;
+}
+
+static int skw_async_adma_tx_free(int id, struct scatterlist *sg, int nents,
+			   void *data, int status)
+{
+	struct skw_sg_node node = {0};
+	struct skw_core *skw = data;
+	struct wiphy *wiphy = priv_to_wiphy(skw);
+
+	node.sg = sg;
+	node.nents = nents;
+	node.status = status;
+
+	skw_queue_work(wiphy, NULL, SKW_WORK_TX_FREE, &node, sizeof(node));
+
+	return 0;
+}
+
+static int skw_async_sdma_tx_free(int id,  void *buffer, int size,
+			   void *data, int status)
+{
+	struct skw_sg_node *node;
+
+	if (unlikely(!buffer)) {
+		skw_err("buffer is invalid\n");
+		return 0;
+	}
+
+	node = (struct skw_sg_node *) ((u8 *)buffer - sizeof(struct skw_sg_node));
+	if (unlikely(status < 0)) {
+		skw_err("failed status:%d %p\n", status, node->data);
+		if (node->data == NULL)
+			skw_err("buffer content was broke is NULL\n");
+		else
+			skw_add_credit((struct skw_core *)node->data, node->lmac_id, node->nents);
+	}
+
+	buffer = (void *) node;
+	SKW_KFREE(buffer);
+
+	return 0;
+}
+
+int skw_sdio_xmit(struct skw_core *skw, int lmac_id, struct sk_buff_head *txq)
+{
+	struct sk_buff *skb, *tmp;
+	struct skw_lmac *lmac = &skw->hw.lmac[lmac_id];
+
+	skb_queue_walk_safe(txq, skb, tmp) {
+		int aligned;
+		struct skw_packet_header *extra_hdr;
+
+		extra_hdr = (void *)skb_push(skb, SKW_EXTER_HDR_SIZE);
+		aligned = round_up(skb->len, skw->hw.align);
+		skw_set_extra_hdr(skw, extra_hdr, lmac->lport, aligned, 0, 0);
+
+		skw_vring_set(skw, skb->data, skb->len, lmac_id);
+		__skb_unlink(skb, txq);
+		skb->dev->stats.tx_packets++;
+		skb->dev->stats.tx_bytes += SKW_SKB_TXCB(skb)->skb_native_len;
+		dev_kfree_skb_any(skb);
+	}
+	schedule_work(&skw->hw.lmac[lmac_id].dy_work);
+
+	return 0;
+}
+
+#if 0
+int skw_sdio_xmit(struct skw_core *skw, int lmac_id, struct sk_buff_head *txq)
+{
+	struct sk_buff *skb;
+	int nents = 0, tx_bytes = 0;
+	struct skw_lmac *lmac = &skw->hw.lmac[lmac_id];
+
+	sg_init_table(skw->sgl_dat, SKW_NR_SGL_DAT);
+
+	skb_queue_walk(txq, skb) {
+		int aligned;
+		struct skw_packet_header *extra_hdr;
+
+		extra_hdr = (void *)skb_push(skb, SKW_EXTER_HDR_SIZE);
+
+		aligned = round_up(skb->len, skw->hw.align);
+		skw_set_extra_hdr(skw, extra_hdr, lmac->lport, aligned, 0, 0);
+
+		sg_set_buf(&skw->sgl_dat[nents++], skb->data, aligned);
+
+		tx_bytes += aligned;
+	}
+
+	skw_set_extra_hdr(skw, skw->eof_blk, lmac->lport, skw->hw.align, 0, 1);
+	sg_set_buf(&skw->sgl_dat[nents++], skw->eof_blk, skw->hw.align);
+	tx_bytes += skw->hw.align;
+	skw_detail("nents:%d", nents);
+
+	return skw->hw.dat_xmit(skw, txq, lmac_id, lmac->dport,
+				skw->sgl_dat, nents, tx_bytes);
+}
+#endif
+
+int skw_usb_xmit(struct skw_core *skw, int lmac_id, struct sk_buff_head *txq)
+{
+	struct sk_buff *skb, *tmp;
+	int nents = 0, tx_bytes = 0;
+	unsigned long *skb_addr;
+	struct skw_lmac *lmac = &skw->hw.lmac[lmac_id];
+
+	sg_init_table(skw->sgl_dat, SKW_NR_SGL_DAT);
+
+	skb_queue_walk_safe(txq, skb, tmp) {
+		int aligned;
+		struct skw_packet_header *extra_hdr;
+
+		extra_hdr = (void *)skb_push(skb, SKW_EXTER_HDR_SIZE);
+
+		aligned = round_up(skb->len, skw->hw.align);
+		skw_set_extra_hdr(skw, extra_hdr, lmac->lport, aligned, 0, 0);
+
+		sg_set_buf(&skw->sgl_dat[nents++], skb->data, aligned);
+
+		skb_addr = (unsigned long *)skb_push(skb, sizeof(unsigned long));
+		*skb_addr = (unsigned long)skb;
+		skb_pull(skb, sizeof(unsigned long));
+
+		tx_bytes += aligned;
+
+		if (skw->hw.dma == SKW_ASYNC_ADMA_TX)
+			__skb_unlink(skb, txq);
+	}
+
+	return skw->hw.dat_xmit(skw, txq, lmac_id, lmac->dport,
+				skw->sgl_dat, nents, tx_bytes);
+}
+
+int skw_pcie_xmit(struct skw_core *skw, int lmac_id, struct sk_buff_head *txq)
+{
+	int ret, tx_bytes = 0;
+	unsigned long flags;
+	struct sk_buff *skb;
+	struct skw_lmac *lmac = &skw->hw.lmac[lmac_id];
+	struct wiphy *wiphy = priv_to_wiphy(skw);
+	unsigned long *addr;
+
+	skb_queue_walk(txq, skb) {
+		addr = (unsigned long *)skb_push(skb, sizeof(unsigned long)*2);
+		*addr = (unsigned long)skw->edma.tx_chn[lmac_id].current_node;
+		skb_pull(skb, 2 * sizeof(unsigned long));
+
+		skw_edma_set_data(wiphy, &skw->edma.tx_chn[lmac_id],
+				&SKW_SKB_TXCB(skb)->e,
+				sizeof(SKW_SKB_TXCB(skb)->e));
+
+		tx_bytes += round_up(skb->len, skw->hw.align);
+		addr = (unsigned long *)skb_push(skb, sizeof(unsigned long));
+		*addr = (unsigned long)skb;
+	}
+
+	skb = skb_peek(txq);
+
+	spin_lock_irqsave(&lmac->edma_free_list.lock, flags);
+	skb_queue_splice_tail_init(txq, &lmac->edma_free_list);
+	spin_unlock_irqrestore(&lmac->edma_free_list.lock, flags);
+
+	ret = skw_edma_tx(wiphy, &skw->edma.tx_chn[lmac_id], tx_bytes);
+	if (ret < 0) {
+		skw_err("failed, ret: %d\n", ret);
+		// TODO:
+		// release free list
+	}
+
+	return ret;
+}
+
+static inline int skw_bus_data_xmit(struct skw_core *skw, int mac_id,
+			struct sk_buff_head *txq_list)
+{
+	int ret;
+
+	if (!skb_queue_len(txq_list))
+		return 0;
+
+	skw->tx_packets += skb_queue_len(txq_list);
+
+	skw->dbg.dat_idx = (skw->dbg.dat_idx + 1) % skw->dbg.nr_dat;
+	skw->dbg.dat[skw->dbg.dat_idx].qlen = skb_queue_len(txq_list);
+	skw->dbg.dat[skw->dbg.dat_idx].trigger = skw_local_clock();
+
+	ret = skw->hw.bus_dat_xmit(skw, mac_id, txq_list);
+
+	skw->dbg.dat[skw->dbg.dat_idx].done = skw_local_clock();
+
+	return ret;
+}
+
+static inline int skw_bus_cmd_xmit(struct skw_core *skw, void *cmd, int cmd_len)
+{
+	int ret;
+	unsigned long flags = READ_ONCE(skw->flags);
+
+	if (test_bit(SKW_CMD_FLAG_IGNORE_BLOCK_TX, &skw->cmd.flags))
+		clear_bit(SKW_FLAG_BLOCK_TX, &flags);
+
+	if (!skw_cmd_tx_allowed(flags)) {
+		skw_warn("cmd: %s[%d] not allowed, flags: 0x%lx, cmd flags: 0x%lx\n",
+			 skw->cmd.name, skw->cmd.id, skw->flags, skw->cmd.flags);
+
+		skw_abort_cmd(skw);
+
+		return 0;
+	}
+
+	skw->dbg.cmd[skw->dbg.cmd_idx].xmit = skw_local_clock();
+	skw->dbg.cmd[skw->dbg.cmd_idx].loop = atomic_read(&skw->dbg.loop);
+
+	ret = skw->hw.bus_cmd_xmit(skw, cmd, cmd_len);
+
+	skw->dbg.cmd[skw->dbg.cmd_idx].done = skw_local_clock();
+
+	return ret;
+}
+
+static inline bool is_skw_same_tcp_stream(struct sk_buff *skb,
+					struct sk_buff *next)
+{
+	return ip_hdr(skb)->saddr == ip_hdr(next)->saddr &&
+	       ip_hdr(skb)->daddr == ip_hdr(next)->daddr &&
+	       tcp_hdr(skb)->source == tcp_hdr(next)->source &&
+	       tcp_hdr(skb)->dest == tcp_hdr(next)->dest;
+}
+
+static void skw_merge_pure_ack(struct skw_core *skw, struct sk_buff_head *ackq,
+				struct sk_buff_head *txq)
+{
+	int i, drop = 0;
+	struct sk_buff *skb, *tmp;
+
+	while ((skb = __skb_dequeue_tail(ackq))) {
+		for (i = 0; i < ackq->qlen; i++) {
+			tmp = __skb_dequeue(ackq);
+			if (!tmp)
+				break;
+
+			if (is_skw_same_tcp_stream(skb, tmp)) {
+				if (tcp_optlen(tmp) == 0 &&
+				    tcp_flag_word(tcp_hdr(tmp)) == TCP_FLAG_ACK) {
+					skw_skb_kfree(skw, tmp);
+					drop++;
+				} else {
+					__skb_queue_tail(txq, tmp);
+				}
+			} else {
+				__skb_queue_tail(ackq, tmp);
+			}
+		}
+
+		__skb_queue_tail(txq, skb);
+	}
+}
+
+static bool is_skw_peer_data_valid(struct skw_core *skw, struct sk_buff *skb)
+{
+	struct skw_ctx_entry *entry;
+	bool valid = true;
+	int peer_idx = SKW_SKB_TXCB(skb)->peer_idx;
+	int i;
+
+	rcu_read_lock();
+	for (i = 0; i < skw->hw.nr_lmac; i++) {
+		entry = rcu_dereference(skw->hw.lmac[i].peer_ctx[peer_idx].entry);
+		if (entry) {
+			if (entry->peer) {
+				entry->peer->tx.bytes += skb->len;
+				entry->peer->tx.pkts++;
+
+				if (entry->peer->flags & SKW_PEER_FLAG_DEAUTHED)
+					valid = false;
+			} else {
+				if (is_unicast_ether_addr(eth_hdr(skb)->h_dest))
+					valid = false;
+			}
+		}
+	}
+	rcu_read_unlock();
+
+	return valid;
+}
+
+static inline void skw_reset_ac(struct skw_tx_lmac *txlp)
+{
+	int i;
+
+	for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
+		txlp->ac_reset = 0xF;
+		txlp++;
+	}
+}
+
+bool skw_check_txq(struct skw_core *skw)
+{
+	bool ret;
+	int ac, i;
+	unsigned long flags;
+	struct skw_iface *iface;
+
+	ret = false;
+	for (ac = 0; ac < SKW_WMM_AC_MAX; ac++) {
+		for (i = 0; i < SKW_NR_IFACE; i++) {
+			iface = skw->vif.iface[i];
+			if (!iface || !iface->ndev)
+				continue;
+
+			if (skb_queue_len(&iface->tx_cache[ac]) != 0) {
+				ret = true;
+				goto _exit;
+			}
+
+			spin_lock_irqsave(&iface->txq[ac].lock, flags);
+			if (skb_queue_len(&iface->txq[ac]) != 0) {
+				ret = true;
+				spin_unlock_irqrestore(&iface->txq[ac].lock, flags);
+				goto _exit;
+
+			}
+			spin_unlock_irqrestore(&iface->txq[ac].lock, flags);
+		}
+	}
+
+_exit:
+	return ret;
+}
+
+bool skw_tx_exit_check(struct skw_core *skw, struct skw_tx_lmac *txlp)
+{
+	bool ret;
+	int mac, credit = 0, pending_qlen = 0;
+
+	ret = true;
+
+	if (skw->hw.bus == SKW_BUS_USB || skw->hw.bus == SKW_BUS_USB2) {
+		for (mac = 0; mac < skw->hw.nr_lmac; mac++) {
+			if (skw_lmac_is_actived(skw, mac)) {
+				credit = skw_get_hw_credit(skw, mac);
+				if (credit > 0 && txlp[mac].pending_qlen > 0)
+					ret = false;
+			}
+			trace_skw_tx_exit_check(mac, credit, txlp[mac].pending_qlen);
+		}
+	}
+
+	if (skw->hw.bus == SKW_BUS_SDIO || skw->hw.bus == SKW_BUS_SDIO2) {
+		for (mac = 0; mac < skw->hw.nr_lmac; mac++) {
+			pending_qlen += txlp[mac].pending_qlen;
+		}
+
+		if (pending_qlen == 0) {
+			if (skw_check_txq(skw))
+			ret = false;
+		} else
+			ret = false;
+
+		if (msecs_to_jiffies(100) > (jiffies - skw->trans_start))
+			ret = true;
+	}
+
+	if (test_bit(SKW_CMD_FLAG_XMIT, &skw->cmd.flags))
+		ret = false;
+
+	return ret;
+}
+
+int skw_tx_running_check(struct skw_core *skw, int times)
+{
+	int all_credit, mac, ret;
+
+	ret = SKW_TX_RUNNING_EXIT;
+
+	if (!skw_tx_allowed(READ_ONCE(skw->flags)))
+		return ret;
+
+	all_credit = 0;
+	for (mac = 0; mac < skw->hw.nr_lmac; mac++)
+		if (skw_lmac_is_actived(skw, mac))
+			all_credit += skw_get_hw_credit(skw, mac);
+
+	if (all_credit == 0) {
+		if (skw->hw.bus == SKW_BUS_SDIO || skw->hw.bus == SKW_BUS_SDIO2) {
+			if (times < tx_wait_time)
+				ret = SKW_TX_RUNNING_RESTART;
+		}
+	} else
+		ret = SKW_TX_RUNNING_GO;
+
+	return ret;
+}
+
+void skw_cmd_do(struct skw_core *skw)
+{
+	if (test_and_clear_bit(SKW_CMD_FLAG_XMIT, &skw->cmd.flags)) {
+		skw_bus_cmd_xmit(skw, skw->cmd.data, skw->cmd.data_len);
+
+		if (test_bit(SKW_CMD_FLAG_NO_ACK, &skw->cmd.flags)) {
+			set_bit(SKW_CMD_FLAG_DONE, &skw->cmd.flags);
+			skw->cmd.callback(skw);
+		}
+	}
+}
+
+int skw_txq_prepare(struct skw_core *skw, struct sk_buff_head *pure_ack_list, struct skw_tx_lmac *txl, int ac)
+{
+	int i, qlen, ac_qlen = 0;
+	unsigned long flags;
+	struct sk_buff *skb;
+	struct skw_iface *iface;
+	struct sk_buff_head *qlist;
+	struct netdev_queue *txq;
+	struct skw_tx_lmac *txlp;
+
+	for (i = 0; i < SKW_NR_IFACE; i++) {
+		iface = skw->vif.iface[i];
+		// if (!iface || skw_lmac_is_actived(skw, iface->lmac_id))
+		if (!iface || !iface->ndev)
+			continue;
+
+		if (ac == SKW_WMM_AC_BE && iface->txq[SKW_ACK_TXQ].qlen) {
+			qlist = &iface->txq[SKW_ACK_TXQ];
+
+			__skb_queue_head_init(pure_ack_list);
+
+			spin_lock_irqsave(&qlist->lock, flags);
+			skb_queue_splice_tail_init(&iface->txq[SKW_ACK_TXQ],
+						pure_ack_list);
+			spin_unlock_irqrestore(&qlist->lock, flags);
+
+			skw_merge_pure_ack(skw, pure_ack_list,
+					&iface->tx_cache[ac]);
+		}
+
+		qlist = &iface->txq[ac];
+		if (!skb_queue_empty(qlist)) {
+			spin_lock_irqsave(&qlist->lock, flags);
+			skb_queue_splice_tail_init(qlist,
+					&iface->tx_cache[ac]);
+			spin_unlock_irqrestore(&qlist->lock, flags);
+		}
+
+		if (READ_ONCE(iface->flags) & SKW_IFACE_FLAG_DEAUTH) {
+			while ((skb = __skb_dequeue(&iface->tx_cache[ac])) != NULL)
+				skw_skb_kfree(skw, skb);
+		}
+
+		qlen = skb_queue_len(&iface->tx_cache[ac]);
+		if (qlen < SKW_TXQ_LOW_THRESHOLD) {
+			txq = netdev_get_tx_queue(iface->ndev, ac);
+			if (netif_tx_queue_stopped(txq)) {
+				netif_tx_start_queue(txq);
+				netif_schedule_queue(txq);
+			}
+		}
+
+		if (qlen) {
+			txlp = &txl[iface->lmac_id];
+			txlp->current_qlen += qlen;
+
+			trace_skw_txq_prepare(iface->ndev->name, qlen);
+
+			txlp->txq_map |= BIT(txlp->nr_txq);
+
+			txlp->tx[txlp->nr_txq].list = &iface->tx_cache[ac];
+			txlp->tx[txlp->nr_txq].reset = true;
+			txlp->reset = true;
+			if (txlp->ac_reset & BIT(ac)) {
+				txlp->tx[txlp->nr_txq].quota = iface->wmm.factor[ac];
+				txlp->ac_reset ^= BIT(ac);
+			}
+
+			txlp->nr_txq++;
+			ac_qlen += qlen;
+		}
+	}
+
+	return ac_qlen;
+}
+
+static inline bool skw_vring_check(struct skw_core *skw, struct sk_buff_head *qlist, struct skw_tx_vring *tx_vring)
+{
+	if (skw->hw.bus == SKW_BUS_SDIO || skw->hw.bus == SKW_BUS_SDIO2) {
+		if (skb_queue_len(qlist) + 1 >= skw_vring_available_count(tx_vring))
+			return true;
+	}
+
+	return false;
+}
+
+#ifdef CONFIG_SWT6621S_TX_WORKQUEUE
+void skw_tx_worker(struct work_struct *work)
+{
+	int i, ac, mac;
+	int base = 0;
+	//unsigned long flags;
+	int lmac_tx_capa;
+	//int qlen, pending_qlen = 0;
+	int pending_qlen = 0;
+	int max_tx_count_limit = 0;
+	struct sk_buff *skb;
+	//struct skw_iface *iface;
+	//struct sk_buff_head *qlist;
+	struct skw_tx_lmac txl[SKW_MAX_LMAC_SUPPORT];
+	struct skw_tx_lmac *txlp;
+	struct sk_buff_head pure_ack_list;
+	//int xmit_tx_flag;
+	struct skw_core *skw = container_of(to_delayed_work(work), struct skw_core, tx_worker);
+	int times = 0, ret;
+
+start:
+
+	memset(txl, 0, sizeof(txl));
+	skw_reset_ac(txl);
+
+	max_tx_count_limit = skw->hw.pkt_limit;
+
+	/* reserve one for eof block */
+	if (skw->hw.bus == SKW_BUS_SDIO)
+		max_tx_count_limit--;
+
+	for (i = 0; i < skw->hw.nr_lmac; i++) {
+		__skb_queue_head_init(&txl[i].tx_list);
+		txl[i].tx_count_limit = max_tx_count_limit;
+	}
+
+	while (!atomic_read(&skw->exit)) {
+
+		// TODO:
+		/* CPU bind */
+		/* check if frame in pending queue is timeout */
+
+		atomic_inc(&skw->dbg.loop);
+		skw_cmd_do(skw);
+
+		pending_qlen = 0;
+		ret = skw_tx_running_check(skw, times++);
+		if (ret == SKW_TX_RUNNING_RESTART)
+			goto start;
+		else if (ret == SKW_TX_RUNNING_EXIT) {
+			if (skw->hw.bus == SKW_BUS_PCIE)
+				skw_wakeup_tx(skw, 0);
+
+			return;
+		}
+
+		for (ac = 0; ac < SKW_WMM_AC_MAX; ac++) {
+			int ac_qlen = 0;
+
+			ac_qlen = skw_txq_prepare(skw, &pure_ack_list, txl, ac);
+
+			if (!ac_qlen)
+				continue;
+
+			pending_qlen += ac_qlen;
+
+			lmac_tx_capa = 0;
+
+			for (mac = 0; mac < skw->hw.nr_lmac; mac++) {
+				int credit;
+
+				txlp = &txl[mac];
+				if (!txlp->txq_map)
+					goto reset;
+
+				credit = txlp->cred = skw_get_hw_credit(skw, mac);
+				trace_skw_get_credit(credit, mac);
+				if (!txlp->cred)
+					goto reset;
+
+				if (txlp->reset) {
+					switch (ac) {
+					case SKW_WMM_AC_VO:
+						base = SKW_BASE_VO;
+						break;
+
+					case SKW_WMM_AC_VI:
+						base = SKW_BASE_VI;
+						break;
+
+					case SKW_WMM_AC_BK:
+						if (txlp->bk_tx_limit) {
+							base = min(txlp->cred, txlp->bk_tx_limit);
+							txlp->bk_tx_limit = 0;
+						} else {
+							base = txlp->cred;
+						}
+
+						base = base / txlp->nr_txq;
+						break;
+
+					default:
+						base = min(txlp->cred, txlp->current_qlen);
+						//base = base / txlp->nr_txq;
+						txlp->bk_tx_limit = (txlp->cred + 1) >> 1;
+						break;
+					}
+
+					base = base ? base : 1;
+					txlp->reset = false;
+				} else
+					base = 0;
+
+				trace_skw_txlp_mac(mac, txlp->cred, txlp->current_qlen, base);
+
+				for (i = 0; txlp->txq_map != 0; i++) {
+
+					i = i % txlp->nr_txq;
+					if (!(txlp->txq_map & BIT(i)))
+						continue;
+
+					if (skw_vring_check(skw, &txlp->tx_list, skw->hw.lmac[mac].tx_vring))
+						break;
+
+					if (!txlp->cred)
+						break;
+
+					if (txlp->tx[i].reset) {
+						txlp->tx[i].quota += base;
+
+						if (txlp->tx[i].quota < 0)
+							txlp->tx[i].quota = 0;
+
+						txlp->tx[i].reset = false;
+					}
+
+					skb = skb_peek(txlp->tx[i].list);
+					if (!skb) {
+						txlp->txq_map ^= BIT(i);
+						continue;
+					}
+
+					if (!is_skw_peer_data_valid(skw, skb)) {
+
+						skw_detail("drop dest: %pM\n",
+							   eth_hdr(skb)->h_dest);
+
+						__skb_unlink(skb, txlp->tx[i].list);
+						skw_skb_kfree(skw, skb);
+
+						continue;
+					}
+
+					if (!txlp->tx_count_limit--)
+						break;
+
+					if (txlp->tx[i].quota) {
+						txlp->tx[i].quota--;
+					} else {
+						txlp->txq_map ^= BIT(i);
+						continue;
+					}
+
+					__skb_unlink(skb, txlp->tx[i].list);
+					__skb_queue_tail(&txlp->tx_list, skb);
+
+					trace_skw_txlp_tx_list(skb->dev->name, ((struct skw_iface *)(netdev_priv(skb->dev)))->id);
+
+					txlp->cred--;
+				}
+
+				pending_qlen = pending_qlen - credit + txlp->cred;
+				//txlp->pending_qlen = pending_qlen;
+				txlp->pending_qlen = txlp->current_qlen - credit + txlp->cred;
+
+				trace_skw_tx_info(mac, ac, credit, credit - txlp->cred, txlp->current_qlen, pending_qlen);
+
+				if (skw_bus_data_xmit(skw, mac, &txlp->tx_list) >= 0)
+					skw->trans_start = jiffies;
+
+				txlp->tx_count_limit = max_tx_count_limit;
+
+				if (txlp->cred)
+					lmac_tx_capa |= BIT(mac);
+
+reset:
+				txlp->nr_txq = 0;
+				txlp->txq_map = 0;
+				txlp->current_qlen = 0;
+			}
+
+			if (!lmac_tx_capa)
+				break;
+		}
+
+		if (ac == SKW_WMM_AC_MAX)
+			skw_reset_ac(txl);
+
+		if (skw_tx_exit_check(skw, txl)) {
+			skw_start_dev_queue(skw);
+			return;
+		}
+	}
+}
+
+static void skw_kfree_skb_worker(struct work_struct *work)
+{
+	unsigned long flags;
+	struct sk_buff *skb, *tmp;
+	struct sk_buff_head qlist;
+	struct skw_core *skw = container_of(work, struct skw_core, kfree_skb_task);
+
+	__skb_queue_head_init(&qlist);
+
+	while (!skb_queue_empty(&skw->kfree_skb_qlist)) {
+		spin_lock_irqsave(&skw->kfree_skb_qlist.lock, flags);
+		skb_queue_splice_tail_init(&skw->kfree_skb_qlist, &qlist);
+		spin_unlock_irqrestore(&skw->kfree_skb_qlist.lock, flags);
+
+		skb_queue_walk_safe(&qlist, skb, tmp) {
+			if (likely(0 == SKW_SKB_TXCB(skb)->ret)) {
+				skb->dev->stats.tx_packets++;
+				skb->dev->stats.tx_bytes += SKW_SKB_TXCB(skb)->skb_native_len;
+			} else
+				skb->dev->stats.tx_errors++;
+
+			__skb_unlink(skb, &qlist);
+			skw_skb_kfree(skw, skb);
+		}
+	}
+}
+
+void skw_dump_vring(struct vring *vr)
+{
+	int i;
+	u16 avail_idx = vr->avail->idx;
+	u16 used_idx = vr->used->idx;
+	u16 pending, available;
+
+	available = vr->num - (avail_idx - used_idx);
+	pending = (avail_idx - used_idx) & (vr->num - 1);
+
+	skw_dbg("===== VRING DUMP:=====\n");
+	skw_dbg("  Queue Size: %u\n", vr->num);
+	skw_dbg("  avail->idx: %u, used->idx: %u\n",
+			avail_idx, used_idx);
+	skw_dbg("  Available descriptors: %u\n",
+			available);
+	skw_dbg("  Descriptors pending hardware processing: %u\n", pending);
+
+	// 打印描述符表
+	skw_dbg("  Descriptor Table:\n");
+	for (i = 0; i < vr->num; i++) {
+		struct vring_desc *desc = &vr->desc[i];
+		skw_dbg("    Desc[%03u]: addr=0x%016llx, len=%u, flags=0x%04x, next=%u\n",
+				i, (unsigned long long)desc->addr, desc->len, desc->flags, desc->next);
+		skw_hex_dump("desc addr", phys_to_virt(desc->addr), desc->len, true);
+	}
+
+	// 打印可用环
+	skw_dbg("  Available Ring (avail->idx=%u):\n", avail_idx);
+	for (i = 0; i < vr->num; i++) {
+		unsigned int idx = i % vr->num;
+		skw_dbg("    Avail[%03u]: desc_idx=%u\n", i, vr->avail->ring[idx]);
+	}
+
+	// 打印已用环
+	skw_dbg("  Used Ring (used->idx=%u):\n", used_idx);
+	for (i = 0; i < vr->num; i++) {
+		unsigned int idx = i % vr->num;
+		skw_dbg("    Used[%03u]: id=%u, len=%u\n",
+				i, vr->used->ring[idx].id, vr->used->ring[idx].len);
+	}
+}
+
+u16 skw_vring_available_count(struct skw_tx_vring *tx_vring)
+{
+	struct vring *vr = &tx_vring->vr;
+	u16 avail_idx;
+	u16 used_idx;
+
+	spin_lock(&tx_vring->lock);
+	avail_idx = vr->avail->idx;
+	used_idx = vr->used->idx;
+	spin_unlock(&tx_vring->lock);
+
+	// 可用描述符数量 = 总容量 - (avail_idx - used_idx)
+	// 其中 (avail_idx - used_idx) 是已分配但未被设备处理的描述符数量
+	return vr->num - ((avail_idx - used_idx) & (vr->num - 1));
+}
+
+u16 skw_vring_pending_count(struct skw_tx_vring *tx_vring)
+{
+	u16 avail_idx, used_idx;
+	struct vring *vr;
+
+	vr = &tx_vring->vr;
+
+	spin_lock(&tx_vring->lock);
+	avail_idx = vr->avail->idx;
+	used_idx = vr->used->idx;
+	spin_unlock(&tx_vring->lock);
+
+
+	// 利用位运算处理回绕(要求vr->num是2的幂)
+	return (avail_idx - used_idx) & (vr->num - 1);
+}
+
+bool tx_vring_is_full(struct skw_tx_vring *tx_vring)
+{
+    struct vring *vr = &tx_vring->vr;
+    u16 avail_idx, used_idx;
+    u16 pending;
+
+    //spin_lock(&tx_vring->lock);
+    avail_idx = vr->avail->idx;
+    used_idx = vr->used->idx;
+    //spin_unlock(&tx_vring->lock);
+
+    // 计算待处理的描述符数量
+    pending = (avail_idx - used_idx) & (vr->num - 1);
+
+    // 当待处理数量达到队列大小时,表示队列已满
+    return pending >= (vr->num - 1);
+}
+
+bool vring_has_pending(struct skw_tx_vring *tx_vring)
+{
+	struct vring *vr;
+	u16 avail_idx, used_idx;
+
+	vr = &tx_vring->vr;
+
+	spin_lock(&tx_vring->lock);
+
+	avail_idx = vr->avail->idx;
+	used_idx = vr->used->idx;
+
+	spin_unlock(&tx_vring->lock);
+
+	// 计算待处理描述符数量(考虑回绕)
+	return ((avail_idx - used_idx) & (vr->num - 1)) != 0;
+}
+
+int skw_vring_set(struct skw_core *skw, void *data, u16 len, int lmac_id)
+{
+	u16 desc_idx, aligned;
+	struct vring *vr;
+	struct skw_tx_vring *tx_vring;
+
+	tx_vring = skw->hw.lmac[lmac_id].tx_vring;
+	vr = &tx_vring->vr;
+
+	// 1. 先获取锁,再检查队列状态
+	spin_lock(&tx_vring->lock);
+	smp_rmb();
+
+	if (tx_vring_is_full(tx_vring)) {
+		spin_unlock(&tx_vring->lock);
+		//skw_dbg("tx_vring_is_full\n");
+		return -EINVAL;
+	}
+
+	// 2. 获取当前可用描述符索引(正确方式)
+	desc_idx = vr->avail->idx & (vr->num - 1);
+
+	// 3. 配置描述符指向数据
+	//skw_sdio_hdr_set(skw, skb);
+	aligned = round_up(len, skw->hw.align);
+	memcpy(phys_to_virt(vr->desc[desc_idx].addr), data, aligned);
+	vr->desc[desc_idx].len = aligned;
+	vr->desc[desc_idx].flags = VRING_DESC_F_WRITE;
+
+	// 4. 更新可用环索引
+	vr->avail->ring[desc_idx] = desc_idx;
+
+	// 6. 使用内存屏障确保描述符更新先于索引更新
+	smp_wmb();
+	vr->avail->idx += 1;
+
+	//atomic_inc(&tx_vring->set);
+
+	spin_unlock(&tx_vring->lock);
+
+	return 0;
+}
+
+struct skw_tx_vring *skw_tx_vring_init(void)
+{
+	int i;
+	struct skw_tx_vring *tx_vring;
+	void *queue_mem;
+
+	tx_vring = kcalloc(1, sizeof(struct skw_tx_vring), GFP_KERNEL);
+	if (!tx_vring) {
+		skw_dbg("alloc tx_vring fail\n");
+		goto __1;
+	}
+
+	tx_vring->page = alloc_pages(GFP_KERNEL | GFP_DMA, get_order(SKW_TX_PACK_SIZE*SKW_TX_VRING_SIZE));
+	if (!tx_vring->page) {
+		skw_dbg("alloc page fail\n");
+		goto __2;
+	}
+
+	tx_vring->sgl_dat = kcalloc(SKW_TX_VRING_SIZE, sizeof(struct scatterlist), GFP_KERNEL);
+	if (!tx_vring->sgl_dat) {
+		skw_dbg("alloc sgl_dat fail\n");
+		goto __3;
+	}
+	sg_init_table(tx_vring->sgl_dat, SKW_TX_VRING_SIZE);
+	tx_vring->tx_bytes = 0;
+	spin_lock_init(&tx_vring->lock);
+
+	skw_dbg("size:%d\n", vring_size(SKW_TX_VRING_SIZE, PAGE_SIZE));
+	queue_mem = kzalloc(vring_size(SKW_TX_VRING_SIZE, PAGE_SIZE), GFP_KERNEL);
+	if (!queue_mem) {
+		skw_dbg("Failed to allocate vring memory\n");
+		goto __4;
+	}
+	skw_dbg("queue_mem:%p\n", queue_mem);
+
+	vring_init(&tx_vring->vr, SKW_TX_VRING_SIZE, queue_mem, PAGE_SIZE);
+	for (i = 0; i < SKW_TX_VRING_SIZE; i++) {
+		tx_vring->vr.avail->ring[i] = i;  // 将描述符索引 0~num-1 依次放入可用环
+		tx_vring->vr.desc[i].addr = virt_to_phys(page_address(tx_vring->page)) + i * SKW_TX_PACK_SIZE;
+	}
+	tx_vring->queue_mem = queue_mem;  // 记录指针以便后续释放
+
+	atomic_set(&tx_vring->set, 0);
+	atomic_set(&tx_vring->write, 0);
+
+	return tx_vring;
+
+__4:
+	kfree(tx_vring->sgl_dat);
+__3:
+	__free_pages(tx_vring->page, get_order(SKW_TX_PACK_SIZE*SKW_TX_VRING_SIZE));
+__2:
+	kfree(tx_vring);
+__1:
+	return NULL;
+}
+
+void skw_tx_vring_deinit(struct skw_tx_vring *tx_vring)
+{
+	if (tx_vring)
+		return ;
+	if (tx_vring->page)
+		__free_pages(tx_vring->page, get_order(SKW_TX_PACK_SIZE*SKW_TX_VRING_SIZE));
+	if (tx_vring->sgl_dat)
+		kfree(tx_vring->sgl_dat);
+
+	if (tx_vring->queue_mem)
+		kfree(tx_vring->queue_mem);
+
+	kfree(tx_vring);
+
+	return ;
+}
+
+int skw_sdio_write(struct skw_core *skw, struct skw_tx_vring *tx_vring, u16 limit, int lmac_id)
+{
+	struct vring *vr = &tx_vring->vr;
+	//u16 avail_idx;
+	//u16 used_idx;  // 使用vr->used->idx而不是last_used_idx
+	u16 desc_idx, data_len, used_ring_idx, used_idx, avail_idx;
+	u16 nents;
+	u32 tx_bytes;
+	int ret;
+
+	// 检查是否有新的可用描述符
+	if (!vring_has_pending(tx_vring)) {
+		skw_dbg("no avail data vr->avail->idx:%d vr->used->idx:%d\n", vr->avail->idx, vr->used->idx);
+		return -EINVAL;
+	}
+
+	spin_lock(&tx_vring->lock);
+	avail_idx = vr->avail->idx;
+	used_idx = vr->used->idx;
+
+	// 处理所有待处理的描述符(从used_idx到avail_idx-1)
+	sg_init_table(tx_vring->sgl_dat, SKW_TX_VRING_SIZE);
+	nents = 0;
+	tx_bytes = 0;
+	while ((used_idx & (vr->num - 1)) != (avail_idx & (vr->num - 1))) {
+		if (nents == limit)
+			break;
+		// 获取当前待处理的描述符索引
+		desc_idx = vr->avail->ring[used_idx % vr->num];
+
+		// 从描述符读取数据地址和长度
+		data_len = vr->desc[desc_idx].len;
+
+		// 记录调试信息
+		//printk("Processing descriptor %u: len=%u\n", desc_idx, data_len);
+
+		// 将结果添加到已用环
+		used_ring_idx = used_idx % vr->num;
+		vr->used->ring[used_ring_idx].id = desc_idx;
+		vr->used->ring[used_ring_idx].len = data_len;
+
+		sg_set_buf(&tx_vring->sgl_dat[nents++],  phys_to_virt(vr->desc[desc_idx].addr), vr->desc[desc_idx].len);
+		tx_bytes += vr->desc[desc_idx].len;
+
+		// 清理已处理描述符的状态
+		vr->desc[desc_idx].flags &= ~VRING_DESC_F_WRITE;
+		vr->desc[desc_idx].len = 0;
+
+		// 移动到下一个待处理的描述符
+		used_idx++;
+
+		//atomic_inc(&tx_vring->write);
+	}
+	spin_unlock(&tx_vring->lock);
+
+	skw_set_extra_hdr(skw, skw->eof_blk, skw->hw.lmac[lmac_id].lport, skw->hw.align, 0, 1);
+	sg_set_buf(&tx_vring->sgl_dat[nents++], skw->eof_blk, skw->hw.align);
+	tx_bytes += 512;
+
+	ret = skw->hw_pdata->hw_adma_tx(skw->hw.lmac[lmac_id].dport, tx_vring->sgl_dat, nents, tx_bytes);
+	if (ret)
+		skw_err("hw_adma_tx fail ret:%d nents:%d tx_bytes:%d\n", ret, nents, tx_bytes);
+
+	// 更新已用环索引
+	spin_lock(&tx_vring->lock);
+	smp_wmb();
+	vr->used->idx = used_idx;
+	spin_unlock(&tx_vring->lock);
+
+	return ret;
+}
+
+void skw_dy_worker(struct work_struct *work)
+{
+	int credit;
+	int pending, limit;
+	//int nents;
+	struct skw_tx_vring *tx_vring;
+	//struct skw_core *skw = container_of(work, struct skw_core, dy_work);
+	struct skw_lmac *lmac = container_of(work, struct skw_lmac, dy_work);
+	struct skw_core *skw = lmac->skw;
+
+	//tx_vring = skw->tx_vring;
+	tx_vring = lmac->tx_vring;
+	while (!atomic_read(&skw->exit)) {
+		credit = skw_get_hw_credit(skw, lmac->id);
+		if (credit == 0)
+			break;
+
+		//spin_lock(&tx_vring->lock);
+		pending = skw_vring_pending_count(tx_vring);
+		//spin_unlock(&tx_vring->lock);
+
+		if (pending == 0)
+			break;
+
+		limit = min(pending, credit);
+
+		if (skw_sdio_write(skw, tx_vring, limit, lmac->id) == 0)
+			skw_sub_credit(skw, lmac->id, limit);
+	}
+}
+
+static int __skw_tx_init(struct skw_core *skw)
+{
+	//struct workqueue_attrs wq_attrs;
+	int j;
+#ifdef CONFIG_SWT6621S_SKB_RECYCLE
+	int i;
+	struct sk_buff *skb;
+#endif
+
+	skw->tx_wq = alloc_workqueue("skw_txwq.%d",
+			WQ_UNBOUND | WQ_CPU_INTENSIVE | WQ_HIGHPRI | WQ_SYSFS,
+			0, skw->idx);
+	if (!skw->tx_wq) {
+		skw_err("alloc skwtx_workqueue failed\n");
+		return -EFAULT;
+	}
+
+	//memset(&wq_attrs, 0, sizeof(wq_attrs));
+
+	//wq_attrs.nice = MIN_NICE;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) && LINUX_VERSION_CODE <= KERNEL_VERSION(5, 2, 0)
+	//apply_workqueue_attrs(skw->tx_wq, &wq_attrs);
+#endif
+
+	INIT_DELAYED_WORK(&skw->tx_worker, skw_tx_worker);
+	queue_delayed_work(skw->tx_wq, &skw->tx_worker, msecs_to_jiffies(0));
+	//queue_work_on(cpumask_last(cpu_online_mask), skw->tx_wq, &skw->tx_worker);
+	skw->trans_start = 0;
+
+	skb_queue_head_init(&skw->kfree_skb_qlist);
+	INIT_WORK(&skw->kfree_skb_task, skw_kfree_skb_worker);
+
+	skb_queue_head_init(&skw->skb_recycle_qlist);
+#ifdef CONFIG_SWT6621S_SKB_RECYCLE
+	for (i = 0; i < SKW_SKB_RECYCLE_COUNT; i++) {
+		skb = dev_alloc_skb(SKW_2K_SIZE);
+		if (skb)
+			skb_queue_tail(&skw->skb_recycle_qlist, skb);
+		else {
+			__skb_queue_purge(&skw->skb_recycle_qlist);
+			destroy_workqueue(skw->tx_wq);
+			skw_err("alloc skb recycle failed\n");
+			return -ENOMEM;
+		}
+	}
+#endif
+
+	for (j = 0; j < SKW_MAX_LMAC_SUPPORT; j++) {
+		INIT_WORK(&skw->hw.lmac[j].dy_work, skw_dy_worker);
+		skw->hw.lmac[j].tx_vring = skw_tx_vring_init();
+	}
+
+	return 0;
+}
+
+static void __skw_tx_deinit(struct skw_core *skw)
+{
+	int j;
+
+	atomic_set(&skw->exit, 1);
+	cancel_delayed_work_sync(&skw->tx_worker);
+	cancel_work_sync(&skw->kfree_skb_task);
+	skb_queue_purge(&skw->kfree_skb_qlist);
+	destroy_workqueue(skw->tx_wq);
+	skb_queue_purge(&skw->skb_recycle_qlist);
+
+	for (j = 0; j < SKW_MAX_LMAC_SUPPORT; j++) {
+		cancel_work_sync(&skw->hw.lmac[j].dy_work);
+		skw_tx_vring_deinit(skw->hw.lmac[j].tx_vring);
+	}
+}
+
+#else
+
+static int skw_tx_thread(void *data)
+{
+	struct skw_core *skw = data;
+	int i, ac, mac;
+	int base = 0;
+	unsigned long flags;
+	int lmac_tx_capa;
+	int qlen, pending_qlen = 0;
+	int max_tx_count_limit = 0;
+	int lmac_tx_map = 0;
+	struct sk_buff *skb;
+	struct skw_iface *iface;
+	struct sk_buff_head *qlist;
+	struct skw_tx_lmac txl[SKW_MAX_LMAC_SUPPORT];
+	struct skw_tx_lmac *txlp;
+	struct sk_buff_head pure_ack_list;
+	int xmit_tx_flag;
+	int all_credit;
+
+	memset(txl, 0, sizeof(txl));
+	skw_reset_ac(txl);
+
+	max_tx_count_limit = skw->hw.pkt_limit;
+
+	/* reserve one for eof block */
+	if (skw->hw.bus == SKW_BUS_SDIO)
+		max_tx_count_limit--;
+
+	for (i = 0; i < skw->hw.nr_lmac; i++) {
+		__skb_queue_head_init(&txl[i].tx_list);
+		txl[i].tx_count_limit = max_tx_count_limit;
+	}
+
+	while (!kthread_should_stop()) {
+		// TODO:
+		/* CPU bind */
+		/* check if frame in pending queue is timeout */
+
+		atomic_inc(&skw->dbg.loop);
+
+		if (test_and_clear_bit(SKW_CMD_FLAG_XMIT, &skw->cmd.flags)) {
+			skw_bus_cmd_xmit(skw, skw->cmd.data, skw->cmd.data_len);
+
+			if (test_bit(SKW_CMD_FLAG_NO_ACK, &skw->cmd.flags)) {
+				set_bit(SKW_CMD_FLAG_DONE, &skw->cmd.flags);
+				skw->cmd.callback(skw);
+			}
+		}
+
+		if (!skw_tx_allowed(skw->flags)) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(msecs_to_jiffies(200));
+			continue;
+		}
+
+		pending_qlen = 0;
+		lmac_tx_map = 0;
+
+		for (ac = 0; ac < SKW_WMM_AC_MAX; ac++) {
+			int ac_qlen = 0;
+
+			for (i = 0; i < SKW_NR_IFACE; i++) {
+				iface = skw->vif.iface[i];
+				// if (!iface || skw_lmac_is_actived(skw, iface->lmac_id))
+				if (!iface || !iface->ndev)
+					continue;
+
+				if (ac == SKW_WMM_AC_BE && iface->txq[SKW_ACK_TXQ].qlen) {
+					qlist = &iface->txq[SKW_ACK_TXQ];
+					__skb_queue_head_init(&pure_ack_list);
+
+					spin_lock_irqsave(&qlist->lock, flags);
+					skb_queue_splice_tail_init(&iface->txq[SKW_ACK_TXQ],
+								&pure_ack_list);
+					spin_unlock_irqrestore(&qlist->lock, flags);
+
+					skw_merge_pure_ack(skw, &pure_ack_list, &iface->tx_cache[ac]);
+				}
+
+				qlist = &iface->txq[ac];
+				if (!skb_queue_empty(qlist)) {
+					spin_lock_irqsave(&qlist->lock, flags);
+					skb_queue_splice_tail_init(qlist, &iface->tx_cache[ac]);
+					spin_unlock_irqrestore(&qlist->lock, flags);
+				}
+
+				qlen = skb_queue_len(&iface->tx_cache[ac]);
+				if (qlen) {
+					txlp = &txl[iface->lmac_id];
+					txlp->current_qlen += qlen;
+					txlp->txq_map |= BIT(txlp->nr_txq);
+					txlp->tx[txlp->nr_txq].list = &iface->tx_cache[ac];
+
+					if (txlp->ac_reset & BIT(ac)) {
+						txlp->tx[txlp->nr_txq].quota = iface->wmm.factor[ac];
+						txlp->tx[txlp->nr_txq].reset = true;
+						txlp->reset = true;
+						txlp->ac_reset ^= BIT(ac);
+					}
+
+					txlp->nr_txq++;
+					ac_qlen += qlen;
+				}
+			}
+
+			if (!ac_qlen)
+				continue;
+
+			pending_qlen += ac_qlen;
+			lmac_tx_capa = 0;
+
+			all_credit = 0;
+			for (mac = 0; mac < skw->hw.nr_lmac; mac++)
+				all_credit += skw_get_hw_credit(skw, mac);
+
+			if (all_credit == 0) {
+				skw_stop_dev_queue(skw);
+				wait_event_interruptible_exclusive(skw->tx_wait_q,
+					atomic_xchg(&skw->tx_wake, 0) || atomic_xchg(&skw->exit, 0));
+				skw_start_dev_queue(skw);
+			}
+
+			for (mac = 0; mac < skw->hw.nr_lmac; mac++) {
+				int credit;
+
+				txlp = &txl[mac];
+				if (!txlp->txq_map)
+					goto reset;
+
+				credit = txlp->cred = skw_get_hw_credit(skw, mac);
+				if (!txlp->cred)
+					goto reset;
+
+				if (txlp->reset) {
+					switch (ac) {
+					case SKW_WMM_AC_VO:
+						base = SKW_BASE_VO;
+						break;
+
+					case SKW_WMM_AC_VI:
+						base = SKW_BASE_VI;
+						break;
+
+					case SKW_WMM_AC_BK:
+						if (txlp->bk_tx_limit) {
+							base = min(txlp->cred, txlp->bk_tx_limit);
+							txlp->bk_tx_limit = 0;
+						} else {
+							base = txlp->cred;
+						}
+
+						base = base / txlp->nr_txq;
+
+						break;
+					default:
+						base = min(txlp->cred, txlp->current_qlen);
+						base = base / txlp->nr_txq;
+						txlp->bk_tx_limit = (txlp->cred + 1) >> 1;
+
+						break;
+					}
+
+					base = base ? base : 1;
+					txlp->reset = false;
+				}
+
+				for (i = 0; txlp->txq_map != 0; i++) {
+					i = i % txlp->nr_txq;
+
+					if (!(txlp->txq_map & BIT(i)))
+						continue;
+
+					if (!txlp->cred)
+						break;
+
+					if (txlp->tx[i].reset) {
+						txlp->tx[i].quota += base;
+
+						if (txlp->tx[i].quota < 0)
+							txlp->tx[i].quota = 0;
+
+						txlp->tx[i].reset = false;
+					}
+
+					skb = skb_peek(txlp->tx[i].list);
+					if (!skb) {
+						txlp->txq_map ^= BIT(i);
+						continue;
+					}
+
+					if (!is_skw_peer_data_valid(skw, skb)) {
+						skw_detail("drop dest: %pM\n",
+							   eth_hdr(skb)->h_dest);
+
+						__skb_unlink(skb, txlp->tx[i].list);
+						kfree_skb(skb);
+
+						continue;
+					}
+
+					if (!txlp->tx_count_limit--)
+						break;
+
+					if (txlp->tx[i].quota) {
+						txlp->tx[i].quota--;
+					} else {
+						txlp->txq_map ^= BIT(i);
+						continue;
+					}
+
+					if ((long)skb->data & SKW_DATA_ALIGN_MASK)
+						skw_warn("address unaligned\n");
+#if 0
+					if (skb->len % skw->hw_pdata->align_value)
+						skw_warn("len: %d unaligned\n", skb->len);
+#endif
+					__skb_unlink(skb, txlp->tx[i].list);
+					__skb_queue_tail(&txlp->tx_list, skb);
+					txlp->cred--;
+				}
+
+				pending_qlen = pending_qlen - credit + txlp->cred;
+
+				trace_skw_tx_info(mac, ac, credit, credit - txlp->cred,
+						txlp->current_qlen, pending_qlen);
+
+				// skw_lmac_tx(skw, mac, &txlp->tx_list);
+				skw_bus_data_xmit(skw, mac, &txlp->tx_list);
+				txlp->tx_count_limit = max_tx_count_limit;
+
+				lmac_tx_map |= BIT(mac);
+
+				if (txlp->cred)
+					lmac_tx_capa |= BIT(mac);
+reset:
+				txlp->nr_txq = 0;
+				txlp->txq_map = 0;
+				txlp->current_qlen = 0;
+			}
+
+			if (!lmac_tx_capa)
+				break;
+		}
+
+		if (ac == SKW_WMM_AC_MAX)
+			skw_reset_ac(txl);
+
+		if (pending_qlen == 0) {
+			xmit_tx_flag = 0;
+
+			for (ac = 0; ac < SKW_WMM_AC_MAX; ac++) {
+				for (i = 0; i < SKW_NR_IFACE; i++) {
+					iface = skw->vif.iface[i];
+					if (!iface || !iface->ndev)
+						continue;
+
+					if (skb_queue_len(&iface->tx_cache[ac]) != 0) {
+						xmit_tx_flag = 1;
+						goto need_running;
+					}
+
+					spin_lock_irqsave(&iface->txq[ac].lock, flags);
+					if (skb_queue_len(&iface->txq[ac]) != 0) {
+						xmit_tx_flag = 1;
+						spin_unlock_irqrestore(&iface->txq[ac].lock, flags);
+						goto need_running;
+
+					}
+					spin_unlock_irqrestore(&iface->txq[ac].lock, flags);
+				}
+			}
+need_running:
+
+			if (xmit_tx_flag == 0) {
+				//skw_start_dev_queue(skw);
+				wait_event_interruptible_exclusive(skw->tx_wait_q,
+					atomic_xchg(&skw->tx_wake, 0) || atomic_xchg(&skw->exit, 0));
+			}
+		}
+	}
+
+	skw_info("exit");
+
+	return 0;
+}
+
+static int __skw_tx_init(struct skw_core *skw)
+{
+	skw->tx_thread = kthread_create(skw_tx_thread, skw, "skw_tx.%d", skw->idx);
+	if (IS_ERR(skw->tx_thread)) {
+		skw_err("create tx thread failed\n");
+		return  PTR_ERR(skw->tx_thread);
+	}
+
+	//kthread_bind(skw->tx_thread, cpumask_last(cpu_online_mask));
+
+	skw_set_thread_priority(skw->tx_thread, SCHED_RR, 1);
+	set_user_nice(skw->tx_thread, MIN_NICE);
+	wake_up_process(skw->tx_thread);
+
+	return 0;
+}
+
+static void __skw_tx_deinit(struct skw_core *skw)
+{
+	if (skw->tx_thread) {
+		atomic_set(&skw->exit, 1);
+		kthread_stop(skw->tx_thread);
+		skw->tx_thread = NULL;
+	}
+}
+
+#endif
+
+static int skw_register_tx_callback(struct skw_core *skw, void *func, void *data)
+{
+	int i, map, ret = 0;
+
+	for (map = 0, i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
+		if (!(skw->hw.lmac[i].flags & SKW_LMAC_FLAG_TXCB))
+			continue;
+
+		ret = skw_register_tx_cb(skw, skw->hw.lmac[i].dport, func, data);
+		if (ret < 0) {
+			skw_err("chip: %d, hw mac: %d, port: %d failed, ret: %d\n",
+				skw->idx, i, skw->hw.lmac[i].dport, ret);
+
+			break;
+		}
+
+		map |= BIT(skw->hw.lmac[i].dport);
+	}
+
+	skw_dbg("chip: %d, %s data port: 0x%x\n",
+		skw->idx, func ? "register" : "unregister", map);
+
+	return ret;
+}
+
+int skw_hw_xmit_init(struct skw_core *skw, int dma)
+{
+	int ret = 0;
+
+	skw_dbg("dma: %d\n", dma);
+
+	switch (dma) {
+	case SKW_SYNC_ADMA_TX:
+		skw->hw.dat_xmit = skw_sync_adma_tx;
+		skw->hw.cmd_xmit = skw_sync_adma_tx;
+		skw->hw.cmd_disable_irq_xmit = skw_sync_adma_cmd_disable_irq_tx;
+		break;
+
+	case SKW_SYNC_SDMA_TX:
+		skw->hw.dat_xmit = skw_sync_sdma_tx;
+		skw->hw.cmd_xmit = skw_sync_sdma_tx;
+		skw->hw.cmd_disable_irq_xmit = skw_sync_sdma_cmd_disable_irq_tx;
+
+		if (skw->sdma_buff)
+			break;
+		skw->sdma_buff = SKW_ZALLOC(skw->hw_pdata->max_buffer_size, GFP_KERNEL);
+		if (!skw->sdma_buff)
+			ret = -ENOMEM;
+
+		break;
+
+	case SKW_ASYNC_ADMA_TX:
+		skw->hw.dat_xmit = skw_async_adma_tx;
+		skw->hw.cmd_xmit = skw_sync_adma_tx;
+		skw->hw.cmd_disable_irq_xmit = NULL;
+
+		ret = skw_register_tx_callback(skw, skw_async_adma_tx_free, skw);
+		break;
+
+	case SKW_ASYNC_SDMA_TX:
+		skw->hw.dat_xmit = skw_async_sdma_tx;
+		skw->hw.cmd_xmit = skw_sync_sdma_tx;
+		skw->hw.cmd_disable_irq_xmit = NULL;
+
+		ret = skw_register_tx_callback(skw, skw_async_sdma_tx_free, skw);
+
+		if (skw->sdma_buff)
+			break;
+		skw->sdma_buff = SKW_ZALLOC(skw->hw_pdata->max_buffer_size, GFP_KERNEL);
+		if (!skw->sdma_buff)
+			ret = -ENOMEM;
+		break;
+
+	case SKW_ASYNC_EDMA_TX:
+		skw->hw.dat_xmit = NULL;
+		skw->hw.cmd_xmit = skw_sync_adma_tx;
+		skw->hw.cmd_disable_irq_xmit = NULL;
+		break;
+
+	default:
+		ret = -EINVAL;
+		skw->hw.dat_xmit = NULL;
+		skw->hw.cmd_xmit = NULL;
+		break;
+	}
+
+	return ret;
+}
+
+int skw_tx_init(struct skw_core *skw)
+{
+	int ret;
+
+	skw->skb_share_len = SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+	skw->skb_headroom = sizeof(struct skw_tx_desc_hdr) +
+			    sizeof(struct skw_tx_desc_conf) +
+			    skw->hw.extra.hdr_len +
+			    SKW_DATA_ALIGN_SIZE + sizeof(unsigned long);
+
+	if (skw->hw.bus == SKW_BUS_SDIO) {
+		skw->eof_blk = SKW_ZALLOC(skw->hw.align, GFP_KERNEL);
+		if (!skw->eof_blk)
+			return -ENOMEM;
+	} else if (skw->hw.bus == SKW_BUS_PCIE) {
+		skw->skb_headroom = sizeof(struct skw_tx_desc_conf) +
+				    SKW_DATA_ALIGN_SIZE + 2 * sizeof(unsigned long);
+	}
+
+	ret = skw_hw_xmit_init(skw, skw->hw.dma);
+	if (ret < 0) {
+		SKW_KFREE(skw->eof_blk);
+		return ret;
+	}
+
+	ret = __skw_tx_init(skw);
+	if (ret < 0) {
+		SKW_KFREE(skw->eof_blk);
+		SKW_KFREE(skw->sdma_buff);
+	}
+
+	tx_wait_time = 5000;
+	skw_procfs_file(skw->pentry, "tx_wait_time", 0666, &skw_tx_time_fops, NULL);
+
+	return ret;
+}
+
+int skw_tx_deinit(struct skw_core *skw)
+{
+	__skw_tx_deinit(skw);
+
+	skw_register_tx_callback(skw, NULL, NULL);
+
+	SKW_KFREE(skw->eof_blk);
+	SKW_KFREE(skw->sdma_buff);
+
+	return 0;
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tx.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tx.h
new file mode 100755
index 0000000..e53adbd
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_tx.h
@@ -0,0 +1,150 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_TX_H__
+#define __SKW_TX_H__
+
+#include <linux/virtio_ring.h>
+
+#include "skw_platform_data.h"
+
+/* used for tx descriptor */
+#define SKW_ETHER_FRAME            0
+#define SKW_80211_FRAME            1
+
+#define SKW_SNAP_HDR_LEN           8
+
+#define SKW_TXQ_HIGH_THRESHOLD     768
+#define SKW_TXQ_LOW_THRESHOLD      512
+
+#define SKW_TX_WAIT_TIME 1000000
+
+#define SKW_TX_VRING_SIZE    128
+
+struct skw_tx_vring {
+	struct page *page;
+	int tx_bytes;
+	struct vring vr;
+	void *queue_mem;  // 新增:记录vring内存块指针
+
+	struct scatterlist *sgl_dat;
+	spinlock_t lock;
+
+	atomic_t set, write;
+};
+
+enum SKW_TX_RUNNING_STATE {
+	SKW_TX_RUNNING_EXIT,
+	SKW_TX_RUNNING_RESTART,
+	SKW_TX_RUNNING_GO,
+};
+
+struct skw_tx_desc_hdr {
+	/* pading bytes for gap */
+	u16 padding_gap:2;
+	u16 inst:2;
+	u16 tid:4;
+	u16 peer_lut:5;
+
+	/* frame_type:
+	 * 0: ethernet frame
+	 * 1: 80211 frame
+	 */
+	u16 frame_type:1;
+	u16 encry_dis:1;
+
+	/* rate: 0: auto, 1: sw config */
+	u16 rate:1;
+
+	u16 msdu_len:12;
+	u16 lmac_id:2;
+	u16 rsv:2;
+	u16 eth_type;
+
+	/* pading for address align */
+	u8 gap[0];
+} __packed;
+
+struct skw_tx_desc_conf {
+	u16 l4_hdr_offset:10;
+	u16 csum:1;
+
+	/* ip_prot: 0: UDP, 1: TCP */
+	u16 ip_prot:1;
+	u16 rsv:4;
+} __packed;
+
+struct skw_tx_cb {
+	u8 ret:1;
+	u8 recycle:1;
+	u8 lmac_id;
+	u8 peer_idx;
+	u8 tx_retry;
+	u16 skb_native_len;
+	dma_addr_t skb_data_pa;
+	struct skw_edma_elem e;
+};
+
+static inline void
+skw_set_tx_desc_eth_type(struct skw_tx_desc_hdr *desc_hdr, u16 proto)
+{
+	desc_hdr->eth_type = proto;
+}
+
+static inline int skw_tx_allowed(unsigned long flags)
+{
+	unsigned long mask = BIT(SKW_FLAG_FW_ASSERT) |
+			     BIT(SKW_FLAG_BLOCK_TX) |
+			     BIT(SKW_FLAG_MP_MODE) |
+			     BIT(SKW_FLAG_FW_THERMAL) |
+			     BIT(SKW_FLAG_FW_MAC_RECOVERY);
+
+	return !(mask & flags);
+}
+
+static inline int skw_cmd_tx_allowed(unsigned long flags)
+{
+	clear_bit(SKW_FLAG_FW_THERMAL, &flags);
+
+	return skw_tx_allowed(flags);
+}
+
+#ifdef CONFIG_SWT6621S_SKB_RECYCLE
+void skw_recycle_skb_free(struct skw_core *skw, struct sk_buff *skb);
+int skw_recycle_skb_copy(struct skw_core *skw, struct sk_buff *skb_recycle, struct sk_buff *skb);
+struct sk_buff *skw_recycle_skb_get(struct skw_core *skw);
+#endif
+void skw_skb_kfree(struct skw_core *skw, struct sk_buff *skb);
+
+int skw_pcie_cmd_xmit(struct skw_core *skw, void *data, int data_len);
+int skw_pcie_xmit(struct skw_core *skw, int lmac_id, struct sk_buff_head *txq);
+
+int skw_sdio_cmd_xmit(struct skw_core *skw, void *data, int data_len);
+int skw_sdio_xmit(struct skw_core *skw, int lmac_id, struct sk_buff_head *txq);
+
+int skw_usb_cmd_xmit(struct skw_core *skw, void *data, int data_len);
+int skw_usb_xmit(struct skw_core *skw, int lmac_id, struct sk_buff_head *txq);
+
+int skw_hw_xmit_init(struct skw_core *skw, int dma);
+int skw_tx_init(struct skw_core *skw);
+int skw_tx_deinit(struct skw_core *skw);
+
+void skw_sdio_hdr_set(struct skw_core *skw, struct sk_buff *skb, int lmac_id);
+int skw_vring_set(struct skw_core *skw, void *data, u16 len, int lmac_id);
+u16 skw_vring_available_count(struct skw_tx_vring *tx_vring);
+
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_util.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_util.c
new file mode 100755
index 0000000..671265e
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_util.c
@@ -0,0 +1,657 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/etherdevice.h>
+
+#include "skw_core.h"
+#include "skw_util.h"
+#include "skw_cfg80211.h"
+
+#ifdef CONFIG_PRINTK_TIME_FROM_ARM_ARCH_TIMER
+#include <clocksource/arm_arch_timer.h>
+u64 skw_local_clock(void)
+{
+	u64 ns;
+
+	ns = arch_timer_read_counter() * 1000;
+	do_div(ns, 24);
+
+	return ns;
+}
+#else
+u64 skw_local_clock(void)
+{
+	return local_clock();
+}
+#endif
+
+
+#ifdef SKW_IMPORT_NS
+struct file *skw_file_open(const char *path, int flags, int mode)
+{
+	struct file *fp = NULL;
+
+	fp = filp_open(path, flags, mode);
+	if (IS_ERR(fp)) {
+		skw_err("open fail\n");
+		return NULL;
+	}
+
+	return fp;
+}
+
+int skw_file_read(struct file *fp, unsigned char *data,
+		size_t size, loff_t offset)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
+	return kernel_read(fp, data, size, &offset);
+#else
+	return kernel_read(fp, offset, data, size);
+#endif
+}
+
+int skw_file_write(struct file *fp, unsigned char *data,
+		size_t size, loff_t offset)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
+	return kernel_write(fp, data, size, &offset);
+#else
+	return kernel_write(fp, data, size, offset);
+#endif
+}
+
+int skw_file_sync(struct file *fp)
+{
+	return vfs_fsync(fp, 0);
+}
+
+void skw_file_close(struct file *fp)
+{
+	filp_close(fp, NULL);
+}
+
+MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
+#endif
+
+void *skw_build_presp_frame(struct wiphy *wiphy, struct skw_iface *iface,
+			u8 *da, u8 *sa, u8 *bssid, u8 *ssid, int ssid_len,
+			u16 chan, struct ieee80211_supported_band *sband,
+			u16 capa, u64 tsf, int beacon_int, void *ie, int ie_len)
+{
+	u8 *pos;
+	int i, rate;
+	struct skw_template *temp;
+	struct ieee80211_mgmt *mgmt;
+
+	skw_dbg("ssid: %s, bssid: %pM\n", ssid, bssid);
+
+	temp = SKW_ZALLOC(1600, GFP_KERNEL);
+	if (!temp)
+		return NULL;
+
+	mgmt = temp->mgmt;
+	mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+					IEEE80211_STYPE_PROBE_RESP);
+	memcpy(mgmt->sa, sa, ETH_ALEN);
+	memcpy(mgmt->da, da, ETH_ALEN);
+	memcpy(mgmt->bssid, bssid, ETH_ALEN);
+
+	mgmt->u.beacon.beacon_int = cpu_to_le16(beacon_int);
+	mgmt->u.beacon.timestamp = cpu_to_le64(tsf);
+	mgmt->u.beacon.capab_info = cpu_to_le16(capa);
+
+	pos = mgmt->u.beacon.variable;
+
+	*pos++ = WLAN_EID_SSID;
+	*pos++ = ssid_len;
+	memcpy(pos, ssid, ssid_len);
+	pos += ssid_len;
+
+	*pos++ = WLAN_EID_SUPP_RATES;
+	*pos++ = SKW_BASIC_RATE_COUNT;
+	for (i = 0; i < SKW_BASIC_RATE_COUNT; i++) {
+		rate = DIV_ROUND_UP(sband->bitrates[i].bitrate, 5);
+		if (sband->bitrates[i].flags & 0x1)
+			rate |= 0x80;
+		*pos++ = rate;
+	}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0)
+	if (sband->band == IEEE80211_BAND_2GHZ) {
+#else
+	if (sband->band == NL80211_BAND_2GHZ) {
+#endif
+		*pos++ = WLAN_EID_DS_PARAMS;
+		*pos++ = 1;
+		*pos++ = chan;
+	}
+
+	if (iface->wdev.iftype == NL80211_IFTYPE_ADHOC) {
+		*pos++ = WLAN_EID_IBSS_PARAMS;
+		*pos++ = 2;
+		*pos++ = 0;
+		*pos++ = 0;
+	}
+
+	*pos++ = WLAN_EID_EXT_SUPP_RATES;
+	*pos++ = sband->n_bitrates - SKW_BASIC_RATE_COUNT;
+	for (i = SKW_BASIC_RATE_COUNT; i < sband->n_bitrates; i++) {
+		rate = DIV_ROUND_UP(sband->bitrates[i].bitrate, 5);
+		if (sband->bitrates[i].flags & 0x1)
+			rate |= 0x80;
+		*pos++ = rate;
+	}
+
+	if (ie_len) {
+		memcpy(pos, ie, ie_len);
+		pos += ie_len;
+	}
+
+	return temp;
+}
+
+int skw_key_idx(u16 bitmap)
+{
+	static u8 idx[] = {0xff, 0x00, 0x01, 0xff,
+			   0x02, 0xff, 0xff, 0xff,
+			   0x03, 0xff, 0xff, 0xff,
+			   0xff, 0xff, 0xff, 0xff};
+
+	return idx[bitmap & 0xf];
+}
+
+int skw_build_deauth_frame(void *buf, int buf_len, u8 *da, u8 *sa,
+			u8 *bssid, u16 reason_code)
+{
+	u16 fc = IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_DEAUTH;
+	struct ieee80211_mgmt *mgmt = buf;
+
+	if (!buf || buf_len < SKW_DEAUTH_FRAME_LEN)
+		return -EINVAL;
+
+	mgmt->frame_control = cpu_to_le16(fc);
+	mgmt->duration = 0;
+	mgmt->seq_ctrl = 0;
+	skw_ether_copy(mgmt->da, da);
+	skw_ether_copy(mgmt->sa, sa);
+	skw_ether_copy(mgmt->bssid, bssid);
+
+	mgmt->u.deauth.reason_code = cpu_to_le16(reason_code);
+
+	return SKW_DEAUTH_FRAME_LEN;
+}
+
+char *skw_mgmt_name(u16 fc)
+{
+#define SKW_STYPE_STR(n) {case IEEE80211_STYPE_##n: return #n; }
+
+	switch (fc) {
+	SKW_STYPE_STR(ASSOC_REQ);
+	SKW_STYPE_STR(ASSOC_RESP);
+	SKW_STYPE_STR(REASSOC_REQ);
+	SKW_STYPE_STR(REASSOC_RESP);
+	SKW_STYPE_STR(PROBE_REQ);
+	SKW_STYPE_STR(PROBE_RESP);
+	SKW_STYPE_STR(BEACON);
+	SKW_STYPE_STR(ATIM);
+	SKW_STYPE_STR(DISASSOC);
+	SKW_STYPE_STR(AUTH);
+	SKW_STYPE_STR(DEAUTH);
+	SKW_STYPE_STR(ACTION);
+
+	default:
+		break;
+	}
+
+#undef SKW_STYPE_STR
+
+	return "UNDEFINE";
+}
+
+int skw_freq_to_chn(int freq)
+{
+	if (freq == 2484)
+		return 14;
+	else if (freq >= 2407 && freq < 2484)
+		return (freq - 2407) / 5;
+	else if (freq >= 4910 && freq <= 4980)
+		return (freq - 4000) / 5;
+	else if (freq >= 5000 && freq <= 45000)
+		return (freq - 5000) / 5;
+	else if (freq >= 58320 && freq <= 64800)
+		return (freq - 56160) / 2160;
+	else
+		return 0;
+}
+
+u32 skw_calc_rate(u64 bytes, u32 delta_ms)
+{
+	struct skw_tp_rate ret;
+	u64 cal_bytes = bytes;
+	u16 bps;
+	u16 Kbps;
+	u16 Mbps;
+	u16 Gbps;
+	u16 Tbps;
+
+	ret.ret = 0;
+	cal_bytes *= 8 * 1000;
+	do_div(cal_bytes, delta_ms);
+	bps = do_div(cal_bytes, 1 << 10);
+	Kbps = do_div(cal_bytes, 1 << 10);
+	Mbps = do_div(cal_bytes, 1 << 10);
+	Gbps = do_div(cal_bytes, 1 << 10);
+	Tbps = cal_bytes;
+
+	if (Tbps) {
+		ret.rate.value = Tbps;
+		ret.rate.unit = 'T';
+		ret.rate.two_dec = Gbps * 100 >> 10;
+	} else if (Gbps) {
+		ret.rate.value = Gbps;
+		ret.rate.unit = 'G';
+		ret.rate.two_dec = Mbps * 100 >> 10;
+	} else if (Mbps) {
+		ret.rate.value = Mbps;
+		ret.rate.unit = 'M';
+		ret.rate.two_dec = Kbps * 100 >> 10;
+	} else {
+		ret.rate.value = Kbps;
+		ret.rate.unit = 'K';
+		ret.rate.two_dec = bps * 100 >> 10;
+	}
+
+	return ret.ret;
+}
+
+static u32 skw_peer_rate(struct skw_stats_info *stat)
+{
+	u64 total_bytes, total_jiffies;
+
+	total_bytes = stat->bytes - stat->cal_bytes;
+	stat->cal_bytes = stat->bytes;
+	total_jiffies = jiffies - stat->cal_time;
+	stat->cal_time = jiffies;
+
+	return skw_calc_rate(total_bytes, jiffies_to_msecs(total_jiffies));
+}
+
+int skw_tx_throughput(struct skw_iface *iface, u8 *mac)
+{
+	int ret = 0;
+	struct skw_peer_ctx *ctx;
+
+
+	if (!iface) {
+		ret = -ENOENT;
+		goto exit;
+	}
+
+	if (!mac || is_broadcast_ether_addr(mac)) {
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	ctx = skw_peer_ctx(iface, mac);
+	if (!ctx) {
+		ret = -ENOENT;
+		goto exit;
+	}
+
+	skw_peer_ctx_lock(ctx);
+
+	if (ctx->peer)
+		ret = skw_peer_rate(&ctx->peer->tx);
+
+	skw_peer_ctx_unlock(ctx);
+
+exit:
+	return ret;
+}
+
+int skw_rx_throughput(struct skw_iface *iface, u8 *mac)
+{
+	int ret = 0;
+	struct skw_peer_ctx *ctx;
+
+
+	if (!iface) {
+		ret = -ENOENT;
+		goto exit;
+	}
+
+	if (!mac || is_broadcast_ether_addr(mac)) {
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	ctx = skw_peer_ctx(iface, mac);
+	if (!ctx) {
+		ret = -ENOENT;
+		goto exit;
+	}
+
+	skw_peer_ctx_lock(ctx);
+
+	if (ctx->peer)
+		ret = skw_peer_rate(&ctx->peer->rx);
+
+	skw_peer_ctx_unlock(ctx);
+
+exit:
+	return ret;
+}
+
+int skw_tx_throughput_whole(struct skw_iface *iface, u32 *tp)
+{
+	int ret = 0;
+	u32 peer_idx_map, idx;
+	struct skw_peer_ctx *ctx;
+
+
+	if (!iface) {
+		ret = -ENOENT;
+		goto exit;
+	}
+
+	peer_idx_map = atomic_read(&iface->peer_map);
+
+	while (peer_idx_map) {
+		idx = ffs(peer_idx_map) - 1;
+		SKW_CLEAR(peer_idx_map, BIT(idx));
+
+		ctx = &iface->skw->hw.lmac[iface->lmac_id].peer_ctx[idx];
+
+		skw_peer_ctx_lock(ctx);
+
+		if (ctx->peer)
+			*(&(tp[idx])) = skw_peer_rate(&ctx->peer->tx);
+
+		skw_peer_ctx_unlock(ctx);
+	}
+
+exit:
+	return ret;
+}
+
+int skw_rx_throughput_whole(struct skw_iface *iface, u32 *tp)
+{
+	int ret = 0;
+	u32 peer_idx_map, idx;
+	struct skw_peer_ctx *ctx;
+
+
+	if (!iface) {
+		ret = -ENOENT;
+		goto exit;
+	}
+
+	peer_idx_map = atomic_read(&iface->peer_map);
+
+	while (peer_idx_map) {
+		idx = ffs(peer_idx_map) - 1;
+		SKW_CLEAR(peer_idx_map, BIT(idx));
+
+		ctx = &iface->skw->hw.lmac[iface->lmac_id].peer_ctx[idx];
+
+		skw_peer_ctx_lock(ctx);
+
+		if (ctx->peer)
+			*(&(tp[idx])) = skw_peer_rate(&ctx->peer->rx);
+
+		skw_peer_ctx_unlock(ctx);
+	}
+
+exit:
+	return ret;
+}
+
+const u8 *skw_find_ie_match(u8 eid, const u8 *ies, int len, const u8 *match,
+			    int match_len, int match_offset)
+{
+	const struct skw_element *elem;
+
+	/* match_offset can't be smaller than 2, unless match_len is
+	 * zero, in which case match_offset must be zero as well.
+	 */
+	if (WARN_ON((match_len && match_offset < 2) ||
+		    (!match_len && match_offset)))
+		return NULL;
+
+	skw_foreach_element_id(elem, eid, ies, len) {
+		if (elem->datalen >= match_offset - 2 + match_len &&
+		    !memcmp(elem->data + match_offset - 2, match, match_len))
+			return (void *)elem;
+	}
+
+	return NULL;
+}
+
+bool skw_bss_check_vendor_name(struct cfg80211_bss *bss, const u8 *oui)
+{
+	const struct cfg80211_bss_ies *ies;
+	const struct skw_element *elem;
+	bool ret = false;
+	u8 eid = WLAN_EID_VENDOR_SPECIFIC;
+
+	ies = rcu_dereference(bss->beacon_ies);
+	if (!ies)
+		return false;
+
+	skw_hex_dump("beacon ies:", ies->data, ies->len, false);
+
+	skw_foreach_element_id(elem, eid, ies->data, ies->len) {
+		if (!memcmp(elem->data, oui, 3)) {
+			ret = true;
+			break;
+		}
+	}
+
+	return ret;
+}
+
+int skw_desc_get_rx_rate(struct skw_rate *rate, u8 bw, u8 mode, u8 gi,
+		    u8 nss, u8 dcm, u16 data_rate)
+{
+	u16 skw_supp_bs_rate[] = {
+		20, 55, 110,
+		/*2M,5.5M,11M*/
+	};
+	u16 skw_supp_bl_rate[] = {
+		10, 20, 55, 110,
+		/*1M,2M,5.5M,11M*/
+	};
+	u16 skw_supp_g_rate[] = {
+		60, 90, 120, 180, 240, 360, 480, 540,
+		/*6M, 9M, 12M, 18M, 24M, 36M, 48M, 54M*/
+	};
+
+	memset(rate, 0x0, sizeof(struct skw_rate));
+
+	rate->bw = bw;
+	rate->nss = nss;
+
+	switch (mode) {
+	case SKW_PPDUMODE_HT_MIXED:
+		rate->flags = SKW_RATE_INFO_FLAGS_HT;
+		rate->mcs_idx = 0x3F & data_rate;
+		rate->gi = gi;
+		break;
+
+	case SKW_PPDUMODE_VHT_SU:
+	case SKW_PPDUMODE_VHT_MU:
+		rate->flags = SKW_RATE_INFO_FLAGS_VHT;
+		rate->mcs_idx = 0xF & data_rate;
+		rate->gi = gi;
+		break;
+
+	case SKW_PPDUMODE_HE_SU:
+	case SKW_PPDUMODE_HE_TB:
+	case SKW_PPDUMODE_HE_ER_SU:
+	case SKW_PPDUMODE_HE_MU:
+		rate->flags = SKW_RATE_INFO_FLAGS_HE;
+		rate->mcs_idx = 0xF & data_rate;
+
+		if (dcm) {
+			rate->mcs_idx = 0x3 & data_rate;
+			rate->he_dcm = dcm;
+		} else if (mode == SKW_PPDUMODE_HE_ER_SU) {
+			rate->mcs_idx = 0x3 & data_rate;
+		}
+
+		rate->gi = gi;
+
+		if (bw != SKW_DESC_BW_USED_RU)
+			rate->he_ru = bw + 3;
+		break;
+
+	case SKW_PPDUMODE_11B_SHORT:
+		rate->flags = SKW_RATE_INFO_FLAGS_LEGACY;
+
+		if (data_rate < ARRAY_SIZE(skw_supp_bs_rate))
+			rate->legacy_rate = skw_supp_bs_rate[data_rate];
+		else
+			skw_warn("illegal 11B_SHORT rate:%d\n", data_rate);
+		break;
+
+	case SKW_PPDUMODE_11B_LONG:
+		rate->flags = SKW_RATE_INFO_FLAGS_LEGACY;
+
+		if (data_rate < ARRAY_SIZE(skw_supp_bl_rate))
+			rate->legacy_rate = skw_supp_bl_rate[data_rate];
+		else
+			skw_warn("illegal 11B_LONG rate:%d\n", data_rate);
+		break;
+
+	case SKW_PPDUMODE_11G:
+		rate->flags = SKW_RATE_INFO_FLAGS_LEGACY;
+
+		if (data_rate < ARRAY_SIZE(skw_supp_g_rate))
+			rate->legacy_rate = skw_supp_g_rate[data_rate];
+		else
+			skw_warn("illegal 11G rate:%d\n", data_rate);
+		break;
+
+	default:
+		skw_warn("unsupport ppdu mode:%d\n", mode);
+		break;
+	};
+
+	return 0;
+}
+
+int skw_tlv_add(struct skw_tlv_conf *conf, int type, void *dat, int dat_len)
+{
+	struct skw_tlv *tlv;
+
+	if (!conf || !conf->buff)
+		return -EINVAL;
+
+	if (conf->total_len + dat_len + 4 > conf->buff_len)
+		return -ENOMEM;
+
+	tlv = (struct skw_tlv *)(conf->buff + conf->total_len);
+	tlv->type = type;
+	tlv->len = dat_len;
+	memcpy(tlv->value, dat, dat_len);
+
+	conf->total_len += dat_len + 4;
+
+	return 0;
+}
+
+int skw_tlv_alloc(struct skw_tlv_conf *conf, int len, gfp_t gfp)
+{
+	if (!conf)
+		return -EINVAL;
+
+	conf->buff = SKW_ZALLOC(len, GFP_KERNEL);
+	if (!conf->buff)
+		return -ENOMEM;
+
+	conf->total_len = 0;
+	conf->buff_len = len;
+
+	return 0;
+}
+
+void *skw_tlv_reserve(struct skw_tlv_conf *conf, int len)
+{
+	void *start = NULL;
+
+	if (!conf || !conf->buff)
+		return NULL;
+
+	if (conf->total_len + len > conf->buff_len)
+		return NULL;
+
+	start = conf->buff + conf->total_len;
+	conf->total_len += len;
+
+	return start;
+}
+
+void skw_tlv_free(struct skw_tlv_conf *conf)
+{
+	if (conf) {
+		SKW_KFREE(conf->buff);
+		conf->total_len = 0;
+		conf->buff_len = 0;
+	}
+}
+
+int skw_util_set_mib_enable(struct wiphy *wiphy, int inst, int mib_id, bool enable)
+{
+	int ret;
+	u16 *plen;
+	struct skw_tlv_conf conf;
+	bool state = !!enable;
+
+	skw_dbg("set %d mib:%d\n", mib_id, enable);
+
+	ret = skw_tlv_alloc(&conf, 128, GFP_KERNEL);
+	if (ret) {
+		skw_err("alloc failed\n");
+		return ret;
+	}
+
+	plen = skw_tlv_reserve(&conf, 2);
+	if (!plen) {
+		skw_err("reserve failed\n");
+		skw_tlv_free(&conf);
+		return -ENOMEM;
+	}
+
+	if (skw_tlv_add(&conf, mib_id, &state, 1)) {
+		skw_err("set mib failed\n");
+		skw_tlv_free(&conf);
+
+		return -EINVAL;
+	}
+
+	*plen = conf.total_len;
+
+	ret = skw_msg_xmit(wiphy, inst, SKW_CMD_SET_MIB, conf.buff,
+			   conf.total_len, NULL, 0);
+	if (ret)
+		skw_warn("failed, ret: %d\n", ret);
+
+	skw_tlv_free(&conf);
+
+	return ret;
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_util.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_util.h
new file mode 100755
index 0000000..795cff5
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_util.h
@@ -0,0 +1,338 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_UTIL_H__
+#define __SKW_UTIL_H__
+
+#include <linux/version.h>
+#include <linux/ieee80211.h>
+#include <net/cfg80211.h>
+#include <linux/etherdevice.h>
+#include <linux/netdevice.h>
+#include <linux/sched.h>
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
+#include <uapi/linux/sched/types.h>
+#endif
+
+#define SKW_NULL
+#define SKW_LEAVE                    WLAN_REASON_DEAUTH_LEAVING
+#define SKW_2K_SIZE                  2048
+#define SKW_SKB_RECYCLE_COUNT        4096
+#define SKW_BASIC_RATE_COUNT         8
+/* hdr(24) + reason(2) */
+#define SKW_DEAUTH_FRAME_LEN         26
+
+#define __SKW_STR(x)                 #x
+#define SKW_STR(x)                   __SKW_STR(x)
+
+#define SKW_SET(d, v)                ((d) |= (v))
+#define SKW_CLEAR(d, v)              ((d) &= ~(v))
+#define SKW_TEST(d, v)               ((d) & (v))
+
+#define SKW_ZALLOC(s, f)             kzalloc(s, f)
+
+#define SKW_KFREE(p)                 \
+	do {                         \
+		kfree(p);            \
+		p = NULL;            \
+	} while (0)
+
+#define SKW_KMEMDUP(s, l, f)     (((s) != NULL) ? kmemdup(s, l, f) : NULL)
+#define SKW_MGMT_SFC(fc)         (le16_to_cpu(fc) & IEEE80211_FCTL_STYPE)
+#define SKW_WDEV_TO_IFACE(w)     container_of(w, struct skw_iface, wdev)
+
+#define SKW_OUI(a, b, c)                                       \
+	(((a) & 0xff) << 16 | ((b) & 0xff) << 8 | ((c) & 0xff))
+
+#ifndef list_next_entry
+#define list_next_entry(pos, member)                           \
+	list_entry((pos)->member.next, typeof(*(pos)), member)
+#endif
+
+#ifdef SKWIFI_ASSERT
+#define SKW_BUG_ON(c)         BUG_ON(c)
+#else
+#define SKW_BUG_ON(c)         WARN_ON(c)
+#endif
+
+#ifndef READ_ONCE
+#define READ_ONCE(x)          ACCESS_ONCE(x)
+#endif
+
+#ifndef WRITE_ONCE
+#define WRITE_ONCE(x, v)      (ACCESS_ONCE(x) = v)
+#endif
+
+#ifndef __has_attribute
+#define __has_attribute(x) 0
+#endif
+
+#if __has_attribute(__fallthrough__)
+#define skw_fallthrough       __attribute__((__fallthrough__))
+#else
+#define skw_fallthrough       do {} while (0)  /* fallthrough */
+#endif
+
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0)
+static inline void u64_stats_init(struct u64_stats_sync *syncp)
+{
+#if BITS_PER_LONG == 32 && defined(CONFIG_SMP)
+	seqcount_init(&syncp->seq);
+#endif
+}
+#endif
+
+#ifndef NET_NAME_ENUM
+#define NET_NAME_ENUM 1
+#endif
+
+#ifndef netdev_alloc_pcpu_stats
+#define netdev_alloc_pcpu_stats(type)				\
+({								\
+	typeof(type) __percpu *pcpu_stats = alloc_percpu(type); \
+	if (pcpu_stats)	{					\
+		int i;						\
+		for_each_possible_cpu(i) {			\
+			typeof(type) *stat;			\
+			stat = per_cpu_ptr(pcpu_stats, i);	\
+			u64_stats_init(&stat->syncp);		\
+		}						\
+	}							\
+	pcpu_stats;						\
+})
+#endif
+
+#define skw_foreach_element(_elem, _data, _datalen)			     \
+	for (_elem = (struct skw_element *)(_data);                          \
+	     (const u8 *)(_data) + (_datalen) - (const u8 *)_elem >=	     \
+		(int)sizeof(*_elem) &&					     \
+	     (const u8 *)(_data) + (_datalen) - (const u8 *)_elem >=	     \
+		(int)sizeof(*_elem) + _elem->datalen;			     \
+	     _elem = (struct skw_element *)(_elem->data + _elem->datalen))
+
+#define skw_foreach_element_id(element, _id, data, datalen)		     \
+	skw_foreach_element(element, data, datalen)			     \
+		if (element->id == (_id))
+
+struct skw_tp_rate {
+	union {
+		struct {
+			u16 value;
+			u8 two_dec;
+			u8 unit;
+		} rate;
+
+		u32 ret;
+	};
+};
+
+struct skw_template {
+	u16 head_offset;
+	u16 head_len;
+	u16 tail_ofsset;
+	u16 tail_len;
+	struct ieee80211_mgmt mgmt[0];
+};
+
+struct skw_rate {
+	u8 flags;
+	u8 mcs_idx;
+	u16 legacy_rate;
+	u8 nss;
+	u8 bw;
+	u8 gi;
+	u8 he_ru;
+	u8 he_dcm;
+} __packed;
+
+struct skw_arphdr {
+	__be16          ar_hrd; /* format of hardware address */
+	__be16          ar_pro; /* format of protocol address */
+	unsigned char   ar_hln; /* length of hardware address */
+	unsigned char   ar_pln; /* length of protocol address */
+	__be16          ar_op;  /* ARP opcode (command) */
+
+	unsigned char   ar_sha[ETH_ALEN]; /* sender hardware address */
+	__be32          ar_sip;  /* sender IP address */
+	unsigned char   ar_tha[ETH_ALEN]; /* target hardware address */
+	__be32          ar_tip;  /* target IP address */
+} __packed;
+
+struct skw_element {
+	u8 id;
+	u8 datalen;
+	u8 data[];
+} __packed;
+
+struct skw_tlv {
+	u16 type;
+	u16 len;
+	char value[0];
+};
+
+struct skw_tlv_conf {
+	void *buff;
+	int buff_len, total_len;
+};
+
+static inline struct skw_arphdr *skw_arp_hdr(struct sk_buff *skb)
+{
+	if (!skb)
+		return NULL;
+
+	return (struct skw_arphdr *)(skb->data + 14);
+}
+
+static inline u64 skw_mac_to_u64(const u8 *addr)
+{
+	u64 u = 0;
+	int i;
+
+	for (i = 0; i < ETH_ALEN; i++)
+		u = u << 8 | addr[i];
+
+	return u;
+}
+
+static inline void skw_u64_to_mac(u64 u, u8 *addr)
+{
+	int i;
+
+	for (i = ETH_ALEN - 1; i >= 0; i--) {
+		addr[i] = u & 0xff;
+		u = u >> 8;
+	}
+}
+
+static inline void *skw_put_skb_data(struct sk_buff *skb, const void *data,
+				 unsigned int len)
+{
+	void *tmp = skb_put(skb, len);
+
+	memcpy(tmp, data, len);
+
+	return tmp;
+}
+
+static inline void *skw_put_skb_zero(struct sk_buff *skb, unsigned int len)
+{
+	void *tmp = skb_put(skb, len);
+
+	memset(tmp, 0, len);
+
+	return tmp;
+}
+
+static inline struct ethhdr *skw_eth_hdr(const struct sk_buff *skb)
+{
+	return (struct ethhdr *)skb->data;
+}
+
+static inline int skw_mac_offset(const struct sk_buff *skb)
+{
+	return skb_mac_header(skb) - skb->data;
+}
+
+static inline void skw_set_thread_priority(struct task_struct *thread,
+					   int policy, int priority)
+{
+#ifdef CONFIG_SWT6621S_HIGH_PRIORITY
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
+	sched_set_fifo_low(thread);
+#else
+	struct sched_param param = {
+		.sched_priority = priority,
+	};
+
+	sched_setscheduler(thread, policy, &param);
+#endif
+#endif
+}
+
+static inline const char *skw_iftype_name(enum nl80211_iftype iftype)
+{
+	static const char * const ifname[] = {"IFTYPE_NONE",
+					"ADHOC",
+					"STA",
+					"AP",
+					"AP_VLAN",
+					"WDS",
+					"MONITOR",
+					"MESH",
+					"P2P_GC",
+					"P2P_GO",
+					"P2P_DEVICE",
+					"OCB",
+					"NAN",
+					"IFTYPE_LAST"};
+
+	return ifname[iftype];
+}
+
+/**
+ * skw_ether_copy - Copy an Ethernet address
+ * @dst: Pointer to a six-byte array Ethernet address destination
+ * @src: Pointer to a six-byte array Ethernet address source
+ *
+ * Please note: dst & src must both be aligned to u16.
+ */
+static inline void skw_ether_copy(u8 *dst, const u8 *src)
+{
+#if defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)
+	*(u32 *)dst = *(const u32 *)src;
+	*(u16 *)(dst + 4) = *(const u16 *)(src + 4);
+#else
+	u16 *a = (u16 *)dst;
+	const u16 *b = (const u16 *)src;
+
+	a[0] = b[0];
+	a[1] = b[1];
+	a[2] = b[2];
+#endif
+}
+
+#ifdef SKW_IMPORT_NS
+struct file *skw_file_open(const char *path, int flags, int mode);
+int skw_file_read(struct file *fp, unsigned char *data,
+		size_t size, loff_t offset);
+int skw_file_write(struct file *fp, unsigned char *data,
+		size_t size, loff_t offset);
+int skw_file_sync(struct file *fp);
+void skw_file_close(struct file *fp);
+#endif
+
+u64 skw_local_clock(void);
+int skw_key_idx(u16 bitmap);
+char *skw_mgmt_name(u16 fc);
+int skw_freq_to_chn(int freq);
+u32 skw_calc_rate(u64 bytes, u32 delta_ms);
+int skw_build_deauth_frame(void *buf, int buf_len, u8 *da, u8 *sa,
+			   u8 *bssid, u16 reason_code);
+const u8 *skw_find_ie_match(u8 eid, const u8 *ies, int len, const u8 *match,
+			    int match_len, int match_offset);
+
+int skw_desc_get_rx_rate(struct skw_rate *rate, u8 bw, u8 mode, u8 gi,
+		    u8 nss, u8 dcm, u16 data_rate);
+void skw_tlv_free(struct skw_tlv_conf *conf);
+void *skw_tlv_reserve(struct skw_tlv_conf *conf, int len);
+int skw_tlv_alloc(struct skw_tlv_conf *conf, int len, gfp_t gfp);
+int skw_tlv_add(struct skw_tlv_conf *conf, int type, void *dat, int dat_len);
+bool skw_bss_check_vendor_name(struct cfg80211_bss *bss, const u8 *oui);
+int skw_util_set_mib_enable(struct wiphy *wiphy, int inst, int mib_id, bool enable);
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_vendor.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_vendor.c
new file mode 100755
index 0000000..5c43fbb
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_vendor.c
@@ -0,0 +1,495 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <net/cfg80211.h>
+#include <net/genetlink.h>
+
+#include "skw_vendor.h"
+#include "skw_cfg80211.h"
+#include "skw_core.h"
+#include "skw_iface.h"
+#include "skw_util.h"
+#include "skw_regd.h"
+#include "version.h"
+
+const struct nla_policy
+skw_set_country_policy[SKW_SET_COUNTRY_RULES] = {
+	[SKW_ATTR_SET_COUNTRY] = {.type = NLA_STRING},
+};
+
+const struct nla_policy
+skw_get_valid_channels_policy[SKW_GET_VALID_CHANNELS_RULES] = {
+	[SKW_ATTR_GET_VALID_CHANNELS] = {.type = NLA_U32},
+};
+
+const struct nla_policy
+skw_get_version_policy[SKW_GET_VERSION_RULES] = {
+	[SKW_ATTR_VERSION_DRIVER] = {.type = NLA_U32},
+	[SKW_ATTR_VERSION_FIRMWARE] = {.type = NLA_U32},
+};
+
+#if 0
+static int skw_vendor_dbg_reset_logging(struct wiphy *wiphy,
+	struct wireless_dev *wdev, const void  *data, int len)
+{
+	int ret = SKW_OK;
+
+	skw_dbg("Enter\n");
+
+	return ret;
+}
+
+static int skw_vendor_set_p2p_rand_mac(struct wiphy *wiphy,
+		struct wireless_dev *wdev, const void *data, int len)
+{
+	int type;
+	//struct skw_iface *iface = netdev_priv(wdev->netdev);
+	u8 mac_addr[6] = {0};
+
+	skw_dbg("set skw mac addr\n");
+	type = nla_type(data);
+
+	if (type == SKW_ATTR_DRIVER_RAND_MAC) {
+		memcpy(mac_addr, nla_data(data), 6);
+		skw_dbg("mac:%pM\n", mac_addr);
+	}
+
+	return 0;
+}
+
+static int skw_vendor_set_rand_mac_oui(struct wiphy *wiphy,
+		struct wireless_dev *wdev, const void *data, int len)
+{
+	char *oui = nla_data(data);
+	struct skw_iface *iface = SKW_WDEV_TO_IFACE(wdev);
+
+	if (!oui || (nla_len(data) != DOT11_OUI_LEN))
+		return -EINVAL;
+
+	skw_dbg("%02x:%02x:%02x\n", oui[0], oui[1], oui[2]);
+
+	memcpy(iface->rand_mac_oui, oui, DOT11_OUI_LEN);
+
+	return 0;
+}
+#endif
+
+static int skw_vendor_cmd_reply(struct wiphy *wiphy, const void *data, int len)
+{
+	struct sk_buff *skb;
+	int err;
+
+	/* Alloc the SKB for vendor_event */
+	skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, len);
+	if (unlikely(!skb)) {
+		skw_err("skb alloc failed");
+		return -ENOMEM;
+	}
+
+	/* Push the data to the skb */
+	err = nla_put_nohdr(skb, len, data);
+	if (err) {
+		kfree_skb(skb);
+		return -EINVAL;
+	}
+
+	return cfg80211_vendor_cmd_reply(skb);
+}
+
+static int skw_vendor_start_logging(struct wiphy *wiphy,
+		struct wireless_dev *wdev, const void  *data, int len)
+{
+	skw_dbg("inst: %d\n", SKW_WDEV_TO_IFACE(wdev)->id);
+
+	return 0;
+}
+
+static int skw_vendor_get_wake_reason_stats(struct wiphy *wiphy,
+		struct wireless_dev *wdev, const void *data, int len)
+{
+	skw_dbg("inst: %d\n", SKW_WDEV_TO_IFACE(wdev)->id);
+
+	return 0;
+}
+
+static int skw_vendor_get_apf_capabilities(struct wiphy *wiphy,
+		struct wireless_dev *wdev, const void *data, int len)
+{
+	struct sk_buff *skb;
+
+	skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, NLMSG_DEFAULT_SIZE);
+	if (!skb)
+		return -ENOMEM;
+
+	if (nla_put_u32(skb, SKW_ATTR_APF_VERSION, 4) ||
+	    nla_put_u32(skb, SKW_ATTR_APF_MAX_LEN, 1024)) {
+		kfree_skb(skb);
+		return -ENOMEM;
+	}
+
+	return cfg80211_vendor_cmd_reply(skb);
+}
+
+static int skw_vendor_get_ring_buffer_data(struct wiphy *wiphy,
+		struct wireless_dev *wdev, const void  *data, int len)
+{
+	skw_dbg("inst: %d\n", SKW_WDEV_TO_IFACE(wdev)->id);
+
+	return 0;
+}
+
+static int skw_vendor_get_firmware_dump(struct wiphy *wiphy,
+		struct wireless_dev *wdev, const void  *data, int len)
+{
+	skw_dbg("inst: %d\n", SKW_WDEV_TO_IFACE(wdev)->id);
+
+	return 0;
+}
+
+static int skw_vendor_select_tx_power_scenario(struct wiphy *wiphy,
+		struct wireless_dev *wdev, const void *data, int len)
+{
+	skw_dbg("inst: %d\n", SKW_WDEV_TO_IFACE(wdev)->id);
+
+	return 0;
+}
+
+static int skw_vendor_set_latency_mode(struct wiphy *wiphy,
+		struct wireless_dev *wdev, const void *data, int len)
+{
+	skw_dbg("inst: %d\n", SKW_WDEV_TO_IFACE(wdev)->id);
+
+	return 0;
+}
+
+static int skw_vendor_get_feature_set(struct wiphy *wiphy,
+		struct wireless_dev *wdev, const void *data, int len)
+{
+	u32 feature_set = 0;
+
+	/* Hardcoding these values for now, need to get
+	 * these values from FW, will change in a later check-in
+	 */
+	feature_set |= WIFI_FEATURE_INFRA;
+	feature_set |= WIFI_FEATURE_INFRA_5G;
+	feature_set |= WIFI_FEATURE_P2P;
+	feature_set |= WIFI_FEATURE_SOFT_AP;
+	feature_set |= WIFI_FEATURE_AP_STA;
+	//feature_set |= WIFI_FEATURE_TDLS;
+	//feature_set |= WIFI_FEATURE_TDLS_OFFCHANNEL;
+	//feature_set |= WIFI_FEATURE_NAN;
+	//feature_set |= WIFI_FEATURE_HOTSPOT;
+	//feature_set |= WIFI_FEATURE_LINK_LAYER_STATS; //TBC
+	//feature_set |= WIFI_FEATURE_RSSI_MONITOR; //TBC with roaming
+	//feature_set |= WIFI_FEATURE_MKEEP_ALIVE; //TBC compare with QUALCOM
+	//feature_set |= WIFI_FEATURE_CONFIG_NDO; //TBC
+	//feature_set |= WIFI_FEATURE_SCAN_RAND;
+	//feature_set |= WIFI_FEATURE_RAND_MAC;
+	//feature_set |= WIFI_FEATURE_P2P_RAND_MAC ;
+	//feature_set |= WIFI_FEATURE_CONTROL_ROAMING;
+
+	skw_dbg("feature: 0x%x\n", feature_set);
+
+	return skw_vendor_cmd_reply(wiphy, &feature_set, sizeof(u32));
+}
+
+static int skw_vendor_set_country(struct wiphy *wiphy, struct wireless_dev *wdev,
+				  const void *data, int data_len)
+{
+	char *country = nla_data(data);
+
+	if (nla_type(data) != SKW_ATTR_SET_COUNTRY)
+		skw_warn("attr mismatch, type: %d\n", nla_type(data));
+
+	if (!country || strlen(country) != 2) {
+		skw_err("invalid, country: %s\n", country ? country : "null");
+
+		return -EINVAL;
+	}
+
+	skw_dbg("country: %c%c\n", country[0], country[1]);
+
+	return skw_set_regdom(wiphy, country);
+}
+
+static int skw_vendor_get_version(struct wiphy *wiphy, struct wireless_dev *wdev,
+				const void *data, int len)
+{
+	char version[64] = {0};
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	switch (nla_type(data)) {
+	case SKW_ATTR_VERSION_DRIVER:
+		strncpy(version, SKW_VERSION, sizeof(version));
+		break;
+
+	case SKW_ATTR_VERSION_FIRMWARE:
+		snprintf(version, sizeof(version), "%s-%s",
+			 skw->fw.plat_ver, skw->fw.wifi_ver);
+		break;
+
+	default:
+		skw_err("invalid nla type\n");
+		strcpy(version, "invalid");
+		break;
+	}
+
+	return skw_vendor_cmd_reply(wiphy, version, sizeof(version));
+}
+
+static int skw_vendor_get_usable_channels(struct wiphy *wiphy,
+			struct wireless_dev *wdev, const void *data, int len)
+{
+	int i, nr, max;
+	struct sk_buff *skb;
+	enum nl80211_band band;
+	struct skw_usable_chan *chans;
+	struct skw_usable_chan_req *req = (struct skw_usable_chan_req *)data;
+
+	skw_dbg("band_mask: 0x%x\n", req->band_mask);
+
+	max = ieee80211_get_num_supported_channels(wiphy);
+
+	chans = SKW_ZALLOC(max * sizeof(*chans), GFP_KERNEL);
+	if (!chans)
+		return -ENOMEM;
+
+	skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, NLMSG_DEFAULT_SIZE);
+	if (!skb) {
+		SKW_KFREE(chans);
+		return -ENOMEM;
+	}
+
+	for (nr = 0, band = 0; band < NUM_NL80211_BANDS; band++) {
+		if (!(req->band_mask & BIT(to_skw_band(band))) ||
+		    !wiphy->bands[band])
+			continue;
+
+		for (i = 0; i < wiphy->bands[band]->n_channels; i++) {
+			struct ieee80211_channel *chan;
+
+			chan = &wiphy->bands[band]->channels[i];
+
+			if (chan->flags & IEEE80211_CHAN_DISABLED)
+				continue;
+
+			chans[nr].center_freq = chan->center_freq;
+			chans[nr].band_width = SKW_CHAN_WIDTH_20;
+			chans[nr].iface_mode_mask = BIT(SKW_STA_MODE) |
+						    BIT(SKW_AP_MODE) |
+						    BIT(SKW_GC_MODE) |
+						    BIT(SKW_GO_MODE);
+			nr++;
+		}
+	}
+
+	if (nla_put_nohdr(skb, nr * sizeof(*chans), chans)) {
+		SKW_KFREE(chans);
+		kfree_skb(skb);
+
+		return -ENOMEM;
+	}
+
+	SKW_KFREE(chans);
+
+	return cfg80211_vendor_cmd_reply(skb);
+}
+
+static int skw_vendor_get_valid_channels(struct wiphy *wiphy,
+		struct wireless_dev *wdev, const void *data, int len)
+{
+	int channels[32], size;
+	int i, band, nr_channels;
+	struct sk_buff *skb;
+
+	if (nla_type(data) != SKW_ATTR_GET_VALID_CHANNELS)
+		skw_warn("attr mismatch, type: %d", nla_type(data));
+
+	band = nla_get_u32(data);
+	if (band > NL80211_BAND_5GHZ) {
+		skw_err("invalid band: %d\n", band);
+		return -EINVAL;
+	}
+
+	skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, NLMSG_DEFAULT_SIZE);
+	if (!skb)
+		return -ENOMEM;
+
+	nr_channels = wiphy->bands[band]->n_channels;
+	size = nr_channels * sizeof(int);
+
+	for (i = 0; i < nr_channels; i++)
+		channels[i] = wiphy->bands[band]->channels[i].hw_value;
+
+	if (nla_put_u32(skb, SKW_ATTR_VALID_CHANNELS_COUNT, nr_channels) ||
+	    nla_put(skb, SKW_ATTR_VALID_CHANNELS_LIST, size, channels)) {
+		kfree_skb(skb);
+
+		return -ENOMEM;
+	}
+
+	return cfg80211_vendor_cmd_reply(skb);
+}
+
+static int skw_vendor_get_ring_buffers_status(struct wiphy *wiphy,
+			struct wireless_dev *wdev, const void  *data, int len)
+{
+	struct sk_buff *skb;
+	struct skw_ring_buff_status status = {
+		.name = "skw_drv",
+		.flags = 0,
+		.ring_id = 0,
+		.ring_buffer_byte_size = 1024,
+		.verbose_level = 0,
+		.written_bytes = 0,
+		.read_bytes = 0,
+		.written_records = 0,
+	};
+
+	skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, NLMSG_DEFAULT_SIZE);
+	if (!skb)
+		return -ENOMEM;
+
+	if (nla_put_u32(skb, SKW_ATTR_RING_BUFFERS_COUNT, 1) ||
+	    nla_put(skb, SKW_ATTR_RING_BUFFERS_STATUS, sizeof(status), &status)) {
+		kfree_skb(skb);
+
+		return -ENOMEM;
+	}
+
+	return cfg80211_vendor_cmd_reply(skb);
+}
+
+static int skw_vendor_get_logger_feature(struct wiphy *wiphy,
+			struct wireless_dev *wdev, const void  *data, int len)
+{
+	u32 features = 0;
+
+	skw_dbg("features: 0x%x\n", features);
+
+	return skw_vendor_cmd_reply(wiphy, &features, sizeof(features));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0))
+#define SKW_VENDOR_CMD(oui, cmd, flag, func, nla_policy, max_attr)  \
+{                                                                   \
+	.info = {.vendor_id = oui, .subcmd = cmd},                  \
+	.flags = flag,                                              \
+	.doit = func,                                               \
+	.policy = nla_policy,                                       \
+	.maxattr = max_attr,                                        \
+}
+#else
+#define SKW_VENDOR_CMD(oui, cmd, flag, func, nla_policy, max_attr)  \
+{                                                                   \
+	.info = {.vendor_id = oui, .subcmd = cmd},                  \
+	.flags = flag,                                              \
+	.doit = func,                                               \
+}
+#endif
+
+#define SKW_VENDOR_DEFAULT_FLAGS (WIPHY_VENDOR_CMD_NEED_WDEV |      \
+				  WIPHY_VENDOR_CMD_NEED_NETDEV)
+
+static struct wiphy_vendor_command skw_vendor_cmds[] = {
+	SKW_VENDOR_CMD(OUI_GOOGLE, SKW_VID_GET_VALID_CHANNELS,
+			SKW_VENDOR_DEFAULT_FLAGS,
+			skw_vendor_get_valid_channels,
+			skw_get_valid_channels_policy,
+			SKW_GET_VALID_CHANNELS_RULES),
+	SKW_VENDOR_CMD(OUI_GOOGLE, SKW_VID_GET_FEATURE_SET,
+			SKW_VENDOR_DEFAULT_FLAGS,
+			skw_vendor_get_feature_set,
+			VENDOR_CMD_RAW_DATA, 0),
+	SKW_VENDOR_CMD(OUI_GOOGLE, SKW_VID_GET_VERSION,
+			SKW_VENDOR_DEFAULT_FLAGS,
+			skw_vendor_get_version,
+			skw_get_version_policy,
+			SKW_GET_VERSION_RULES),
+	SKW_VENDOR_CMD(OUI_GOOGLE, SKW_VID_GET_RING_BUFFERS_STATUS,
+			SKW_VENDOR_DEFAULT_FLAGS,
+			skw_vendor_get_ring_buffers_status,
+			VENDOR_CMD_RAW_DATA, 0),
+	SKW_VENDOR_CMD(OUI_GOOGLE, SKW_VID_GET_LOGGER_FEATURE,
+			SKW_VENDOR_DEFAULT_FLAGS,
+			skw_vendor_get_logger_feature,
+			VENDOR_CMD_RAW_DATA, 0),
+	SKW_VENDOR_CMD(OUI_GOOGLE, SKW_VID_GET_APF_CAPABILITIES,
+			SKW_VENDOR_DEFAULT_FLAGS,
+			skw_vendor_get_apf_capabilities,
+			VENDOR_CMD_RAW_DATA, 0),
+	SKW_VENDOR_CMD(OUI_GOOGLE, SKW_VID_GET_USABLE_CHANS,
+			SKW_VENDOR_DEFAULT_FLAGS,
+			skw_vendor_get_usable_channels,
+			VENDOR_CMD_RAW_DATA, 0),
+	SKW_VENDOR_CMD(OUI_GOOGLE, SKW_VID_SET_COUNTRY,
+			SKW_VENDOR_DEFAULT_FLAGS,
+			skw_vendor_set_country,
+			skw_set_country_policy, SKW_SET_COUNTRY_RULES),
+	SKW_VENDOR_CMD(OUI_GOOGLE, SKW_VID_START_LOGGING,
+			SKW_VENDOR_DEFAULT_FLAGS,
+			skw_vendor_start_logging,
+			VENDOR_CMD_RAW_DATA, 0),
+	SKW_VENDOR_CMD(OUI_GOOGLE, SKW_VID_GET_FIRMWARE_DUMP,
+			SKW_VENDOR_DEFAULT_FLAGS,
+			skw_vendor_get_firmware_dump,
+			VENDOR_CMD_RAW_DATA, 0),
+	SKW_VENDOR_CMD(OUI_GOOGLE, SKW_VID_GET_RING_BUFFER_DATA,
+			SKW_VENDOR_DEFAULT_FLAGS,
+			skw_vendor_get_ring_buffer_data,
+			VENDOR_CMD_RAW_DATA, 0),
+	SKW_VENDOR_CMD(OUI_GOOGLE, SKW_VID_GET_WAKE_REASON_STATS,
+			SKW_VENDOR_DEFAULT_FLAGS,
+			skw_vendor_get_wake_reason_stats,
+			VENDOR_CMD_RAW_DATA, 0),
+	SKW_VENDOR_CMD(OUI_GOOGLE, SKW_VID_SELECT_TX_POWER_SCENARIO,
+			SKW_VENDOR_DEFAULT_FLAGS,
+			skw_vendor_select_tx_power_scenario,
+			VENDOR_CMD_RAW_DATA, 0),
+	SKW_VENDOR_CMD(OUI_GOOGLE, SKW_VID_SET_LATENCY_MODE,
+			SKW_VENDOR_DEFAULT_FLAGS,
+			skw_vendor_set_latency_mode,
+			VENDOR_CMD_RAW_DATA, 0),
+};
+
+static struct nl80211_vendor_cmd_info skw_vendor_events[] = {
+	{
+		.vendor_id = 0,
+		.subcmd = 0,
+	},
+};
+
+void skw_vendor_init(struct wiphy *wiphy)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
+	wiphy->vendor_commands = skw_vendor_cmds;
+	wiphy->n_vendor_commands = ARRAY_SIZE(skw_vendor_cmds);
+	wiphy->vendor_events = skw_vendor_events;
+	wiphy->n_vendor_events = ARRAY_SIZE(skw_vendor_events);
+#else
+	skw_dbg("cmd: %d, event: %d\n", ARRAY_SIZE(skw_vendor_cmds),
+		ARRAY_SIZE(skw_vendor_events));
+#endif
+}
+
+void skw_vendor_deinit(struct wiphy *wiphy)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
+	wiphy->vendor_commands = NULL;
+	wiphy->n_vendor_commands = 0;
+#endif
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_vendor.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_vendor.h
new file mode 100755
index 0000000..b8a3de5
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_vendor.h
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_VENDOR_H__
+#define __SKW_VENDOR_H__
+
+#define OUI_GOOGLE	                         0x001A11
+
+/* Vendor Command ID */
+#define SKW_VID_GET_VALID_CHANNELS               0x1009
+#define SKW_VID_GET_FEATURE_SET                  0x100A
+#define SKW_VID_SET_COUNTRY                      0x100E
+#define SKW_VID_SET_LATENCY_MODE                 0x101B
+#define SKW_VID_START_LOGGING                    0x1400
+#define SKW_VID_GET_FIRMWARE_DUMP                0x1401
+#define SKW_VID_GET_VERSION                      0x1403
+#define SKW_VID_GET_RING_BUFFERS_STATUS          0x1404
+#define SKW_VID_GET_RING_BUFFER_DATA             0x1405
+#define SKW_VID_GET_LOGGER_FEATURE               0x1406
+#define SKW_VID_GET_WAKE_REASON_STATS            0x140D
+#define SKW_VID_GET_APF_CAPABILITIES             0x1800
+#define SKW_VID_SELECT_TX_POWER_SCENARIO         0x1900
+#define SKW_VID_GET_USABLE_CHANS                 0x2000
+
+/* ATTR IE Start */
+
+/* ATTR GET_VALID_CHANNELS */
+#define SKW_ATTR_GET_VALID_CHANNELS              20
+#define SKW_ATTR_VALID_CHANNELS_COUNT            36
+#define SKW_ATTR_VALID_CHANNELS_LIST             37
+#define SKW_GET_VALID_CHANNELS_RULES             38
+/* ATTR GET_VALID_CHANNELS */
+
+/* ATTR SET_COUNTRY */
+#define SKW_ATTR_SET_COUNTRY                     5
+#define SKW_SET_COUNTRY_RULES                    6
+/* ATTR SET_COUNTRY */
+
+/* ATTR GET_APF_CAPABILITIES */
+#define SKW_ATTR_APF_VERSION                     0
+#define SKW_ATTR_APF_MAX_LEN                     1
+/* ATTR GET_APF_CAPABILITIES */
+
+/* ATTR GET_VERSION */
+#define SKW_ATTR_VERSION_DRIVER                  1
+#define SKW_ATTR_VERSION_FIRMWARE                2
+#define SKW_GET_VERSION_RULES                    3
+/* ATTR GET_VERSION */
+
+/* ATTR RING_BUFFERS_STATUS */
+#define SKW_ATTR_RING_BUFFERS_STATUS             13
+#define SKW_ATTR_RING_BUFFERS_COUNT              14
+/* ATTR RING_BUFFERS_STATUS */
+
+/* END OF ATTR IE */
+
+/* Feature enums */
+#define WIFI_FEATURE_INFRA                       0x1      // Basic infrastructure mode
+#define WIFI_FEATURE_INFRA_5G                    0x2      // Support for 5 GHz Band
+#define WIFI_FEATURE_HOTSPOT                     0x4      // Support for GAS/ANQP
+#define WIFI_FEATURE_P2P                         0x8      // Wifi-Direct
+#define WIFI_FEATURE_SOFT_AP                     0x10      // Soft AP
+#define WIFI_FEATURE_GSCAN                       0x20      // Google-Scan APIs
+#define WIFI_FEATURE_NAN                         0x40      // Neighbor Awareness Networking
+#define WIFI_FEATURE_D2D_RTT                     0x80      // Device-to-device RTT
+#define WIFI_FEATURE_D2AP_RTT                    0x100      // Device-to-AP RTT
+#define WIFI_FEATURE_BATCH_SCAN                  0x200      // Batched Scan (legacy)
+#define WIFI_FEATURE_PNO                         0x400      // Preferred network offload
+#define WIFI_FEATURE_ADDITIONAL_STA              0x800      // Support for two STAs
+#define WIFI_FEATURE_TDLS                        0x1000      // Tunnel directed link setup
+#define WIFI_FEATURE_TDLS_OFFCHANNEL             0x2000      // Support for TDLS off channel
+#define WIFI_FEATURE_EPR                         0x4000      // Enhanced power reporting
+#define WIFI_FEATURE_AP_STA                      0x8000      // Support for AP STA Concurrency
+#define WIFI_FEATURE_LINK_LAYER_STATS            0x10000     // Link layer stats collection
+#define WIFI_FEATURE_LOGGER                      0x20000     // WiFi Logger
+#define WIFI_FEATURE_HAL_EPNO                    0x40000     // WiFi PNO enhanced
+#define WIFI_FEATURE_RSSI_MONITOR                0x80000     // RSSI Monitor
+#define WIFI_FEATURE_MKEEP_ALIVE                 0x100000    // WiFi mkeep_alive
+#define WIFI_FEATURE_CONFIG_NDO                  0x200000    // ND offload configure
+#define WIFI_FEATURE_TX_TRANSMIT_POWER           0x400000    // Capture Tx transmit power levels
+#define WIFI_FEATURE_CONTROL_ROAMING             0x800000    // Enable/Disable firmware roaming
+#define WIFI_FEATURE_IE_WHITELIST                0x1000000   // Support Probe IE white listing
+#define WIFI_FEATURE_SCAN_RAND                   0x2000000   // Support MAC & Probe Sequence Number randomization
+#define WIFI_FEATURE_SET_TX_POWER_LIMIT          0x4000000   // Support Tx Power Limit setting
+#define WIFI_FEATURE_USE_BODY_HEAD_SAR           0x8000000   // Support Using Body/Head Proximity for SAR
+#define WIFI_FEATURE_DYNAMIC_SET_MAC             0x10000000  // Support changing MAC address without iface reset(down and up)
+#define WIFI_FEATURE_SET_LATENCY_MODE            0x40000000  // Support Latency mode setting
+#define WIFI_FEATURE_P2P_RAND_MAC                0x80000000  // Support P2P MAC randomization
+#define WIFI_FEATURE_INFRA_60G                   0x100000000 // Support for 60GHz Band
+
+/* Vendor Event ID */
+#define SKW_NL80211_VENDOR_SUBCMD_MONITOR_RSSI   80
+#define EXT_VENDOR_EVENT_BUF_SIZE                4096
+enum skw_wlan_vendor_attr_rssi_monitoring {
+	SKW_WLAN_VENDOR_ATTR_RSSI_MONITORING_INVALID = 0,
+	/* Takes valid value from the enum
+	 * skw_wlan_rssi_monitoring_control
+	 * Unsigned 32-bit value enum skw_wlan_rssi_monitoring_control
+	 */
+	SKW_WLAN_VENDOR_ATTR_RSSI_MONITORING_CONTROL,
+	/* Unsigned 32-bit value */
+	SKW_WLAN_VENDOR_ATTR_RSSI_MONITORING_REQUEST_ID,
+	/* Signed 8-bit value in dBm */
+	SKW_WLAN_VENDOR_ATTR_RSSI_MONITORING_MAX_RSSI,
+	/* Signed 8-bit value in dBm */
+	SKW_WLAN_VENDOR_ATTR_RSSI_MONITORING_MIN_RSSI,
+	/* attributes to be used/received in callback */
+	/* 6-byte MAC address used to represent current BSSID MAC address */
+	SKW_WLAN_VENDOR_ATTR_RSSI_MONITORING_CUR_BSSID,
+	/* Signed 8-bit value indicating the current RSSI */
+	SKW_WLAN_VENDOR_ATTR_RSSI_MONITORING_CUR_RSSI,
+	/* keep last */
+	SKW_WLAN_VENDOR_ATTR_RSSI_MONITORING_AFTER_LAST,
+	SKW_WLAN_VENDOR_ATTR_RSSI_MONITORING_MAX =
+	SKW_WLAN_VENDOR_ATTR_RSSI_MONITORING_AFTER_LAST - 1,
+};
+
+struct skw_ring_buff_status {
+	u8 name[32];
+	u32 flags;
+	int ring_id;                  // unique integer representing the ring
+	u32 ring_buffer_byte_size;    // total memory size allocated for the buffer
+	u32 verbose_level;            // verbose level for ring buffer
+	u32 written_bytes;            // number of bytes that was written to the buffer by driver
+	u32 read_bytes;               // number of bytes that was read from the buffer by user land
+	u32 written_records;          // number of records that was written to the buffer by driver
+};
+
+struct skw_usable_chan {
+	u16 center_freq;
+	u16 band_width;
+	u16 iface_mode_mask;
+	u16 flags;
+	u32 resvd;
+};
+
+struct skw_usable_chan_req {
+	u32 band_mask;
+	u32 filter_mask;
+	u32 iface_mode_mask;
+	u32 flags;
+	u32 resvd;
+};
+
+void skw_vendor_init(struct wiphy *wiphy);
+void skw_vendor_deinit(struct wiphy *wiphy);
+
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_work.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_work.c
new file mode 100755
index 0000000..dd75905
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_work.c
@@ -0,0 +1,420 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/skbuff.h>
+
+#include "skw_core.h"
+#include "skw_cfg80211.h"
+#include "skw_iface.h"
+#include "skw_mlme.h"
+#include "skw_msg.h"
+#include "skw_work.h"
+#include "skw_timer.h"
+#include "skw_recovery.h"
+#include "skw_tx.h"
+#include "skw_dfs.h"
+
+#define SKW_WORK_FLAG_ASSERT        0
+#define SKW_WORK_FLAG_RCU_FREE      1
+#define SKW_WORK_FLAG_UNLOCK        2
+#define SKW_WORK_FLAG_RESUME        3
+
+static void skw_ap_acl_check(struct wiphy *wiphy, struct skw_iface *iface)
+{
+	int idx;
+	struct skw_peer_ctx *ctx = NULL;
+	u32 peer_idx_map = atomic_read(&iface->peer_map);
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	if (peer_idx_map == 0)
+		return;
+
+	while (peer_idx_map) {
+		idx = ffs(peer_idx_map) - 1;
+
+		SKW_CLEAR(peer_idx_map, BIT(idx));
+
+		ctx = &skw->hw.lmac[iface->lmac_id].peer_ctx[idx];
+		if (!ctx)
+			continue;
+
+		if (ctx->peer && !skw_acl_allowed(iface, ctx->peer->addr))
+			skw_mlme_ap_del_sta(wiphy, iface->ndev,
+					ctx->peer->addr, false);
+	}
+}
+
+static void skw_work_async_adma_tx_free(struct skw_core *skw,
+				struct scatterlist *sglist, int nents)
+{
+	int idx;
+	struct scatterlist *sg;
+	struct sk_buff *skb;
+	unsigned long *skb_addr, *sg_addr;
+
+	for_each_sg(sglist, sg, nents, idx) {
+		sg_addr = (unsigned long *)sg_virt(sg);
+
+		skb_addr = sg_addr - 1;
+		skb = (struct sk_buff *)*skb_addr;
+		if (!virt_addr_valid(skb)) {
+			/* Invalid skb pointer */
+			skw_dbg("wrong address p_data:0x%lx from FW\n", (unsigned long)sg_addr);
+			continue;
+		}
+
+		skb->dev->stats.tx_packets++;
+		skb->dev->stats.tx_bytes += SKW_SKB_TXCB(skb)->skb_native_len;
+		//kfree_skb(skb);
+		skw_skb_kfree(skw, skb);
+		atomic_dec(&skw->txqlen_pending);
+	}
+
+	SKW_KFREE(sglist);
+}
+
+#ifdef CONFIG_RPS
+int skw_init_rps_map(struct skw_iface *iface)
+{
+	int i, cpu;
+	struct rps_map *map, *old_map;
+	static DEFINE_SPINLOCK(rps_map_lock);
+	struct netdev_rx_queue *queue = iface->ndev->_rx;
+
+	map = kzalloc(max_t(unsigned int,
+			    RPS_MAP_SIZE(cpumask_weight(cpu_online_mask)), L1_CACHE_BYTES),
+		      GFP_KERNEL);
+	if (!map)
+		return -ENOMEM;
+
+	i = 0;
+	for_each_cpu(cpu, cpu_online_mask)
+		if (cpu != iface->cpu_id)
+			map->cpus[i++] = cpu;
+
+	if (i) {
+		map->len = i;
+	} else {
+		kfree(map);
+		map = NULL;
+	}
+
+	spin_lock(&rps_map_lock);
+	old_map = rcu_dereference_protected(queue->rps_map,
+					    lockdep_is_held(&rps_map_lock));
+	rcu_assign_pointer(queue->rps_map, map);
+	spin_unlock(&rps_map_lock);
+
+	if (map) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0))
+		static_key_slow_inc(&rps_needed.key);
+#else
+		static_key_slow_inc(&rps_needed);
+#endif
+	}
+
+	if (old_map) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0))
+		static_key_slow_dec(&rps_needed.key);
+#else
+		static_key_slow_dec(&rps_needed);
+#endif
+		kfree_rcu(old_map, rcu);
+	}
+
+	return 0;
+}
+#endif
+
+static int skw_work_process(struct wiphy *wiphy, struct skw_iface *iface,
+			int work_id, void *data, int data_len, const u8 *name)
+{
+	int ret = 0;
+	struct skw_sg_node *node;
+	struct skw_ba_action *ba;
+	struct skw_core *skw = wiphy_priv(wiphy);
+//	int *umask;
+	struct skw_mgmt_status *status;
+
+	if (!iface) {
+		ret = -ENOENT;
+		goto exit;
+	}
+
+	skw_log(SKW_WORK, "[%s]: iface: %d, %s (id: %d)\n",
+		SKW_TAG_WORK, iface ? iface->id : -1, name, work_id);
+
+	switch (work_id) {
+	case SKW_WORK_BA_ACTION:
+		ret = skw_send_msg(wiphy, iface->ndev, SKW_CMD_BA_ACTION,
+				data, data_len, NULL, 0);
+		break;
+
+	case SKW_WORK_SCAN_TIMEOUT:
+		skw_scan_done(skw, iface, true);
+		break;
+
+	case SKW_WORK_ACL_CHECK:
+		skw_ap_acl_check(wiphy, iface);
+		break;
+
+	case SKW_WORK_SET_MC_ADDR:
+		ret = skw_send_msg(wiphy, iface->ndev, SKW_CMD_SET_MC_ADDR,
+				data, data_len, NULL, 0);
+		break;
+
+	case SKW_WORK_SET_IP:
+		ret = skw_send_msg(wiphy, iface->ndev, SKW_CMD_SET_IP,
+				data, data_len, NULL, 0);
+		break;
+
+	case SKW_WORK_TX_FREE:
+		node = data;
+		skw_work_async_adma_tx_free(skw, node->sg, node->nents);
+
+		break;
+
+	case SKW_WORK_SETUP_TXBA:
+		ba = data;
+
+		skw_dbg("%s, iface: %d, peer: %d, tid: %d\n",
+			name, iface->id, ba->peer_idx, ba->tid);
+
+		ret = skw_send_msg(wiphy, iface->ndev, SKW_CMD_BA_ACTION,
+				data, data_len, NULL, 0);
+		if (ret) {
+			struct skw_peer_ctx *ctx;
+
+			skw_err("setup TXBA failed, ret: %d\n", ret);
+
+			ctx = skw_get_ctx(skw, iface->lmac_id, ba->peer_idx);
+
+			skw_peer_ctx_lock(ctx);
+
+			if (ctx->peer)
+				SKW_CLEAR(ctx->peer->txba.bitmap, BIT(ba->tid));
+
+			skw_peer_ctx_unlock(ctx);
+		}
+
+		break;
+
+	case SKW_WORK_TX_ETHER_DATA:
+		ret = skw_send_msg(wiphy, iface->ndev, SKW_CMD_TX_DATA_FRAME,
+				data, data_len, NULL, 0);
+		break;
+#if 0
+	case SKW_WORK_RADAR_PULSE:
+		skw_dfs_radar_pulse_event(wiphy, iface, data, data_len);
+		break;
+#endif
+
+	case SKW_WORK_IFACE_RPS_INIT:
+#if 0
+#ifdef CONFIG_RPS
+		umask = (int *)data;
+		skw_init_rps_map(iface, *umask);
+#endif
+#endif
+		break;
+
+	case SKW_WORK_SPLIT_MGMT_TX_STATUS:
+		status = data;
+		skw_mgmt_tx_status(iface, status->mgmt_status_cookie,
+			status->mgmt_status_data, status->mgmt_status_data_len, true);
+
+		skw_dbg("split ack cookie:0x%llx\n", status->mgmt_status_cookie);
+
+		SKW_KFREE(status->mgmt_status_data);
+		break;
+
+	default:
+		skw_info("invalid work: %d\n", work_id);
+		break;
+	}
+
+exit:
+	return ret;
+}
+
+//workaround for bug3384
+static void skw_work_unlock(struct work_struct *work)
+{
+	struct skw_core *skw = container_of(work, struct skw_core, work_unlock);
+
+	skw_del_timer_work(skw, skw->cmd.data);
+	__pm_relax(skw->cmd.ws);
+	up(&skw->cmd.lock);
+}
+
+static void skw_work(struct work_struct *work)
+{
+	struct sk_buff *skb;
+	struct skw_work_cb *cb;
+	struct skw_core *skw = container_of(work, struct skw_core, work);
+	struct wiphy *wiphy = priv_to_wiphy(skw);
+
+	while (READ_ONCE(skw->work_data.flags) || skb_queue_len(&skw->work_data.work_list)) {
+
+		if (test_bit(SKW_WORK_FLAG_RCU_FREE, &skw->work_data.flags)) {
+			struct rcu_head *head;
+
+			spin_lock_bh(&skw->work_data.rcu_lock);
+
+			head = skw->work_data.rcu_hdr;
+			if (head)
+				skw->work_data.rcu_hdr = head->next;
+
+			spin_unlock_bh(&skw->work_data.rcu_lock);
+
+			if (head) {
+				synchronize_rcu();
+				head->func(head);
+			} else {
+				skw->work_data.rcu_tail = &skw->work_data.rcu_hdr;
+				clear_bit(SKW_WORK_FLAG_RCU_FREE, &skw->work_data.flags);
+			}
+		}
+
+		if (test_and_clear_bit(SKW_WORK_FLAG_ASSERT, &skw->work_data.flags))
+			skw_hw_assert(skw, false);
+
+		if (test_and_clear_bit(SKW_WORK_FLAG_RESUME, &skw->work_data.flags))
+			skw_resume(wiphy);
+
+		if (!skb_queue_len(&skw->work_data.work_list))
+			continue;
+
+		skb = skb_dequeue(&skw->work_data.work_list);
+		cb = SKW_WORK_CB(skb);
+		if (skb && cb)
+			skw_work_process(wiphy, cb->iface, cb->id,
+				skb->data, skb->len, cb->name);
+
+		kfree_skb(skb);
+	}
+}
+
+void skw_assert_schedule(struct wiphy *wiphy)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	set_bit(SKW_WORK_FLAG_ASSERT, &skw->work_data.flags);
+	schedule_work(&skw->work);
+}
+
+void skw_work_resume(struct wiphy *wiphy)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	set_bit(SKW_WORK_FLAG_RESUME, &skw->work_data.flags);
+	schedule_work(&skw->work);
+}
+
+void skw_unlock_schedule(struct wiphy *wiphy)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	//set_bit(SKW_WORK_FLAG_UNLOCK, &skw_work_flags);
+	schedule_work(&skw->work_unlock);
+	//schedule_work(&skw->work);
+}
+
+#ifdef CONFIG_SWT6621S_GKI_DRV
+void skw_call_rcu(void *core, struct rcu_head *head, rcu_callback_t func)
+{
+	struct skw_core *skw = core;
+
+	spin_lock_bh(&skw->work_data.rcu_lock);
+
+	head->func = func;
+	head->next = NULL;
+
+	*skw->work_data.rcu_tail = head;
+	skw->work_data.rcu_tail = &head->next;
+
+	spin_unlock_bh(&skw->work_data.rcu_lock);
+
+	set_bit(SKW_WORK_FLAG_RCU_FREE, &skw->work_data.flags);
+
+	schedule_work(&skw->work);
+}
+#endif
+
+int __skw_queue_work(struct wiphy *wiphy, struct skw_iface *iface,
+		     enum SKW_WORK_ID id, void *data,
+		     int dat_len, const u8 *name)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+	struct skw_work_cb *wcb;
+	struct sk_buff *skb;
+
+	skb = dev_alloc_skb(dat_len);
+	if (!skb)
+		return -ENOMEM;
+
+	if (data)
+		skw_put_skb_data(skb, data, dat_len);
+
+	wcb = SKW_WORK_CB(skb);
+	wcb->iface = iface;
+	wcb->id = id;
+	wcb->name = name;
+
+	skb_queue_tail(&skw->work_data.work_list, skb);
+	schedule_work(&skw->work);
+
+	return 0;
+}
+
+int skw_queue_event_work(struct wiphy *wiphy, struct skw_event_work *work,
+			 struct sk_buff *skb)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	if (!atomic_read(&work->enabled))
+		return -EINVAL;
+
+	skb_queue_tail(&work->qlist, skb);
+
+	if (!work_pending(&work->work))
+		queue_work(skw->event_wq, &work->work);
+
+	return 0;
+}
+
+void skw_work_init(struct wiphy *wiphy)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	skw->work_data.rcu_hdr = NULL;
+	skw->work_data.rcu_tail = &skw->work_data.rcu_hdr;
+
+	spin_lock_init(&skw->work_data.rcu_lock);
+	skb_queue_head_init(&skw->work_data.work_list);
+	INIT_WORK(&skw->work, skw_work);
+	INIT_WORK(&skw->work_unlock, skw_work_unlock);
+}
+
+void skw_work_deinit(struct wiphy *wiphy)
+{
+	struct skw_core *skw = wiphy_priv(wiphy);
+
+	flush_work(&skw->work_unlock);
+	flush_work(&skw->work);
+	skb_queue_purge(&skw->work_data.work_list);
+}
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_work.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_work.h
new file mode 100755
index 0000000..dbda393
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/skw_work.h
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#ifndef __SKW_WORK_MSG_H__
+#define __SKW_WORK_MSG_H__
+
+struct skw_sg_node {
+	struct scatterlist *sg;
+	int nents;
+	int status;
+	int lmac_id;
+	void *data;
+};
+
+enum SKW_WORK_ID {
+	SKW_WORK_BA_ACTION,
+	SKW_WORK_SCAN_TIMEOUT,
+	SKW_WORK_ACL_CHECK,
+	SKW_WORK_SET_MC_ADDR,
+	SKW_WORK_SET_IP,
+	SKW_WORK_TX_FREE,
+	SKW_WORK_SETUP_TXBA,
+	SKW_WORK_TX_ETHER_DATA,
+	SKW_WORK_RADAR_PULSE,
+	SKW_WORK_RADAR_CAC,
+	SKW_WORK_RADAR_CAC_END,
+	SKW_WORK_IFACE_RPS_INIT,
+	SKW_WORK_SPLIT_MGMT_TX_STATUS,
+};
+
+struct skw_work_cb {
+	struct skw_iface *iface;
+	const u8 *name;
+	u32 id;
+};
+
+struct skw_event_work {
+	atomic_t enabled;
+	struct work_struct work;
+	struct sk_buff_head qlist;
+};
+
+struct skw_mgmt_status {
+	void *mgmt_status_data;
+	int mgmt_status_data_len;
+	u64 mgmt_status_cookie;
+} __packed;
+
+static inline struct skw_work_cb *SKW_WORK_CB(struct sk_buff *skb)
+{
+	return (struct skw_work_cb *)skb->cb;
+}
+
+static inline void skw_event_work_init(struct skw_event_work *work,
+					work_func_t func)
+{
+	atomic_set(&work->enabled, 0);
+	skb_queue_head_init(&work->qlist);
+	INIT_WORK(&work->work, func);
+	atomic_set(&work->enabled, 1);
+}
+
+static inline void skw_event_work_deinit(struct skw_event_work *work)
+{
+	atomic_set(&work->enabled, 0);
+	cancel_work_sync(&work->work);
+	skb_queue_purge(&work->qlist);
+}
+
+int __skw_queue_work(struct wiphy *wiphy, struct skw_iface *iface,
+		     enum SKW_WORK_ID id, void *data,
+		     int dat_len, const u8 *name);
+
+#define skw_queue_work(wiphy, iface, id, data, len) \
+	__skw_queue_work(wiphy, iface, id, data, len, #id)
+
+int skw_queue_event_work(struct wiphy *wiphy, struct skw_event_work *work,
+			 struct sk_buff *skb);
+
+void skw_assert_schedule(struct wiphy *wiphy);
+void skw_unlock_schedule(struct wiphy *wiphy);
+void skw_work_init(struct wiphy *wiphy);
+void skw_work_deinit(struct wiphy *wiphy);
+int skw_init_rps_map(struct skw_iface *iface);
+void skw_work_resume(struct wiphy *wiphy);
+
+#ifdef CONFIG_SWT6621S_GKI_DRV
+void skw_call_rcu(void *skw, struct rcu_head *head, rcu_callback_t func);
+#endif
+
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/trace.c b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/trace.c
new file mode 100755
index 0000000..a460494
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/trace.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#include <linux/module.h>
+
+#ifndef __CHECKER__
+#include <linux/ieee80211.h>
+#include <linux/nl80211.h>
+#include <net/cfg80211.h>
+
+#define CREATE_TRACE_POINTS
+#include "trace.h"
+
+#endif
diff --git a/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/trace.h b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/trace.h
new file mode 100755
index 0000000..21ecd8a
--- /dev/null
+++ b/longan/kernel/linux-4.9/drivers/net/wireless/swt6621s_wifi/trace.h
@@ -0,0 +1,684 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/******************************************************************************
+ *
+ * Copyright (C) 2020 SeekWave Technology Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ ******************************************************************************/
+
+#if !defined(__SKW_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ)
+#define __SKW_TRACE_H__
+
+#include <linux/tracepoint.h>
+#include <linux/etherdevice.h>
+#include <linux/version.h>
+
+#include "skw_rx.h"
+#include "skw_msg.h"
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM skwifi
+
+TRACE_EVENT(skw_tx_add_credit,
+	    TP_PROTO(int mac, int cred),
+	    TP_ARGS(mac, cred),
+
+	    TP_STRUCT__entry(
+	    __field(int, mac)
+	    __field(int, cred)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->mac = mac;
+	    __entry->cred = cred;
+	    ),
+
+	    TP_printk("mac: %d, credit: %d", __entry->mac, __entry->cred)
+);
+
+TRACE_EVENT(skw_tx_xmit,
+	    TP_PROTO(u8 *mac, int peer_idx, int ip_prot, int fix_rate,
+		     int do_csum, int tid, int qlen),
+	    TP_ARGS(mac, peer_idx, ip_prot, fix_rate, do_csum, tid, qlen),
+	    TP_STRUCT__entry(
+	    __array(u8, dest, ETH_ALEN)
+	    __field(int, peer_idx)
+	    __field(int, ip_prot)
+	    __field(int, fix_rate)
+	    __field(int, do_csum)
+	    __field(int, tid)
+	    __field(int, qlen)
+	    ),
+
+	    TP_fast_assign(
+	    memcpy(__entry->dest, mac, ETH_ALEN);
+	    __entry->peer_idx = peer_idx;
+	    __entry->ip_prot = ip_prot;
+	    __entry->fix_rate = fix_rate;
+	    __entry->do_csum = do_csum;
+	    __entry->tid = tid;
+	    __entry->qlen = qlen;
+	    ),
+
+	    TP_printk("dest: %pM, peer: %d, ip_prot: %d, fix rate: %d, csum: %d, queue[%d]: %d",
+		      __entry->dest, __entry->peer_idx, __entry->ip_prot,
+		      __entry->fix_rate, __entry->do_csum,
+		      __entry->tid, __entry->qlen)
+);
+
+TRACE_EVENT(skw_tx_info,
+	    TP_PROTO(int mac, int ac, int cred, int tx_count, int qlen, int pending_qlen),
+	    TP_ARGS(mac, ac, cred, tx_count, qlen, pending_qlen),
+
+	    TP_STRUCT__entry(
+	    __field(int, mac)
+	    __field(int, ac)
+	    __field(int, cred)
+	    __field(int, tx_count)
+	    __field(int, qlen)
+		__field(int, pending_qlen)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->mac = mac;
+	    __entry->ac = ac;
+	    __entry->cred = cred;
+	    __entry->tx_count = tx_count;
+	    __entry->qlen = qlen;
+		__entry->pending_qlen = pending_qlen;
+	    ),
+
+	    TP_printk("mac: %d, ac: %d, credit: %d, qlen: %d, tx count: %d pending_qlen:%d",
+		      __entry->mac, __entry->ac, __entry->cred,
+		      __entry->qlen, __entry->tx_count, __entry->pending_qlen)
+);
+
+TRACE_EVENT(skw_tx_exit_check,
+	TP_PROTO(int mac, int credit, int pending_qlen),
+	TP_ARGS(mac, credit, pending_qlen),
+
+	TP_STRUCT__entry(
+	__field(int, mac)
+	__field(int, credit)
+	__field(int, pending_qlen)
+	),
+
+	TP_fast_assign(
+	__entry->mac = mac;
+	__entry->credit = credit;
+	__entry->pending_qlen = pending_qlen;
+	),
+
+	TP_printk("mac: %d, credit: %d pending_qlen:%d",
+		  __entry->mac, __entry->credit, __entry->pending_qlen)
+);
+
+TRACE_EVENT(skw_txlp_tx_list,
+	    TP_PROTO(s8 *name, int id),
+	    TP_ARGS(name, id),
+
+	    TP_STRUCT__entry(
+	    __field(s8 *, name)
+		__field(int, id)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->name = name;
+		__entry->id = id;
+	    ),
+
+	    TP_printk("name: %s, iface id:%d",
+		      __entry->name, __entry->id)
+);
+
+TRACE_EVENT(skw_txlp_mac,
+	    TP_PROTO(int mac, int cred, int current_qlen, int base),
+	    TP_ARGS(mac, cred, current_qlen, base),
+
+	    TP_STRUCT__entry(
+		__field(int, mac)
+		__field(int, cred)
+		__field(int, current_qlen)
+		__field(int, base)
+	    ),
+
+	    TP_fast_assign(
+		__entry->mac = mac;
+		__entry->cred = cred;
+		__entry->current_qlen = current_qlen;
+		__entry->base = base;
+	    ),
+
+	    TP_printk("mac:%d, cred:%d, current_qlen:%d, base:%d",
+			  __entry->mac,
+			  __entry->cred,
+			  __entry->current_qlen,
+			  __entry->base)
+);
+
+TRACE_EVENT(skw_txq_prepare,
+	    TP_PROTO(s8 *name, int qlen),
+	    TP_ARGS(name, qlen),
+
+	    TP_STRUCT__entry(
+	    __field(s8 *, name)
+		__field(int, qlen)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->name = name;
+		__entry->qlen = qlen;
+	    ),
+
+	    TP_printk("name: %s, qlen:%d",
+		      __entry->name, __entry->qlen)
+);
+
+TRACE_EVENT(skw_get_credit,
+	    TP_PROTO(int credit, int mac),
+	    TP_ARGS(credit, mac),
+
+	    TP_STRUCT__entry(
+		__field(int, credit)
+		__field(int, mac)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->credit = credit;
+		__entry->mac = mac;
+	    ),
+
+	    TP_printk("credit: %d, mac:%d",
+		      __entry->credit, __entry->mac)
+);
+
+#if 0
+TRACE_EVENT(skw_tx_thread,
+	    TP_PROTO(u32 cred, u32 nents, u32 tx_data_len, u32 tx_buff_len, u8 *tx_ac, u32 *cached),
+	    TP_ARGS(cred, nents, tx_data_len, tx_buff_len, tx_ac, cached),
+
+	    TP_STRUCT__entry(
+	    __field(u32, cred)
+	    __field(u32, nents)
+	    __field(u32, tx_data_len)
+	    __field(u32, tx_buff_len)
+	    __array(u8, tx_ac, SKW_WMM_AC_MAX)
+	    __array(u32, cached, SKW_WMM_AC_MAX)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->cred = cred;
+	    __entry->nents = nents;
+	    __entry->tx_data_len = tx_data_len;
+	    __entry->tx_buff_len = tx_buff_len;
+	    memcpy(__entry->tx_ac, tx_ac, sizeof(u32) * SKW_WMM_AC_MAX);
+	    memcpy(__entry->cached, cached, sizeof(u32) * SKW_WMM_AC_MAX);
+	    ),
+
+	    TP_printk("creds: %d, tx: %d, len: %d/%d, VO[%d:%d] VI[%d:%d] BE[%d:%d] BK[%d:%d]",
+		      __entry->cred, __entry->nents,
+		      __entry->tx_data_len,
+		      __entry->tx_buff_len,
+
+		      __entry->tx_ac[SKW_WMM_AC_VO],
+		      __entry->cached[SKW_WMM_AC_VO],
+
+		      __entry->tx_ac[SKW_WMM_AC_VI],
+		      __entry->cached[SKW_WMM_AC_VI],
+
+		      __entry->tx_ac[SKW_WMM_AC_BE],
+		      __entry->cached[SKW_WMM_AC_BE],
+
+		      __entry->tx_ac[SKW_WMM_AC_BK],
+		      __entry->cached[SKW_WMM_AC_BK])
+);
+
+TRACE_EVENT(skw_tx_thread_ret,
+	    TP_PROTO(int ret, u32 pending_qlen, u32 ac_reset),
+	    TP_ARGS(ret, pending_qlen, ac_reset),
+
+	    TP_STRUCT__entry(
+	    __field(int, ret)
+	    __field(u32, pending_qlen)
+	    __field(u32, ac_reset)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->ret = ret;
+	    __entry->pending_qlen = pending_qlen;
+	    __entry->ac_reset = ac_reset;
+	    ),
+
+	    TP_printk("ret: %d, pending: %d, ac_reset: 0x%x",
+		      __entry->ret, __entry->pending_qlen, __entry->ac_reset)
+);
+
+#endif
+
+TRACE_EVENT(skw_tx_runing,
+	    TP_PROTO(int tx, int pending_qlen, long timeout, int keep_running),
+	    TP_ARGS(tx, pending_qlen, timeout, keep_running),
+
+	    TP_STRUCT__entry(
+	    __field(int, tx)
+	    __field(int, pending_qlen)
+	    __field(long, timeout)
+	    __field(int, keep_running)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->tx = tx;
+	    __entry->pending_qlen = pending_qlen;
+	    __entry->timeout = timeout;
+	    __entry->keep_running = keep_running;
+	    ),
+
+	    TP_printk("tx: %d, pending_qlen: %d, timeout: %ld, pending_qlen: %d",
+		      __entry->tx, __entry->pending_qlen,
+		      __entry->timeout, __entry->keep_running)
+);
+
+TRACE_EVENT(skw_tx_pcie_edma_free,
+	    TP_PROTO(u16 count),
+	    TP_ARGS(count),
+
+	    TP_STRUCT__entry(
+	    __field(u16, count)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->count = count;
+	    ),
+
+	    TP_printk("count: %u",
+		      __entry->count)
+);
+
+TRACE_EVENT(skw_rx_set_reorder_timer,
+	    TP_PROTO(u8 inst, u8 pid, u16 tid, u16 seq, unsigned long rx_time, unsigned long timeout),
+	    TP_ARGS(inst, pid, tid, seq, rx_time, timeout),
+
+	    TP_STRUCT__entry(
+	    __field(u8, inst)
+	    __field(u8, pid)
+	    __field(u16, tid)
+	    __field(u16, seq)
+	    __field(unsigned long, rx_time)
+	    __field(unsigned long, timeout)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->inst = inst;
+	    __entry->pid = pid;
+	    __entry->tid = tid;
+	    __entry->seq = seq;
+	    __entry->rx_time = rx_time;
+	    __entry->timeout = timeout;
+	    ),
+
+	    TP_printk("I: %d, P: %d, T: %d, seq: %d, rx_time: %ld, timeout: %ld",
+		      __entry->inst, __entry->pid,
+		      __entry->tid, __entry->seq,
+		      __entry->rx_time, __entry->timeout)
+);
+
+TRACE_EVENT(skw_rx_reorder_timeout,
+	    TP_PROTO(u8 inst, u8 pid, u16 tid, u16 expired_sn),
+	    TP_ARGS(inst, pid, tid, expired_sn),
+
+	    TP_STRUCT__entry(
+	    __field(u8, inst)
+	    __field(u8, pid)
+	    __field(u16, tid)
+	    __field(u16, expired_sn)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->inst = inst;
+	    __entry->pid = pid;
+	    __entry->tid = tid;
+	    __entry->expired_sn = expired_sn;
+	    ),
+
+	    TP_printk("I: %d, P: %d, T: %d, expired_sn: %d",
+		    __entry->inst, __entry->pid,
+		    __entry->tid, __entry->expired_sn)
+);
+
+TRACE_EVENT(skw_rx_expired_release,
+	    TP_PROTO(u8 inst, u8 pid, u16 tid, u16 expired_sn),
+	    TP_ARGS(inst, pid, tid, expired_sn),
+
+	    TP_STRUCT__entry(
+	    __field(u8, inst)
+	    __field(u8, pid)
+	    __field(u16, tid)
+	    __field(u16, expired_sn)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->inst = inst;
+	    __entry->pid = pid;
+	    __entry->tid = tid;
+	    __entry->expired_sn = expired_sn;
+	    ),
+
+	    TP_printk("I: %d, P: %d, T: %d, expired_sn: %d",
+		    __entry->inst, __entry->pid,
+		    __entry->tid, __entry->expired_sn)
+);
+
+TRACE_EVENT(skw_rx_data,
+	    TP_PROTO(u8 inst, u8 pid, u8 tid, u8 filter, u16 seq, u8 qos,
+		     u8 retry, u8 amsdu, u8 idx, u8 first, u8 last, bool fake_ack),
+	    TP_ARGS(inst, pid, tid, filter, seq, qos, retry, amsdu,
+		    idx, first, last, fake_ack),
+
+	    TP_STRUCT__entry(
+	    __field(u8, inst)
+	    __field(u8, pid)
+	    __field(u8, tid)
+	    __field(u8, filter)
+	    __field(u16, seq)
+	    __field(u8, qos)
+	    __field(u8, retry)
+	    __field(u8, amsdu)
+	    __field(u8, idx)
+	    __field(u8, first)
+	    __field(u8, last)
+	    __field(bool, fake_ack)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->inst = inst;
+	    __entry->pid = pid;
+	    __entry->tid = tid;
+	    __entry->filter = filter;
+	    __entry->seq = seq;
+	    __entry->qos = qos;
+	    __entry->retry = retry;
+	    __entry->amsdu = amsdu;
+	    __entry->idx = idx;
+	    __entry->first = first;
+	    __entry->last = last;
+	    __entry->fake_ack = fake_ack;
+	    ),
+
+	    TP_printk("I: %d, P: %d, T: %d, filter: %d, seq: %d, qos: %d, "
+		      "retry: %d, amsdu: %d, idx: %d(F: %d, L: %d), fake ack: %d",
+		      __entry->inst, __entry->pid,
+		      __entry->tid, __entry->filter,
+		      __entry->seq, __entry->qos,
+		      __entry->retry, __entry->amsdu,
+		      __entry->idx, __entry->first,
+		      __entry->last, __entry->fake_ack)
+);
+
+TRACE_EVENT(skw_rx_reorder,
+	    TP_PROTO(u8 inst, u8 pid, u16 tid, u16 sn, u8 is_amsdu, u8 amsdu_idx,
+		     u16 win_size, u16 win_start, u32 stored_num,
+		     bool release, bool drop),
+	    TP_ARGS(inst, pid, tid, sn, is_amsdu, amsdu_idx, win_size,
+		    win_start, stored_num, release, drop),
+
+	    TP_STRUCT__entry(
+	    __field(u8, inst)
+	    __field(u8, pid)
+	    __field(u16, tid)
+	    __field(u16, sn)
+	    __field(u8, is_amsdu)
+	    __field(u8, amsdu_idx)
+	    __field(u16, win_size)
+	    __field(u16, win_start)
+	    __field(u32, stored_num)
+	    __field(bool, release)
+	    __field(bool, drop)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->inst = inst;
+	    __entry->pid = pid;
+	    __entry->tid = tid;
+	    __entry->sn = sn;
+	    __entry->is_amsdu = is_amsdu;
+	    __entry->amsdu_idx = amsdu_idx;
+	    __entry->win_size = win_size;
+	    __entry->win_start = win_start;
+	    __entry->stored_num = stored_num;
+	    __entry->release = release;
+	    __entry->drop = drop;
+	    ),
+
+	    TP_printk("ssn: %d, sn: %d (%d, %d), release: %d, drop: %d, stored: %d, "
+		      "I: %d, P: %d, T: %d, win_sz: %d, amsdu: %d, idx: %d",
+		      __entry->win_start, __entry->sn,
+		      __entry->win_start % __entry->win_size,
+		      __entry->sn % __entry->win_size,
+		      __entry->release, __entry->drop,
+		      __entry->stored_num,
+		      __entry->inst, __entry->pid,
+		      __entry->tid, __entry->win_size,
+		      __entry->is_amsdu, __entry->amsdu_idx)
+);
+
+TRACE_EVENT(skw_rx_reorder_release,
+	    TP_PROTO(u8 inst, u8 pid, u16 tid, u16 win_start, u16 seq, u16 index, u16 ssn, u16 left),
+	    TP_ARGS(inst, pid, tid, win_start, seq, index, ssn, left),
+
+	    TP_STRUCT__entry(
+	    __field(u8, inst)
+	    __field(u8, pid)
+	    __field(u16, tid)
+	    __field(u16, win_start)
+	    __field(u16, seq)
+	    __field(u16, index)
+	    __field(u16, ssn)
+	    __field(u16, left)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->inst = inst;
+	    __entry->pid = pid;
+	    __entry->tid = tid;
+	    __entry->win_start = win_start;
+	    __entry->seq = seq;
+	    __entry->index = index;
+	    __entry->ssn = ssn;
+	    __entry->left = left;
+	    ),
+
+	    TP_printk("I: %d, P: %d, T: %d, win start: %d, seq: %d (index: %d), ssn: %d, left: %d",
+		      __entry->inst, __entry->pid, __entry->tid, __entry->win_start,
+		      __entry->seq, __entry->index, __entry->ssn, __entry->left)
+);
+
+TRACE_EVENT(skw_rx_force_release,
+	    TP_PROTO(u8 inst, u8 pid, u16 tid, u16 index, u16 ssn, u16 target, u16 left, int reason),
+	    TP_ARGS(inst, pid, tid, index, ssn, target, left, reason),
+
+	    TP_STRUCT__entry(
+	    __field(u8, inst)
+	    __field(u8, pid)
+	    __field(u16, tid)
+	    __field(u16, index)
+	    __field(u16, ssn)
+	    __field(u16, target)
+	    __field(u16, left)
+	    __field(int, reason)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->inst = inst;
+	    __entry->pid = pid;
+	    __entry->tid = tid;
+	    __entry->index = index;
+	    __entry->ssn = ssn;
+	    __entry->target = target;
+	    __entry->left = left;
+	    __entry->reason = reason;
+	    ),
+
+	    TP_printk("I: %d, P: %d, T: %d, seq: %d(index: %d), target: %d, left: %d, reason: %d",
+		    __entry->inst, __entry->pid, __entry->tid,
+		    __entry->ssn, __entry->index, __entry->target,
+		    __entry->left, __entry->reason)
+);
+
+TRACE_EVENT(skw_rx_handler_seq,
+	    TP_PROTO(u16 sn, u8 msdu_filter),
+	    TP_ARGS(sn, msdu_filter),
+
+	    TP_STRUCT__entry(
+	    __field(u16, sn)
+	    __field(u8, msdu_filter)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->sn = sn;
+	    __entry->msdu_filter = msdu_filter;
+	    ),
+
+	    TP_printk("seq: %d msdu_filter: %u",
+		    __entry->sn, __entry->msdu_filter)
+);
+
+TRACE_EVENT(skw_rx_add_ba,
+	    TP_PROTO(u8 inst, u8 pid, u16 tid, u16 ssn, u16 buf_size),
+	    TP_ARGS(inst, pid, tid, ssn, buf_size),
+
+	    TP_STRUCT__entry(
+	    __field(u8, inst)
+	    __field(u8, pid)
+	    __field(u16, tid)
+	    __field(u16, ssn)
+	    __field(u16, buf_size)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->inst = inst;
+	    __entry->pid = pid;
+	    __entry->tid = tid;
+	    __entry->ssn = ssn;
+	    __entry->buf_size = buf_size;
+	    ),
+
+	    TP_printk("I: %d, P: %d, T: %d,  ssn: %d, buf_size: %d",
+		      __entry->inst, __entry->pid, __entry->tid,
+		      __entry->ssn, __entry->buf_size)
+);
+
+TRACE_EVENT(skw_rx_update_ba,
+	    TP_PROTO(u8 inst, u8 pid, u16 tid, u16 ssn),
+	    TP_ARGS(inst, pid, tid, ssn),
+
+	    TP_STRUCT__entry(
+	    __field(u8, inst)
+	    __field(u8, pid)
+	    __field(u16, tid)
+	    __field(u16, ssn)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->inst = inst;
+	    __entry->pid = pid;
+	    __entry->tid = tid;
+	    __entry->ssn = ssn;
+	    ),
+
+	    TP_printk("I: %d, P: %d, T: %d, ssn: %d",
+		      __entry->inst, __entry->pid,
+		      __entry->tid, __entry->ssn)
+);
+
+TRACE_EVENT(skw_rx_del_ba,
+	    TP_PROTO(u16 tid),
+	    TP_ARGS(tid),
+
+	    TP_STRUCT__entry(
+	    __field(u16, tid)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->tid = tid;
+	    ),
+
+	    TP_printk("del ba, tid: %d", __entry->tid)
+);
+
+TRACE_EVENT(skw_rx_irq,
+	    TP_PROTO(int nents, int idx,  int port, int len),
+	    TP_ARGS(nents, idx, port, len),
+
+	    TP_STRUCT__entry(
+	    __field(int, nents)
+	    __field(int, idx)
+	    __field(int, port)
+	    __field(int, len)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->nents = nents;
+	    __entry->idx = idx;
+	    __entry->port = port;
+	    __entry->len = len;
+	    ),
+
+	    TP_printk("nents: %d, idx: %d, port: %d, len: %d",
+		      __entry->nents, __entry->idx,
+		      __entry->port, __entry->len)
+);
+
+TRACE_EVENT(skw_msg_rx,
+	    TP_PROTO(u8 inst, u8 type, u16 id, u16 seq, u16 len),
+	    TP_ARGS(inst, type, id, seq, len),
+
+	    TP_STRUCT__entry(
+	    __field(u8, inst)
+	    __field(u8, type)
+	    __field(u16, id)
+	    __field(u16, seq)
+	    __field(u16, len)
+	    ),
+
+	    TP_fast_assign(
+	    __entry->inst = inst;
+	    __entry->type = type;
+	    __entry->id = id;
+	    __entry->seq = seq;
+	    __entry->len = len;
+	    ),
+
+	    TP_printk("inst: %d, type: %d, id: %d, seq: %d, len: %d",
+		      __entry->inst, __entry->type, __entry->id,
+		      __entry->seq, __entry->len)
+);
+
+TRACE_EVENT(skw_hw_adma_tx_done,
+	TP_PROTO(u8 num),
+	TP_ARGS(num),
+
+	TP_STRUCT__entry(__field(u8, num)),
+
+	TP_fast_assign(__entry->num = num;),
+
+	TP_printk("num:%d", __entry->num)
+);
+
+#endif
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE trace
+
+#include <trace/define_trace.h>
diff --git a/longan/kernel/linux-4.9/include/linux/platform_data/skw_platform_data.h b/longan/kernel/linux-4.9/include/linux/platform_data/skw_platform_data.h
new file mode 100755
index 0000000..b7f9857
--- /dev/null
+++ b/longan/kernel/linux-4.9/include/linux/platform_data/skw_platform_data.h
@@ -0,0 +1,227 @@
+/*
+ * seekwave - Platform data for sv6160 platform.
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * License ("GPL") version 2, as published by the Free Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef __SKW_PLATFORM_DATA_H__
+#define __SKW_PLATFORM_DATA_H__
+
+#define MAX_PORT_COUNT 8
+
+#define	WIFIDATA_PORTNO	0
+#define	WIFICMD_PORTNO  1
+#define	BRDATA_PORTNO   2
+#define	BTCMD_PORTNO    3
+#define SKW_LOG		4
+#define SKW_AT		5
+#define SKW_LOOPCHECK   6
+#define SKW_ASSERT      7
+
+#define DEVICE_ASSERT_EVENT	0
+#define DEVICE_BSPREADY_EVENT	1
+#define DEVICE_DUMPDONE_EVENT	2
+#define DEVICE_BLOCKED_EVENT	3
+#define DEVICE_DISCONNECT_EVENT 4
+#define DEVICE_DUMPMEM_EVENT 5
+#define DEVICE_SUSPEND_EVENT 6
+#define DEVICE_RESUME_EVENT 7
+#define DEVICE_BOOTUP_EVENT 8
+
+#define	SV6160_WIRELESS	"sv6160_wireless"
+#define	SV6621S_WIRELESS "sv6621s_wireless"
+#define SV6160_BTDRIVER	"sv6160_btdriver"
+#define SV6316_WIRELESS "sv6316_wireless"
+
+#define RX_CALLBACK        0
+#define ADMA_TX_CALLBACK   1
+#define SDMA_TX_CALLBACK   2
+#define SKW_ADMA_BUFF_LEN  PAGE_SIZE
+
+struct skw_packet_header {
+    u32 pad:7;
+    u32 len:16;
+    u32 eof:1;
+    u32 channel:8;
+};
+
+struct skw_packet2_header {
+    u32 len:16;
+	u32 pad:7;
+    u32 eof:1;
+    u32 channel:8;
+};
+
+struct EDMA_Node{
+	u64 data_addr:40;
+	u64 user_len:16;
+	u64 tx_int:1;
+	u64 rsv1:6;
+	u64 done:1;
+
+	u64 next_hdr:40;
+	u64 edma_no:8;
+	u64 length:16;//cur_trans_length,except header
+} __attribute__((packed));
+
+struct skw_operation {
+    u8 port;
+    int (*open) (int id, void *callback, void *data);
+    int (*close) (int id);
+    int (*read) (int id, char *buff, int len);
+    int (*write) (int id, char *buff, int len);
+    /*
+     * actual :  buffer to save actual read /write data size;
+     * timeout : timeout unit ms.
+     * return value
+     * ret=0: transfer successfully, actual save the data size
+     * ret=-ETIMEDOUT, timer out
+     * otherwise error happened.
+     */
+    int (*read_tm) (int id, char *buff, int len, int *actual, int timeout);
+    int (*write_tm) (int id, char *buff, int len, int *actual, int timeout);
+};
+
+/*****************************************************************
+ * add EDMA parameters, usage:
+ * direction: CP is source: 1; AP is source: 0,
+ * priority:  EDMA channel priority: 4 level: 0(highest)~3(lowest)
+ * split: 0: not split;
+ *        1: split.
+ *	  AP driver: not successive
+ * ring:   1:ring  node; 0: list mode; AP driver:ring buffer.
+ * endian: 0;
+ * irq_threshold: processed node count that raise complete IRQ.
+ * req_mode: 1:linklist mode
+ *           0:std mode
+ * fix_linklist_len(linklist mode):
+ *           1: current node transfer length is trsc_len
+ *           0: current node transfer length in head
+ * trsc_len: ditto
+ * opposite_node_done:
+ *           1: report local complete int to opposite end
+ *           0: no
+ * node_count: node count in list ready for EDMA to process.
+ * header: this is the free node EDMA is going to process.
+ * timeout: timeout value for Complete IRQ, timeout unit is uS.
+ * maximum timeout value is 4ms.
+ * list header is set to CHNn_SRC_DSCR_PTR_HIGH(direction=1)/
+ * or to CHNn_DST_DSCR_PTR_HIGH(direction=0)/
+ * context: save service context to be referred in callbck function.
+ * header: ring buffer header, it's better to be aligned to 8 bytes.
+ *         header = &edma_node.next_addr_l32
+ ******************************************************************/
+
+struct skw_channel_cfg {
+    u8 direction;
+    u8 priority;
+    u8 split;
+    u8 ring;
+    u8 endian;
+    u8 irq_threshold;
+    u8 req_mode;
+    u8 fix_linklist_len;
+    u16 trsc_len;
+    u8 opposite_node_done;
+    u16 timeout;
+    dma_addr_t	 header; //PCIe Address
+    u16 node_count;
+    u32 buf_cnt;
+    u32 buf_level;
+    void *context;
+    int (*complete_callback) (void *context, void *header, void *tailed, int node_count);
+    int (*empty_callback) (void *context);
+    void (*rx_callback) (void *context, void *data_addr, u16 data_len);
+};
+typedef int (*rx_submit_fn) (int id,  struct scatterlist *sg, int nets, void *data);
+typedef int (*adma_callback) (int id,  struct scatterlist *sg, int nets, void *data, int status);
+typedef int (*sdma_callback) (int id,  void *buffer, int size, void *data, int status);
+typedef int (*status_notify) (u8 event);
+struct sv6160_platform_data {
+	u8				data_port;
+	u8				cmd_port;
+	u8				audio_port;
+	u8				bus_type;
+
+#define SDIO_LINK		(0<<0)
+#define USB_LINK		(1<<0)
+#define PCIE_LINK		(2<<0)
+#define SDIO2_LINK		(3<<0)
+#define USB2_LINK		(4<<0)
+
+#define TYPE_MASK		0x07
+#define TX_ADMA			(0<<3)
+#define TX_SDMA			(1<<3)
+#define TX_ASYN			(1<<4)
+#define RX_ADMA			(0<<5)
+#define RX_SDMA			(1<<5)
+#define CP_DBG			(0<<6)
+#define CP_RLS			(1<<6)
+#define REINIT_USB_STR          (1<<7)
+
+
+	u32				max_buffer_size;
+	u16				align_value;
+	char				chipid[16];
+	char 				*port_name;
+
+	int (*hw_channel_init) (int id, void *channl_cfg, void *data);
+	int (*hw_channel_deinit) (int id);
+	int (*open_port) (int id, void *callback, void *data);
+	int (*hw_adma_tx)(int id, struct scatterlist *sg, int nets, int size);
+	int (*hw_sdma_tx)(int id, char *buff, int len);
+	int (*hw_adma_tx_async)(int id, struct scatterlist *sg, int nets, int size);
+	int (*hw_sdma_tx_async)(int id, char *buff, int len);
+	int (*hw_sdma_rx)(int id, char *buff, int len);
+	int (*read_timeout)(int id, char *buffer, int len, int timeout);
+	int (*write_timeout)(int id, char *buffer, int len, int timeout);
+	int (*callback_register)(int id, void *function, void *para);
+	int (*close_port) (int id);
+	int (*modem_assert) (void);
+	dma_addr_t (*phyaddr_to_pcieaddr)(dma_addr_t phy_addr);
+	dma_addr_t (*pcieaddr_to_phyaddr)(dma_addr_t pcie_addr);
+	dma_addr_t (*virtaddr_to_pcieaddr)(void *virt_addr);
+	u64 (*pcieaddr_to_virtaddr)(dma_addr_t phy_addr);
+	struct skw_operation at_ops;
+	void (*modem_register_notify)(struct notifier_block *nb);
+	void (*modem_unregister_notify)(struct notifier_block *nb);
+	int  (*wifi_get_credit)(void);
+	int  (*service_start)(void);
+	int  (*service_stop)(void);
+	int  (*skw_dloader)(int index);
+	int  (*wifi_store_credit)(unsigned char val);
+	int  (*skw_dump_mem)(unsigned int system_addr, void *buf,unsigned int len);
+	int  (*tx_callback_register)(int id, void *function, void *para);
+	int (*submit_list_to_edma_channel)(int ch_id, void *header, int count);
+	void (*edma_mask_irq)(int channel);
+	void (*edma_unmask_irq)(int channel);
+	int (*wifi_power_on)(int is_on);
+	void (*usb_speed_switch)(char *mode);
+	int (*edma_get_node_tot_cnt)(int channel);
+	u32 (*edma_clear_src_node_count)(int channel);
+	void (*rx_thread_wakeup)(void);
+	int  (*suspend_adma_cmd)(int id, struct scatterlist *sg, int nets, int size);
+	int (*suspend_sdma_cmd)(int id, char *buff, int len);
+	void (*dump_modem_memory)(char *buffer, int size, int *log_size);
+	int (*bluetooth_log_disable)(int disable);
+	/*
+	 * add edma channel mask for WIFI platform device.
+	 * value=0x7ff, means first 11 channels owned by WIFI.
+	 */
+	u64				wifi_channel_map;//0x7ff;
+	char *debug_info;
+};
+
+#endif

--
Gitblit v1.6.2