4. I2S控制器驱动开发:DMA引擎初始化、FIFO阈值配置、主从模式设置、采样率与格式配置
好,咱们进入正题。I2S控制器驱动开发,说白了就是让CPU和音频编解码器能顺畅地“对话”。你想想看,音频数据流就像一条高速公路,DMA是卡车队,FIFO是收费站,主从模式决定了谁指挥交通,采样率和格式则规定了卡车能装什么货。这几个环节任何一个出问题,声音要么破音,要么无声,要么根本跑不起来。
我个人习惯,写I2S驱动前,先理清楚硬件框图。别急着敲代码,先看数据手册里的I2S控制器模块图。搞清楚DMA请求线怎么连的,FIFO深度是多少,主时钟从哪里来。这一步省了,后面调试能让你怀疑人生。
核心要点:I2S控制器驱动开发,本质是配置好数据传输的“管道”和“规则”。DMA负责搬运,FIFO负责缓冲,主从模式决定时钟谁产生,采样率和格式决定数据怎么解析。
4.1 DMA引擎初始化
DMA引擎是音频数据传输的“心脏”。没有DMA,CPU得亲自搬数据,那系统就别干别的了。我见过不少新手,DMA配置错了,音频数据要么重复,要么丢失,声音就像卡带的录音机。
DMA初始化,核心就三件事:
- 请求线映射:把I2S控制器的DMA请求信号,连接到DMA控制器的通道上。不同芯片映射方式不同,有的固定,有的可编程。
- 传输方向配置:播放(内存→I2S)用DMA写通道,录音(I2S→内存)用DMA读通道。别搞反了,我吃过这个亏。
- 传输粒度设置:一般是半字(16位)或字(32位),要和I2S数据格式匹配。
我的经验:DMA初始化时,先配通道优先级。音频流对实时性要求高,建议把音频DMA通道优先级设高一点。我曾经在一个项目里,DMA优先级配低了,结果SD卡读写时音频出现咔咔声,查了两天才找到原因。
来看一段典型的DMA初始化代码(以STM32为例,但思路通用):
static int i2s_dma_init(struct i2s_dev *i2s)
{
struct dma_slave_config config;
int ret;
/* 1. 请求DMA通道 */
i2s->dma_tx = dma_request_chan(i2s->dev, "tx");
if (IS_ERR(i2s->dma_tx))
return PTR_ERR(i2s->dma_tx);
/* 2. 配置传输参数 */
memset(&config, 0, sizeof(config));
config.direction = DMA_MEM_TO_DEV;
config.dst_addr = i2s->phys_base + I2S_DR_REG; /* 数据寄存器地址 */
config.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; /* 16位 */
config.dst_maxburst = 4; /* 每次触发传输4个数据 */
ret = dmaengine_slave_config(i2s->dma_tx, &config);
if (ret)
goto err_free;
/* 3. 准备DMA描述符(循环模式) */
i2s->dma_tx_desc = dmaengine_prep_dma_cyclic(
i2s->dma_tx,
i2s->dma_buf_phys,
i2s->dma_buf_size,
i2s->period_size,
DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT);
if (!i2s->dma_tx_desc)
goto err_free;
/* 4. 注册回调 */
i2s->dma_tx_desc->callback = i2s_dma_tx_callback;
i2s->dma_tx_desc->callback_param = i2s;
return 0;
err_free:
dma_release_channel(i2s->dma_tx);
return -ENODEV;
}
注意:DMA缓冲区大小必须是周期大小的整数倍,而且通常要求对齐到缓存行大小。我曾经在一个ARM平台上忘了做缓存一致性处理,播放出来的声音全是噪声——DMA写内存了,但CPU读的是Cache里的旧数据。
4.2 FIFO阈值配置
FIFO是I2S控制器的“蓄水池”。阈值设得太低,容易欠载(underflow),声音断断续续;设得太高,延迟变大,实时性变差。说白了,这是个权衡。
FIFO阈值通常用“触发级别”来表示。比如一个16深度的FIFO,阈值设为4,表示FIFO里数据少于4个时触发DMA请求(播放方向),或者FIFO里数据多于12个时触发DMA请求(录音方向)。
| FIFO深度 | 阈值设置 | 适用场景 | 注意事项 |
|---|---|---|---|
| 8 | 2(播放)/ 6(录音) | 低延迟场景 | 容易欠载,需配合高优先级DMA |
| 16 | 4(播放)/ 12(录音) | 通用场景 | 平衡延迟和稳定性 |
| 32 | 8(播放)/ 24(录音) | 高稳定性场景 | 延迟较大,适合非实时应用 |
| 64 | 16(播放)/ 48(录音) | 抗抖动场景 | 适合USB音频等异步传输 |
我个人习惯,先把阈值设到FIFO深度的1/4(播放)和3/4(录音),然后根据实际测试调整。如果出现欠载,就提高播放阈值;如果出现溢出,就降低录音阈值。
调试技巧:在驱动里加一个计数器,统计FIFO欠载/溢出次数。我一般会在/proc或debugfs里暴露这个信息,调试时一目了然。曾经有个项目,播放偶尔出现“啪”的一声,查了三天,最后发现是FIFO阈值和DMA burst size不匹配导致的。
4.3 主从模式设置
主从模式,决定了谁产生位时钟(BCLK)和帧时钟(LRCK)。主模式:I2S控制器自己产生时钟。从模式:I2S控制器接收外部时钟。
这个选择其实很简单:
- 主模式:CPU端I2S控制器做主,Codec做从。优点是时钟由主控端控制,容易同步。缺点是主控端需要提供精确的时钟源。
- 从模式:Codec做主,CPU端I2S控制器做从。优点是Codec可以自己产生时钟,适合多设备同步。缺点是需要外部时钟稳定。
我建议,除非Codec有特殊要求(比如某些Codec必须做主),否则一律用主模式。为什么?因为主模式下时钟抖动可控,调试也方便。我曾经在一个项目里用了从模式,结果Codec的时钟源有抖动,导致录音数据偶尔出现毛刺,后来换成主模式就解决了。
配置代码通常很简单,就是设置一个寄存器位:
/* 设置I2S为主模式 */
writel(I2S_CFG_MASTER, i2s->base + I2S_CFG_REG);
/* 设置I2S为从模式 */
writel(I2S_CFG_SLAVE, i2s->base + I2S_CFG_REG);
注意:主从模式必须在I2S接口使能之前设置。我曾经在调试时忘了这个顺序,结果时钟一直出不来,折腾了半天才发现是配置顺序的问题。
4.4 采样率与格式配置
采样率和数据格式,是I2S通信的“语言”。采样率决定了每秒传输多少帧,数据格式决定了每帧有多少位、怎么排列。
采样率配置:
采样率通过分频器产生。主时钟(MCLK)除以分频系数得到位时钟(BCLK),位时钟再除以通道数和位宽得到帧时钟(LRCK)。公式很简单:
BCLK = 采样率 × 通道数 × 位宽 × 2(因为I2S是双通道)
MCLK = BCLK × MCLK分频系数(通常是256或512倍采样率)
举个例子,48kHz采样率、16位、立体声:
- BCLK = 48000 × 2 × 16 = 1.536MHz
- MCLK = 1.536MHz × 8 = 12.288MHz(或者直接用256×48kHz=12.288MHz)
数据格式配置:
常见的数据格式有I2S、左对齐、右对齐、DSP/PCM模式。I2S格式是最常用的,数据在LRCK变化后的第二个BCLK上升沿开始传输。
| 格式 | 数据对齐 | 适用场景 |
|---|---|---|
| I2S | LRCK变化后延迟1位 | 最通用,几乎所有Codec都支持 |
| 左对齐 | LRCK变化后立即开始 | 部分老式Codec使用 |
| 右对齐 | 数据对齐到LRCK结束边界 | 少见,某些DAC使用 |
| DSP/PCM | 帧同步脉冲后连续传输 | 多通道TDM场景 |
配置代码示例:
/* 配置采样率:48kHz,MCLK=12.288MHz */
u32 mclk = 12288000; /* 12.288MHz */
u32 sample_rate = 48000;
u32 channels = 2;
u32 bits_per_sample = 16;
/* 计算分频系数 */
u32 bclk = sample_rate * channels * bits_per_sample;
u32 div = mclk / bclk;
writel(div, i2s->base + I2S_CLK_DIV_REG);
/* 配置数据格式:I2S格式,16位 */
u32 fmt = I2S_FMT_I2S | I2S_DATA_WIDTH_16;
writel(fmt, i2s->base + I2S_FMT_REG);
我的经验:采样率配置最容易出问题的是分频精度。有些芯片的分频器只能整数分频,导致实际采样率和目标采样率有偏差。比如44.1kHz用12.288MHz MCLK分频,可能得到44.118kHz,偏差0.04%。这个偏差人耳听不出来,但如果做录音和播放同步,时间长了就会累积误差。我一般会查芯片手册,看它支持哪些MCLK频率,选一个能精确分频的。
总结一下:I2S控制器驱动开发,DMA是骨架,FIFO是缓冲,主从模式是规则,采样率和格式是语言。把这四块配好了,音频数据流就能跑起来。但别高兴太早——配好了不代表能出声,后面还有时钟同步、电源管理、中断处理一堆坑等着你。嗯,咱们一步一步来。