1. 为什么sa522上的tac5x1x适配成了硬骨头
先交代一下背景。最近接手了一个项目,主控平台是高通的SA522系列,板子上挂的是一颗型号为tac5x1x的音频编解码芯片。任务看起来很简单:把驱动适配好,让音频通路能跑起来。结果这个"看起来简单"的活,前前后后折腾了好几周。
如果你之前在某平台调过tac5x1x,你可能会觉得这芯片挺成熟,代码应该拿来就能用。但在SA522这种相对较新的平台上,事情没那么顺。最大的问题不是芯片本身,而是平台差异——SA522的音频框架、时钟管理、I2S/音频总线配置,和通常那套Linux+ALSA的通用套路有不少出入。再加上tac5x1x这颗芯片的驱动对外部时钟依赖非常重,一个时钟没配好,出来的声音就是花屏级的噪音。
这篇文章不是给你贴代码的,代码在官方包里都有。我想讲的是:接到这个任务后,你是怎么一步步把驱动从"能编译"做到"能出声",再到"声音不出问题"的整个过程。里面包括我踩过的坑、排查的思路、调试工具的使用方法,以及一些在文档里很难找到的细节。
适合谁看?如果你也是做嵌入式Linux音频驱动,而且用的是高通平台(不管是SA522还是类似的SA系列),这篇可以帮你省下至少一周的排查时间。即便你用的不是高通平台,看看这套"时钟先行、DTS为准、逐步验证"的适配思路,也会有启发。
2. tac5x1x驱动适配前的平台摸底
2.1 确认为什么不能直接照搬
tac5x1x的驱动代码在芯片厂商的BSP里一般是齐全的,ALSA驱动、DSP配置、寄存器头文件都有。理论上,你把它加到内核里,配置好I2C地址,应该就能工作。
但实际情况下,高通平台的音频架构和"通用"Linux音频架构有一个显著区别:高通平台通常使用PCM/ADSP音频框架,ALSA这一层只是壳,真正的音频路由、混音、时钟控制都在ADSP的音频DSP里完成。所以你在ALSA层能看到声卡,能看到PCM设备节点,但声音通路是否真正连通,取决于DSP侧的拓扑配置。
这意味着,驱动适配不只是"把tac5x1x的codec驱动编译进去",而是要同时确认:
- 音频DSP侧的拓扑(topology)是否包含了这颗codec的路径
- I2S/串行总线的时钟是否按codec的要求配置
- MCLK/BCLK/LRCK的频率和相位是否匹配
- codec的I2C控制通道是否正常
我第一版直接把厂商BSP里的驱动拉进来编译,理论上没有任何问题,但录音播放都是无声。后来才意识到:这颗codec的驱动虽然加载了,但整个音频图(audio graph)里根本没有它的位置。
2.2 音频树的完整梳理
所以在动手适配之前,我花了半天把SA522平台的音频树结构画了出来。简化的框图是这样的:
- CPU端:SA522的音频DSP负责音频数据处理,通过SLIMBUS或I2S接口与外部codec连接。我们项目用的是I2S。
- 总线层:I2S有几组,每组有独立的MCLK、BCLK、LRCK引脚,引脚可以复用。
- codec层:tac5x1x挂在I2C上,地址通常是0x18或者0x1A(看硬件配置),音频数据走I2S。
- 系统层:整个音频链路由DSP拓扑配置决定,混音器控件(kcontrol)控制通路的开关。
画完这个之后,适配的重点就很清楚了:不是只要把codec驱动弄好,而是要让"CPU侧I2S端口 + DSP拓扑 + codec驱动"三者对齐。
3. 驱动迁移的第一步:DTS设备节点
3.1 dtsi中tac5x1x节点应该怎么写
tac5x1x的驱动适配,第一步是设备树。我当时在SA522的dtsi里加了这么一段(伪代码,具体寄存器地址以你的硬件原理图为准):
&i2c3 { status = "okay"; tac5x1x: audio-codec@18 { compatible = "tac,tac5x1x"; reg = <0x18>; reset-gpio = <&tlmm 42 0>; interrupt-parent = <&tlmm>; interrupts = <43 IRQ_TYPE_EDGE_RISING>; clock-names = "mclk"; clocks = <&audio_ext_mclk>; sound-dai-name = "tac5x1x-codec"; #sound-dai-cells = <0>; }; };有些细节值得注意。reg地址很关键,如果写错,驱动就会在i2c_check_functionality或者regmap_read阶段卡住。硬件原理图上如果标注的是0x1A,就要写0x1A,不要凭经验猜0x18。另外reset-gpio如果不配,有些版本的驱动会在初始化时跳过本来该做的硬件复位,导致codec内部状态异常。
如果你用的内核版本较老,#sound-dai-cells这个属性可能不需要,要根据BSP里codec驱动的绑定逻辑决定。最好的办法是:去看厂商BSP里参考平台的dts,把那边的写法原样搬过来,再改引脚。
3.2 时钟配置是dts里的重头戏
tac5x1x对外部MCLK的要求非常高。数据手册一般会给出一个MCLK频率范围,比如4.096MHz或者12.288MHz——具体取决于采样率。但SA522平台的MCLK不是随便就能供给的,需要确认这颗MCLK是从哪个GPIO出来的,时钟源又是哪颗。
我们的板子上,MCLK是从某个音频PLL分出来的,dts里需要显式配置时钟频率。常见做法是在machine driver里用clk_set_rate设置,或者在dts里关联固定频率的时钟。
实用的检查方法:
在系统起来后,进入debugfs看时钟树:
cat /sys/kernel/debug/clk/clk_summary | grep -i mclk如果看到clk_mclk的enable_count大于0,而且rate是283MHz之类的"无效值",那说明时钟虽然启用了,但频率设定不对。这时候即使codec驱动能用I2C控制,I2S传输的数据也会因为时钟不匹配导致完全没有声音或者爆音。
4. 内核态驱动的编译与注册确认
4.1 把tac5x1x驱动编进内核
驱动代码一般以模块或编入内核的方式存在。为了调试方便,建议先编成模块,这样在运行时可以加载/卸载,也能用modprobe手动指定参数。比如:
make ARCH=arm64 CROSS_COMPILE=你的交叉编译器 -j8编完之后你会得到一个tac5x1x.ko。用adb push或者挂载rootfs替换的方式放到目标板上,然后:
insmod /vendor/lib/modules/tac5x1x.ko如果成功,dmesg里应该能看到tac5x1x: probe success或者类似的日志。如果看到tac5x1x: Failed to request reset GPIO,那就说明dts里的reset-gpio配置有误,或者gpio号在编译时就没解析到。
4.2 machine driver与sound card的绑定
在SA522平台上,仅仅有codec驱动是不够的,还需要一个machine driver来把CPU侧DAI和codec侧的DAI绑定在一起。这个machine driver的匹配是通过设备树里的compatible来完成的。
比如你的sound card节点:
sound { compatible = "qcom,sa522-audio-cards"; model = "sa522-tac5x1x-card"; qcom,audio-routing = "IN1", "MIC BIAS1", "SPK", "OUT1"; };machine driver在probe过程中会遍历codec节点(通过sound_dai属性指向),然后调用snd_soc_register_card。如果codec侧的#sound-dai-cells配错,或者节点不匹配,内核日志里会出现类似ASoC: failed to link DAI ...的错误。
我把这个阶段的经验总结成了一张自查表:
| 检查项 | 正常时的表现 | 异常时的常见日志 |
|---|---|---|
| I2C通信 | tac5x1x出现在i2cdetect列表 | regmap: Failed to read |
| 时钟开启 | clk_summary里MCLK正常且频率正确 | clk_enable failed |
| DAI链接 | ASoC链路注册成功 | ASoC: failed to link DAI |
| 声卡创建 | /proc/asound/cards出现新声卡 | snd_soc_register_card fail |
| PCM设备 | 播放录音节点存在 | Unable to create PCM device |
4.3 ALSA控件与通路路由的验证
驱动注册成功并不代表声音通路是通的。tac5x1x作为codec,需要把它的几个关键kcontrol打开或调到合适的值,比如"DAC Volume"、"ADC Volume"、"PGA Gain"等。在调通之前,我通常先用tinymix看看这些控件的当前值:
tinymix然后逐步设置:
tinymix set "DAC Volume" 85 tinymix set "ADC PGA Gain" 65如果你用的是高通平台,还可以配合tinymix -D查看所有声卡。确认控件存在并且能更新值,说明codec侧驱动和硬件之间的交互正常。如果控件列表为空,那问题大概率出在驱动注册链路,而不是声音数据通路。
5. 踩坑环节:无声、破音、录不上这三座大山
5.1 无声问题排查的完整链路
先讲讲最典型的场景:板子上电,声卡注册成功,tinymix能看到控件,播放歌曲也没有报错,但耳机里一片死寂。
我不会第一时间去看驱动源码,因为这种"无声但不报错"的问题,绝大多数不是驱动bug,而是链路中某个时钟或者路由没打通。我的排查顺序非常固定:
从DSP侧的音频路由查起。用高通平台自带的方式,确认当前的modemSSR/ADSP状态是否正常,所有PD(power domain)有没有拉起。有时候ADSP起不来,声卡照样注册,但数据流根本走不到I2S。
查看I2S总线的实际电平。用示波器或者逻辑分析仪去抓BCLK、LRCK、MCLK的波形。这一步是决定性的,能够在五分钟内判断出时钟有没有送出来。因为不是每次都能拿到示波器,所以我先在debugfs里看时钟状态,如果clk_summary显示MCLK的rate不对,就先解决时钟源,再往下查。
切换到ALSA调试层面,用
tinyplay或者tinycap播放一个1kHz正弦波,然后抓取PCM数据确认有没有数据从DSP送到总线。在Linux里,你可以打开PCM设备节点,用hrtimer或者/proc/asound/cardN/pcm0p/sub0等节点辅助判断状态。如果总线数据有,时钟也正常,那就要怀疑codec侧的寄存器配置了。用I2C总线工具直接读取tac5x1x的寄存器,看DAC是否处于非静音状态,MCLK是否在codec内部被启用。很多codec都有个
POWER_DOWN或者SUSPEND位,驱动在初始化时如果没清,那播放到天荒地老也没声音。
排查到这里,死寂的问题基本都能定位了。在我这次的适配中,最终问题出在时钟树上——MCLK虽然送出去了,但被ADSP重采样后频率变成了一个奇怪的数值,经过clk_set_parent调整后解决。
5.2 破音的成因为数不多但非常隐蔽
破音比无声更难搞,因为它意味着链路是通的,但某个环节信号质量出了问题。
一种情况是BCLK和LRCK的时序不匹配。比如BCLK在空闲时应该拉高还是拉低,LRCK左对齐还是右对齐,这些参数在codec驱动里一般是可以配置的。但高通平台这套机器驱动可能默认了一套参数,如果和tac5x1x不一致,就会导致采样数据错位,表现出来就是刺耳的破音。
我采用的验证方法是:在DSP侧导出一个已知的PCM数据文件,直接通过I2C/tinymix把codec改成Bypass模式对比。具体是这样:如果codec支持Bypass,直接把输入信号绕到输出,如果Bypass模式下破音依旧,说明是模拟前端或总线时序问题;如果Bypass正常而DSP通路破音,则方向在DSP算法侧。
其实还有一种容易被忽略的破音源——电源纹波。tac5x1x是高精度音频DAC,对AVDD比较敏感,如果板子上电源纹波大,破音是必然的。这种问题你会误以为是驱动配置问题,排查半天。所以当你确认寄存器配置无误、时序也正常,但破音依然存在,记得拿示波器看供电引脚上的纹波,尤其是播放高幅值音频的时候。
5.3 录音通道不通:不是简单翻转一个开关
录音比播放更容易出问题。因为播放链路是从DSP到codec再到耳机/喇叭,而录音是从麦克风到codec到DSP,多了模拟前端和增益的控制,而且和Mic Bias的关系很大。
tac5x1x通常支持多路模拟输入,每一路都带PGA(可编程增益放大器)。录音没声的第一个排查点就是PGA有没有开启,第二个是Mic Bias有没有上拉,第三个是DSP拓扑中是否有录音路径。
在高通平台上,录音分前端cap和后端cap,有的拓扑默认只有前端捕获,没有绑定后端,所以即使录音节点存在,也是没有数据的。当时的解决办法是在machine driver里明确配置qcom,routing加入录音路径,或者直接使用高通自带的音频路由工具切换到正确的录音场景。
我记得那次调试录音的时候,用了好长时间看PGA寄存器,后来才发现是DSP侧的Tx path根本没有被打通。因此在适配阶段,不要只看codec这半边,要打开高通的音频日志,比如在某个节点上设置/d/audio_route/...等方式来确认DSP侧的路由。
6. 从能出声到声音正常:DSP拓扑与ASoC框架之间的契约
6.1 理解高通平台的拓扑加载机制
当codec驱动和machine driver都正常,你有声卡也有通路,但录音出来的声音很小或者出现回声,这时候要考虑DSP侧拓扑。SA522平台的拓扑文件通常是一个或多个.bin格式的音频拓扑文件,在启动时被加载到ADSP侧。这些拓扑文件描述了音频数据通路中的处理组件(比如EQ、DRC、混音器、音量控制等),以及它们之间的连接关系。
tac5x1x作为外部codec,在拓扑中通常体现为一个I2S终端接口。如果拓扑文件中这个接口没有连接到你需要的PCM设备,那么即使你在ALSA层打开节点,实际数据流也不会走到codec。
查看已加载拓扑的方法是:
cat /sys/kernel/debug/remoteproc/remoteproc0/resources或者用高通提供的工具,查看ASoC拓扑控件列表:
tinymix -D 1 | grep -i "tac"如果看到类似tac5x1x DAC控件存在,说明拓扑中包含了对应通路。如果没有,那问题是拓扑文件没有包含tac5x1x相关路径——这时候你需要修改音频拓扑,加入I2S网关和对应的PCM映射。
6.2 声学参数的适配经验
同样一颗tac5x1x,在不同板卡上的声学参数差异很大。如果你做的是智能音音箱或者语音方案,还要注意Speaker Protection、回声消除等功能的参数。我们这次做一个语音交互方案,所以需要把AEC和NS参数配到合理范围,不然在通话测试时,对方能听到自己的回声。
具体到驱动适配层面,你需要关注:
- codec的采样率支持范围(tac5x1x通常支持48k/96k/192k,但某些时钟配置下只稳定在48k)
- MCLK与采样率之间的倍频关系
- codec的DAC输出增益和模拟输出级的配置,避免失真
- 如果外接了功放,需要配置gpio(如
spk_enable、pa_gpio)并在machine driver里处理
这些参数在初期可以先用保守值(比如DAC音量设到80%左右,PGA增益设到20dB),等声学调试阶段再细调。我的习惯是:先把通路调通,再用逐级扫描的方式确定稳定音量区间。
6.3 I2S格式的对齐真相
tac5x1x支持标准I2S、左对齐、右对齐以及DSP格式。不同平台上,声卡的硬件参数(HW params)可能控制了set_fmt的调用,但有时候因为拓扑中的格式受限,代码会走不到set_fmt的某个分支。
我当时调试时发现在播放PCM数据时,snd_soc_dai_set_fmt会被调用一次,但后续如果再打开录音,格式会被覆盖成默认值,导致播放和录音的格式不一致。这种问题往往就是机器驱动里的dai_link->dai_fmt字段配置不完整。正确的做法是显式地给每个dai_link设置好dai_fmt,例如:
static struct snd_soc_dai_link_component codec_dai = { .name = "tac5x1x-codec", }; static struct snd_soc_dai_link comp_dai = { .name = "sa522-i2s0", .stream_name = "TAC5X1X", .cpu_dai_name = "sa522-i2s0", .codec_dai_name = "tac5x1x-codec", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS, .init = comp_dai_init, };CBS_CFS表示codec作为从设备(即时钟由CPU侧提供),一般外挂codec都是这么配;如果codec是主设备,就反过来用CBM_CFM。搞反了的结果就是时钟相位不对,容易在边缘音频上出现掉字。
7. 适配期必用的调试命令与排查经验
这部分是我个人的调试工具箱,平时为了适配这种codec驱动,我建立了一套固定的流程,节约了很多时间。
7.1 一组高性价比的调试命令
确认驱动是否注册、I2C通信是否正常,冒烟测试阶段用这几条就够:
# 看声卡列表 cat /proc/asound/cards # 看I2C总线上挂了哪些设备 i2cdetect -y -r 3 # 直接访问codec寄存器 i2cset -y 3 0x18 0x00 0x11 i2cget -y 3 0x18 0x00 # 看dmesg中ASoC相关日志 dmesg | grep -i "asoc\|tac5x1x\|sa522" # 查看所有混音控件 tinymix调试播放录音通路时:
# 播放一个测试音频文件 tinyplay /data/1k_48k_stereo.wav -D 1 -p 64 -n 512 # 录制3秒钟的音频 tinycap /data/test.wav -D 1 -c 2 -r 48000 -b 16 -T 3用dmesg配合toggle追踪,还能看到dai_link的打开/关闭流程。
7.2 日志开关位与debugfs入口
Linux ALSA驱动中,snd_pcm调试可以通过动态debug(dynamic debug)开启,但高通平台更常用的是开启ADSP侧日志。有时候驱动适配明明是好的,但是声音进不了DSP,这种情况用ALSA侧日志很难看出问题,需要用高通平台特定的日志工具(比如某个日志守护进程)来抓ADSP侧的错误。
此外,在驱动源码里加临时的printk也是必要的。比如在tac5x1x_set_sysclk里面打印MCLK的频率,在tac5x1x_hw_params里面打印采样率和格式。别嫌丑,这是最直接的确认方式。当整个链路的每一级都打印出来的时候,问题出现在哪一段就非常清晰了。
7.3 一个高效的排查顺序建议
我总结了一套适合SA522+tac5x1x的排查顺序:
- 先看设备树节点是否被正确解析(
/sys/firmware/devicetree/base/...检查) - 再看驱动是否加载成功(
dmesg) - 确认I2C通信(
i2cdetect) - 确认声卡和控件是否存在(
/proc/asound/cards+tinymix) - 确认时钟校验(
clk_summary) - 用示波器确认时序波形
- 播放正弦波,用回环方式逐步定位
第6步的成本最高,但能直接终结一大类问题。如果你手头没有示波器,可以在I2S数据线和MCLK线上外接一个简单的逻辑分析仪,哪怕是几百块的USB逻辑分析仪,也能抓出明显的时钟异常。
8. 实测数据与性能观察
8.1 播放性能测试对比
适配完成后,我做了一组基本性能测试,以确认驱动稳定性和音质基础达标。
| 测试项 | 结果 | 备注 |
|---|---|---|
| 采样率支持 | 44.1kHz/48kHz/96kHz | MCLK分别配为11.2896MHz/12.288MHz/24.576MHz |
| 播放长时间压力 | 8小时连续播放,无卡顿、无声 | 使用标准48kHz/16bit音频 |
| 录音噪声底噪 | -95dBFS左右 | 16bit下表现良好 |
| 温度循环 | -20℃~70℃ 冷热冲击后功能正常 | 需要确认MCLK的温度漂移在可接受范围 |
功耗方面,tac5x1x在播放状态下实测电流比某些编解码方案高一些,但在SA522平台上表现正常。需要留意的是,如果进入低功耗模式,关闭MCLK时可能导致codec的状态丢失,唤醒后要重新初始化,这属于常见的电源管理适配工作。
8.2 性能瓶颈的观察
实际适配过程中,我发现I2S数据在极端情况下可能有总线冲突,因为SA522的I2S引脚可能和其他外设共用。如果板上同时跑蓝牙音频或者其它I2S设备,要确认interrupt或者DMA channel有没有冲突,或者GPIO的function有没有复用对。
有一次问题是:当DMA传输大量数据时,codec侧偶尔出现I2C NACK(无应答)。查到最后是I2C总线上挂了另一个设备,因为总线频率过快且距离较长,信号质量下降。解决办法是把I2C频率从400kHz降到100kHz,或者等长走线。所以适配不止是软件层面的事,遇到奇怪的硬件表现,也要敢于怀疑PCB走线和供电。
9. 最后,一份自用的适配checklist
每次做新的codec适配,我最后都会回到这个checklist,能有效避免中途遗漏一些细节。如果你也准备在SA522上做类似的事情,可以直接按这个列表来:
- 拿到原理图后,先确认codec的I2C地址、I2S引脚分配、MCLK来源和复位GPIO。
- 在dtsi中添加codec节点,
compatible要和驱动匹配,reg要和原理图一致。 - 编译驱动为模块,加载到系统后确认probe成功。
- 配置machine driver绑定CPU DAI和codec DAI,确保
dai_fmt正确。 - 用
tinymix逐项操作codec的控件,确认I2C读写正常。 - 检查时钟树,确认MCLK频率符合标准音频时钟。
- 播放测试音频,按从CPU到codec的层级逐步排查无声/破音。
- 录音测试,确认PGA、Mic Bias和DSP侧拓扑正常。
- 做长期播放和录音压力测试,观察有无偶发卡顿。
- 检查休眠唤醒流程,确认状态恢复无问题。
做完这些,基本可以认为tac5x1x在SA522平台的适配已经到了可以交付的阶段。当然,不同的音频场景(通话、AEC、喇叭外放、耳机输出)还需要针对具体配置再细致调优,但那已经不是"驱动适配"的范畴,而是声学系统调试的范畴了。反正对我个人而言,每一次和这种"平台细节"较劲的过程,都是对这块芯片和高通平台理解加深的机会。希望这篇文章能让你少走点弯路。