diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index 60c8043749e28..7a8c4ec553d0d 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -23,6 +23,11 @@ static bool rtw89_disable_ps_mode;
 module_param_named(disable_ps_mode, rtw89_disable_ps_mode, bool, 0644);
 MODULE_PARM_DESC(disable_ps_mode, "Set Y to disable low power mode");
 
+bool rtw89_use_chanctx;
+module_param_named(use_chanctx, rtw89_use_chanctx, bool, 0644);
+MODULE_PARM_DESC(use_chanctx,
+		 "Indicate whether to use channel context required for concurrency");
+
 #define RTW89_DEF_CHAN(_freq, _hw_val, _flags, _band)	\
 	{ .center_freq = _freq, .hw_value = _hw_val, .flags = _flags, .band = _band, }
 #define RTW89_DEF_CHAN_2G(_freq, _hw_val)	\
@@ -3220,7 +3225,7 @@ struct ieee80211_hw *rtw89_alloc_ieee80211_hw(u32 bus_data_size,
 	if (!ops)
 		goto err;
 
-	if (chip->support_chanctx_num == 0) {
+	if (!rtw89_use_chanctx || chip->support_chanctx_num == 0) {
 		ops->add_chanctx = NULL;
 		ops->remove_chanctx = NULL;
 		ops->change_chanctx = NULL;
diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index 25292465a3718..47dee2b37620a 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -16,6 +16,7 @@ struct rtw89_dev;
 struct rtw89_pci_info;
 
 extern const struct ieee80211_ops rtw89_ops;
+extern bool rtw89_use_chanctx;
 
 #define MASKBYTE0 0xff
 #define MASKBYTE1 0xff00
@@ -2560,6 +2561,10 @@ struct rtw89_fw_suit {
 	(((major) << 24) | ((minor) << 16) | ((sub) << 8) | (idx))
 #define RTW89_FW_SUIT_VER_CODE(s)	\
 	RTW89_FW_VER_CODE((s)->major_ver, (s)->minor_ver, (s)->sub_ver, (s)->sub_idex)
+#define RTW89_FW_VER_MAJOR(ver_code) FIELD_GET(GENMASK(31, 24), ver_code)
+#define RTW89_FW_VER_MINOR(ver_code) FIELD_GET(GENMASK(23, 16), ver_code)
+#define RTW89_FW_VER_SUB(ver_code) FIELD_GET(GENMASK(15, 8), ver_code)
+#define RTW89_FW_VER_IDX(ver_code) FIELD_GET(GENMASK(7, 0), ver_code)
 
 struct rtw89_fw_info {
 	const struct firmware *firmware;
diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c
index cee9815b6df6f..2867f395db4b3 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.c
+++ b/drivers/net/wireless/realtek/rtw89/fw.c
@@ -196,6 +196,29 @@ int __rtw89_fw_recognize(struct rtw89_dev *rtwdev, enum rtw89_fw_type type)
 	return 0;
 }
 
+static
+int __fw_feat_err_check_non_hw_scan(struct rtw89_dev *rtwdev, u32 required_vercode)
+{
+	const struct rtw89_chip_info *chip = rtwdev->chip;
+
+	if (!rtw89_use_chanctx)
+		return 0;
+
+	if (chip->support_chanctx_num == 0) {
+		rtw89_warn(rtwdev,
+			   "Run without chanctx because chip hasn't claimed it.\n");
+		return 0;
+	}
+
+	rtw89_err(rtwdev,
+		  "Please upgrade FW to at least %lu.%lu.%lu.%lu or disable use_chanctx.\n",
+		  RTW89_FW_VER_MAJOR(required_vercode),
+		  RTW89_FW_VER_MINOR(required_vercode),
+		  RTW89_FW_VER_SUB(required_vercode),
+		  RTW89_FW_VER_IDX(required_vercode));
+	return -ENOENT;
+}
+
 #define __DEF_FW_FEAT_COND(__cond, __op) \
 static bool __fw_feat_cond_ ## __cond(u32 suit_ver_code, u32 comp_ver_code) \
 { \
@@ -210,29 +233,36 @@ struct __fw_feat_cfg {
 	enum rtw89_fw_feature feature;
 	u32 ver_code;
 	bool (*cond)(u32 suit_ver_code, u32 comp_ver_code);
+	int (*err_check)(struct rtw89_dev *rtwdev, u32 required_vercode);
 };
 
-#define __CFG_FW_FEAT(_chip, _cond, _maj, _min, _sub, _idx, _feat) \
+#define __CFG_FW_FEAT(_chip, _cond, _maj, _min, _sub, _idx, _feat, _argv...) \
 	{ \
 		.chip_id = _chip, \
 		.feature = RTW89_FW_FEATURE_ ## _feat, \
 		.ver_code = RTW89_FW_VER_CODE(_maj, _min, _sub, _idx), \
 		.cond = __fw_feat_cond_ ## _cond, \
+		##_argv,\
 	}
 
+#define __CFG_FW_FEAT_SCAN_OFFLOAD(_chip, _cond, _maj, _min, _sub, _idx) \
+	__CFG_FW_FEAT(_chip, _cond, _maj, _min, _sub, _idx, SCAN_OFFLOAD, \
+		      .err_check = __fw_feat_err_check_non_hw_scan)
+
 static const struct __fw_feat_cfg fw_feat_tbl[] = {
 	__CFG_FW_FEAT(RTL8852A, le, 0, 13, 29, 0, OLD_HT_RA_FORMAT),
-	__CFG_FW_FEAT(RTL8852A, ge, 0, 13, 35, 0, SCAN_OFFLOAD),
+	__CFG_FW_FEAT_SCAN_OFFLOAD(RTL8852A, ge, 0, 13, 35, 0),
 	__CFG_FW_FEAT(RTL8852A, ge, 0, 13, 35, 0, TX_WAKE),
 	__CFG_FW_FEAT(RTL8852A, ge, 0, 13, 36, 0, CRASH_TRIGGER),
 };
 
-static void rtw89_fw_recognize_features(struct rtw89_dev *rtwdev)
+static int rtw89_fw_recognize_features(struct rtw89_dev *rtwdev)
 {
 	const struct rtw89_chip_info *chip = rtwdev->chip;
 	const struct __fw_feat_cfg *ent;
 	const struct rtw89_fw_suit *fw_suit;
 	u32 suit_ver_code;
+	int ret;
 	int i;
 
 	fw_suit = rtw89_fw_suit_get(rtwdev, RTW89_FW_NORMAL);
@@ -243,9 +273,19 @@ static void rtw89_fw_recognize_features(struct rtw89_dev *rtwdev)
 		if (chip->chip_id != ent->chip_id)
 			continue;
 
-		if (ent->cond(suit_ver_code, ent->ver_code))
+		if (ent->cond(suit_ver_code, ent->ver_code)) {
 			RTW89_SET_FW_FEATURE(ent->feature, &rtwdev->fw);
+			continue;
+		}
+
+		if (ent->err_check) {
+			ret = ent->err_check(rtwdev, ent->ver_code);
+			if (ret)
+				return ret;
+		}
 	}
+
+	return 0;
 }
 
 int rtw89_fw_recognize(struct rtw89_dev *rtwdev)
@@ -259,7 +299,9 @@ int rtw89_fw_recognize(struct rtw89_dev *rtwdev)
 	/* It still works if wowlan firmware isn't existing. */
 	__rtw89_fw_recognize(rtwdev, RTW89_FW_WOWLAN);
 
-	rtw89_fw_recognize_features(rtwdev);
+	ret = rtw89_fw_recognize_features(rtwdev);
+	if (ret)
+		return ret;
 
 	return 0;
 }
