3、LPASS(低功耗音频子系统)深度解析:时钟域、电源域、音频路径与DMA引擎
好,咱们今天来啃一块硬骨头——LPASS。说实话,很多做高通音频的工程师,调了两三年驱动,对LPASS的理解还停留在“它是个低功耗模块”这个层面。我个人觉得,如果你不理解LPASS的时钟域和电源域,你根本没法真正搞定那些诡异的音频问题,比如播放到一半突然没声、唤醒时有爆音、或者录音底噪突然变大。
LPASS,全称Low Power Audio Subsystem。它为什么叫“低功耗”?因为它有自己独立的时钟和电源管理。说白了,即使AP(应用处理器)休眠了,LPASS还能自己干活——比如播放MP3、处理语音唤醒。嗯,这里要注意,这个独立性既是优势,也是坑。
3.1 LPASS的时钟域:别让时钟成为瓶颈
LPASS内部有多个时钟域,我习惯把它们分成三类:
- 系统时钟(LPASS_CLK):给LPASS内部总线、DMA控制器、寄存器接口用的。这个时钟通常来自XO(晶振)或者PLL。
- 音频主时钟(MCLK):给音频编解码器(比如WCD9385)用的。采样率是48kHz还是44.1kHz,决定了MCLK是12.288MHz还是11.2896MHz。
- 内部处理时钟:给音频DSP、滤波器、SRC(采样率转换器)用的。这个时钟频率比较高,通常在100MHz以上。
我在项目中遇到过一个问题:播放44.1kHz的歌曲时,声音每隔几秒就卡一下。查了半天,发现是MCLK配置错了——系统默认给了12.288MHz,但44.1kHz需要11.2896MHz。你想想看,时钟不对,SRC硬转,不卡才怪。
关键点:LPASS的时钟源选择,必须和音频采样率匹配。48kHz家族用12.288MHz,44.1kHz家族用11.2896MHz。混用会导致采样率失配,产生可闻的杂音或卡顿。
时钟域的调试,我建议你重点关注lpass_clk_get()和clk_set_rate()这两个接口。在驱动初始化时,打印出实际获取到的时钟频率,跟预期值对比一下。我曾经遇到过一个案子,硬件工程师把MCLK的走线绕了个大圈,导致时钟信号质量下降,频率倒是对的,但抖动太大,音频DSP直接罢工。
3.2 LPASS的电源域:休眠与唤醒的艺术
LPASS的电源域设计,说白了就是为了省电。它有几个电源状态:
| 电源状态 | 描述 | 典型场景 |
|---|---|---|
| Active | 全功率运行,所有时钟和电源都打开 | 播放音乐、通话中 |
| Low Power | 关闭部分内部时钟,保留关键路径供电 | 待机播放、语音唤醒待命 |
| Retention | 仅保留寄存器状态,大部分逻辑断电 | 系统深度休眠,但需要快速恢复音频 |
| Off | 完全断电 | 系统关机 |
这里有个坑,我踩过好几次。当系统从Retention状态唤醒时,LPASS的某些寄存器会丢失配置。比如,你之前设置好的音频路径、增益值,醒来后全变成默认值了。结果就是——播放没声音,或者声音巨大(因为增益复位了)。
避坑指南:我曾经在调试一个智能音箱项目时,发现每次从休眠唤醒后,录音音量都特别小。查了两天才发现,是LPASS的MIC Bias寄存器在Retention模式下没有保存。解决方案是在PMIC(电源管理芯片)的休眠回调中,手动保存并恢复这些寄存器的值。
电源域的调试,我个人的习惯是:在lpass_suspend()和lpass_resume()函数中,打印出所有关键寄存器的值。对比休眠前后的差异,就能快速定位是哪个寄存器丢了。
3.3 音频路径:Playback与Capture的完整链路
音频路径,说白了就是声音从哪来到哪去。咱们分两条线讲。
3.3.1 Playback路径(播放)
播放路径的典型流程是这样的:
- AP侧:应用层把音频数据(比如PCM)通过ALSA框架送到内核。
- DMA引擎:把数据从系统内存搬运到LPASS内部的音频缓冲区。
- 音频DSP:对数据进行处理(EQ、DRC、SRC等)。
- 数字接口:通过I2S或TDM接口发送给外部Codec。
- Codec:数模转换,输出模拟信号到喇叭或耳机。
嗯,这里要注意,LPASS内部有一个叫做Audio Router的模块,它负责把DMA通道和数字接口连接起来。你想想看,如果Router配置错了,数据就送不到Codec那边去。
调试技巧:当播放没声音时,我建议你先检查/proc/asound/card0/pcm0p/sub0/status文件。如果状态是RUNNING,说明ALSA层没问题。然后去查LPASS的寄存器,看看DMA有没有传输完成中断。我一般用cat /sys/kernel/debug/lpass/regs来dump寄存器。
3.3.2 Capture路径(录音)
录音路径是播放的逆过程:
- MIC:模拟信号进入Codec。
- Codec:模数转换,变成数字PCM流。
- 数字接口:通过I2S/TDM送到LPASS。
- 音频DSP:可能做降噪、AEC(回声消除)等处理。
- DMA引擎:把数据从LPASS缓冲区搬回系统内存。
- AP侧:应用层读取数据。
录音路径的坑,往往出在时钟同步上。我记得有一次,客户反馈录音有周期性爆音。查了半天,发现是Codec输出的BCLK(位时钟)和LPASS期望的BCLK有微小偏差。说白了,就是两个芯片的时钟不同步。解决方案是启用LPASS内部的PLL,让它作为时钟主设备,Codec作为从设备。
3.4 DMA引擎:音频数据的搬运工
LPASS的DMA引擎,我习惯叫它Audio DMA。它和系统DMA不一样,是专门为音频设计的。有几个特点:
- 环形缓冲区:支持循环传输,适合音频流这种连续数据。
- 多通道支持:可以同时处理多个音频流(比如同时播放和录音)。
- 低延迟模式:可以配置为极小的缓冲区,减少延迟。
DMA引擎的配置,核心是描述符链。每个描述符指定了:源地址、目的地址、传输长度、中断使能等。我见过很多新手,在配置描述符时把地址写错了,结果DMA把数据搬到了奇怪的地方,导致系统崩溃。
代码示例:配置一个简单的DMA传输
// 伪代码,展示DMA描述符的配置思路
struct lpass_dma_desc desc;
desc.src_addr = system_mem_buf; // 系统内存中的音频数据
desc.dst_addr = lpass_i2s_tx_fifo; // LPASS的I2S发送FIFO
desc.length = 1024; // 每次传输1024字节
desc.irq_en = 1; // 传输完成后产生中断
desc.next = &next_desc; // 指向下一个描述符,形成链
lpass_dma_start(chan, &desc);
调试DMA时,我建议你重点关注中断频率和FIFO水位。如果中断太频繁,CPU占用会很高;如果中断太少,缓冲区可能溢出。我一般把FIFO水位设为半满,这样既不会太频繁,又能保证数据连续。
注意:DMA的优先级设置也很关键。在LPASS内部,播放DMA的优先级通常高于录音DMA。如果你发现录音有丢数据,可以检查一下是不是播放DMA占用了太多带宽。我曾经在一个多路录音的项目中,把录音DMA的优先级调高了一级,问题就解决了。
好了,LPASS的核心内容就这些。时钟域、电源域、音频路径、DMA引擎,这四个东西是相互关联的。时钟不对,DMA可能跑不起来;电源状态不对,音频路径可能断掉。你调试时,一定要有全局观,别只盯着一个点。
下一章,咱们聊聊音频Codec的配置和调试,那又是另一片天地了。