6.8.5. 设计说明
6.8.5.1. 源码说明
源代码位于:linux-5.10/sound/soc/zx/aic-i2s.c
6.8.5.2. 模块架构
内核中音频采用ALSA驱动框架,该框架管理所有与音频相关的软件与硬件,I2S的驱动设计需要遵循该框架的基本要求。ALSA音频框架将底层的硬件驱动分为三个部分:machine、pltform与codec。三者的关系如下图所示:
ALSA框架将底层划分为三部分后,使得platform和codec的驱动实现变得更加简单,二者只专注于实现自己的功能代码,由machine驱动来实现platform和codec的耦合,二者依靠cpu_dai和codec_dai进行数据传输。platform驱动的主要作用是完成音频数据的管理,最终通过SOC的数字音频接口(cpu_dai)把音频数据传送给codec进行播放或将codec采集的音频数据存储到内存中。
在具体的实现上,ALSA将platform驱动(platform可以简单理解为SOC端的驱动)分为两部分:实现音频数据传输和管理的DMA驱动和CPU DAI的驱动。且ALSA框架中已实现了DMA对音频数据管理部分的驱动 代码,所以I2S的驱动只需要实现CPU DAI部分的驱动,音频数据管理部分只需要指定数据传输的起始地址或目的地址,以及传输位宽即可。
6.8.5.2.1. CPU DAI驱动
在驱动实现上,无论是codec还是platform,ALSA将它们统一划分为snd_soc_component和snd_soc_dai,所以,就要相应的实现snd_soc_component_driver和snd_soc_dai_driver。 然后调用snd_soc_register_component进行统一注册。snd_soc_component_driver主要是注册与dapm相关的音频控件等信息,snd_soc_dai_driver主要是注册数字音频接口I2S或PCM等的信息及底层操作函数。
由于在platform端,主要是I2S接口和DMA的传输配置,不存在音频控件,所以CPU DAI的驱动主要是实现snd_soc_dai_driver。包括指定I2S接口支持的通道数、采样率、支持的数据格式,以及对I2S配置和控制的回调函数集合snd_soc_dai_ops的实现
6.8.5.2.2. 音频DMA驱动
ALSA架构中,对DMA的一些配置和传输的函数已经由ALSA框架实现,所以这部分驱动实现只需要指定playback和capture中DMA传输的地址以及传输的位宽,然后调用devm_snd_dmaengine_pcm_register 进行注册即可。
6.8.5.3. 关键流程设计
6.8.5.3.1. 操作函数集实现
在I2S的驱动设计中,snd_soc_dai_ops是一个非常重要的结构体,它是cpu_dai的操作函数集,所有对I2S接口的设置都是通过此结构体完成。所以,I2S驱动中一项非常重要的部分就是实现此结构体中的函数接口。snd_soc_dai_ops函数集可以分为如下几个部分:
cpu_dai时钟配置函数,通常由machine驱动调用
set_sysclk:设置cpu_dai的主时钟MCLK
set_clkdiv:设置分频系数,用于实现BCLK和LRCK的分频系数
set_bclk_ratio:设置BCLK和LRCK的比率
cpu_dai格式设置,通常由machine驱动调用
set_fmt:设置主从模式(LRCK和BCLK时钟由SOC提供还是由codec提供),BCLK和LRCK的极性,以及传输模式
set_tdm_slot:cpu_dai支持时分复用时,用于设置时分复用的slot
set_channel_map:声道时分复用时的映射关系设置
ALSA PCM音频操作,由ALSA的soc-core在执行音频操作时调用
hw_params:硬件参数设置,一般用于采样精度,通道位宽的设置
trigger:命令触发函数,用于执行音频数据传输的开始、结束、暂停、恢复等
在I2S的驱动中,需要实现的接口有:
static const struct snd_soc_dai_ops aic_i2s_dai_ops = {
.set_sysclk = aic_i2s_set_sysclk,
.set_bclk_ratio = aic_i2s_set_bclk_ratio,
.set_fmt = aic_i2s_set_fmt,
.set_tdm_slot = aic_i2s_set_tdm_slot,
.hw_params = aic_i2s_hw_params,
.trigger = aic_i2s_trigger,
};
在实现的几个接口函数中,除hw_params和trigger外,其它函数是需要在machine驱动中根据I2S和codec双方所支持的格式、时钟等进行调用设置的,使I2S和codec两边的格式设置相同。
6.8.5.3.2. I2S时钟设置
6.8.5.3.2.1. MCLK
MCLK是I2S的主时钟,主要作用是向外部的codec芯片提供工作时钟,由I2S模块的工作时钟分频得到。在驱动中由aic_i2s_set_sysclk设置MCLK的频率,MCLK一般采用128fs,256fs,512fs的表示方式,具体的设置需要参考实际使用的codec芯片规格书。Fs是采样频率,常见的采样频率有44.1khz,48khz,32khz等,可以据此算出MCLK的频率值。一般会在machine驱动中调用设置MCLK的函数。
6.8.5.3.2.2. LRCK和BCLK
LRCK是左右声道时钟。LRCK的时钟频率等于fs,在M4中,通过LRCK_PERIOD位域设置LRCK的频率,LRCK_PERIOD表示一个LRCK时钟周期内,有多少个BCLK周期。在I2S模式下,若为立体声(2通道),32bit采样深度,则BCLK=64fs,则LRCK_PERIOD应设置为(64/2-1)。若为4通道,24bit采样深度,则BCLK=96fs,则LRCK_PERIOD应设置为(96/2-1)。由采样频率可以算出BCLK时钟的频率。并由BCLK的频率算出LRCK,即采样率。
6.8.5.3.3. period bytes对齐
在使用DMA传输音频数据时,DMA要求每次传输的数据长度必须128bytes/8bytes对齐。在ALSA框架下,音频数据以period为周期调用DMA传输,每次传输的数据长度为period bytes。所以,必须满足period bytes按照128bytes/8bytes对齐。ALSA中提供了相应的API接口(snd_pcm_hw_constraint_step)来满足这一需求。
static int aic_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int ret;
/* Make sure that the period bytes are 8/128 bytes aligned according to
* the DMA transfer requested.
*/
if (of_device_is_compatible(dai->dev->of_node,
"zx,aic-i2s-v1.0")) {
ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 8);
if (ret < 0) {
dev_err(dai->dev,
"Could not apply period step: %d\n", ret);
return ret;
}
ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 8);
if (ret < 0) {
dev_err(dai->dev,
"Could not apply buffer step: %d\n", ret);
return ret;
}
} else {
ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128);
if (ret < 0) {
dev_err(dai->dev,
"Could not apply period step: %d\n", ret);
return ret;
}
ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 128);
if (ret < 0) {
dev_err(dai->dev,
"Could not apply buffer step: %d\n", ret);
return ret;
}
}
return ret;
}
6.8.5.4. 数据结构设计
6.8.5.4.1. aic_i2s
struct aic_i2s {
struct clk *clk;
struct reset_control *rst;
struct regmap *regmap;
struct snd_dmaengine_dai_dma_data playback_dma_data;
struct snd_dmaengine_dai_dma_data capture_dma_data;
unsigned int mclk_freq;
unsigned int bclk_ratio;
unsigned int format;
unsigned int slots;
unsigned int slot_width;
};
部分变量说明:
playback_dma_data:播放时的音频数据结构,用于配置DMA传输的目的地址,数据宽度等信息
capture_dma_data:录音时音频数据结构,用于配置DMA传输的起始地址,数据宽度等信息
mclk_freq:I2S的MCLK时钟频率
bclk_ratio:LRCK与BCLK的比率
format:设置I2S的传输格式
slots:设置I2S的通道数
slot_width:设置I2S的每个通道占用位数
6.8.5.4.2. aic_i2s_clk_div
struct aic_i2s_clk_div {
u8 div; /* bclk和mclk的分频系数 */
u8 val; /* 分频系数div所对应的寄存器的值 */
};
6.8.5.5. 接口设计
6.8.5.5.1. aic_i2s_set_sysclk
函数原型 |
static int aic_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) |
---|---|
功能说明 |
设置I2S模块输出的mclk时钟频率 |
参数定义 |
dai:指向cpu_dai的指针
| clk_id:要设置的时钟id
| freq:设置的时钟频率
| dir: unused
|
返回值 |
0:执行成功
| -EINVAL:参数非法
|
注意事项 |
6.8.5.5.2. aic_i2s_set_bclk_ratio
函数原型 |
static int aic_i2s_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) |
---|---|
功能说明 |
设置I2S模块LRCK与BCLK时钟频率的比率 |
参数定义 |
dai:指向cpu_dai的指针
| ratio:需要设置的比率
|
返回值 |
0:执行成功
| -EINVAL:参数非法
|
注意事项 |
6.8.5.5.3. aic_i2s_set_fmt
函数原型 |
static int aic_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
---|---|
功能说明 |
设置I2S模块的格式 |
参数定义 |
dai:指向cpu_dai的指针
| fmt:需要设置的格式
|
返回值 |
0:执行成功
| -EINVAL:参数非法
|
注意事项 |
通过该函数可以设置的格式有:
| 1. I2S的主从模式
| 2. BCLK和LRCK的极性
| 3. I2S的数据格式
|
6.8.5.5.4. aic_i2s_set_tdm_slot
函数原型 |
static int aic_i2s_set_tdm_slot(struct snd_soc_dai *dai,unsigned int tx_mask, unsigned int rx_mask,int slots, int slot_width) |
---|---|
功能说明 |
设置I2S模块TDM模式下的通道个数和宽度 |
参数定义 |
dai:指向cpu_dai的指针
| tx_mask:tx slot的mask
| rx_mask:rx slot的mask
| slots:设置的通道个数
| slot_width:设置的通道宽度
|
返回值 |
0:执行成功
| -EINVAL:参数非法
|
注意事项 |
6.8.5.5.5. aic_i2s_hw_params
函数原型 |
static int aic_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) |
---|---|
功能说明 |
设置I2S模块硬件参数 |
参数定义 |
substream:指向playback或capture的substream
| params:指向硬件参数指针
| dai:指向cpu_dai的指针
|
返回值 |
0:执行成功
| -EINVAL:参数非法
|
注意事项 |
通过该函数,可以设置采样精度,帧率,以及时钟等参数 |
6.8.5.5.6. aic_i2s_trigger
函数原型 |
static int aic_i2s_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) |
---|---|
功能说明 |
I2S的触发函数 |
参数定义 |
substream:指向playback或capture的substream
| cmd:触发的命令
| dai:指向cpu_dai的指针
|
返回值 |
0:执行成功
| -EINVAL:参数非法
|
注意事项 |
通过该函数,可以开始或停止音频的播放或录音 |