RK Android WiFi模组 AIC8800 驱动移植流程
- 作者:Witheart
- 更新时间:20250220
概要:本文介绍了基于 AIC8800D40 芯片的 WiFi6 模组 BL-M8800DS2-40 在 RK3568 平台上的驱动移植流程。主要涉及环境搭建、驱动代码分析、设备树修改、驱动编译配置、蓝牙库集成、wpa_supplicant 配置及 WiFi HAL 适配等内容,并提供详细的移植步骤和注意事项。
问题调试在另一篇文章:https://blog.csdn.net/Beihai_Van/article/details/145772085
1. 环境
- WiFi 模组:BL-M8800DS2-40(基于 AIC8800D40 芯片)
- CPU:RK3568
- OS:Android
2. WiFi芯片与WiFi模组的区别
- WiFi 芯片:核心部件,集成射频前端、基带处理器、数字信号处理等功能。
- WiFi 模组:基于 WiFi 芯片的完整无线通信组件,包含天线、外围电路、接口等。
AIC8800 属于 WiFi 芯片,而本次移植的 WiFi 模组是 BL-M8800DS2-40(基于 AIC8800D40)。
WiFi6 模组_必联(LB-LINK)官方网站
3. AIC8800 驱动代码包详解
3.1 驱动代码包结构
厂家送样后,需要获取最新的驱动代码包,并确保版本与模组匹配。驱动代码版本错误可能引发诸多问题。
3.2 Patch 注意事项
在 SDIO\patch\for_Rockchip\3566\Android11 目录下,提供了 Rockchip 平台的移植补丁(更像是移植成功的参考案例,不同wifi芯片还要具体配置),移植过程中需对比 mod 和 orig 文件夹的区别,可利用 git 进行差异分析。
在移植过程中需要注意一个问题:patch 仅仅指示了需要修改哪些文件以及具体的修改方法,但其中的驱动可能并不适用于你手头的模组。原因在于,官方提供的驱动包适用于多款模组,而 patch 仅是其中某款模组的案例,并且官方更新驱动时,并不会同步更新 patch 中的驱动。
因此,在移植时可以参考 patch 进行修改,但对于驱动适配系统的相关配置,一般无需更改。而对于新增的驱动文件,应在
driver_fw
文件夹中查找正确的驱动文件进行添加,而不是直接使用 patch 中的文件。
3.3 Patch 使用方法
在使用 patch 进行移植时,主要是对比 mod
文件夹和 orig
文件夹的区别,然后在你的源码中进行相应的配置修改。直接手动对比可能比较繁琐,因此可以借助 Git 进行对比分析。
3.3.1. 初始化 Git 仓库
首先,在 orig
文件夹中初始化一个 Git 仓库.
接着,找到 mod
相比 orig
新增的文件,通常包含以下三部分:
- 模组固件:
- 主要是厂家编译好的二进制
.bin
文件以及.txt
配置文件。
- 主要是厂家编译好的二进制
- 模组驱动:
- 用于与内核交互,主要是
.c
源码文件,编译后生成.ko
驱动文件。
- 用于与内核交互,主要是
- 模组库:
- 例如
libbt
之类的库,编译后生成.so
共享库文件。
- 例如
3.3.2. 删除 mod
中的新增文件
由于我们只关心 源码的修改内容,而这些新增的二进制文件、驱动和库文件无需对比修改,仅需直接复制,所以应当先删除 mod
中的新增文件
原因:
- 这些新增文件会导致 Git 对比时产生大量无关内容,影响分析。
- 厂商在更新驱动时,不会同步更新
patch
中的驱动,直接使用patch
提供的驱动可能会导致 bug。
(别问为什么知道的,踩坑经验)
3.3.3. 进行 Git 对比
删除新增文件后,将 mod
文件夹的内容 覆盖 orig
,然后使用 Git 进行对比:
⚠ VSCode 的 Git 对比 Bug
坑点提醒:
VSCode 的 Git 对比窗口 在路径长度超过 219 个字符 时,不会显示差异,容易导致遗漏修改。参考 Issue:
Git diff does not show files with long paths in Source Control View (Windows) #240770
4 设备树(DTS)修改
此处主要是参考RK官方文档去修改
01、Linux\Linux\Wifibt\Rockchip_Developer_Guide_Linux_WIFI_BT_CN.pdf
02、Android\android\wifi\Rockchip_Introduction_WIFI_Configuration_CN&EN.pdf
02、Android\common\MMC\Rockchip_Developer_Guide_SDMMC_SDIO_eMMC_CN.pdf
4.1 蓝牙部分
&wireless_bluetooth {
compatible = "bluetooth-platdata";
clocks = <&rk809 1>;
clock-names = "ext_clock";
uart_rts_gpios = <&gpio2 RK_PB1 GPIO_ACTIVE_LOW>;
pinctrl-names = "default", "rts_gpio";
pinctrl-0 = <&uart8m0_rtsn>;
pinctrl-1 = <&uart8_gpios>;
BT,reset_gpio = <&gpio3 RK_PD5 GPIO_ACTIVE_LOW>;
status = "okay";
};
wireless-bluetooth {
uart8_gpios: uart8-gpios {
rockchip,pins = <2 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
- 这里比较重要的是BT,reset_gpio,这个模块的34脚,PWR_BT,根据描述来看,高电平时蓝牙部分关闭,低电平时蓝牙部分开启
- 但是此部分的配置不一定是最终生效的版本,最终还要看驱动里是怎么处理的,有的驱动会对电平作反相处理,所以这部分可以GPIO_ACTIVE_LOW和GPIO_ACTIVE_HIGH都试试看
- UART 配置: 确保蓝牙与 CPU 通过 UART8 进行通信。
4.2 WiFi 部分
sdio_pwrseq: sdio-pwrseq {
compatible = "mmc-pwrseq-simple";
clocks = <&rk809 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 = <&gpio3 RK_PD4 GPIO_ACTIVE_LOW>;
};
重点配置 WiFi reset-gpios,确保正确的电源控制。
wireless_wlan: wireless-wlan {
compatible = "wlan-platdata";
rockchip,grf = <&grf>;
wifi_chip_type = "AIC8800";
status = "okay";
};
此处的WiFi芯片名称的配置,应该和frameworks\opt\net\wifi\libwifi_hal\rk_wifi_ctrl.cpp
这个文件中的
static wifi_device supported_wifi_devices[]中配置的名称一致(至少前三个字符要为AIC)
&sdmmc2 {
max-frequency = <150000000>;
supports-sdio;
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 = <&sdmmc2m0_bus4 &sdmmc2m0_cmd &sdmmc2m0_clk>;
sd-uhs-sdr104;
status = "okay";
};
- sd-uhs-sdr104表示支持sdio3.0
5. 驱动部分
-
添加驱动文件,也就是
SDIO\driver_fw\driver\aic8800
下所有文件添加到
kernel\drivers\net\wireless\aic8800
-
然后在同级目录下的mk文件中添加编译选项,通过CONFIG_AIC_WLAN_SUPPORT宏定义控制是否编译aic8800/目录下的内容
kernel\drivers\net\wireless\Makefile
obj-$(CONFIG_AIC_WLAN_SUPPORT) += aic8800/
- 并且在同级目录下的kconfig中,引用aic8800/目录下的Kconfig文件,用于增加menuconfig中的aic8800驱动编译选项
source "drivers/net/wireless/aic8800/Kconfig"
- 配置内核编译配置文件kernel\arch\arm64\configs\rockchip_defconfig,请修改你实际上应用的内核编译配置文件
CONFIG_AIC_WLAN_SUPPORT=y
CONFIG_AIC_FW_PATH="/vendor/etc/firmware"
CONFIG_AIC8800_WLAN_SUPPORT=m
-
- CONFIG_AIC_WLAN_SUPPORT用于控制编译这个驱动
- CONFIG_AIC_FW_PATH用于配置模组固件存放的位置,固件最后会被复制到配置的这个位置
- CONFIG_AIC8800_WLAN_SUPPORT说明驱动将不会被编译进内核,而是以模块的形式动态插入(如果是以模块的形式动态插入,内核将根据sdio读到的vid和pid匹配不同的模块进行加载)
-
在
vendor\rockchip\common\wifi\wifi.mk
文件中,添加编译出来的ko文件的路径,因为是动态加载ko文件的,所以ko文件编译出来后,将被复制到/vendor/lib/modules
路径下进行动态加载
AIC_WIFI_KO_FILES := $(shell find $(TOPDIR)kernel/drivers/net/wireless/aic8800 -name "*.ko" -type f)
BOARD_VENDOR_KERNEL_MODULES += \
$(foreach file, $(AIC_WIFI_KO_FILES), $(file))
此处的配置用于找到编译出的ko文件
- 配置Android 启动时用于加载内核模块(.ko) 的配置文件
device\rockchip\common\init.insmod.cfg
加入
insmod /vendor/lib/modules/aic8800_bsp.ko
- 最终会编译出三个ko文件,分别是
aic8800_bsp.ko aic8800_btlpm.ko aic8800_fdrv.ko -
- aic8800_bsp用于模组的初始化等基础功能
- aic8800_fdrv用于WiFi
- aic8800_btlpm用于蓝牙
- 移植到RK Android平台时,实际上只加载aic8800_bsp.ko,aic8800_fdrv.ko
6. 蓝牙库 libbt
libbt用于完成对蓝牙模块硬件初始化与控制。
6.1 添加蓝牙库文件
将 driver_fw\aic\libbt\8800 目录下所有文件添加到 hardware\aic\aicbt\libbt 目录。
6.2 配置编译 libbt
- 在libbt同级目录下添加Android.mk,如果BOARD_HAVE_BLUETOOTH_AIC被配置了,那么子目录下所有makefile都生效
ifeq ($(BOARD_HAVE_BLUETOOTH_AIC),true)
LOCAL_PATH := $(call my-dir)
include $(call all-subdir-makefiles)
endif
而BOARD_HAVE_BLUETOOTH_AIC在device\rockchip\common\wifi_bt_common.mk这个mk文件中定义
BOARD_HAVE_BLUETOOTH_AIC := true
- 同样的,在libbt同级目录下添加aicbt.mk文件
CUR_PATH := hardware/aic/aicbt
BOARD_HAVE_BLUETOOTH := true
PRODUCT_PACKAGES += \
libbt-vendor-aic
PRODUCT_PACKAGES += libbt-vendor-aic 定义了 libbt-vendor-aic 这个包,用于指定需要编译并包含到最终镜像
注意,此处有一个坑点,需要保证libbt中Android.mk中的LOCAL_MODULE与PRODUCT_PACKAGES一致,原厂的patch由于没有更新,这两者不一致
hardware\aic\aicbt\libbt\Android.mk
LOCAL_MODULE := libbt-vendor-aic
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_MODULE_OWNER := aic
LOCAL_PROPRIETARY_MODULE := true
LOCAL_MODULE 是用于定义模块名称,也就是最终会编译出libbt-vendor-aic.so这个模块。
7. wpa_supplicant 配置
7.1 wpa_supplicant 概念
wpa_supplicant 是一个用于管理 WiFi 连接的用户空间守护进程,主要负责:
- 处理 WPA/WPA2 认证
- 管理 WiFi 连接(扫描、连接、断开)
- 支持 WiFi Direct(P2P)
- 通过 socket 接口与 Android WiFi 框架交互
7.2 wpa_supplicant 具体配置
配置aic模块的wpa配置,设置aic模块的启动参数
device\rockchip\common\wpa_config.txt
添加
[aic]
/vendor/bin/hw/wpa_supplicant
-O/data/vendor/wifi/wpa/sockets
-puse_p2p_group_interface=1
-g@android:wpa_wlan0
-
[aic]
:表示该配置适用于 AIC WiFi 模块。/vendor/bin/hw/wpa_supplicant
:指定 wpa_supplicant 的可执行文件路径。-O/data/vendor/wifi/wpa/sockets
:指定 wpa_supplicant 使用的 socket 目录,通常用于与其他组件(如 hostapd 或 Android WiFi 框架)通信。-puse_p2p_group_interface=1
:启用 P2P 组接口支持,允许 WiFi Direct 功能。-g@android:wpa_wlan0
:定义全局控制接口,@android:wpa_wlan0 允许 Android 通过 wpa_supplicant 进行 WiFi 控制
设置加载wpa_config.txt中的配置
external\wpa_supplicant_8\wpa_supplicant\main.c
#define AIC_MODULE_NAME "[aic]"
else if (0 == strncmp(wifi_type, "AIC", 3)) {
wpa_printf(MSG_INFO,"Start aic_wpa_supplicant\n");
ret = read_wpa_param_config(AIC_MODULE_NAME,argv[1]);
}
WiFi芯片类型前缀为AIC时,加载对应的aic的wpa_supplicant参数。
8. WiFi HAL 配置
这部分主要是通过配置vid:pid,选择加载不同的库。
8.1 动态加载原理
sdio握手成功后,就会读到vid:pid,将读到的数值与已经配置vid:pid比较,动态加载不同的库
8.2 具体配置
vid:pid配置
frameworks\opt\net\wifi\libwifi_hal\rk_wifi_ctrl.cpp
supported_wifi_devices 结构体数组中添加WiFi名称以及对应的vid:pid
{"AIC8800", "5449:0145"},
- 这里又有一个坑点,patch中提供的这个vid:pid不适用于我这个模组,需要具体配置。
- 获取真正的vid:pid有两种方式,一种是如果WiFi模组正常上电且sdio握手成功,那么是可以通过读/sys/bus/sdio/devices下的设备目录下的uevent文件得到的
rk3568_HW:/ # cat /sys/bus/sdio/devices/
mmc3:390b:1/ mmc3:390b:2/
rk3568_HW:/ # cat /sys/bus/sdio/devices/mmc3\:390b\:1/uevent
DRIVER=aicwf_sdio
SDIO_CLASS=07
SDIO_ID=C8A1:0082
MODALIAS=sdio:c07vC8A1d0082
rk3568_HW:/ # cat /sys/bus/sdio/devices/mmc3\:390b\:2/uevent
DRIVER=aicbsp_sdio
SDIO_CLASS=07
SDIO_ID=C8A1:0182
MODALIAS=sdio:c07vC8A1d0182
这个WiFi模组扫卡成功后可以读到两个mmc设备,分别是mmc3:390b:1/ mmc3:390b:2/,
读取到的mmc3:390b:1设备的vid:pid为C8A1:0082,那么增加这个vid:pid到rk_wifi_ctrl.cpp中即可。
- 二是直接向厂家询问,或在驱动包中寻找
接下来是检测流程,调用check_wifi_chip_type_string(wifi_type)函数,尝试获取wifi芯片类型,保存到wifi_type
frameworks\opt\net\wifi\libwifi_hal\rk_wifi_ctrl.cpp
int check_wifi_chip_type_string(char *type)
{
if (identify_sucess == -1) {
if (get_wifi_device_id(SDIO_DIR, PREFIX_SDIO) == 0)
PLOG(DEBUG) << "SDIO WIFI identify sucess";
else if (get_wifi_device_id(USB_DIR, PREFIX_USB) == 0)
PLOG(DEBUG) << "USB WIFI identify sucess";
else if (get_wifi_device_id(PCIE_DIR, PREFIX_PCIE) == 0)
PLOG(DEBUG) << "PCIE WIFI identify sucess";
else {
PLOG(DEBUG) << "maybe there is no usb wifi or sdio or pcie wifi,set default wifi module Brocom APXXX";
strcpy(recoginze_wifi_chip, "APXXX");
identify_sucess = 1 ;
}
}
strcpy(type, recoginze_wifi_chip);
PLOG(ERROR) << "check_wifi_chip_type_string : " << type;
return 0;
}
还未进行识别时,identify_sucess为-1,此时开始使用get_wifi_device_id()获取设备id,依次尝试sdio、usb、pcie设备
get_wifi_device_id()函数中,通过读uevent获取vid:pid,然后用一个for循环,比较已经在supported_wifi_devices中添加的设备vid:pid与读到的vid:pid,有相符的,就将对应的wifi芯片名称复制到recoginze_wifi_chip中,get_wifi_device_id()执行完成之后,会将recoginze_wifi_chip复制到type中,也就是wifi_type。
接下来配置动态加载不同的库
- 在
frameworks\opt\net\wifi\libwifi_hal\wifi_hal_common.cpp
中,有一个wifi_load_driver()
函数,调用了check_wifi_chip_type_string(wifi_type)
函数,尝试获取wifi芯片类型wifi_type
- 需要添加wifi ko驱动的路径,以及驱动与wifi类型的对应
wifi_hal_common.cpp
#define AIC8800_DRIVER_MODULE_PATH WIFI_MODULE_PATH"aic8800_fdrv.ko"
#define AIC8800_DRIVER_MODULE_NAME "aic8800_fdrv"
wifi_ko_file_name module_list[] ={
{"AIC8800", AIC8800_DRIVER_MODULE_NAME, AIC8800_DRIVER_MODULE_PATH, UNKKOWN_DRIVER_MODULE_ARG},
}
- 定义蓝牙使用的库
在hardware\interfaces\bluetooth\1.0\default\vendor_interface.cc
中
static const char* VENDOR_AIC_LIBRARY_NAME = "libbt-vendor-aic.so";
然后根据wifi芯片类型,得到要加载的库的名称
if ((0 == strncmp(wifi_type, "AIC", 3))) {
ALOGE("%s try to open %s \n", __func__, VENDOR_AIC_LIBRARY_NAME);
strcpy(vendor_lib_name, VENDOR_AIC_LIBRARY_NAME);
}
根据这个名称进行加载
lib_handle_ = dlopen(vendor_lib_name, RTLD_NOW);
- 根据 Wi-Fi 芯片类型来确定应该使用哪个Wi-Fi 直连(P2P)接口名称
hardware\interfaces\wifi\1.4\default\wifi_chip.cpp
std::string getP2pIfaceName() {
std::array<char, PROPERTY_VALUE_MAX> buffer;
if (wifi_type[0] == 0) {
check_wifi_chip_type_string(wifi_type);
}
if (0 == strncmp(wifi_type, "AP", 2) || 0 == strncmp(wifi_type, "AIC", 3)) {
property_set("vendor.wifi.direct.interface", "p2p-dev-wlan0");
property_get("wifi.direct.interface", buffer.data(), "p2p-dev-wlan0");
} else {
property_set("vendor.wifi.direct.interface", "p2p0");
property_get("wifi.direct.interface", buffer.data(), "p2p0");
}
return buffer.data();
}
注:此处的AP应该是AP系列WiFi芯片的意思
9. 固件添加
-
主要是厂家编译好的二进制 .bin 文件以及 .txt 配置文件。
-
将厂家给的驱动包中
driver_fw\fw
下具体的固件放置在vendor\rockchip\common\wifi\firmware
下。 -
BL-M8800DS2-40
使用driver_fw\fw\aic8800D80
目录下的固件。
至此,驱动配置成功,但是在调试过程中还遇到一些问题,详情见另一篇文章。