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路径(播放)

播放路径的典型流程是这样的:

  1. AP侧:应用层把音频数据(比如PCM)通过ALSA框架送到内核。
  2. DMA引擎:把数据从系统内存搬运到LPASS内部的音频缓冲区。
  3. 音频DSP:对数据进行处理(EQ、DRC、SRC等)。
  4. 数字接口:通过I2S或TDM接口发送给外部Codec。
  5. 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路径(录音)

录音路径是播放的逆过程:

  1. MIC:模拟信号进入Codec。
  2. Codec:模数转换,变成数字PCM流。
  3. 数字接口:通过I2S/TDM送到LPASS。
  4. 音频DSP:可能做降噪、AEC(回声消除)等处理。
  5. DMA引擎:把数据从LPASS缓冲区搬回系统内存。
  6. 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的配置和调试,那又是另一片天地了。