从MTK到RKAW88195音频驱动移植实战全解析最近在做一个基于瑞芯微PX30Android 8.1的智能音箱项目客户指定要使用艾为的AW88195这颗高性能Codec来提升扬声器的音质和响度。拿到手的驱动包是厂商基于联发科平台适配的版本号V0.1.6。对于习惯了高通或原厂方案的工程师来说跨平台移植音频驱动听起来有点棘手但实际上只要理清Linux音频子系统的框架抓住几个关键节点整个过程会清晰很多。这篇文章我就以这次真实的RK平台移植经历为蓝本拆解每一步操作分享其中遇到的“坑”和解决方案目标是让任何一位具备基础Android驱动开发经验的工程师都能独立完成类似的移植工作。1. 移植前的准备与内核框架理解在动手拷贝文件之前花点时间理解Linux ALSAAdvanced Linux Sound Architecture的架构是值得的。简单来说一个完整的音频播放链路涉及三个核心驱动Platform Driver、Codec Driver和Machine Driver。Platform Driver通常由SoC厂商提供负责管理SoC内部的音频接口控制器比如I2S、PCM、SPDIF等。在RK平台上它对应的是rockchip_i2s.c这类文件。Codec Driver这就是我们今天的主角由芯片厂商如艾为提供负责控制外部的音频编解码芯片实现数模转换、音量调节、EQ等音效处理。AW88195的驱动就属于这一类。Machine Driver它是“粘合剂”负责将特定的Platform和特定的Codec配对起来描述它们之间的连接方式如使用哪个I2S端口、数据格式是什么。RK内核常用simple-card.c作为通用的Machine驱动它通过解析设备树Device Tree来动态建立连接。移植的核心就是将MTK平台适配的Codec Driver集成到RK的Kernel编译体系中并通过Machine Driver正确地与RK的Platform Driver关联起来。这就像给一台新电脑安装一个旧打印机需要找到正确的驱动安装位置并配置好连接端口。注意不同Android版本的内核目录结构可能有细微差异本文以Android 8.1的常见内核结构为例。实际操作前请先确认你项目所用内核的准确路径。2. 驱动文件集成与Kconfig/Makefile配置厂商提供的AW88195_Driver_MTK_V0.1.6.zip解压后里面通常包含.c、.h文件可能还有一些寄存器配置表或文档。我们的第一步是把这些核心代码放到内核的正确位置。2.1 创建专属目录并放置文件我习惯为每个外设驱动创建一个独立的目录便于管理。进入内核的Codec驱动目录cd kernel/sound/soc/codecs/ mkdir aw88195将驱动包里的aw88195.c、aw88195.h等关键源文件和头文件拷贝到新建的aw88195目录下。如果厂商代码中包含了针对MTK平台的特定适配文件例如aw88195-mtk.c你需要仔细评估哪些是通用逻辑哪些是平台相关代码。通常平台相关的初始化、电源管理或GPIO控制部分需要调整。2.2 编写目录内的Kconfig和Makefile在aw88195目录下需要创建两个文件来告诉内核编译系统这个驱动的存在。Makefile定义要编译哪些目标文件。# kernel/sound/soc/codecs/aw88195/Makefile snd-soc-aw88195-objs : aw88195.o aw88195-reg.o # 列出所有需要编译成模块的 .o 文件根据你的实际文件调整 obj-$(CONFIG_SND_SOC_AW88195) snd-soc-aw88195.o这里snd-soc-aw88195-objs指定了最终模块snd-soc-aw88195.ko由哪些目标文件链接而成。Kconfig为这个驱动创建一个配置选项方便在make menuconfig时进行选择。# kernel/sound/soc/codecs/aw88195/Kconfig config SND_SOC_AW88195 tristate ASoC support for AWinic AW88195 Smart PA depends on I2C SND_SOC help Say Y or M if you want to add support for AW88195 Codec. This driver provides support for AWinic AW88195 Smart K PA with I2C control interface.tristate表示可以编译成模块M、内置Y或不编译N。depends on指明了依赖AW88195通常使用I2C总线控制并且依赖于ALSA SoC核心框架。2.3 修改上级目录的编译配置光有自己的目录还不够需要让上一级的编译系统知道这个新目录。返回上级目录kernel/sound/soc/codecs/修改这里的Makefile和Kconfig。在codecs/Makefile末尾添加obj-$(CONFIG_SND_SOC_AW88195) aw88195/在codecs/Kconfig文件中找一个合适的位置通常在其他Codec配置附近添加source sound/soc/codecs/aw88195/Kconfig2.4 配置内核Defconfig为了让驱动默认被编译进内核需要修改你项目所使用的内核默认配置文件。对于RK PX30这个文件通常是kernel/arch/arm64/configs/rockchip_defconfig或类似名称。在文件末尾或CONFIG_SND_SOC_*相关的区域添加CONFIG_SND_SOC_AW88195y这里的y表示直接编译内置而不是模块。完成以上步骤后你可以尝试编译内核确保没有语法错误。执行make ARCHarm64 rockchip_defconfig然后make ARCHarm64 -jNN为你的CPU核心数。如果编译成功说明驱动代码已经成功集成到内核构建系统中。3. 设备树DTS配置与Machine Driver连接驱动能编译通过只是第一步更重要的是让系统在运行时能识别并初始化这个设备。这主要通过设备树来完成。设备树描述了硬件拓扑结构对于音频来说就是描述I2C总线上的AW88195设备以及它如何连接到SoC的I2S接口。3.1 配置I2C设备节点首先需要根据硬件原理图找到AW88195连接在哪一条I2C总线上例如i2c1。在对应的DTS文件如kernel/arch/arm64/boot/dts/rockchip/px30-xxx.dtsi或具体的板级.dts文件中找到该I2C控制器的节点并在其中添加AW88195的子节点。i2c1 { status okay; clock-frequency 400000; // I2C速率根据芯片要求设置 aw88195: aw8819534 { // 34 表示芯片的7位I2C地址需确认是0x34还是0x35等 compatible awinic,aw88195; reg 0x34; reset-gpio gpio2 RK_PB7 GPIO_ACTIVE_LOW; // 复位引脚根据实际硬件连接修改 irq-gpio gpio2 RK_PC0 GPIO_ACTIVE_HIGH; // 中断引脚如果有的话 #sound-dai-cells 0; status okay; // 可选的初始化寄存器配置有时会放在这里或通过固件加载 awinic,monitor-flag 1; }; };关键属性解释compatible必须与驱动中of_device_id表里定义的字符串匹配这是驱动和设备节点绑定的关键。regI2C从设备地址。reset-gpio、irq-gpio根据实际硬件连接配置在驱动中会用来申请和控制GPIO。#sound-dai-cells 0这是一个重要的属性它告诉ASoC框架这个Codec节点在作为sound-dai引用时不需要额外的参数。3.2 配置音频路由Simple Audio CardRK平台常用simple-audio-card作为Machine驱动。我们需要在DTS中定义一个声卡将CPU端的I2SPlatform和Codec端的AW88195连接起来。在DTS文件的根节点或sound节点下添加sound { compatible simple-audio-card; simple-audio-card,name aw88195-sound; simple-audio-card,format i2s; simple-audio-card,mclk-fs 256; // 主时钟与采样率倍数关系根据芯片手册设置 simple-audio-card,widgets // 可选定义音频路径组件 Microphone, Mic Jack, Headphone, Headphone Jack; simple-audio-card,routing // 可选定义组件连接关系 Headphone Jack, HPOL, Headphone Jack, HPOR; simple-audio-card,dai-link0 { format i2s; bitclock-master codec_dai; frame-master codec_dai; cpu { sound-dai i2s0_8ch; // 指向SoC的I2S控制器节点 }; codec_dai: codec { sound-dai aw88195; // 指向前面定义的aw88195节点 }; }; };这里sound-dai属性就像两根“数据线”分别插到了CPU的I2S接口和Codec芯片上。bitclock-master和frame-master指定了时钟的主从模式这里设为Codec主模式具体需参考硬件设计。4. 固件加载与系统集成AW88195作为一款智能功放通常需要加载专用的DSP固件aw88195_x.bin来运行其音效算法。这是移植中最容易出问题的一环。4.1 内核固件查找路径内核通过request_firmware()等API加载固件。它会在一系列预定义的路径中搜索固件文件。我们需要确保固件被放置在这些路径之一。常见的路径在kernel/drivers/base/firmware_class.c中定义static const char * const fw_path[] { fw_path_para, /system/vendor/firmware, /system/etc/firmware, /vendor/firmware, /lib/firmware/updates/ UTS_RELEASE, /lib/firmware/updates, /lib/firmware/ UTS_RELEASE, /lib/firmware };对于Android系统我们通常将固件放在/vendor/etc/firmware/目录下。4.2 在驱动中请求固件在AW88195驱动的探测probe函数或初始化后期需要添加固件请求代码。厂商驱动通常已经实现了这部分逻辑你需要检查其固件文件名和请求逻辑是否与你的固件匹配。// 示例代码片段 static int aw88195_fw_load(struct aw88195 *aw88195) { const struct firmware *cont NULL; int ret; ret request_firmware(cont, aw88195_x.bin, aw88195-dev); if (ret 0) { dev_err(aw88195-dev, load firmware failed); return ret; } // ... 解析并下发固件数据到芯片 ... release_firmware(cont); return 0; }4.3 将固件打包进系统镜像最后需要确保编译系统能将固件文件拷贝到镜像的对应目录。在Android的device.mk或你的产品专属*.mk文件中添加PRODUCT_COPY_FILES \ vendor/awinic/aw88195/firmware/aw88195_x.bin:$(TARGET_COPY_OUT_VENDOR)/etc/firmware/aw88195_x.bin这样在编译系统时固件就会被自动打包到/vendor/etc/firmware/目录下。5. 调试、验证与常见问题排查编译并烧录新的内核和系统镜像后真正的挑战才开始。以下是一些关键的验证步骤和问题排查方法。5.1 基础系统状态检查检查设备节点系统启动后首先查看I2C设备是否被识别。adb shell ls /sys/bus/i2c/devices/应该能看到类似1-0034的目录取决于你的I2C总线号和地址。检查驱动加载adb shell lsmod | grep aw88195或者查看内核日志adb shell dmesg | grep -i aw88195观察驱动probe函数是否被调用是否有错误信息。5.2 声卡与PCM设备注册成功验证这是最关键的一步证明Machine Driver成功将Platform和Codec链接了起来。adb shell cat /proc/asound/cards adb shell cat /proc/asound/pcm如果成功你会在cards中看到新增的声卡如aw88195-sound在pcm中看到对应的playback和capture设备。例如00-00: ff060000.i2s-aw88195-aif aw88195-aif-0 : : playback 1 : capture 05.3 使用Tinyplay进行底层音频测试在确认声卡注册成功后先不要急于测试上层应用。使用Android自带的tinyplay工具进行最底层的音频回路测试可以排除HAL硬件抽象层及以上层的干扰。adb push test.wav /data/ adb shell cd /data tinyplay test.wav -D 0 -d 0 # -D 指定声卡号-d 指定PCM设备号根据/proc/asound/pcm输出调整如果此时能听到声音恭喜你驱动移植的核心部分已经成功如果没声音检查时钟和格式DTS中simple-audio-card,format和mclk-fs是否与芯片要求一致I2S主从模式设置是否正确电源和复位检查驱动中电源管理序列确认复位引脚的控制时序是否符合芯片上电要求。用万用表或示波器测量芯片的供电和复位引脚波形。I2C通信使用i2cdetect工具确认I2C总线是否能探测到芯片地址。在驱动中增加I2C读写寄存器的调试打印确认通信是否正常。固件加载查看内核日志确认固件请求是否成功固件数据是否被正确下发。有时固件文件名或路径不匹配会导致静默失败。5.4 上层音频通路测试底层tinyplay测试通过后再进行上层播放如使用音乐APP。如果上层没声音问题可能出在Audio HAL或Audio Policy配置上。你需要检查audio_policy.conf和audio_effects.conf等配置文件是否正确引用了新的声卡和设备。Audio HAL是否为新声卡实现了正确的audio_hw_device操作集。使用dumpsys media.audio_flinger和dumpsys media.audio_policy命令查看音频策略和焦点情况。5.5 调试利器内核日志与Ftrace遇到复杂问题时善用调试工具动态日志在驱动代码的关键函数probe、i2c读写、硬件操作、中断处理中加入dev_dbg()或pr_debug()并通过adb shell echo file aw88195.c p /sys/kernel/debug/dynamic_debug/control动态开启调试信息。Ftrace可以跟踪函数调用图、中断延迟、调度情况对于分析音频播放过程中的卡顿、断流问题非常有效。整个移植过程就像是在一个复杂的乐高系统中插入一个新的定制模块。最花时间的往往不是步骤本身而是对每一个环节状态的确认和异常现象的推理。我这次移植在固件加载和I2S主从时钟模式上各卡了大半天最后都是通过最基础的信号测量和寄存器打印找到了问题根源。所以当你觉得走投无路时不妨回到硬件信号和最基本的数据通信层面一步步验证问题总会水落石出。