本文說明如何直接接入 AIC8800 晶片驅動到 Linux Buildroot / Windows 下。
模組
本文選用 WB800D80 系列晶片來作為 Wifi 6 的模組,其中晶片的版本有好幾種,這些型號幾乎都支援 USB 訊號,因此選用 USB 接入會相對支援度高一些,反而用 SDIO 類訊號不見得每一個 模組都有。
本文測試:
- SKI.WB800D80S.1 (Windows 可以連線、Linux 驅動好像會引起一些問題)
- SKI.WB800D80U.2_D40 (Windows 可以連線、Linux 可以正常使用)
模組配線
需要準備的材料:
- 熱熔膠
- WB800D80U.2 模組 (建議多買幾個避免燒毀或燙壞)
- 電解電容 470uF / 25V
- 按鈕開關
- USB 線
模組腳位 - SKI.WB800D80S.1
如果你是使用 SKI.WB800D80S.1 可以參考下圖的腳位焊接,不過這個版本因為腳位密集,焊接一定要注意不要讓相鄰的腳位短路了 (切記不要燙太久,晶片會燒掉)。
模組腳位 - SKI.WB800D80U.2_D40
如果使用與本文一致的這個版本,就焊接 PWR, GND, USB_DP (綠), USB_DM (白), VBAT(3.3v)
熱熔膠焊接
因為這幾個腳位都很細,建議用熱溶膠把他們在底部架高,然後單根單芯線穿上去焊接。
焊接點解釋
- 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 自帶系統的那個驅動,但是我沒有用那個
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/
#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;
}
/**********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;
};
測試連線
掃描 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
藍芽
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
完成之後去載入:
(前面那兩個 .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
沒有留言:
張貼留言