编解码器类驱动程序是最基本的,他实现的代码应该利用编解码器设备并公开其硬件属性,以便amixer等用户空间工具可以使用它。
由于驱动程序针对特定的编解码器,因此它应该包含音频控制、音频接口功能、编解码器DAPM定义和I/O功能,每个编解码器必须满足:
- 通过定义DAI和PCM配置来提供与其他模块的接口
- 提供编解码器控制I/O hook(使用I2C、SPI的API)
- 根据用户空间的需要,公开其他内核控件(Kernel control,Kcontrol)以动态控制模块行为
- 定义DAPM widget,为动态电源切换建立DAPM路由,并提供DAC数字静音控制(option)
Component
包含编解码器的路由、widget、控件金额一组编解码器相关函数回调指针,以及一个或多个dai驱动。
1 | /* component interface */ |
- name: 此组件的名称对于编解码器和platform来说都是必须的,platform可能不需要其他字段
- probe:组件驱动probe函数,组件probe函数,当组件驱动被mechine驱动probe到时(实际上是当machine驱动向ASOC core注册一个由该组件组成的card时)执行,必要时完成组件初始化
- control:控制接口指针,如控制音量,通道选择等,主要用于编解码器
- set_pll: 设置锁相环的函数指针
- read:读取编解码器寄存器的函数
- write:写入编解码器寄存器的函数
- dapm_widget: dapm的widget指针
- dapm_routes: dapm路由指针
- ops: platform DMA相关回调
此结构体同时抽象编解码器和Platform机器DAI驱动程序
DAI
编解码驱动程序包括编解码设备本身和DAI组件,他们在与platform绑定时使用。
1 | struct snd_soc_dai_driver { |
- name: DAI接口名称
- probe:dai probe函数,当machine 驱动程序probe到该dai驱动程序所属的组件驱动程序时(实际是machine驱动程序向ASOC core注册card时)执行
- ops:提供用于配置和控制DAI的回调函数
- capture:它表示音频采集的硬件参数,包括通道数、rate、format等
- playback:它表示音频回放的硬件参数,包括通道数、rate、format等
1 | struct snd_soc_dai_ops { |
第一类:时钟配置回调函数
- set_sysclk:设置DAI的主时钟
- set_pll: 设置PLL函数
- set_clkdiv: 设置时钟分频值
第二类:DAI的格式配置函数
- set_fmt:设置DAI的格式
- set_tdm_slot: 设置TDM的时隙
- set_channel_map: 通道TDM的映射设置 machine驱动通过snd_soc_dai_set_channel_map调用
- set_tristate: 设置DAI引脚的状态,在于其他DAI并行使用同一引脚时需要使用该状态
第三类:普通的标准前端
- startup:打开采集/回放设备时,在打开PCM子流时由ALSA调用
- hw_params:设置音频流时调用
- prepare:当PCM准备好时调用
- trigger: PCM启动、停止、暂停时调用
Control
编解码器驱动程序通常需要公开一些可以从用户空间更改的编解码器属性,这些就是编解码器控件(control)。
1 | struct snd_kcontrol_new { |
- iface :控件类型 可能是简单开关控件、立体声控件(Stereo)、混音器(MIXER)、MUX控件等
- name: 控件名称,命名包含源(Master、PCM、CD、Line)、方向(playback、capture、Bypass)以及功能(switch、volume、route等)
- access : 控件访问权限,READ、WRITE、VOLATILE等
- get : 读取控件的当前值并返回给用户空间
- put : 按照应用程序要求设置控件值
- tlv : 为控件提供元数据,有些混音器控件需要以分贝(db)为单位提供信息,可以使用DECLARE_TLV_xxx宏定义一些包含这些信息的变量,然后将控制tlv.p字段指向的变量,tlv是类型-长度-值(type-length-value)是一种编码方案。
设置开关控件
1 | SOC_SINGLE(xname, reg,shift.max,invert) |
这种类型的控件只有一个设置,一般用于组件开关
设置带有音量级别的开关
1 | SOC_SINGLE_TLV(name, reg,shift.max,invert,tlv_array) |
用于定义具有增益的控件,如音量控件、EQ均衡器等
立体声控件
1 | SOC_DOUBLE_R(xname, reg_left,reg_right,xshift,xmax,xinvert) |
在一个寄存器中控制两个相似的变量,因此可以同时控制左右声道
带音量级别的立体声控件
1 | SOC_DOUBLE_R_TLV(xname, reg_left,reg_right,xshift,xmax,xinvert,tlv_array) |
混音器控件
1 | SOC_SINGLE("Input Switch", WM8960_SPEAKER_MIXER,7,1,0) |
DAPM(dynamic audio power management)
widget
普通kcontrol具有以下特点:
- 自我描述,无法描述每个kcontrol之间的关系
- 缺乏电源管理机制
- 缺乏响应回放、停止、开机、关机等音频事件的时间处理机制
- 缺少爆音防止机制
- 无法自动关闭音频路径中涉及的所有控件
为解决以上问题,DAPM引入widget,widget是kcontrol的进一步升级和封装。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54struct snd_soc_dapm_widget {
enum snd_soc_dapm_type id;
const char *name; /* widget name */
const char *sname; /* stream name */
struct list_head list;
struct snd_soc_dapm_context *dapm;
void *priv; /* widget specific data */
struct regulator *regulator; /* attached regulator */
struct pinctrl *pinctrl; /* attached pinctrl */
/* dapm control */
int reg; /* negative reg = no direct dapm */
unsigned char shift; /* bits to shift */
unsigned int mask; /* non-shifted mask */
unsigned int on_val; /* on state value */
unsigned int off_val; /* off state value */
unsigned char power:1; /* block power status */
unsigned char active:1; /* active stream on DAC, ADC's */
unsigned char connected:1; /* connected codec pin */
unsigned char new:1; /* cnew complete */
unsigned char force:1; /* force state */
unsigned char ignore_suspend:1; /* kept enabled over suspend */
unsigned char new_power:1; /* power from this run */
unsigned char power_checked:1; /* power checked this run */
unsigned char is_supply:1; /* Widget is a supply type widget */
unsigned char is_ep:2; /* Widget is a endpoint type widget */
int subseq; /* sort within widget type */
int (*power_check)(struct snd_soc_dapm_widget *w);
/* external events */
unsigned short event_flags; /* flags to specify event types */
int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int);
/* kcontrols that relate to this widget */
int num_kcontrols;
const struct snd_kcontrol_new *kcontrol_news;
struct snd_kcontrol **kcontrols;
struct snd_soc_dobj dobj;
/* widget input and output edges */
struct list_head edges[2];
/* used during DAPM updates */
struct list_head work_list;
struct list_head power_list;
struct list_head dirty;
int endpoints[2];
struct clk *clk;
int channel;
};
- id: widget类型,如snd_soc_dapm_output、snd_soc_dapm_mixer等
- name:widget 名称
- shift和mask:用于控制widget的电源状态,对应寄存器地址reg
- on_val和off_val:表示更改widget当前电源状态的值
- event:表示DAPM事件处理回调函数指针
- kcontrol_news:组成该kcontrol的控件数组
- dirty:当widget状态改变时,dirty用于将这个widget插入到dirty列表中,然后扫描整个列表执行整个路径的更新
定义widget
有很多宏定义来定义widget控件,包括:
- 编解码域:如VREF和VMID,可以提供参考电压widget
- platform/machine domain:需要物理连接的platfoem或板卡的输入输出接口,如耳机、扬声器、麦克风等
- 音频路径域:指在编解码器中控制音频路径的MUX、Mixer和其他widget
- 音频流域:处理音频流数据,如ADC、DAC等
路径的概念--widget之间的连接器
由struct snd_soc_dapm_path结构体表示,该结构体表示两个widget之间的连接,他的source字段指向连接的开始widget,而sink字段则指向连接到达的widget。所有输入端点的snd_soc_dapm_path连接到list_node[SND_SOC_DAPM_DIR_IN]列表中,所有输出端点的snd_soc_dapm_path连接到list_node[SND_SOC_DAPM_DIR_OUT]列表中。
路由的概念--widget互联
为了不想处理path,引入route概念,路由至少包括,起始widget,跳线路径jumper path和接收方widger组成,使用struct snd_soc_dapm_route表示。 1
2
3
4
5
6
7
8
9
10
11struct snd_soc_dapm_route {
const char *sink;
const char *control;
const char *source;
/* Note: currently only supported for links where source is a supply */
int (*connected)(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink);
struct snd_soc_dobj dobj;
};
- sink: 指向到达widget的名称串
- source:指向起始widget的名称串
- control:指向负责控制连接的kcontrol名称串
- connected:定义了自定义连接检查的回调函数
source通过kcontrol连接到sink,可以调用connected检查连接状态
数据结构
前面列出了一些关键数据结构,除此之外还有很多其他关键数据结构,以及各数据结构之间的关系没有一一列出来,下面只通过一副图片来将相关数据结构以及他们之间的关系列出来。
原图
参考文献
《Linux设备驱动开发-约翰-马德奥》