Adapting the FCS950U (UWE5622) SDIO Wi-Fi Module on the OK3576-C Platform

Integrating a new SDIO Wi-Fi module into the Android system requires more than just kernel device recognition and driver loading. It involves a comprehensive process including firmware pre-provisioning, Vendor DLKM module packaging, Wi-Fi HAL configuration, wpa_supplicant startup parameter setup, and Android’s automatic recognition logic. Incomplete configuration at any step can lead to issues such as ''SDIO device detected but no wlan0 interface appears, '' ''driver module pre-provisioned but not auto-loaded, '' or ''Wi-Fi can be enabled but fails to connect. ''

This document outlines the complete adaptation process using the OK3576-C platform (Android 14, Linux 6.1.75) and the FCS950U module as a case study. The FCS950U corresponds to the UNISOC UWE5622 solution in this project, utilizing the SDIO hardware interface and employing sprdwl/unisoc-related Wi-Fi driver components.

  • Target Platform: OK3576-C, Android 14, Linux 6.1.75

  • Wireless Module: FCS950U (Corresponds to UWE5622)

  • Hardware Interface: SDIO, 4-bit bus

  • Core Tasks: Driver source code porting, Device Tree configuration, firmware & HAL integration, auto-loading setup, functional verification, and log analysis.

1. Wi-Fi Recognition and Loading Flow on Android Platform

On the Rockchip Android platform, the Wi-Fi module generally goes through four stages from power-up to becoming operational: ‘hardware scan’, ‘device identification’, ‘driver loading’ and ‘user-space service launch’. Understanding this flow is crucial for quickly pinpointing whether an issue lies at the hardware, kernel, or Android framework level.

  1. Hardware Scan: The system powers the Wi-Fi module upon boot, and the SDIO controller automatically scans for devices on the bus.

  2. Device Identification: When the user turns on Wi-Fi, the system reads uevent information from directories like /sys/bus/sdio, /sys/bus/usb, or PCI/PCIe device paths.

  3. Driver Loading: The system extracts the device VID/PID or corresponding identifier from the uevent and loads the matching Wi-Fi kernel module (.ko).

  4. Service Initialization: After Wi-Fi type identification, Android selects the appropriate wpa_supplicant parameters and Wi-Fi HAL based on the chipset, ultimately creating the wlan0 interface and initiating the connection process.

Therefore, during debugging, problems can be categorized into three layers:

  • Layer 1: Can the SDIO bus scan and detect the device?

  • Layer 2: Can the kernel driver and firmware load normally, and does the wlan0 interface get created?

  • Layer 3: Do the Android Wi-Fi HAL, wpa_supplicant, and Framework correctly recognize and invoke the driver?

2. Driver Source Code Porting

2.1 Pre-provisioning FCS950U Driver Source Code

Copy the UNISOC driver directory from the FCS950U driver source package to the external/wifi_driver directory in the Android source tree. The source material path is as follows:

FCS950U\uwe562x-main\uwe562x-main\Code\Wi-Fi\drivers\unisoc
↓
Android Source Code Path: external/wifi_driver/unisoc

After source code pre-provisioning, the kernel build system will compile the corresponding WCN BSP, Wi-Fi, and Bluetooth modules from the external/wifi_driver directory.

2.2 Adapting cfg80211 Interface for Linux 6.1.75

Due to changes in cfg80211 interface parameters across different kernel versions, the original driver’s calls to cfg80211_ch_switch_notify() need adjustment to match the Linux 6.1.75 interface. The following two files were modified in this project:

File 1: cfg80211.c

diff --git a/external/wifi_driver/unisoc/unisocwifi/sc2355/cfg80211.c \
b/external/wifi_driver/unisoc/unisocwifi/sc2355/cfg80211.c
@@ -1298,9 +1298,9 @@ static int sprdwl_cfg80211_start_ap(struct wiphy *wiphy,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)
cfg80211_ch_switch_notify(vif->ndev, &chandef, 0, 0);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)
- cfg80211_ch_switch_notify(vif->ndev, &chandef, 0);
+ cfg80211_ch_switch_notify(vif->ndev, &chandef, 0, 0);
#else
- cfg80211_ch_switch_notify(vif->ndev, &chandef);
+ cfg80211_ch_switch_notify(vif->ndev, &chandef, 0, 0);
#endif

This modification is located in the SoftAP channel switch notification flow and is used to adapt the function prototype of the current kernel.

File 2: cmdevt.c

diff --git a/external/wifi_driver/unisoc/unisocwifi/sc2355/cmdevt.c \
b/external/wifi_driver/unisoc/unisocwifi/sc2355/cmdevt.c
@@ -3592,9 +3592,9 @@ void sprdwl_event_chan_changed(struct sprdwl_vif *vif,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)
cfg80211_ch_switch_notify(vif->ndev, &chandef, 0, 0);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)
- cfg80211_ch_switch_notify(vif->ndev, &chandef, 0);
+ cfg80211_ch_switch_notify(vif->ndev, &chandef, 0, 0);
#else
- cfg80211_ch_switch_notify(vif->ndev, &chandef);
+ cfg80211_ch_switch_notify(vif->ndev, &chandef, 0, 0);
#endif

The corresponding section describes the cfg80211 notification flow after the firmware reports a channel change event. If the number of parameters does not match, a compilation error will typically occur directly during the build phase.

2.3 Google GKI-Related Configuration

Android 14 source code enables Google GKI-related mechanisms by default. Since GKI restricts external drivers from calling certain unexported kernel symbols—such as functions like kernel_read—Unisoc drivers provide the CONFIG_WCN_GKI macro to switch between compatible implementations.

  • In external/wifi_driver/unisoc/unisocwcn/Kbuild, enable the CONFIG_WCN_GKI macro.

  • In external/wifi_driver/unisoc/unisocwifi/Kbuild, check for the macro with the same name.

  • When enabling the GKI-compatible path, turn off the READ_INI_FILP macro to avoid using restricted file-reading interfaces.

The project documentation indicates that even if the current project does not actually use a GKI kernel, enabling CONFIG_WCN_GKI did not affect compilation. However, during actual porting, it is still recommended to verify this based on the current kernel configuration and driver version.

2.4 Configuring the Main Control Platform, Chip Model, and Hardware Interface

The driver package supports multiple main control platforms and various Unisoc WCN chips. Before porting, confirm the following three key variables in external/wifi_driver/unisoc/Makefile:

Variables: Configuration: Description
COUSTOM_PLATFORM rk Main Control Platform: Rockchip Variable names have been retained exactly as they appear in the driver source code.
UNISOC_WCN_CHIP_ID uwe5622 The FCS950U corresponds to the UWE5622.
UNISOC_WCN_HW_TYPE sdio The module connects to the main controller via SDIO.

Source code path: external/wifi_driver/unisoc/Makefile

# AP platform
# unisoc/qcom/rk/mtk/aw/aml
export COUSTOM_PLATFORM ?= rk
# UNISOC WCN chip ID
# umw2651/umw2652/umw2653
# uwe5621/uwe5622/uwe5623
export UNISOC_WCN_CHIP_ID ?= uwe5622
# UNISOC WCN hardware interface type
# sdio/usb/pcie
export UNISOC_WCN_HW_TYPE ?= sdio
EXTRA_CFLAGS += -Wno-unused-variable
EXTRA_CFLAGS += -D__linux__
obj-y += unisocwcn/
obj-y += unisocwifi/
obj-y += unisocbt/
ifeq ($(UNISOC_WCN_CHIP_ID), umw2652)
obj-y += unisocfm/
endif

Specifically, unisocwcn is responsible for the WCN low-level bus, firmware download and power management; unisocwifi is responsible for the Wi-Fi network interface; and unisocbt is responsible for Bluetooth-related functions.

2.5 Integrating the Unisoc Driver into external/wifi_driver Compilation

Source code path: external/wifi_driver/Makefile

diff --git a/external/wifi_driver/Makefile b/external/wifi_driver/Makefile
@@ -23,6 +23,10 @@ CONFIG_EA6621Q=y
export CONFIG_EA6621Q
obj-$(CONFIG_EA6621Q) += ea6621q/
+CONFIG_UNISOC=y
+export CONFIG_UNISOC
+obj-$(CONFIG_UNISOC) += unisoc/

After adding CONFIG_UNISOC, the relevant Wi-Fi/BT kernel modules will be built when compiling the kernel. When building the Android image, these modules will then be pre-installed into the vendor_dlkm.img. The corresponding directory in the running system is /vendor_dlkm/lib/modules.

3. Pre-installation Process of Wi-Fi Kernel Modules in Android 14

Android 14 places vendor kernel modules in the Vendor DLKM (Dynamically Loadable Kernel Modules) partition. Understanding the path from the compiled artifacts to vendor_dlkm.img helps troubleshoot issues like ''the driver is compiled but the .ko file is missing from the image. ''

3.1 Enabling the Vendor DLKM Image

# device/rockchip/common/build/rockchip/DynamicPartitions.mk
PRODUCT_BUILD_VENDOR_DLKM_IMAGE := true
TARGET_COPY_OUT_VENDOR_DLKM := vendor_dlkm
BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE := $(ROCKCHIP_READ_ONLY_FILE_SYSTEM_TYPE)

Once BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE has been defined, the Android build system will enable BOARD_USES_VENDOR_DLKMIMAGE and generate vendor_dlkm.img.

# build/make/core/board_config.mk
BOARD_USES_VENDOR_DLKMIMAGE :=
ifdef BOARD_PREBUILT_VENDOR_DLKMIMAGE
BOARD_USES_VENDOR_DLKMIMAGE := true
endif
ifdef BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE
BOARD_USES_VENDOR_DLKMIMAGE := true
endif

3.2 Module Directory Compatibility Soft Links

After enabling the Vendor DLKM partition, the system creates a compatibility access path from /vendor/lib/modules to /vendor_dlkm/lib/modules. Applications and underlying components should prioritize using the module path defined by the Android build system.

# build/make/core/Makefile(key logic)
ifdef BOARD_USES_VENDOR_DLKMIMAGE
_vendor_dlkm_lib_modules_symlink := \
$(call create-partition-compat-symlink,\
$(TARGET_OUT_VENDOR)/lib/modules,\
/vendor_dlkm/lib/modules,\
vendor_dlkm.img)
INTERNAL_VENDORIMAGE_FILES += $(_vendor_dlkm_lib_modules_symlink)
ALL_DEFAULT_INSTALLED_MODULES += $(_vendor_dlkm_lib_modules_symlink)
endif

3.3 external/wifi_driver Entering BOARD_VENDOR_KERNEL_MODULES

# vendor/rockchip/common/wifi/wifi.mk
HAVE_EXT_WIFI_KO_FILE := $(shell test -d \
$(TOPDIR)external/wifi_driver/ && echo yes)
ifeq ($(HAVE_EXT_WIFI_KO_FILE),yes)
EXT_WIFI_KO_FILES := $(shell find \
$(TOPDIR)external/wifi_driver -name "*.ko" -type f)
BOARD_VENDOR_KERNEL_MODULES += \
$(foreach file, $(EXT_WIFI_KO_FILES), $(file))
# external/wifi_driver First, remove modules of the same name that may exist in the kernel directoryEXT_WIFI_DRIVER := $(shell find \
$(TOPDIR)external/wifi_driver -name "*.ko" -type f | \
awk -F "wifi_driver/" '{print $$2}' | awk -F "/" '{print $$1}')
$(shell for line in $(EXT_WIFI_DRIVER); do \
rm $(TOPDIR)$(PRODUCT_KERNEL_PATH)/drivers/net/wireless/rockchip_wlan/$$line/*.ko \
> /dev/null 2>&1; done)
$(shell for line in $(EXT_WIFI_DRIVER); do \
rm $(TOPDIR)$(PRODUCT_KERNEL_PATH)/drivers/net/wireless/rockchip_wlan/rkwifi/$$line/*.ko \
> /dev/null 2>&1; done)
endif

wifi.mk will search for all generated .ko files under external/wifi_driver and add them to BOARD_VENDOR_KERNEL_MODULES. Subsequently, the Android build system copies the modules into the Vendor DLKM image through processes such as build-image-kernel-modules and copy-many-files.

4. Hardware Connections and Device Tree Configuration

The FCS950U communicates with the host via 4-bit SDIO, while utilizing independent module enable, reset, and wake-up pins. During adaptation, it’s essential to simultaneously verify the SDIO bus, power sequencing, GPIO polarity, and the specific module variant.

Hardware block diagram showing FCS950U SDIO 4-bit bus connections, including module enable, reset, and wake-up pins for Device Tree configuration on the Android system

4.1 SDIO Controller Configuration

&sdio {
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 = <&sdmmc1m0_bus4
&sdmmc1m0_clk
&sdmmc1m0_cmd>;
sd-uhs-sdr104;
status = "okay";
};

Key Configuration Notes:

  • bus-width = <4>: Uses a 4-bit SDIO data bus.

  • non-removable: Indicates the module is an onboard device, not handled as a removable SD card.

  • keep-power-in-suspend: Maintains power to the module during system suspend to support wake-up functionality.

  • cap-sdio-irq: Declares controller support for SDIO interrupts.

  • mmc-pwrseq: Binds to the module’s power-on/reset sequencing node.

  • sd-uhs-sdr104: Enables SDR104 capability. Actual frequency and stability are still affected by PCB layout, module characteristics, and controller configuration.

4.2 Unisoc Marlin3 and Wi-Fi Device Node Configuration

sprd_marlin3: sprd-marlin3 {
compatible = "unisoc,marlin3";
sprd,btwf-file-name = "/vendor/etc/firmware/wcnmodem.bin";
sdhci-name = <&sdio>;
keep-power-on;
// adma-tx;
// adma-rx;
// blksz-512;
// sdio-irq-type = "data-irq";
// sdio-irq-gpio = "esmd3";
m2-wakeup-ap-gpios = <&gpio1 RK_PD4 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio1 RK_PC7 GPIO_ACTIVE_LOW>;
enable-gpios = <&gpio1 RK_PC6 GPIO_ACTIVE_LOW>;
m2-to-ap-irq-gpios = <&gpio0 RK_PB1 GPIO_ACTIVE_HIGH>;
status = "okay";
};
sprd-wlan {
compatible = "sprd,sc2355-wifi";
status = "okay";
};
Configurations: Purpose and Project Notes
sprd,btwf-file-name Configures the path for the CP2 firmware wcnmodem.bin. This path must match the actual pre-installed location in the Android image.
sdio-irq-type If not configured, an external GPIO interrupt is used by default (recommended in source material). data-irq uses SDIO_DATA1; rx-polling uses a polling method.
sdio-irq-gpio FCS851U requires esmd3 to be configured; FCS950U should remain commented out (as per the original note).
m2-wakeup-ap-gpios The WLAN_WAKE signal, used by the Wi-Fi module to send RX interrupts to the host.
m2-to-ap-irq-gpios The BT_WAKE_HOST related INT signal, used for sleep/wake control.
reset-gpios Module RESET_N signal, active low, optional.
enable-gpios Module CHIP_EN/WIFI_REG_ON enable signal, optional.
adma-tx / adma-rx Configures SDIO to use ADMA. If not configured, SDMA is used by default.

4.3 SDIO Power Sequencing Node

sdio_pwrseq: sdio-pwrseq {
compatible = "mmc-pwrseq-simple";
pinctrl-names = "default";
pinctrl-0 = <&wifi_poweren_gpio>;
post-power-on-delay-ms = <200>;
reset-gpios = <&gpio1 RK_PC7 GPIO_ACTIVE_LOW>;
status = "okay";
};

During project debugging, initially the reset pin was configured in the reset-gpios of the sprd_marlin3 node, and the system could not scan the SDIO card. Once the reset operation was reassigned to be performed by sdio-pwrseq, the module was recognised correctly. This phenomenon indicates that the timing stage of the reset operation affects SDIO enumeration.

Timing diagram for SDIO power sequencing illustrating the required interval between VIO/VBAT power-up and CHIP_EN/RESET_N signals for successful SDIO module enumeration

As seen from the timing diagram, a reasonable sequential relationship must be maintained among VIO, VBAT, CHIP_EN, and RESET_N. Project experience shows that the interval between power-up of RESET and VIO/VBAT should not be too long; otherwise, the module may not have entered an enumerable state when the host controller begins scanning.

4.4 Differences in Wake-up Pins for FCS950U Different OC Models

Pinout diagram showing the wake-up pin differences for FCS950U OC models, specifically highlighting WLAN_WAKE1 on Pin 6 for the FCS950UAAMD variant

Different OC models of the module may use different physical pins as WLAN_WAKE. FCS950UAAMD corresponds to WLAN_WAKE1 (Pin 6).
FCS950UABMD corresponds to WLAN_WAKE2 (Pin 13).

Pinout diagram showing the wake-up pin differences for FCS950U OC models, highlighting WLAN_WAKE2 on Pin 13 for the FCS950UABMD variant

4.5 Verifying Whether the SDIO Device Has Been Enumerated

ok3576_c:/sys/bus/sdio/devices/mmc2:8800:1 # cat uevent
DRIVER=sdiohal
SDIO_CLASS=00
SDIO_ID=0000:0000
SDIO_REVISION=0.0
MODALIAS=sdio:c00v0000d0000

The SDIO_ID of the FCS950U in this project is 0000:0000. Subsequently, the Android auto-detection logic will use this identifier to match SPRDWL devices. Therefore, this ID needs to be added to supported_wifi_devices.

5. Android 14 System Integration

5.1 Preloading Wi-Fi Firmware and Board-Level Configuration Files

Copy the corresponding wcnmodem.bin and wifi_board_config.ini for the FCS950U from the driver package to the Rockchip common Wi-Fi firmware directory.

Source Directory:
FCS950U\uwe562x-main\uwe562x-main\Code\Wi-Fi\fw\FCS950U
Target Directory:
vendor/rockchip/common/wifi/firmware

Among these:
wcnmodem.bin is the WCN/CP2 firmware.
wifi_board_config.ini is used to load board-level radio frequency and Wi-Fi configurations. The sprd,btwf-file-name in the device tree must match the final firmware path in the image.

5.2 Adding Unisoc wpa_supplicant Startup Parameters

diff --git a/device/rockchip/common/wpa_config.txt \
b/device/rockchip/common/wpa_config.txt
@@ -48,3 +48,9 @@
+[sprdwl]
+/vendor/bin/hw/wpa_supplicant
+-O/data/vendor/wifi/wpa/sockets
+-puse_p2p_group_interface=1
+-g@android:wpa_wlan0

When the system identifies the sprdwl driver, it will use this configuration to launch wpa_supplicant. The parameters specify the control socket directory, P2P group interface policy, and global control interface, respectively.

5.3 Adding libwifi-hal-sprd to the public Wi-Fi HAL

diff --git a/hardware/rockchip/wifi/wifi_hal/common/Android.mk \
b/hardware/rockchip/wifi/wifi_hal/common/Android.mk
@@ -25,5 +25,6 @@ LOCAL_REQUIRED_MODULES := \
libwifi-hal-rtk \
libwifi-hal-bes \
libwifi-hal-aic \
- libwifi-hal-skw
+ libwifi-hal-skw \
+ libwifi-hal-sprd

This modification ensures that the Wi-Fi HAL dynamic library corresponding to Unisoc is generated and preloaded during product build.

5.4 Adding Android.mk for Unisoc Wi-Fi HAL

The source material requires creating a unisoc directory under the Rockchip Wi-Fi HAL’s vendor directory, adding Android.mk, and copying the wifihal source code from the driver package to the hardware directory. The core build configuration is as follows:

# Copyright (C) 2011 The Android Open Source Project
# Licensed under the Apache License, Version 2.0
LOCAL_PATH := hardware/unisoc/wlan/wifi_hal
include $(CLEAR_VARS)
LOCAL_CFLAGS := \
-Wall \
-Werror \
-Wno-format \
-Wno-reorder \
-Wno-unused-function \
-Wno-unused-parameter \
-Wno-unused-private-field \
-Wno-unused-variable
LOCAL_C_INCLUDES += \
external/libnl/include \
$(call include-path-for, libhardware_legacy)/hardware_legacy \
external/wpa_supplicant_8/src/drivers
LOCAL_HEADER_LIBRARIES := libutils_headers liblog_headers
LOCAL_SRC_FILES := \
wifi_hal.cpp \
rtt.cpp \
common.cpp \
cpp_bindings.cpp \
gscan.cpp \
link_layer_stats.cpp \
wifi_logger.cpp \
wifi_offload.cpp
LOCAL_SHARED_LIBRARIES := \
librkwifi-ctrl \
libcrypto \
libnl \
libutils \
libcutils \
liblog
LOCAL_MODULE := libwifi-hal-sprd
LOCAL_PROPRIETARY_MODULE := true
include $(BUILD_SHARED_LIBRARY)

5.5 Adding Automatic Wi-Fi Device Recognition

Uncomment the SPRDWL device in the Rockchip Wi-Fi control logic and use the ID 0000:0000 read from the SDIO uevent as the matching ID.

// frameworks/opt/net/wifi/libwifi_hal/rk_wifi_ctrl.cpp
static wifi_device supported_wifi_devices[] = {
{"AP6611S", "06CB:AABF"},
{"MVL88W8977", "02df:9145"},
{"NXP88W8987", "02df:9149"},
{"SPRDWL", "0000:0000"},
{"BES2600", "be57:2002"},
{"AIC8800", "5449:0145"},
{"AIC8800", "c8a1:0082"},
};

After the system reads the SDIO device ID, it can recognize the module as the SPRDWL type and proceed to the corresponding driver loading and HAL selection process.

5.6 Adding BSP Module Pre-loading Logic

// frameworks/opt/net/wifi/libwifi_hal/wifi_hal_common.cpp
#define SPRD_BSP_DRIVER_MODULE_PATH \
WIFI_MODULE_PATH "uwe5622_bsp_sdio.ko"
#define SPRDWL_DRIVER_MODULE_NAME "sprdwl"
int wifi_load_driver() {
...
if (strstr(wifi_ko_path, SPRDWL_DRIVER_MODULE_NAME)) {
insmod(SPRD_BSP_DRIVER_MODULE_PATH, "");
usleep(200000);
}
...
}

sprdwl_ng.ko relies on the underlying UWE5622 BSP and SDIO transmission capabilities. Therefore, before loading the main Wi-Fi driver, it is necessary to insert uwe5622_bsp_sdio.ko first and wait for module initialization.

5.7 Building Android Image

source build/envsetup.sh
lunch 6
./build.sh -UKAup

After compilation is complete, verify that the Kernel modules, vendor_dlkm.img, vendor.img, and related system images have been updated. Merely replacing boot.img or the kernel image may not synchronously update the HAL, firmware, and Vendor DLKM modules.

6. Driver and Function Verification

6.1 Checking SDIO Enumeration

console:/sys/bus/sdio/devices # ls
mmc2:8800:1

If the directory is empty, prioritize checking the SDIO controller, pin multiplexing, power supply, and the timing of RESET and CHIP_EN instead of continuing to troubleshoot the Android HAL.

6.2 Checking if Driver Modules are Preloaded

console:/ # ls /vendor_dlkm/lib/modules/
bcmdhd.ko
mlan.ko
moal.ko
modules.alias
modules.dep
modules.load
modules.softdep
r8168.ko
sprdbt_tty_sdio.ko
sprdwl_ng.ko
uwe5622_bsp_sdio.ko

This project requires at least uwe5622_bsp_sdio.ko and sprdwl_ng.ko. The Bluetooth function will also use sprdbt_tty_sdio.ko.

6.3 Checking Module Loading Dependencies

console:/sys/bus/sdio/devices # lsmod
Module Size Used by
sprdwl_ng 507904 0
sprdbt_tty_sdio 53248 0
uwe5622_bsp_sdio 286720 2 sprdwl_ng,sprdbt_tty_sdio
r8168 589824 0

The Used by field can confirm that both Wi-Fi and Bluetooth drivers depend on the underlying uwe5622_bsp_sdio module. If loading only sprdwl_ng fails, check whether the BSP module was loaded in advance and whether the dependency symbols are satisfied.

6.4 Checking the wlan0 Network Interface

console:/ # ifconfig wlan0
wlan0 Link encap:Ethernet
HWaddr ec:b5:0a:55:5a:6f
Driver unisoc_wifi
BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

The appearance of wlan0 with the driver unisoc_wifi indicates that the kernel driver, firmware startup, and network interface creation have been completed. You can then proceed to Android Settings for scanning, connecting, and network verification.

Phenomenon Priority Troubleshooting Direction
Unable to scan SDIO card Check SDIO controller configuration, pin multiplexing, power supply, and RESET/CHIP_EN power-up timing.
Card scanned but driver not loaded Check Android auto-recognition ID, module preloading, module path, and wifi_load_driver logic.
Driver loaded but no wlan0 Capture dmesg, check firmware download, wifi_board_config.ini, and BSP & sprdwl initialization logs.
wlan0 exists but Wi-Fi cannot be enabled in settings interface Check libwifi-hal-sprd, wpa_config, framework recognition, and SELinux/permission logs.
Can connect but cannot access the internet Check DHCP, routing, DNS, and Captive Portal detection related logcat messages.

7. Log Toggles and Problem Diagnosis

7.1 Enabling or Disabling WCN Driver Logs

# Turn OFF Wi-Fi/WCN driver logs
echo "bsplog=0" > /proc/mdbg/at_cmd
# Turn ON Wi-Fi/WCN driver logs
echo "bsplog=1" > /proc/mdbg/at_cmd

You can also modify the default log level in the driver source code.

// File path: external/wifi_driver/unisoc/unisocwcn/platform/wcn_procfs.c
// Default log level: INFO
u32 wcn_print_level = LOG_INFO;
// To keep only ERROR logs:
// u32 wcn_print_level = LOG_ERR;

During the development phase, it is recommended to retain the INFO level. For the product version, the log volume can be appropriately reduced to avoid continuous logging affecting performance or overwhelming other kernel logs.

7.2 Capturing CP2 Firmware Logs

# Enable cp2log
echo -e "at+armlog=1\r" > /proc/mdbg/at_cmd
# Save cp2log
cat /dev/slog_wcn0 > /sdcard/cp2log.txt &

You can also adjust the default behavior in the platform configuration header file:

// File path: unisocwcn/include/platform_config/rk_config.h
// true: Disable cp2log by default
// false: Enable cp2log by default
config_wcn_user = false;

7.3 Capturing Dmesg

The default kernel log buffer in Android might be small, causing old logs to be overwritten by new ones during issue reproduction. Before capturing, you can increase the ''Logger Buffer Sizes '' in Developer Options.

Screenshot of Android Developer Options interface demonstrating how to increase the Logger Buffer Sizes for capturing complete dmesg and logcat kernel logs

Figure 5: Logger Buffer Sizes in Android Developer Options

adb shell dmesg > D:\log\dmesg.txt

After capturing, you can prioritize filtering for keywords related to sprdwl, WCN, SDIO, and unisoc:

grep -Ei "sprdwl|wcn|sdio|unisoc|marlin" dmesg.txt

7.4 Key Phases of Successful Driver Load Logs

The complete log can be lengthy. You can assess the initialization progress according to the following phases.

Phase 1: Device Tree Parsing and Low-level Driver Probe

WCN BASE: marlin_init entry!
WCN BASE: marlin_probe: unisoc wcn driver build time: Mar 19 2025 06:11:16 (UTC)
WCN BASE: parse_wcn_globle_config entry
WCN BASE: marlin_probe: device node name: sprd-marlin3
WCN BASE: btwf firmware name:/vendor/etc/firmware/wcnmodem.bin
WCN SLP_MGR: slp_mgr_init ok!
WCN SDIO: adma enable tx:0, rx:0
WCN SDIO: sdiohal_init sdiohal driver init successful
WCN BASE: marlin_probe driver match successful!

Driver match successful indicates that the sprd-marlin3 node has matched with the WCN low-level driver.

Phase 2: Module Power-up and SDIO Scanning

WCN BASE: start_marlin [MARLIN_WIFI]
WCN BASE: the first power on start
WCN BASE: marlin_clk_enable successfully!
WCN BASE: marlin_digital_power_enable D1v2 1
WCN BASE: wifipa 3v3 1
WCN SDIO: sdiohal_scan_card
WCN SDIO: sdiohal_probe func num is 1!!!
WCN SDIO: sdiohal_probe: func->class=0, vendor=0x0000, device=0x0000
WCN SDIO: enable sdio func1 ok
WCN BASE: marlin_scan_finish!
WCN SDIO: sdiohal_probe scan card successful!

scan card successful is the key log for determining whether the SDIO link and power-up timing are normal.

Phase 3: Downloading wcnmodem.bin Firmware

WCN BASE: xtal_26m clock XO mode
WCN BASE: then marlin start to download
WCN BASE: marlin btwifi_download_firmware from /system/etc/firmware/ start!
WCN BASE: download count=29,len=0,trans_size=32768
WCN BASE: download count=29,len=32768,trans_size=32768
...
WCN BASE: download count=29,len=917504,trans_size=29616
WCN BASE: marlin btwifi_download_firmware successfully!
WCN BASE: marlin_start_run

Firmware download logs will print repeatedly in fixed fragment lengths. As long as download_firmware successfully appears at the end, it indicates the firmware file is accessible and the download is complete.

Phase 4: SDIO Parameter Synchronization and CP Startup

WCN BASE: sdio_config rx mode:[sdma]
WCN BASE: sdio_config blksize:[512]
WCN BASE: sdio_config bt_wake_host:[dis]
WCN BASE: sdio_config sdio_irq:[gpio1]
WCN BASE: marlin_send_sdio_config_to_cp sdio_config:0x31
WCN BASE: pre_btwifi_download_sdio check_cp_ready start
WCN BASE: check_cp_ready sync val:0xf0f0f0f2, prj_type val:0x0
...
WCN BASE: check_cp_ready sync val:0xf0f0f0ff, prj_type val:0x0
...
WCN BASE: then marlin download finished and run ok

Here, you can confirm the use of SDMA, 512-byte block size, and GPIO interrupts. Once the synchronization value enters the ready state, the CP firmware begins normal operation.

Phase 5: Reading Version, Loading INI, and Creating wlan0

WCN BASE: at cmd read:WCN_VER:Platform Version:MARLIN3_20A_W25.08.1
Project Version:sc2355_marlin3_lite_2in1~02-17-2025 10:47:43~
sc2355:Spreadtrum WLAN Version:
sc2355:Kernel:6.1.75-android14-11-g8942136b2744-ab11900366,
Driver:Marlin3,update:000e,reserved:
sc2355:get_wifi_config_param, chip id of marlin3 lite is 2,
open wifi_board_config.ini
sc2355:iface 'wlan0'(c4:a6:4e:c4:5c:12) type 2 added

Finally, the appearance of open wifi_board_config.ini and iface wlan0 added indicates that the main Wi-Fi driver has completed configuration and successfully created the network interface.

7.5 Capturing Android logcat

Android upper-layer logs can be used to analyze Wi-Fi toggling, scanning, connecting, P2P, and internet connectivity detection. After entering Developer Mode and raising the log level, capture logs in real-time via ADB:

adb logcat -v time > D:\log\logcat.txt

Start capturing, reproduce the issue, then press Ctrl+C to end Common keywords are as follows:

Keywords: Meaning
AP-ENABLED SoftAP has been successfully enabled.
freq = 2412 The current channel frequency of the SoftAP, for example 2412 MHz.
setWifiEnabled Wi-Fi switch call; ''true '' indicates on, ''false '' indicates off.
package=com.android.settings uid=1000 enable=true Enable Wi-Fi via the Android settings menu.
wifi state: 0 Wi-Fi is being switched off
wifi state: 1 Wi-Fi has been switched off.
wifi state: 2 Wi-Fi is being switched on.
wifi state: 3 Wi-Fi has been switched on.
connectToUserSelectNetwork Begin connecting to the network selected by the user.
prepareForForcedConnection: SSID="AP" Prepare to connect to the target network named ‘AP’.
wlan0: CTRL-EVENT-CONNECTED The Wi-Fi STA connection was successful.
isCaptivePortal: isSuccessful()=true Internet connectivity test successful; ‘false’ indicates that a connection may not be possible.
P2P-DEVICE-FOUND Scan for a Wi-Fi Direct/P2P device.
p2p-wlan0-0: CTRL-EVENT-CONNECTED The P2P connection has been established.

8. Typical Issues and Troubleshooting Approaches

8.1 No mmc2:8800:1 in the SDIO Directory

  • Confirm the VIO and VBAT voltages for the FCS950U, as well as the power-up sequence.

  • Verify the active levels and release timing for CHIP_EN and RESET_N.

  • Check the pin multiplexing and hardware pull-up resistors for SDIO CMD, CLK, and DATA0~DATA3.

  • Attempt using the sdio-pwrseq or performing the reset earlier in the U-Boot stage.

  • Reduce max-frequency to investigate signal integrity issues.

8.2 SDIO Enumerated, but the System Does Not Automatically Load the Driver

  • Read the uevent to confirm if the SDIO_ID is still 0000:0000.

  • Check if SPRDWL and its corresponding ID have been added in rk_wifi_ctrl.cpp.

  • Verify the existence of uwe5622_bsp_sdio.ko and sprdwl_ng.ko in /vendor_dlkm/lib/modules.

  • Ensure wifi_hal_common.cpp loads the BSP module first.

  • Check modules.dep and kernel symbol dependencies.

8.3 Driver Modules Loaded, but No wlan0 Interface

  • Check if marlin btwifi_download_firmware successfully appears in the logs.

  • Confirm the actual path, permissions, and integrity of the wcnmodem.bin file.

  • Verify that wifi_board_config.ini is loaded correctly.

  • Inspect if check_cp_ready ultimately enters the ready state.

  • Enable bsplog and cp2log to examine errors on the WCN firmware side.

8.4 wlan0 Exists, but Wi-Fi is Abnormal in Android Settings

  • Confirm that libwifi-hal-sprd has been built and is a dependency for the product.

  • Ensure the wpa_config.txt file contains the [sprdwl] configuration.

  • Check the consistency between the HAL source directory and LOCAL_PATH in the Android.mk file.

  • Capture logcat and observe errors related to setWifiEnabled, wifi state, and wpa_supplicant.

  • Check for SELinux denials, missing dynamic libraries, or service startup failures.

9. Conclusion and Key Points for Adaptation

Adapting the FCS950U SDIO Wi-Fi module on the OK3576 Android 14 platform does not revolve around modifying a single driver file in isolation. Instead, the core is to establish the complete chain from hardware power-up to the Android Framework:

FCS950U Power Supply and Reset Sequence
↓
SDIO Controller Enumeration and Device Tree Matching
↓
UWE5622 WCN BSP Module Loading
↓
wcnmodem.bin Firmware Download and CP Startup
↓
sprdwl_ng Creates wlan0
↓
Android Recognizes SPRDWL Device Type
↓
libwifi-hal-sprd and wpa_supplicant Startup
↓
Settings App: Scanning, Connection, Internet Access, and P2P Function Verification

During actual debugging, it is recommended to always follow the order of ''First the bus, then the driver, and finally Android '' for problem localization. If SDIO is not enumerated successfully, troubleshooting should not immediately jump to the HAL.If the wlan0 interface has not been created, network connection logs should not be the primary focus. By verifying each layer step-by-step using uevent, lsmod, dmesg, cp2log, and logcat, the time required for problem diagnosis can be significantly reduced.

This project also highlights two common risks:

  • The timing of the RESET signal release directly affects SDIO enumeration.
  • Naming discrepancies may exist between the module’s part number (OC), schematic net names, and device tree GPIOs. For wireless module adaptation, the hardware datasheet, physical component markings, schematics, and system logs must be cross-referenced. One should not rely solely on a single configuration example.

Appendix: Quick Checklist After Successful Adaptation

Check Item Passing Criteria
Power supply VIO/VBAT stable; CHIP_EN and RESET_N timing meet module requirements.
SDIO Enumeration mmc2:8800:1 appears under /sys/bus/sdio/devices.
Device ID SDIO_ID=0000:0000 can be read from uevent.
Module Pre-installed uwe5622_bsp_sdio.ko and sprdwl_ng.ko exist in /vendor_dlkm/lib/modules.
Module Loading lsmod shows that sprdwl_ng depends on uwe5622_bsp_sdio.
Firmware Download dmesg shows btwifi_download_firmware successfully.
Board Configuration dmesg shows open wifi_board_config.ini.
Network Interface The command ifconfig / ip link shows wlan0, with its Driver listed as unisoc_wifi.
Android HAL libwifi-hal-sprd has been built, and Wi-Fi can be enabled in the Settings app.
Connection Verification Logcat shows CTRL-EVENT-CONNECTED, and network connectivity tests succeed.
Logging Capability Able to capture dmesg, cp2log, and logcat normally.



Contact Sales Team

Our sales team will connect you with FAE engineers for one-on-one technical support.

Talk to Our Engineers

Get a Quote

Get pricing and project evaluation support from our team.

Request a Quote

Apply for Samples

Submit your request to receive product samples for evaluation.

Get Samples

Join Facebook Group

Get Forlinx technical updates and hands-on sharing from our experts.

Join Now