ALSA是Advanced Linux Sound Architecture 的缩写, 是linux声卡的一种驱动框架,类似的还有OSS, 简单的说,声卡就是一块外接硬件,用来播放和录音的设备,将需处理的数据通过iis和soc进行交互,因此,声卡需要2个部分共同作用,即soc侧和codec侧。典型的音频设计是: 一块电路板上一颗CPU的I2S接口外挂一颗Codec芯片, Codec在外接耳机或功放等. 如下图所示
以播放为例, 在这样一个硬件结构下,这里准确的说是播放音频所需的一条完整的链路, 涉及到几个模块:
DMA : 负责把用户空间的音频数据搬移至I2S的FIFO.
I2S : 负责以某个采样频率、采样深度、通道数发送音频数据, 也叫dai (Digital Audio Interface).
AFIx (Audio Interface): 负责以某个采样频率、采样深度、通道数接收音频数据, 也称作dai
DAC : 并把数据通过DAC转换后送给耳机等播放.
播放音频的简单流程:
音频文件数据==>DMA==> I2S (按照某种采样 单双通道等发送)==>AFIx (按照某种采样 单双通道等接受)==>DAC (数模转换)==>音频输出设备
那么,要使整个功能有效,即我们需要实现platform和codec的驱动,linux驱动还是采用通用的做法来解耦合,即分别实现platform 配置soc侧的i2s和dma的功能,codec实现codec侧的APF和数据处理功能。但是由于模块化的关系,每个部分的驱动并非一家公司所写,假设每家公司写单独的模块,那么必须有一个能使platform 和codec关联起来的部分,即machine。从v4.18开始,这些模块采用了component系统来管理了,但这里我们还是用以前的方式说明,便于理解。
因此声卡驱动部分可以初步划为三大部分:
Platform : 该模块负责DMA的控制和I2S的控制, 由CPU厂商负责编写此部分代码.
Codec : 该模块负责AFIx的控制和DAC部分的控制(也可以说是芯片自身的功能的控制), 由Codec厂商负责编写此部分代码.
Machine : 用于描述一块电路板, 它指明此块电路板上用的是哪个Platform和哪个Codec, 由电路板商负责编写此部分代码.
从设计的角度来讲,不同的独立模块需要匹配,最容易的做法就是查询链表,按照属性匹配即可,那么asoc需要维护2个链表来维护Platform 和 Codec ,即 Platform _list和Codec_list,machine驱动负责遍历这两条链表来匹配需要的Platform和Codec 。
因此驱动的编写就变成填充Platform和 Codec 链表结点。
到这里,其实就可以初步的划分出2个模块就可以了,但是linux为了进一步划分,将i2s和AFIx这类负责数据传输的再次抽出为dai,dai就可分为soc-dai和codec-dai,Platform和Codec 主负责CPU配置,内存分配,dai就主负责数据传输和接口控制器配置。
综上:alsa声卡框架就分为5大部分
硬件相关:Platform Codec soc-dai codec-dai
匹配逻辑相关: machine
从数据结构的角度来说,上面5大部分可用下面结构体来表示:
ü struct snd_soc_platform : 用于抽象一个platform, 作用是描述一个CPU的DMA设备及操作函数. 系统中可能有多个platforms, 它们都挂载在全局链表头static LIST_HEAD(platform_list)下面, 不同的platform以name区分.
ü struct snd_soc_codec : 用于抽象一颗Codec. 一颗Codec可能会有多个dai接口, 该结构体的作用是描述与具体dai无关的、Codec内部的工作逻辑, 例如控件/微件/音频路由的描述信息、时钟配置、IO 控制等. 系统中可能有多个Codecs, 它们都挂载在全局链表头static LIST_HEAD(codec_list) 下面, 不同的Codec以name区分.
ü struct snd_soc_dai : 用于描述一个dai, 既可以是CPU侧的dai(I2S), 也可以是Codec侧的dai(AFIx). 一颗CPU可能有多个I2S, 一个Codec也可能有多个AFIx, 因此系统中会有很多个dai, 它们都挂载在全局链表头static LIST_HEAD(dai_list)下面, 不同的dai以name区分.
ü struct snd_soc_card:用来描述platform、codec、dai的联系关系。
注册到list的函数:
针对platform:使用snd_soc_register_platform挂载一个snd_soc_platform 节点
针对Codec : 使用snd_soc_register_codec挂载一个snd_soc_codec 节点
针对cpu_dai: 使用snd_soc_register_dai挂载一个snd_soc_dai 节点
针对Machine: 使用snd_soc_register_card挂载一个 snd_soc_card节点(声卡节点)
假设上面的几大部分的都实现并已通过注册函数挂载到list上,那么,还需要实现一个Machine 的数据结构,来进行上列组件的匹配,linux中使用struct snd_soc_card,结构体dai_link属性即指定了上列组件的关系。dailink可以认为是一条完整数据流的链路,一个声卡中不止有一条链路,可存在多个dailink。
对于一个声卡WM8994原理图:
针对上图我们可知,一张声卡存在多个完整的数据链路,一条链路用一个dailink来描述其所需的硬件资源,那么一个声卡就是多个完整链路描述的集合。
例如:
static struct snd_soc_dai_link imx_dai[] = {
{
.name = "HiFi",
.stream_name = "HiFi",
.codec_dai_name = "wm8904", //指定codec_dai
.codec_name = "wm8904.0-001a", //指定codec
.cpu_dai_name = "imx-ssi.1", //指定soc-dai
.platform_name = "imx-pcm-audio.1", //指定platform
.init = imx_wm8904_init,
.ops = &imx_hifi_ops,
},
那么上面这个结构体,表示了当前一条dalink所用的4大部分资源:
Platform:imx-pcm-audio
Codec:wm8904.0-001a
soc-dai: imx-ssi
codec-dai:wm8904
网上找的一张图,画得很好,这里直接拿来用用,表示了一个dalink的4大部分。
最后在调用snd_soc_register_card注册一个声卡,在这个注册函数中,会按照上面各个的
name去在相应的链表中去查找匹配,由于我们前面假设了3大部分的驱动已经添加到list
中,那么就可以实现整个声卡的功能了,上面5大部分的驱动实际上是所需的硬件资源,提
供了硬件操作接口,对于整个系统来讲,如何有效的调用硬件资源而实现具体功能,必然会
有一个核心逻辑框架,以回调的方式来实现整个框架的正常运行,这与同样的tty、spi等
驱动框架思想一致。
总结一下这一篇讲的内容,即将声卡分为5个部分,platfrom、codec、soc-dai、codec-dai、
machine。 分别注册进platfrom_list、codec_list、dai_list,通过machine中定义的
dailink来描述链路的关联,从而通过遍历匹配来获取链路所需要的硬件资源和操作接口,
将这些接口再注册进一个与硬件无关的逻辑的音频音频处理框架中,从而利用回调实现了真
整个音频功能。
下一篇,将分析这5大硬件部分具体实现的部分和可提供的回调接口。