总体介绍 1.1 引言 Fn-Link Technology 特此推出一款兼具所有Wi-Fi功能、低成本且低功耗的模块化设备。该产品为高度集成的IEEE 802.11 a/b/g/n/ac标准MAC协议/基带/射频无线局域网单芯片方案,专为无线局域网(WLAN)应用设计。模块配备 SDIO 接口支持Wi-Fi连接,并采用简化的传统协议兼容机制及20MHz/40MHz/80MHz频段共存方案,确保向后兼容性与网络兼容性。该无线模块符合IEEE 802.11 a/b/g/n/ac标准,在802.11ac草案规范下单流传输速率最高可达433.3Mbps。模块同时提供 SDIO 接口用于Wi-Fi通信,以及UART/PCM接口用于蓝牙连接。这款紧凑型模块是Wi-Fi与蓝牙技术集成的完整解决方案,专为智能手机及便携式设备量身打造。


硬件参考设计原理:






实际设计:


BT_PWREN_H_GPIO4_A7 这个管脚控制供3.3V过去

上电时序的控制:

驱动部分:

另外一个框架就是现在默认设计采用的:

Step1: add dts
RK_KERNEL_DTS=kernel/arch/arm64/boot/dts/rockchip/rk3562-idr2-bt.dts
sdio_pwrseq: sdio-pwrseq {
compatible = "mmc-pwrseq-simple";
clocks = <&rk817 1>;
clock-names = "ext_clock";
pinctrl-names = "default";
// pinctrl-0 = <&wifi_enable_h>;
/*
* On the module itself this is one of these (depending
* on the actual card populated):
* - SDIO_RESET_L_WL_REG_ON
* - PDN (power down when low)
*/
post-power-on-delay-ms = <200>;
reset-gpios = <&gpio1 RK_PB6 GPIO_ACTIVE_LOW>;
//GPIO_ACTIVE_HIGH GPIO_ACTIVE_LOW
};
wireless-bluetooth {
compatible = "bluetooth-platdata";
uart_rts_gpios = <&gpio1 RK_PD3 GPIO_ACTIVE_LOW>;//GPIO_ACTIVE_LOW
clocks = <&cru CLK_RTC_32K>;
clock-names = "ext_clock";
pinctrl-names = "default", "rts_gpio";
pinctrl-0 = <&uart1m0_rtsn &clk0_32k_out>;
pinctrl-1 = <&uart_rts_gpio>;
/* BT_REG_ON 蓝牙电源的开关 */
BT,power_gpio = <&gpio3 RK_PA0 GPIO_ACTIVE_HIGH>;
BT,wake_gpio = <&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>;
status = "okay";
};
wireless_wlan: wireless-wlan {
compatible = "wlan-platdata";
wifi_chip_type = "rtl8189fs";
pinctrl-names = "default";
pinctrl-0 = <&wifi_host_wake_irq &wifi_enable_h>;
WIFI,host_wake_irq = <&gpio1 RK_PB5 GPIO_ACTIVE_HIGH>;
WIFI,poweren_gpio = <&gpio1 RK_PB6 GPIO_ACTIVE_HIGH>;
//配置对应WIFI_REG_ON的PIN GPIO_ACTIVE_LOW
status = "okay";
// status = "disabled";
};
.....
&pinctrl {
wireless-bluetooth {
uart_rts_gpio: uart-rts-gpio {
rockchip,pins = <1 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
sdio-pwrseq {
wifi_enable_h: wifi-enable-h {
rockchip,pins = <1 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
wireless-wlan {
wifi_host_wake_irq: wifi-host-wake-irq {
rockchip,pins = <1 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
};
.....
//BT uart data transfer
&uart1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart1m0_xfer &uart1m0_ctsn>;
};
.....
&sdmmc1 {
max-frequency = <200000000>;
no-sd;
no-mmc;
bus-width = <4>;
disable-wp;
cap-sd-highspeed;
cap-sdio-irq;
keep-power-in-suspend;
mmc-pwrseq = <&sdio_pwrseq>;
non-removable;
pinctrl-names = "default";
pinctrl-0 = <&sdmmc1_bus4 &sdmmc1_cmd &sdmmc1_clk>;
// sd-uhs-sdr104;
supports-sdio;
status = "okay";
};
Driver Ctrl:
dts:
wireless_wlan: wireless-wlan {
compatible = "wlan-platdata";
driver:
static struct of_device_id wlan_platdata_of_match\[\] = {
{ .compatible = "wlan-platdata" },
{}
};

#ifdef CONFIG_OF
static struct of_device_id wlan_platdata_of_match[] = {
{ .compatible = "wlan-platdata" },
{}
};
MODULE_DEVICE_TABLE(of, wlan_platdata_of_match);
#endif //CONFIG_OF
static struct platform_driver rfkill_wlan_driver = {
.probe = rfkill_wlan_probe,
.remove = rfkill_wlan_remove,
.shutdown = rfkill_wlan_shutdown,
.suspend = rfkill_wlan_suspend,
.resume = rfkill_wlan_resume,
.driver = {
.name = "wlan-platdata",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(wlan_platdata_of_match),
},
};
int __init rfkill_wlan_init(void)
{
LOG("Enter %s\n", __func__);
return platform_driver_register(&rfkill_wlan_driver);
}
void __exit rfkill_wlan_exit(void)
{
LOG("Enter %s\n", __func__);
platform_driver_unregister(&rfkill_wlan_driver);
}
MODULE_DESCRIPTION("rock-chips rfkill for wifi v0.1");
MODULE_AUTHOR("gwl@rock-chips.com");
MODULE_LICENSE("GPL");

LOG("%s: wifi power controled by gpio.\n", __func__);
gpio = of_get_named_gpio_flags(node, "WIFI,poweren_gpio", 0,
&flags);
if (gpio_is_valid(gpio)) {
data->power_n.io = gpio;
data->power_n.enable =
(flags == GPIO_ACTIVE_HIGH) ? 1 : 0;
LOG("%s: WIFI,poweren_gpio = %d flags = %d.\n",
__func__, gpio, flags);
} else {
data->power_n.io = -1;
}
//通过解析dts获取到WIFI,host_wake_irq = <&gpio1 RK_PB5 GPIO_ACTIVE_HIGH>;为GPIO_ACTIVE_HIGH
//data->power_n.enable = (flags == GPIO_ACTIVE_HIGH) ? 1 : 0; //赋值给power_n.enable
//在后面的控制则拉为高:
static int rfkill_wlan_probe(struct platform_device *pdev)
{
struct rfkill_wlan_data *rfkill;
struct rksdmmc_gpio_wifi_moudle *pdata = pdev->dev.platform_data;
int ret = -1;
LOG("Enter %s\n", __func__);
.....
//通过gpio_direction_output拉高操作
#ifdef CONFIG_SDIO_KEEPALIVE
if (gpio_is_valid(pdata->power_n.io) &&
gpio_direction_output(pdata->power_n.io, pdata->power_n.enable);
#endif
.....
依葫芦画瓢
gpio = of_get_named_gpio_flags(node, "WIFI,host_wake_irq", 0,
&flags);
if (gpio_is_valid(gpio)) {
data->wifi_int_b.io = gpio;
data->wifi_int_b.enable = !flags;
LOG("%s: WIFI,host_wake_irq = %d, flags = %d.\n",
__func__, gpio, flags);
} else {
data->wifi_int_b.io = -1;
}
型号的选择也都是通过dts获取:
ret = of_property_read_string(node, "wifi_chip_type", &strings);
if (ret) {
LOG("%s: Can not read wifi_chip_type, set default to rkwifi.\n",
__func__);
strcpy(wifi_chip_type_string, "rkwifi");
} else {
if (strings && strlen(strings) < 64)
strcpy(wifi_chip_type_string, strings);
}
LOG("%s: wifi_chip_type = %s\n", __func__, wifi_chip_type_string);
Add Driver
Step2:报错
+ make -C /home/hzs/rk3562-v1.2.0-sdk/kernel/ -j81 CROSS_COMPILE=/home/hzs/rk3562-v1.2.0-sdk/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu- ARCH=arm64 rk3562_idr2_bt_defconfig
make: Entering directory '/home/hzs/rk3562-v1.2.0-sdk/kernel-5.10'
'rivers/net/wireless/rockchip_wlan/rtl8821cs_opt/Kconfig:1:warning: ignoring unsupported character '
'rivers/net/wireless/rockchip_wlan/rtl8821cs_opt/Kconfig:2:warning: ignoring unsupported character '
#
# configuration written to .config
#
make: Leaving directory '/home/hzs/rk3562-v1.2.0-sdk/kernel-5.10'
+ make -C /home/hzs/rk3562-v1.2.0-sdk/kernel/ -j81 CROSS_COMPILE=/home/hzs/rk3562-v1.2.0-sdk/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu- ARCH=arm64 rk3562-idr2-bt.img
make: Entering directory '/home/hzs/rk3562-v1.2.0-sdk/kernel-5.10'
SYNC include/config/auto.conf.cmd
'rivers/net/wireless/rockchip_wlan/rtl8821cs_opt/Kconfig:1:warning: ignoring unsupported character '
'rivers/net/wireless/rockchip_wlan/rtl8821cs_opt/Kconfig:2:warning: ignoring unsupported character '
CALL scripts/atomic/check-atomics.sh
CALL scripts/checksyscalls.sh
CHK include/generated/compile.h
MODPOST modules-only.symvers
ERROR: modpost: module 8821cs uses symbol kernel_write from namespace VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver, but does not import it.
ERROR: modpost: module 8821cs uses symbol kernel_read from namespace VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver, but does not import it.
ERROR: modpost: module 8821cs uses symbol filp_open from namespace VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver, but does not import it.
make[2]: *** [scripts/Makefile.modpost:168: modules-only.symvers] Error 1
make[2]: *** Deleting file 'modules-only.symvers'
make[1]: *** [Makefile:1856: modules] Error 2
make: *** [arch/arm64/Makefile:214: rk3562-idr2-bt.img] Error 2
make: Leaving directory '/home/hzs/rk3562-v1.2.0-sdk/kernel-5.10'
ERROR: Running /home/hzs/rk3562-v1.2.0-sdk/device/rockchip/common/build-hooks/10-kernel.sh - run_command failed!
ERROR: exit code 2 from line 36:
$@
ERROR: call stack:
build-helper: run_command(36)
10-kernel.sh: do_build(59)
10-kernel.sh: build_hook(407)
build-helper: try_func(63)
build-helper: try_hook(96)
build-helper: source(174)
10-kernel.sh: main(455)
ERROR: Running /home/hzs/rk3562-v1.2.0-sdk/device/rockchip/common/build-hooks/10-kernel.sh - try_func build_hook kernel failed!
ERROR: exit code 2 from line 67:
build_hook
ERROR: call stack:
build-helper: try_func(67)
build-helper: try_hook(96)
build-helper: source(174)
10-kernel.sh: main(455)
hzs@sr658:~/rk3562-v1.2.0-sdk$
这个编译错误是 Linux 内核的符号命名空间机制导致的。
具体来说,8821cs 驱动调用了 kernel_write、kernel_read 等 VFS 相关函数,但这些函数被内核放在了 VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver 这个命名空间里。内核要求,使用这些符号的模块必须显式声明导入该命名空间。
解决方案:添加命名空间导入声明
最直接的修复方法是在驱动源码中,为使用了这些 VFS 函数的源文件添加 MODULE_IMPORT_NS 声明。
-
定位文件 :错误涉及
kernel_write、kernel_read、filp_open等符号,它们通常在驱动中用于文件操作。你需要找到驱动中调用这些函数的具体.c文件 。对于 Realtek 驱动,这个文件很可能是os_dep/linux/os_intfs.c。 -
添加声明 :在找到的源文件中,在包含头文件(
#include)的区域之后,添加下面这行代码:MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver); -
重新编译:保存文件后,重新执行你的编译命令。
其他备选方案
-
使用
make nsdeps:这是内核提供的一个自动化工具,可以扫描模块并自动添加缺失的命名空间导入。你可以在内核源码根目录下尝试运行:make nsdeps然后再重新编译你的驱动。
-
注意:如果上述方法无效,你可能需要检查驱动源码,看是否有可能通过其他更合规的 API 替代这些有命名空间限制的函数。
通常,第一种手动添加声明的方法是最快且最有效的。
为了让解答更具针对性
如果上述方法未能解决问题,你可以提供以下信息,以便进一步分析:
-
你是在哪个具体文件中添加的
MODULE_IMPORT_NS声明? -
修改后重新编译,是否出现了新的错误信息?

解决编译ok的:
hzs@sr658:~/rk3562-v1.2.0-sdk/kernel$ git show f94cbae526651d559e46bac08b093af5601cca70
commit f94cbae526651d559e46bac08b093af5601cca70 (HEAD -> optmv-master)
Author: hzs <hzs@optmv.com>
Date: Thu Jul 2 13:58:14 2026 +0800
drivers: net: wireless: rockchip_wlan: 1. modified rtl8821cs_opt Makefile and Kconfig for add rtl8821cs driver if need
2. add MODULE_IMPORT_NS define to fit rtl8821cs driver compiled OK
3. Note: open CONFIG_RTL8821CS=m if need
diff --git a/drivers/net/wireless/rockchip_wlan/Kconfig b/drivers/net/wireless/rockchip_wlan/Kconfig
index 75fe1c067f1a..b3598149b787 100644
--- a/drivers/net/wireless/rockchip_wlan/Kconfig
+++ b/drivers/net/wireless/rockchip_wlan/Kconfig
@@ -41,4 +41,6 @@ source "drivers/net/wireless/rockchip_wlan/infineon/Kconfig"
source "drivers/net/wireless/rockchip_wlan/rtl8188fu/Kconfig"
source "drivers/net/wireless/rockchip_wlan/rk96x/Kconfig"
source "drivers/net/wireless/rockchip_wlan/rtl8189fs/Kconfig"
+source "drivers/net/wireless/rockchip_wlan/rtl8821cs_opt/Kconfig"
+#source "drivers/net/wireless/rockchip_wlan/rtl8821cs/Kconfig"
endif # WL_ROCKCHIP
diff --git a/drivers/net/wireless/rockchip_wlan/Makefile b/drivers/net/wireless/rockchip_wlan/Makefile
index 7da07a921f78..8d5e32ab1282 100644
--- a/drivers/net/wireless/rockchip_wlan/Makefile
+++ b/drivers/net/wireless/rockchip_wlan/Makefile
@@ -5,3 +5,5 @@ obj-$(CONFIG_INFINEON_DHD) += infineon/
obj-$(CONFIG_RTL8188FU) += rtl8188fu/
obj-$(CONFIG_RK960) += rk96x/
obj-$(CONFIG_RTL8189FS) += rtl8189fs/
+obj-$(CONFIG_RTL8821CS) += rtl8821cs_opt/
+#obj-$(CONFIG_RTL8821CS) += rtl8821cs/
diff --git a/drivers/net/wireless/rockchip_wlan/rtl8821cs_opt/Kconfig b/drivers/net/wireless/rockchip_wlan/rtl8821cs_opt/Kconfig
index 8f290b0eb376..71b9a646d3e9 100644
--- a/drivers/net/wireless/rockchip_wlan/rtl8821cs_opt/Kconfig
+++ b/drivers/net/wireless/rockchip_wlan/rtl8821cs_opt/Kconfig
@@ -1,4 +1,4 @@
-config RTL8821CS
- tristate "Realtek 8821C SDIO WiFi"
- ---help---
- Help message of RTL8821CS
+config RTL8821CS
+ tristate "Realtek 8821C SDIO WiFi"
+ #help---
+ # Help message of RTL8821CS
diff --git a/drivers/net/wireless/rockchip_wlan/rtl8821cs_opt/core/rtw_mem.c b/drivers/net/wireless/rockchip_wlan/rtl8821cs_opt/core/rtw_mem.c
index 3721469475eb..229b05b63019 100644
--- a/drivers/net/wireless/rockchip_wlan/rtl8821cs_opt/core/rtw_mem.c
+++ b/drivers/net/wireless/rockchip_wlan/rtl8821cs_opt/core/rtw_mem.c
@@ -16,6 +16,7 @@
#include <drv_types.h>
#include <rtw_mem.h>
+MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);^M
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Realtek Wireless Lan Driver");
MODULE_AUTHOR("Realtek Semiconductor Corp.");
diff --git a/drivers/net/wireless/rockchip_wlan/rtl8821cs_opt/os_dep/linux/os_intfs.c b/drivers/net/wireless/rockchip_wlan/rtl8821cs_opt/os_dep/linux/os_intfs.c
index 165c8d7e0bfc..aa82e04dd9d4 100644
--- a/drivers/net/wireless/rockchip_wlan/rtl8821cs_opt/os_dep/linux/os_intfs.c
+++ b/drivers/net/wireless/rockchip_wlan/rtl8821cs_opt/os_dep/linux/os_intfs.c
@@ -17,6 +17,7 @@
#include <drv_types.h>
#include <hal_data.h>
+MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);^M
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Realtek Wireless Lan Driver");
MODULE_AUTHOR("Realtek Semiconductor Corp.");
hzs@sr658:~/rk3562-v1.2.0-sdk/kernel$
最终是可以成功编译出来该ko驱动

接着将该ko挂载上系统看看,那么代码上又是如何控制的呢 ?
扩展一下 hexdump的使用说明:
hexdump 是 Linux 下查看文件原始数据的利器,它能把二进制文件转换成人类能看懂的十六进制或ASCII格式,常用来调试二进制文件或分析文件结构 。
怎么用最顺手
最推荐直接用 -C 参数 ,它会把十六进制和对应的ASCII字符并排显示,左边是偏移量,中间是十六进制字节值,右边是可打印的ASCII字符,不可打印的用点号代替,一眼就能看清文件内容 。
基本命令格式就是 hexdump [选项] 文件名,比如查看一个二进制文件:hexdump -C myfile.bin 。
常用选项有哪些
- **
-C**:规范十六进制+ASCII显示,最常用。 - **
-n 长度** :只显示前多少个字节,比如-n 100只看前100字节。 - **
-s 偏移量** :从指定位置开始显示,比如-s 512跳过前512字节。 - **
-v** :强制显示所有数据,默认会压缩重复行用*代替。 - **
-x/-d/-o/-b**:分别以双字节十六进制、十进制、八进制或单字节八进制显示。
实际能怎么用
查看文件头识别类型:hexdump -n 128 -C unknown_file 可以快速看文件前128字节,ELF文件以7F 45 4C 46开头,PNG以89 50 4E 47开头 。
跳过指定位置看特定内容:hexdump -s 20 -n 128 -C photo.jpg 跳过前20字节查看后续数据 。
自定义输出格式需要配合 -e 参数,但语法较复杂,日常用 -C 就够了

譬如解析出来的63 对应的就是16进制的,那边转成10进制就是99,转成ascll码就是对应为c字符
