RK3562移植rtl8821cs驱动

总体介绍 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_writekernel_read 等 VFS 相关函数,但这些函数被内核放在了 VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver 这个命名空间里。内核要求,使用这些符号的模块必须显式声明导入该命名空间。

解决方案:添加命名空间导入声明

最直接的修复方法是在驱动源码中,为使用了这些 VFS 函数的源文件添加 MODULE_IMPORT_NS 声明。

  1. 定位文件 :错误涉及 kernel_writekernel_readfilp_open 等符号,它们通常在驱动中用于文件操作。你需要找到驱动中调用这些函数的具体 .c 文件 。对于 Realtek 驱动,这个文件很可能是 os_dep/linux/os_intfs.c

  2. 添加声明 :在找到的源文件中,在包含头文件(#include)的区域之后,添加下面这行代码:

    复制代码
    MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
  3. 重新编译:保存文件后,重新执行你的编译命令。

其他备选方案

  • 使用 make nsdeps:这是内核提供的一个自动化工具,可以扫描模块并自动添加缺失的命名空间导入。你可以在内核源码根目录下尝试运行:

    复制代码
    make nsdeps

    然后再重新编译你的驱动。

  • 注意:如果上述方法无效,你可能需要检查驱动源码,看是否有可能通过其他更合规的 API 替代这些有命名空间限制的函数。

通常,第一种手动添加声明的方法是最快且最有效的。


为了让解答更具针对性

如果上述方法未能解决问题,你可以提供以下信息,以便进一步分析:

  1. 你是在哪个具体文件中添加的 MODULE_IMPORT_NS 声明?

  2. 修改后重新编译,是否出现了新的错误信息?

解决编译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字符