AP6212驱动移植:SDIO Wi-Fi与UART蓝牙到wlan0/hci0
简介:面向Linux平台无线通信开发的AP6212驱动包,主要服务于需要为Broadcom AP6212芯片适配WiFi与蓝牙功能的驱动开发者、系统集成者及内核移植初学者。内容围绕驱动源码、内核移植方法和实际排错经验展开,可用于定制驱动、处理设备树与编译配置,并排查硬件识别失败、驱动加载异常和网络连接不稳定等问题。压缩包共14个文件,以txt移植说明与问题汇总、pdf用户指南、c源码、bz2/tgz/rar固件及驱动归档、jpg版本对比图为主,整体约5.31MB,便于按文档、源码和固件分类查阅。目前已有2035人学习。读者可据此获得AP6212 WiFi/蓝牙驱动的完整参考材料,包括源码与固件资源、移植流程说明、蓝牙固件自动检测思路、BlueZ编译设置记录,以及前人在个人移植中遇到的问题与解决办法,对理解Linux内核驱动开发、完成无线模块适配和快速定位兼容性问题具有较高参考价值。
1. AP6212 驱动不是插上就认:从一块哑板到 wlan0 出现
新板子第一次上电,ls /sys/class/net里只有lo和eth0,dmesg里搜不到半点和 SDIO 无线相关的东西,串口那头倒是老老实实躺着/dev/ttyS1——这就是 AP6212 驱动移植最常见的开场。这颗二合一模组不是「内核里打个勾就能用」的器件,它的 Wi-Fi 走 SDIO、蓝牙走 UART、上电时序压在两根 GPIO 和一路 32.768kHz 时钟上,四者缺一,结果就是设备树写得再漂亮也白搭。这篇把我自己在一块嵌入式板子上把 AP6212 从零拉通的过程拆开:设备树节点怎么摆、固件和 nvram 放哪儿、wlan0和hci0怎么验、上电不认卡怎么查,以及后面改 MAC、做产测自恢复的路子。适合正在做 Linux 板级移植、被一颗无线模组卡住的开发。
2. 模块架构:SDIO 走数据、UART 走控制、三根 GPIO 定生死
先把模组内部拆开看,不然调起来全是蒙的。AP6212 是 Wi-Fi 加蓝牙的组合器件,Wi-Fi 侧挂在一路 SDIO 总线上,蓝牙侧挂在 UART 上,两者共享一颗 32.768kHz 的低功耗时钟,各自有一根独立的电源使能脚,Wi-Fi 侧还有一根 host wake 中断线回来通知主机。这四类信号构成了整个移植的全部硬件接口,剩下的都是软件的事。
2.1 硬件连接:两条总线加三根控制线
SDIO 侧最少要 4 根数据线加命令、时钟,跑 4-bit 模式,热启动靠WL_REG_ON拉高。蓝牙侧是UART_TXD/RXD/CTS/RTS四线,硬件流控建议一定接上,模组在固件里会临时拉低 RTS 来反压主机,不接流控在高速率下必然丢数据。BT_REG_ON是蓝牙独立电源使能,和 Wi-Fi 那根互不影响,这一点很容易被误认为共用一根。
那根 32.768kHz 时钟要单独说。很多板子为了省一颗晶振,指望模组内部 RC 起振,结果 Wi-Fi 能连但一进低功耗就掉线,蓝牙则干脆连地址都出不来。我一般会要求硬件把 LPO 时钟引出来,实在没有的话,在设备树里把时钟源配成内部,接受功耗和稳定性的折损。
| 信号 | 归属 | 作用 | 不接的后果 |
|---|---|---|---|
| SDIO CLK/CMD/D0-D3 | Wi-Fi | 数据与控制通道 | 内核完全不枚举,卡都不认 |
WL_REG_ON | Wi-Fi | 模组 Wi-Fi 侧上电使能 | 卡枚举失败,日志里只有 mmc 超时 |
WL_HOST_WAKE | Wi-Fi | 模组唤醒主机的下降沿中断 | 能连网,挂起后唤不醒 |
| UART TX/RX/CTS/RTS | 蓝牙 | HCI 传输 | 只能出地址,扫不到设备 |
BT_REG_ON | 蓝牙 | 蓝牙侧上电使能 | hci0不出现或固件下发失败 |
| 32.768kHz LPO | 共用 | 低功耗时钟基准 | 断流、掉线、蓝牙初始化慢 |
2.2 软件分层:brcmfmac 与 hci_uart 各管一段
Wi-Fi 侧在内核里是brcmfmac这个驱动,它把自己注册成cfg80211的一个 wiphy,向上对接nl80211,用户态用wpa_supplicant、iw、hostapd这一套标准工具就能操作,不需要厂商私有接口。brcmfmac内部再分三层:总线层(brcmf_sdio)、协议层(固件下载、消息帧、msgbuf 环形队列)、配置层(对接 cfg80211)。调的时候看dmesg前缀基本能判断卡在哪一层。
蓝牙侧走的是标准 HCI 路径:hci_uart负责 UART 上的 H4 协议封装,上面的btbcm负责给模组下载小补丁文件(.hcd)并读取芯片版本,再往上就是 BlueZ 的bluetoothd。所以蓝牙不通的时候,先分清是「串口没通」「固件没下」还是「BlueZ 没起」,三层现象完全不一样。
用户态: wpa_supplicant / iw / hostapd bluetoothctl / bluetoothd | nl80211 | HCI socket 内核态: cfg80211 -> brcmfmac -> brcmf_sdio hci_uart -> btbcm | SDIO 总线 | UART + 流控 硬件: AP6212 Wi-Fi 侧 AP6212 蓝牙侧2.3 固件、nvram、驱动版本三者的匹配关系
这是最多人翻车的地方。brcmfmac不是「有固件就行」,它按芯片 ID 拼文件名去/lib/firmware/brcm/下找,找主固件.bin、板级参数.txt(业内一般叫 nvram),新内核还会找.clm_blob做射频法规表。三者版本必须来自同一套发布包,混用会出现「驱动加载成功但一扫描就崩」这种玄学现象。
蓝牙侧同理,btbcm会按芯片 ID 去找对应的.hcd补丁。文件名对不上,hci0能出来,但bluetoothctl里show看不到厂商信息,扫描也是空的。我的习惯是拿到模组厂给的发布包后,先把这几个文件在本地列一遍,记下芯片 ID 和文件名的对应关系,再往板子上放,别等到板子上靠猜。
提示:nvram 里的 PA 参数、天线配置是按模组本体标定过的,不是通用配置。同一份 nvram 换一块天线布局不同的板子,发射功率和灵敏度可能直接超标或掉档。
3. 移植落地:设备树节点与内核配置怎么一项项填
硬件确认完,接下来是把这些线在设备树里描述清楚,再把内核配置打开。这一步的产出是可验证的:dmesg里能看到 SDIO 设备枚举、能看到固件下载、能看到wlan0注册。
3.1 SDIO 侧:mmc 控制器、电源序列与 32.768kHz
SDIO 控制器节点本身要先确认bus-width和non-removable,模组是焊死的,不能按可插拔卡来配,否则内核会去做卡检测,白白多出几秒延迟。电源序列单独用一个mmc-pwrseq-simple节点描述,把WL_REG_ON挂上去,让内核在枚举前按时序拉高。
/* SDIO 控制器:4 线、不可热插拔、50MHz */ &sdio0 { bus-width = <4>; non-removable; keep-power-in-suspend; cap-power-off-card; max-frequency = <50000000>; mmc-pwrseq = <&wifi_pwrseq>; status = "okay"; #address-cells = <1>; #size-cells = <0>; /* 功能号 1 是 Wi-Fi,中断线回连到 SoC */ wifi@1 { reg = <1>; compatible = "brcm,bcm4329-fmac"; interrupt-parent = <&gpio1>; interrupts = <17 IRQ_TYPE_EDGE_FALLING>; /* WL_HOST_WAKE */ interrupt-names = "host-wake"; }; }; /* 上电时序:拉 WL_REG_ON,并给 32.768kHz 时钟 */ wifi_pwrseq: wifi-pwrseq { compatible = "mmc-pwrseq-simple"; reset-gpios = <&gpio1 12 GPIO_ACTIVE_LOW>; /* WL_REG_ON */ post-power-on-delay-ms = <200>; /* 等模组内部上电稳定 */ clocks = <&rtc_32k>; clock-names = "ext_clock"; };reset-gpios的有效电平要照原理图来,写反了就是「内核以为已经上电、实际模组还在复位」,日志里表现为 mmc 命令超时。post-power-on-delay-ms给 200ms 是我在几块板子上试出来的稳妥值,模组 datasheet 通常要求上电到可通信不小于 100ms,留点余量比卡着下限强。max-frequency先别贪高,50MHz 跑稳了再往上试,SDIO 走线长一点就很容易在高速下出现 CRC 错误。
interrupts那根WL_HOST_WAKE配错不会影响连网,但会让系统挂起后彻底睡死,属于典型「上线三天才发现」的问题,配的时候顺手用cat /proc/interrupts确认一下中断号有没有真的挂上。
3.2 蓝牙侧:UART 节点、硬件流控与 shutdown GPIO
蓝牙节点挂在对应的 UART 控制器下面,用brcm,bcm43438-bt这个 compatible 让hci_uart的btbcm协议驱动接管。shutdown-gpios就是BT_REG_ON,max-speed要和后面btattach用的波特率一致。
&uart1 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart1_pins>; bluetooth { compatible = "brcm,bcm43438-bt"; shutdown-gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>; /* BT_REG_ON */ max-speed = <1500000>; /* 与 btattach 一致 */ clocks = <&rtc_32k>; clock-names = "lpo"; }; };max-speed这里有个细节:模组上电后先以 115200 握手,btbcm下发补丁成功后再切到max-speed指定的高波特率。如果 UART 控制器本身分频算不出精确的 1.5M,实际波特率偏差超过 2%,切速后就会失步,现象是hci0出现又消失。串口时钟源选 48MHz 或 24MHz 这种能被整除的,比事后调参省事得多。
UART 的引脚复用里,CTS/RTS 千万别漏配成普通 GPIO。漏了的话内核不会报错,只会在高吞吐时随机丢 HCI 包,扫设备时好时坏,非常难查。
3.3 内核配置清单与编译顺序
配置项分三组,漏一个就是白忙。Wi-Fi 组要CONFIG_BRCMFMAC、CONFIG_BRCMFMAC_SDIO、CONFIG_BRCMFMAC_PROTO_MSGBUF,以及底层的CONFIG_CFG80211、CONFIG_MMC、CONFIG_MMC_SDHCI。蓝牙组要CONFIG_BT、CONFIG_BT_HCIUART、CONFIG_BT_HCIUART_BCM,接了硬件流控就再加CONFIG_BT_HCIUART_3WIRE。
# 生成最小化配置后逐项确认,别只看 .config 里有没有,要看最终是否 =y scripts/config --module CONFIG_BRCMFMAC scripts/config --module CONFIG_BRCMFMAC_SDIO scripts/config --module CONFIG_BRCMFMAC_PROTO_MSGBUF scripts/config --module CONFIG_CFG80211 scripts/config --module CONFIG_BT_HCIUART scripts/config --module CONFIG_BT_HCIUART_BCM scripts/config --enable CONFIG_BT_HCIUART_3WIRE make olddefconfig grep -E "BRCMFMAC|HCIUART" .configolddefconfig这一步不能省,scripts/config只改符号,被依赖项不满足时符号会被静默关掉,.config里看着有、编译出来却没有。grep完确认是=y还是=m,brcmfmac建议编成模块,调的时候rmmod/modprobe就能反复试,不用每次重启。
编译顺序上,先只开 Wi-Fi 相关配置把wlan0跑出来,再加蓝牙。两套一起开,出问题时日志混在一起,判断成本翻倍——这是血泪经验,别问我怎么知道的。
4. 跑通验证:从 modprobe 到 wlan0 出包、hci0 能扫到设备
配置和节点都齐了,接下来是真正判断「通没通」的阶段。判据要硬:wlan0出现在/sys/class/net且能扫到 AP 并拿到 IP;hci0出现且bluetoothctl能扫到周边设备。中间任何一步失败,都在对应的日志层停下来查,别跳步。
4.1 固件与 nvram 的放置和命名
brcmfmac按芯片 ID 拼名字找文件,2.4G 单频这颗芯片对应的是brcmfmac43430-sdio.bin和brcmfmac43430-sdio.txt,法规表是brcmfmac43430-sdio.clm_blob。模组厂给的包一般叫fw_bcm43438a0.bin、nvram_ap6212.txt这种名字,直接拷进去驱动是不认的,得改名。
# 统一放到内核默认查找路径,按芯片 ID 命名 cp fw_bcm43438a0.bin /lib/firmware/brcm/brcmfmac43430-sdio.bin cp nvram_ap6212.txt /lib/firmware/brcm/brcmfmac43430-sdio.txt cp regulatory.clm_blob /lib/firmware/brcm/brcmfmac43430-sdio.clm_blob # 蓝牙补丁文件同样按芯片 ID 放 cp BCM43438A0.hcd /lib/firmware/brcm/BCM43438A0.hcd sync改名的依据是芯片 ID 而不是模组型号,同一颗芯片不同模组厂的产品,固件文件名是一样的。.txt文件本质是 nvram 参数表,驱动下载固件后会把这串键值写进模组内存,里面的macaddr、maxp2ga0、pa2ga0直接决定射频行为,所以它必须和固件同批。
如果板子上有多个无线器件,/lib/firmware/brcm/下会有多套同名规则的文件,dmesg里那句Direct firmware load for brcmfmac43430-sdio.bin failed with error -2就是没找到,-110则是找到了但下载超时,两者含义完全不同。
4.2 Wi-Fi 起链路的命令序列
驱动加载后先确认枚举,再看固件下载,最后才谈连网。这三步分开做,出问题容易定位。
# 1. 加载驱动并观察枚举与固件下载 modprobe brcmfmac dmesg | grep -iE "brcmfmac|mmc1" | tail -n 30 ip link show wlan0 # 2. 拉起接口、设置法规域、扫描 ip link set wlan0 up iw reg set CN iw dev wlan0 scan | grep -E "SSID|signal" | head -n 20 # 3. 连接并拿地址 wpa_passphrase YourSSID YourPassword > /etc/wpa_supplicant.conf wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf udhcpc -i wlan0 ping -c 4 -I wlan0 223.5.5.5dmesg里正常应该能看到brcmfmac: brcmf_fw_alloc_request: using brcm/brcmfmac43430-sdio for chip BCM43430/1和brcmf_c_preinit_dcmds: Firmware: BCM43430/1之类的行,说明固件已经进到模组里跑起来了。只枚举没这行,说明卡在固件下载。
iw reg set CN不是可选项。法规域没设,驱动会按世界域限制信道和功率,扫描列表很短,看起来像「天线不行」。设完再扫,如果列表还是空,才去查天线和匹配电路。
wpa_supplicant用-B后台跑,调试阶段建议去掉-B前台跑,日志直接打屏,四次握手失败、密码错、AP 拒绝这些一眼就能看到。udhcpc是 BusyBox 环境的写法,桌面发行版用dhclient。
4.3 蓝牙上电与 hciattach
蓝牙侧在设备树正确的前提下,内核启动时会自动把hci0建出来。如果没有,用btattach手动绑一下串口,把问题范围缩小到「是驱动没接管」还是「串口本身不通」。
# 手动把 HCI 绑到串口,用于定位问题 btattach -B /dev/ttyS1 -S 1500000 -P bcm & # 验证控制器与扫描 hciconfig -a bluetoothctl <<'EOF' power on agent on default-agent scan on EOF-S 1500000必须和设备树里的max-speed一致,不一致会在切速后失步。-P bcm指定用btbcm协议去下发补丁,去掉这个参数就退化成裸 H4,芯片版本读不出来,扫描也没结果。
hciconfig -a输出里重点看BD Address和Manufacturer。地址是全 0 说明 nvram 或补丁没生效,制造商标识为空说明.hcd没下发成功。这两条能覆盖大部分蓝牙「能出节点不能干活」的情况。
scan on之后要给它十几秒,蓝牙扫描是分窗口的,秒出结果反而可疑。扫不到任何设备时,先用手机端能看到的设备做个对照,排除环境里本来就没东西的可能。
5. 避坑与排查:五类现场翻车记录,从卡不枚举到只出 hci0
下面这几条是我在不同板子上真实遇到过的,现象、原因、解决办法按顺序写,遇到对应症状直接对号入座就行。
第一条:SDIO 完全不枚举,dmesg里连超时都没有。
现象是ls /sys/bus/mmc/devices/是空的,dmesg | grep mmc只有控制器注册成功的信息。原因八成是WL_REG_ON没拉起来,或者电源序列节点没被控制器引用。先拿万用表量模组使能脚,上电瞬间应该是高电平;再看设备树里mmc-pwrseq属性有没有漏写,写了但 pwrseq 节点里reset-gpios有效电平反了,等于一直按着复位。
解决办法是先绕过驱动,在 bootloader 或内核早期把WL_REG_ON手动拉高,然后echo 1 > /sys/.../mmc1/...触发一次重新扫描。能枚举说明软件问题,不能就是硬件供电或走线问题,别在设备树里死磕。
第二条:卡能枚举,但固件下载失败,报-2或-110。
-2是文件没找到,检查/lib/firmware/brcm/下的命名是否严格等于brcmfmac<chipid>-sdio.bin,用dmesg里打印的芯片 ID 去比对,别看模组型号。-110是超时,常见于 SDIO 时钟太高、走线太差,把max-frequency从 50MHz 降到 25MHz 试一轮,能过就说明是信号完整性问题。
还有一种隐蔽情况:文件名对了,但拷进去的固件是从另一版驱动包里拿的,版本不匹配。表现是固件下载成功、wlan0也出来了,一扫描就 oops。换回同批发布的固件和 nvram,立刻正常。
第三条:wlan0正常,扫描列表是空的或者只有一两个 AP。
先排除法规域,iw reg get看当前域,没设就iw reg set CN再扫。再排除天线,模组天线脚到天线座的匹配电路如果虚焊,接收灵敏度会掉十几 dB,近距离 AP 能扫到、远一点就没有。最后才怀疑 nvram 里的 PA 参数,但那通常是发射侧问题,接收几乎不受影响。
还有一种情况是 MAC 地址非法,比如全 0 或全 F。部分 AP 会直接拒绝关联,现象是扫描有列表、连不上。到 nvram 里把macaddr改成合法值,或者在设备树节点上加local-mac-address覆盖。
第四条:蓝牙只出hci0,bluetoothctl扫不到任何设备。
先看hciconfig -a的BD Address。全 0 说明补丁没下发,检查.hcd文件名是否和芯片 ID 一致;有地址但扫不到,检查 UART 的 CTS/RTS 有没有真的接上并复用正确。流控没接时,低速率下勉强能跑,一扫描就丢包,表现就是「偶尔能扫到一个」。
再往下就是 LPO 时钟,蓝牙侧对 32.768kHz 依赖比 Wi-Fi 更重,时钟缺失时初始化能过,但射频校准过不去。这类问题日志里往往没有明显报错,只能靠示波器量时钟脚,属于硬件问题,软件层面没有后悔药。
第五条:能连网,但系统挂起后再也醒不过来,或者唤醒后 Wi-Fi 掉线。
WL_HOST_WAKE那根中断配错是首因。设备树里interrupts的属性要和原理图上的 GPIO 编号、触发沿都对应上,配完用cat /proc/interrupts确认中断计数在挂起/唤醒过程中有变化。没变化就是没接上。
另外keep-power-in-suspend和cap-power-off-card这两个属性要按需组合。前者保持模组供电,唤醒快但待机功耗高;后者允许关电,省电但唤醒要走完整重新初始化流程。做低功耗产品时这两项的选择直接影响续航,别照抄别人的设备树。
6. 二次开发:改 MAC、调功率、做上电自恢复
把链路跑通只是起点,产品化阶段真正要动的是参数和容错。
6.1 nvram 里哪些参数能改、哪些别碰
macaddr可以改,而且建议改。批量生产时每台设备的 MAC 必须唯一,做法是在产测工位上把 MAC 写进 nvram 的macaddr字段,或者写进 SoC 的 efuse,由 bootloader 通过local-mac-address传给驱动。前者改动简单,后者更抗篡改。
maxp2ga0、pa2ga0这类 PA 参数不要动。它们是模组厂按射频前端标定出来的,改大了一点可能过认证指标,改小一点灵敏度直接掉档。真要调整发射功率,用iw dev wlan0 set txpower limit <dBm>在运行时限,别去改 nvram。
sd_gpio、bt_shutdown_gpio这类 GPIO 描述字段,如果设备树已经描述清楚了,nvram 里就不要再写一份,两处冲突时以谁为准在不同驱动版本里行为不一致,属于给自己埋雷。
6.2 上电自恢复脚本
产线偶发问题里,SDIO 枚举失败占的比例不低。与其让用户返修,不如在启动脚本里加一层检测和重试。
#!/bin/sh # 启动后检测 wlan0,未出现则重拉使能脚并重载驱动,最多重试 3 次 i=0 while [ $i -lt 3 ]; do if [ -e /sys/class/net/wlan0 ]; then logger -t wifi "wlan0 ready after ${i} retries" exit 0 fi logger -t wifi "wlan0 missing, retry $i" echo 0 > /sys/class/gpio/gpio12/value 2>/dev/null sleep 1 echo 1 > /sys/class/gpio/gpio12/value 2>/dev/null sleep 2 modprobe -r brcmfmac 2>/dev/null modprobe brcmfmac sleep 3 i=$((i + 1)) done logger -t wifi "wlan0 not available, give up" exit 1这段脚本的核心是「断使能、等放电、重新上电、重载驱动」这个顺序。少了中间那 1 秒的放电等待,模组内部电容没放干净,重新上电等于没断,重试也是白试。脚本退出码留给上层 init 系统判断,失败时上报而不是静默继续,产线才能统计出真实不良率。
6.3 出厂验证清单
| 检查项 | 命令 | 通过判据 |
|---|---|---|
| SDIO 枚举 | ls /sys/bus/mmc/devices/ | 出现对应 mmc 设备 |
| 固件下载 | dmesg | grep brcmf_fw | 输出固件版本行 |
| 接口存在 | ip link show wlan0 | 状态 UP |
| 关联与 DHCP | udhcpc -i wlan0 | 拿到非 169.254 地址 |
| 蓝牙控制器 | hciconfig -a | BD Address 非全 0 |
| 蓝牙扫描 | bluetoothctl scan on | 能列出至少一个已知设备 |
| 挂起唤醒 | echo mem > /sys/power/state | 按键或定时可唤醒且 wlan0 存活 |
这张表我在每个新板子上都会从头跑一遍,尤其是最后一行,前六项都过、挂起醒不过来,产品照样不能用。
我现在的习惯是:每次拿到一块新板子,先不碰设备树,用万用表和示波器把使能脚、LPO 时钟、UART 流控这三处量一遍,确认硬件侧没毛病再动软件。听起来慢,但比在驱动日志里猜三天快得多。希望帮到你。
本文还有配套的精品资源,点击获取