2026年5月9日 星期六

Compiling and Installing AIC8800 on RV1106: A Guide Using the WB800D80 Module

 本文說明如何直接接入 AIC8800 晶片驅動到 Linux Buildroot / Windows 下。


模組

本文選用 WB800D80 系列晶片來作為 Wifi 6 的模組,其中晶片的版本有好幾種,這些型號幾乎都支援 USB 訊號,因此選用 USB 接入會相對支援度高一些,反而用 SDIO 類訊號不見得每一個 模組都有。


本文測試:

  • SKI.WB800D80S.1 (Windows 可以連線、Linux 驅動好像會引起一些問題)
  • SKI.WB800D80U.2_D40 (Windows 可以連線、Linux 可以正常使用)
最終本文是以 SKI.WB800D80U.2_D40 來接入。

模組配線

需要準備的材料:

  • 熱熔膠
  • WB800D80U.2 模組 (建議多買幾個避免燒毀或燙壞)
  • 電解電容 470uF / 25V
  • 按鈕開關
  • USB 線


模組腳位 - SKI.WB800D80S.1

如果你是使用 SKI.WB800D80S.1 可以參考下圖的腳位焊接,不過這個版本因為腳位密集,焊接一定要注意不要讓相鄰的腳位短路了 (切記不要燙太久,晶片會燒掉)。


模組腳位 - SKI.WB800D80U.2_D40

如果使用與本文一致的這個版本,就焊接 PWR, GND, USB_DP (綠), USB_DM (白), VBAT(3.3v)

熱熔膠焊接

因為這幾個腳位都很細,建議用熱溶膠把他們在底部架高,然後單根單芯線穿上去焊接。

WB800D80U.2_D40

WB800D80S.1


焊接點解釋

  • PWR : 根據手冊說明,接入電源後要大概 200ms 後做邊緣觸發 (Edge Triggering) : 對 PWR 接地一下 (接地一下拿起來)
  • GND: 共地
  • USB_DP (綠): 接入 USB 線 D+ 
  • USB_DM (白): 接入 USB 線 D-
  • VBAT(3.3v)

模組範例


把單芯線鐵絲從下面穿上去碰著腳位焊接,然後把錫從上而下流比較輕鬆,鐵絲會高一點,焊完再剪掉也可以


USB

主要注意 USB 線是差分信號,會有阻抗匹配問題,但因為實驗板子無法考慮到非常細微,需要注意這幾項:

  • D+ D- 一定要等長,到晶片上的線也要等長
  • USB 線接點要有一點隔離避免信號干擾
  • 不一定需要繞 GND 線在外層參考
  • 切勿使用麵包版、杜邦線,這兩個拿來做實驗失敗率很高


電容

建議在電源附近並聯一個 470uF 電解電容,避免 Wifi 連線時讓嵌入式系統的主機電壓驟降。


模組啟動方法 PWR KEY

啟動方法是在 PWR KEY 也就是上圖側邊第四個腳位寫 GND (觸發) 那個,在接入電源後 200ms 按一下開關觸發接地 (不要一直按著),就會啟動晶片了。


在 Windows 上測試連線

可以在 Windows 上找一下 AIC8800 驅動:

https://github.com/peckishrine/aic8800_windows_drivers

然後接入 USB 之後按一下開關啟動,注意不要把 5v 接近去晶片,會燒掉。

接入成功後會像這樣找到這個晶片






Buildroot 編譯環境

選擇驅動 repository

  • luckfox official: https://github.com/LuckfoxTECH/luckfox-pico.git
  • (本文測試的是這個) radxa-aic8800: https://github.com/radxa-pkg/aic8800
  • 也可以用 luckfox-pico 自帶系統的那個驅動,但是我沒有用那個

修改 Makefile

找到這個文件: radxa-aic8800\src\USB\driver_fw\drivers\aic8800\Makefile
進去裡面改:

CONFIG_AIC_LOADFW_SUPPORT := m
CONFIG_AIC8800_WLAN_SUPPORT := m

obj-$(CONFIG_AIC_LOADFW_SUPPORT)    += aic_load_fw/
obj-$(CONFIG_AIC8800_WLAN_SUPPORT) += aic8800_fdrv/

########## config option ##########
export CONFIG_USE_FW_REQUEST = y
export CONFIG_PREALLOC_RX_SKB = n
export CONFIG_PREALLOC_TXQ = n
export CONFIG_WOWLAN = n
###################################

########## platform support list ##########
export CONFIG_PLATFORM_ROCKCHIP = y
export CONFIG_PLATFORM_ALLWINNER = n
export CONFIG_PLATFORM_AMLOGIC = n
export CONFIG_PLATFORM_HI = n
export CONFIG_PLATFORM_UBUNTU = n

ifeq ($(CONFIG_PLATFORM_ROCKCHIP), y)
ARCH = arm
CROSS_COMPILE = arm-rockchip830-linux-uclibcgnueabihf-
KDIR = /home/hpcslag/esp-idf/esp-hosted/esp_hosted_ng/kernel-build
PWD = $(shell pwd)
ccflags-y += -DANDROID_PLATFORM
endif


###########################################

MAKEFLAGS +=-j$(shell nproc)

all: modules
modules:
# make KCFLAGS="-Wno-error -Wno-unused-function -Wno-date-time" <--- 這樣執行
	make -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
# 複製出來
	mv ./aic_load_fw/aic_load_fw.ko ./
	mv ./aic8800_fdrv/aic8800_fdrv.ko ./

# install:
# 	mkdir -p $(MODDESTDIR)
# 	install -p -m 644 aic_load_fw/aic_load_fw.ko  $(MODDESTDIR)/
# 	install -p -m 644 aic8800_fdrv/aic8800_fdrv.ko  $(MODDESTDIR)/
# 	/sbin/depmod -a ${KVER}

# uninstall:
# 	rm -rfv $(MODDESTDIR)/aic_load_fw.ko
# 	rm -rfv $(MODDESTDIR)/aic8800_fdrv.ko
# 	/sbin/depmod -a ${KVER}

clean:
	cd aic_load_fw/;make clean;cd ..
	cd aic8800_fdrv/;make clean;cd ..
	rm -rf modules.order Module.symvers .modules.order.cmd .Module.symvers.cmd .tmp_versions/

前面發現啟動時會在 RF calib 訊號發送時失敗(有可能是驅動版本跟晶片版本不同引起或其他原因 timeout) 所以我這裡做了一個 patch 就是就算 rf 發送失敗仍要啟動,然後會在背景重試連接,可以斟酌要不要改,這個是可選的。 

在 radxa-aic8800\src\USB\driver_fw\drivers\aic8800\aic8800_fdrv\aicwf_compat_8800d80.c 改:

#include "rwnx_main.h"
#include "rwnx_msg_tx.h"
#include "reg_access.h"
#include "aicwf_compat_8800d80.h"
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/delay.h>

#define FW_USERCONFIG_NAME_8800D80         "aic_userconfig_8800d80.txt"
#define FW_POWERLIMIT_NAME_8800D80         "aic_powerlimit_8800d80.txt"

extern char aic_fw_path[200];
static struct task_struct *d80_rf_calib_task;
static int aicwf_8800d80_rf_calib_thread(void *data)
{
	struct rwnx_hw *rwnx_hw = data;
	struct mm_set_rf_calib_cfm cfm;
	int ret;
	int retry = 0;

	printk(KERN_ERR "aic8800d80: rf calib background thread started\n");

	while (!kthread_should_stop()) {
		memset(&cfm, 0, sizeof(cfm));
		retry++;

		/* bus 已經 down 就不要送 command */
		if (!rwnx_hw || !rwnx_hw->plat || !rwnx_hw->plat->enabled) {
			printk(KERN_ERR "aic8800d80: rf calib abort, bus/platform not ready\n");
			break;
		}

		ret = rwnx_send_rf_calib_req(rwnx_hw, &cfm);

		printk(KERN_ERR
			"aic8800d80: rf calib retry #%d ret=%d rx24=0x%x rx5=0x%x tx24=0x%x tx5=0x%x\n",
			retry, ret,
			cfm.rxgain_24g_addr, cfm.rxgain_5g_addr,
			cfm.txgain_24g_addr, cfm.txgain_5g_addr);

		/* ret=0 但 cfm 全 0,不要當成功 */
		if (!ret &&
		    (cfm.rxgain_24g_addr || cfm.rxgain_5g_addr ||
		     cfm.txgain_24g_addr || cfm.txgain_5g_addr)) {
			printk(KERN_ERR
				"aic8800d80: rf calib success at retry #%d\n",
				retry);
			break;
		}

		if (retry >= 5) {
			printk(KERN_ERR "aic8800d80: rf calib failed after retries\n");
			break;
		}

		msleep(3000);
	}

	printk(KERN_ERR "aic8800d80: rf calib background thread stopped\n");
	return 0;
}

static int skip_8800d80_rf_calib = 1;
module_param(skip_8800d80_rf_calib, int, 0644);
MODULE_PARM_DESC(skip_8800d80_rf_calib,
	"Skip AIC8800D80 MM_SET_RF_CALIB_REQ during probe (default: 1)");

int rwnx_request_firmware_common(struct rwnx_hw *rwnx_hw,
	u32** buffer, const char *filename);
void rwnx_plat_userconfig_parsing(char *buffer, int size);
void rwnx_release_firmware_common(u32** buffer);


int aicwf_set_rf_config_8800d80(struct rwnx_hw *rwnx_hw, struct mm_set_rf_calib_cfm *cfm)
{
	int ret = 0;

	if ((ret = rwnx_send_txpwr_lvl_v3_req(rwnx_hw))) {
		return -1;
	}
	if ((ret = rwnx_send_txpwr_lvl_adj_req(rwnx_hw))) {
		return -1;
	}
	if ((ret = rwnx_send_txpwr_ofst2x_req(rwnx_hw))) {
		return -1;
	}
	if (skip_8800d80_rf_calib) {
		printk(KERN_ERR "skip AIC8800D80 rf calib req, skip_8800d80_rf_calib=%d\n", skip_8800d80_rf_calib);
		AICWFDBG(LOGINFO,
			"skip AIC8800D80 rf calib req, skip_8800d80_rf_calib=%d\n",
			skip_8800d80_rf_calib);
		
	}

	if (!d80_rf_calib_task) {
		d80_rf_calib_task = kthread_run(aicwf_8800d80_rf_calib_thread,
			rwnx_hw, "aic8800d80_rf_calib");
		if (IS_ERR(d80_rf_calib_task)) {
			printk(KERN_ERR "aic8800d80: start rf calib thread failed ret=%ld\n",
				PTR_ERR(d80_rf_calib_task));
			d80_rf_calib_task = NULL;
			return -1;
		}
		printk(KERN_ERR "aic8800d80: start rf calib background retry loop\n");
	}

	return 0 ;
}


int	rwnx_plat_userconfig_load_8800d80(struct rwnx_hw *rwnx_hw){
    int size;
    u32 *dst=NULL;
    char *filename = FW_USERCONFIG_NAME_8800D80;

#ifndef ANDROID_PLATFORM
            sprintf(aic_fw_path, "%s/%s", aic_fw_path, "aic8800D80");
#endif

    AICWFDBG(LOGINFO, "userconfig file path:%s \r\n", filename);

    /* load file */
    size = rwnx_request_firmware_common(rwnx_hw, &dst, filename);
    if (size <= 0) {
            AICWFDBG(LOGERROR, "wrong size of firmware file\n");
            dst = NULL;
            return 0;
    }

    /* Copy the file on the Embedded side */
    AICWFDBG(LOGINFO, "### Load file done: %s, size=%d\n", filename, size);

    rwnx_plat_userconfig_parsing((char *)dst, size);

    rwnx_release_firmware_common(&dst);

    AICWFDBG(LOGINFO, "userconfig download complete\n\n");
    return 0;

}

#ifdef CONFIG_POWER_LIMIT
extern char country_code[];
int rwnx_plat_powerlimit_load_8800d80(struct rwnx_hw *rwnx_hw)
{
    int size;
    u32 *dst=NULL;
    char *filename = FW_POWERLIMIT_NAME_8800D80;

    AICWFDBG(LOGINFO, "powerlimit file path:%s \r\n", filename);

    /* load file */
    size = rwnx_request_firmware_common(rwnx_hw, &dst, filename);
    if (size <= 0) {
        AICWFDBG(LOGERROR, "wrong size of cfg file\n");
        dst = NULL;
        return 0;
    }

    AICWFDBG(LOGINFO, "### Load file done: %s, size=%d\n", filename, size);

    /* parsing the file */
    rwnx_plat_powerlimit_parsing((char *)dst, size);

    rwnx_release_firmware_common(&dst);

    AICWFDBG(LOGINFO, "powerlimit download complete\n\n");
    return 0;
}
#endif

int system_config_8800d80(struct rwnx_hw *rwnx_hw)
{
	int ret;
	const u32 mem_addr = 0x40500000;
	const u32 read_mem_addr = 0x40241014;
	struct dbg_mem_read_cfm rd_mem_addr_cfm;
	ret = rwnx_send_dbg_mem_read_req(rwnx_hw, mem_addr, &rd_mem_addr_cfm);
	if (ret) {
		AICWFDBG(LOGINFO, "%x rd fail: %d\n", mem_addr, ret);
		return ret;
	}
	if (((rd_mem_addr_cfm.memdata >> 25) & 0x01UL) == 0x00UL) {
		chip_mcu_id = 1;
	}
	chip_id = (u8)(rd_mem_addr_cfm.memdata >> 16);
	AICWFDBG(LOGINFO, "chip_id=%x, chip_mcu_id = %d\n", chip_id, chip_mcu_id);
	if (testmode == 1 && (IS_CHIP_ID_H()))
	{
		struct dbg_mem_read_cfm rd_mem_addr_cfm;
		ret = rwnx_send_dbg_mem_read_req(rwnx_hw, read_mem_addr, &rd_mem_addr_cfm);
		AICWFDBG(LOGINFO, "%s 0x%08x=0x%08x\n", __func__, read_mem_addr, rd_mem_addr_cfm.memdata);
		if (ret) {
			AICWFDBG(LOGERROR, "%x rd fail: %d\n", read_mem_addr, ret);
			return ret;
		} else {
			if (rd_mem_addr_cfm.memdata != 1) {
				AICWFDBG(LOGERROR, "check fail: %x\n", rd_mem_addr_cfm.memdata);
				return -1;
			}
		}
	}
	return 0;
}

接著,要先把編譯環境設定好:

export PATH=$(echo $PATH | tr ':' '\n' | grep -v "/mnt/c/" | tr '\n' ':' | sed 's/:$//')
cd ~/luckfox-pico/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf
source env_install_toolchain.sh
radxa-aic8800/src/USB/driver_fw/drivers/aic8800

然後執行: make KCFLAGS="-Wno-error -Wno-unused-function -Wno-date-time"



修改 USB 版級配置 (可選)

由於某些板子他只有一個 USB Type-C 是只有從端的模式,要修改為 Host 就可以接入外部裝置,但是供電必須從腳位供電,或者是用可以供電的外接盒。

在這裡: luckfox-pico\sysdrv\source\kernel\arch\arm\boot\dts\rv1106g-luckfox-pico-pro-max.dts
修改裡面的 USB 相關配置變成:

/**********USB**********/
&u2phy {
	status = "okay";
};

&u2phy_otg {
	rockchip,dis-u2-susphy;
	status = "okay";
};

&usbdrd {
	status = "okay";
};

&usbdrd_dwc3 {
	status = "okay";
	dr_mode = "host";
        phys = <&u2phy_otg>;
        phy-names = "usb2-phy";
        snps,dis_enblslpm_quirk;
        snps,dis-u2-freeclk-exists-quirk;
};

然後執行 ./build.sh kernel ,就去燒一下板子。



複製文件

把剛才驅動目錄下的

aic_load_fw.ko
aic8800_fdrv.ko

先複製到系統上。

根據這個驅動專案的程式碼裡面可以發現他會去讀取 firmware 參數 (rf大小值等等) ,是放在 /lib/firmware



然後做 mkdir -p /lib/firmware,之後把

luckfox-pico\radxa-aic8800\src\USB\driver_fw\fw\aic8800D80

這個目錄的內容直接複製過去 (直接攤平到 /lib/firmware) ,如果稍後啟動失敗,那就把這個放在 /lib/firmware/aic8800D80


如何啟動接入 USB 偵測

rmmod ./aic8800_fdrv.ko # 移除之前載入的模組
rmmod ./aic_load_fw.ko # 移除之前載入的模組

insmod ./aic_load_fw.ko
sleep 3
insmod ./aic8800_fdrv.ko aicwf_dbg_level=1 # 開啟 debug mode

while true; do dmesg && echo "-----" && lsusb && iwconfig; sleep 1; done

接入後可以按一下開關讓模組啟動,應該就會看到 iwconfig 那邊會出現新的 wifi 模組。

這個驅動的特性是:

usb 接入時用 lsusb 會看到 ID a69c:8d80
 


aic_load_fw.ko 偵測到 lsusb 時會改為 ID a69c:8d81




就會像這樣啟動。

如果啟動一直 failed 是因為 timeout 的原因,可以嘗試上面去改 aicwf_compat_8800d80.c 這個文件的啟動方法。



測試連線

掃描 Wifi

iwlist wlan0 scan;


連線 Wifi

cat > /tmp/wpa.conf <<EOF

network={

    ssid="xxxxxxxxxx"

    psk="xxxxxxxxxxxxxxxxxxxxx"

}

EOF

wpa_supplicant -i wlan0 -c /tmp/wpa.conf -B

while true; do dmesg && echo "-----" && lsusb && iwconfig; sleep 1; done


接下來你看 iwconfig 上面的 SSID 就知道他連線到哪個 Wifi 了


移植到 Buildroot Linux

手動啟動 insmod 只是測試,如果要直接移植請參考手冊:

AIC8800_USB_porting_guide_v1_2_20241021.pdf



藍芽

要使用藍芽功能,需要另外編譯 USB\driver_fw\drivers\aic_btusb ,請先到這個目錄下改一下 Makefile:

MODULE_NAME = aic_btusb

CONFIG_AIC8800_BTUSB_SUPPORT = m
CONFIG_SUPPORT_VENDOR_APCF = n
# Need to set fw path in BOARD_KERNEL_CMDLINE
CONFIG_USE_FW_REQUEST = n

ifeq ($(CONFIG_SUPPORT_VENDOR_APCF), y)
obj-$(CONFIG_AIC8800_BTUSB_SUPPORT) := $(MODULE_NAME).o aic_btusb_external_featrue.o
else
obj-$(CONFIG_AIC8800_BTUSB_SUPPORT) := $(MODULE_NAME).o
endif

ccflags-$(CONFIG_SUPPORT_VENDOR_APCF) += -DCONFIG_SUPPORT_VENDOR_APCF
#$(MODULE_NAME)-y := aic_btusb_ioctl.o\
#	aic_btusb.o \

ccflags-$(CONFIG_USE_FW_REQUEST) += -DCONFIG_USE_FW_REQUEST


# Platform support list
CONFIG_PLATFORM_ROCKCHIP ?= y
CONFIG_PLATFORM_ALLWINNER ?= n
CONFIG_PLATFORM_AMLOGIC ?= n
CONFIG_PLATFORM_UBUNTU ?= n

# MTK linux platform use bluedroid
CONFIG_PLATFORM_MTK_LINUX ?= n


ifeq ($(CONFIG_PLATFORM_ROCKCHIP), y)
ccflags-$(CONFIG_PLATFORM_ROCKCHIP) += -DCONFIG_PLATFORM_ROCKCHIP
ARCH = arm
CROSS_COMPILE = arm-rockchip830-linux-uclibcgnueabihf-
KDIR = /home/hpcslag/esp-idf/esp-hosted/esp_hosted_ng/kernel-build
PWD = $(shell pwd)
endif


all: modules
modules:
	make -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules

# install:
# 	mkdir -p $(MODDESTDIR)
# 	install -p -m 644 $(MODULE_NAME).ko  $(MODDESTDIR)/
# 	/sbin/depmod -a ${KVER}
# 	echo $(MODULE_NAME) >> /etc/modules-load.d/aic_bt.conf

# uninstall:
# 	rm -rfv $(MODDESTDIR)/$(MODULE_NAME).ko
# 	/sbin/depmod -a ${KVER}
# 	rm -f /etc/modules-load.d/aic_bt.conf

clean:
	rm -rf *.o *.ko *.o.* *.mod.* modules.* Module.* .a* .o* .*.o.* *.mod .tmp* .cache.mk .Module.symvers.cmd .modules.order.cmd



然後到 aic_btusb.c 裡面的 static int bt_reboot_notify(struct notifier_block *notifier, ulong pm_event, void *unused) 因為變數定義沒用到導致編譯失敗。 直接把 int ret = 0; 改成  int ret __maybe_unused = 0;

然後到上面把

printk("size = %d  p - rawdata = 0x%0lx \r\n", size, p - rawdata);

換成

printk("size = %d  p - rawdata = 0x%0tx \r\n", size, p - rawdata);


接著到 aic_btusb.h 把

#ifdef CONFIG_PLATFORM_UBUNTU
#define CONFIG_BLUEDROID        1 /* bleuz 0, bluedroid 1, lbh 2 */
#else
#define CONFIG_BLUEDROID        1 /* bleuz 0, bluedroid 1, lbh 2 */
#endif

這段直接換掉變成:

#define CONFIG_BLUEDROID        0 /* bleuz 0, bluedroid 1, lbh 2 */


然後直接編譯:

make KCFLAGS="-Wno-error -Wno-unused-function -Wno-date-time" -Wno-implicit-fallthrough -Wno-unused-variable


完成之後去載入:

(前面那兩個 .ko 也要先載入)

rmmod ./aic_btusb.ko

insmod ./aic_btusb.ko

while true; do dmesg | grep bt && echo "-----"; sleep 1; done


測試藍芽的行為是: 

bt-adapter --set Powered 1

bt-adapter -d


可選:

可以在 ./build.sh buildrootconfig 或是 kernelconfig 去把 HCIUSB 支援打開


完成之後藍芽打開的訊息是這樣。





Reference:

https://github.com/radxa-pkg/aic8800/tree/main/src/AIC_Docs


沒有留言:

張貼留言

© Mac Taylor, 歡迎自由轉貼。
Background Email Pattern by Toby Elliott
Since 2014