4、高通音频编解码器(WCD系列)驱动分析:WCD934x/WCD938x的内部框图、寄存器映射、以及MCLK/BCLK/LRCK时钟树
好,咱们今天来啃一块硬骨头——高通WCD系列的音频编解码器。说实话,WCD934x和WCD938x这两颗芯片,我在项目里摸爬滚打了四五年,踩过的坑能绕办公室一圈。今天我把核心的框图、寄存器映射和时钟树给你掰开揉碎了讲清楚。
4.1 WCD934x与WCD938x的内部框图
先看整体架构。WCD934x和WCD938x都是高通的旗舰级音频Codec,但定位略有不同。934x多见于骁龙845/855平台,938x则跟着骁龙865/888走。我个人的习惯是,拿到一颗新Codec,第一件事不是看寄存器,而是先看它的内部数据流。
这两颗芯片的内部框图,说白了就是三大部分:
- 模拟前端(AFE):负责麦克风输入、耳机输出、线路输入/输出。这里包含了可编程增益放大器(PGA)、ADC/DAC、以及各种混音器。
- 数字核心(Digital Core):这是大脑。包含I2S/TDM接口、数字混音器、采样率转换器(SRC)、以及各种数字信号处理模块(比如高通自家的Audioware)。
- 电源管理与时钟:这个区域最容易被人忽略,但恰恰是出问题最多的地方。包含LDO稳压器、电荷泵、以及整个时钟树。
我记得有一次,客户反馈播放音乐时有“噗噗”的噪声。我排查了三天,最后发现是模拟前端的PGA偏置电压没稳定下来。嗯,这种问题在框图上看不出来,但如果你对内部数据流足够熟悉,就能猜到是哪个模块在捣乱。
关键点:WCD938x相比WCD934x,最大的变化是增加了对更高采样率(384kHz)的支持,并且内部数字混音器的路数更多。如果你做的是Hi-Res音频产品,优先选938x。
4.2 寄存器映射:别被地址吓到
WCD系列的寄存器空间,通常通过SLIMbus或SoundWire总线访问。地址范围从0x00到0xFF,但别以为只有256个寄存器。实际上,每个地址背后可能是一个“页面”,页面内还有子寄存器。
我刚开始接触时,看到寄存器表就头大。后来我总结了一个规律:WCD的寄存器按功能模块分组。比如:
| 地址范围 | 功能模块 | 典型寄存器 |
|---|---|---|
| 0x00 - 0x1F | 芯片ID与版本 | CHIP_ID, CHIP_VERSION |
| 0x20 - 0x3F | 时钟与PLL控制 | MCLK_SEL, PLL_ENABLE |
| 0x40 - 0x5F | 模拟输入路径 | MIC_BIAS, PGA_GAIN |
| 0x60 - 0x7F | 模拟输出路径 | HPH_GAIN, DAC_VOL |
| 0x80 - 0x9F | 数字接口(I2S/TDM) | I2S_CFG, TDM_SLOT |
| 0xA0 - 0xBF | 数字混音器与SRC | MIXER_ROUTE, SRC_RATE |
| 0xC0 - 0xFF | 电源管理与中断 | LDO_CTRL, IRQ_STATUS |
你想想看,有了这个分组概念,调试时就能快速定位。比如耳机没声音,我肯定先去查0x60-0x7F的输出路径寄存器,而不是去翻时钟配置。
我的调试技巧:在驱动初始化时,把整个寄存器空间dump出来保存成文件。出问题时,对比正常和异常的dump,往往一眼就能看出哪个寄存器被意外修改了。我曾经靠这招,半小时就定位到一个第三方算法库偷偷改了DAC增益的问题。
4.3 MCLK/BCLK/LRCK时钟树:音频的命脉
时钟树,说白了就是音频数据的“心跳”。如果时钟乱了,音频数据就是一堆乱码。WCD系列的时钟树,我画过不下十次,每次画完都觉得——嗯,高通这帮人真能折腾。
先看三个时钟的定义:
- MCLK(主时钟):整个Codec的基准时钟。通常来自外部晶振(比如24.576MHz或22.5792MHz),也可以从SoC的GPIO输入。
- BCLK(位时钟):串行数据线上的时钟。每个BCLK脉冲对应一个数据位。48kHz采样率、16位精度、2声道时,BCLK = 48k * 16 * 2 = 1.536MHz。
- LRCK(左右声道时钟):也叫帧时钟。频率等于采样率。48kHz采样时,LRCK就是48kHz。
为什么会把这三个时钟放在一起讲?因为它们之间有严格的倍数关系。我见过太多工程师,配置I2S接口时只改了采样率,忘了同步调整BCLK分频比,结果出来的声音像鸭子叫。
WCD934x/938x的时钟树结构大致如下:
外部晶振 (24.576MHz)
|
v
PLL (锁相环) —— 生成内部MCLK (比如49.152MHz)
|
+--> 分频器1 --> BCLK (分频比由寄存器配置)
|
+--> 分频器2 --> LRCK (分频比 = BCLK / (位宽 * 声道数))
|
+--> 内部模块时钟 (比如DAC调制器、ADC抽取滤波器)
这里有个坑:WCD938x的PLL支持分数分频,而WCD934x只支持整数分频。什么意思呢?如果你要用44.1kHz采样率,WCD934x需要外部晶振是22.5792MHz,否则PLL锁不住。而WCD938x可以通过分数分频,从24.576MHz晶振直接生成44.1kHz所需的时钟。
避坑指南:我曾经在一个项目里,硬件工程师为了省成本,把WCD938x的晶振从24.576MHz换成了25MHz。结果PLL无论如何都锁不住,因为25MHz无法通过整数分频得到任何标准的音频采样率。最后只能换回原规格晶振。所以,晶振频率不要随便改,高通有推荐的晶振列表,照着选最省事。
在驱动代码里,时钟树的配置通常集中在几个关键寄存器:
// 伪代码示例:配置48kHz采样率的时钟树
// 假设外部晶振为24.576MHz
// 1. 使能PLL,设置倍频系数
wcd_write(reg_PLL_ENABLE, 0x01); // 使能PLL
wcd_write(reg_PLL_MULT, 0x02); // 倍频2倍,得到49.152MHz
// 2. 配置BCLK分频
// 目标BCLK = 48k * 16 * 2 = 1.536MHz
// 分频比 = 49.152MHz / 1.536MHz = 32
wcd_write(reg_BCLK_DIV, 0x20); // 32分频
// 3. 配置LRCK分频
// 目标LRCK = 48kHz
// 分频比 = 1.536MHz / 48kHz = 32 (注意:这里是BCLK到LRCK的分频)
wcd_write(reg_LRCK_DIV, 0x20); // 32分频
// 4. 等待PLL锁定
while(!(wcd_read(reg_PLL_STATUS) & 0x01)) {
// 轮询等待,超时处理
}
这段代码看起来简单,但实际项目中,你还要考虑多路音频同时播放的情况。比如一边放音乐,一边录音,时钟树怎么共享?WCD938x内部有独立的时钟域,可以给播放和录音分别配置不同的采样率。但要注意,两个时钟域不能完全独立,它们共享同一个PLL。所以,如果播放用48kHz,录音用44.1kHz,PLL必须工作在两者的公倍数频率上——这又是一个容易出bug的地方。
总结一下时钟树的调试要点:
- 先确认PLL是否锁定。锁定后,MCLK才稳定。
- 用示波器量BCLK和LRCK的波形,看频率是否和预期一致。
- 如果声音有杂音或失真,大概率是BCLK/LRCK的相位关系不对。I2S标准要求LRCK的上升沿对齐到BCLK的下降沿。
- 多路音频同时运行时,检查时钟域是否发生了冲突。
好了,关于WCD934x/938x的框图、寄存器映射和时钟树,今天就先聊到这儿。下一节我会深入讲SLIMbus和SoundWire的驱动实现,那可是高通音频驱动的另一个重头戏。