Linux 驱动之 ALSA(二)编解码器类驱动相关概念

编解码器类驱动程序是最基本的,他实现的代码应该利用编解码器设备并公开其硬件属性,以便 amixer 等用户空间工具可以使用它。

由于驱动程序针对特定的编解码器,因此它应该包含音频控制、音频接口功能、编解码器 DAPM 定义和 I/O 功能,每个编解码器必须满足:

  • 通过定义 DAI 和 PCM 配置来提供与其他模块的接口
  • 提供编解码器控制 I/O hook(使用 I2C、SPI 的 API)
  • 根据用户空间的需要,公开其他内核控件(Kernel control,Kcontrol)以动态控制模块行为
  • 定义 DAPM widget,为动态电源切换建立 DAPM 路由,并提供 DAC 数字静音控制(option)

Component

包含编解码器的路由、widget、控件金额一组编解码器相关函数回调指针,以及一个或多个 dai 驱动。

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/* component interface */
struct snd_soc_component_driver {
const char *name;

/* Default control and setup, added after probe() is run */
const struct snd_kcontrol_new *controls;
unsigned int num_controls;
const struct snd_soc_dapm_widget *dapm_widgets;
unsigned int num_dapm_widgets;
const struct snd_soc_dapm_route *dapm_routes;
unsigned int num_dapm_routes;

int (*probe)(struct snd_soc_component *component);
void (*remove)(struct snd_soc_component *component);
int (*suspend)(struct snd_soc_component *component);
int (*resume)(struct snd_soc_component *component);

unsigned int (*read)(struct snd_soc_component *component,
unsigned int reg);
int (*write)(struct snd_soc_component *component,
unsigned int reg, unsigned int val);

/* pcm creation and destruction */
int (*pcm_new)(struct snd_soc_pcm_runtime *rtd);
void (*pcm_free)(struct snd_pcm *pcm);

/* component wide operations */
int (*set_sysclk)(struct snd_soc_component *component,
int clk_id, int source, unsigned int freq, int dir);
int (*set_pll)(struct snd_soc_component *component, int pll_id,
int source, unsigned int freq_in, unsigned int freq_out);
int (*set_jack)(struct snd_soc_component *component,
struct snd_soc_jack *jack, void *data);

/* DT */
int (*of_xlate_dai_name)(struct snd_soc_component *component,
struct of_phandle_args *args,
const char **dai_name);
int (*of_xlate_dai_id)(struct snd_soc_component *comment,
struct device_node *endpoint);
void (*seq_notifier)(struct snd_soc_component *component,
enum snd_soc_dapm_type type, int subseq);
int (*stream_event)(struct snd_soc_component *component, int event);
int (*set_bias_level)(struct snd_soc_component *component,
enum snd_soc_bias_level level);

const struct snd_pcm_ops *ops;
const struct snd_compr_ops *compr_ops;

/* probe ordering - for components with runtime dependencies */
int probe_order;
int remove_order;

/*
* signal if the module handling the component should not be removed
* if a pcm is open. Setting this would prevent the module
* refcount being incremented in probe() but allow it be incremented
* when a pcm is opened and decremented when it is closed.
*/
unsigned int module_get_upon_open:1;

/* bits */
unsigned int idle_bias_on:1;
unsigned int suspend_bias_off:1;
unsigned int use_pmdown_time:1; /* care pmdown_time at stop */
unsigned int endianness:1;
unsigned int non_legacy_dai_naming:1;

/* this component uses topology and ignore machine driver FEs */
const char *ignore_machine;
const char *topology_name_prefix;
int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params);
bool use_dai_pcm_id; /* use DAI link PCM ID as PCM device number */
int be_pcm_base; /* base device ID for all BE PCMs */
};
  • 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
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
struct snd_soc_dai_driver {
/* DAI description */
const char *name;
unsigned int id;
unsigned int base;
struct snd_soc_dobj dobj;

/* DAI driver callbacks */
int (*probe)(struct snd_soc_dai *dai);
int (*remove)(struct snd_soc_dai *dai);
int (*suspend)(struct snd_soc_dai *dai);
int (*resume)(struct snd_soc_dai *dai);
/* compress dai */
int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
/* Optional Callback used at pcm creation*/
int (*pcm_new)(struct snd_soc_pcm_runtime *rtd,
struct snd_soc_dai *dai);

/* ops */
const struct snd_soc_dai_ops *ops;
const struct snd_soc_cdai_ops *cops;

/* DAI capabilities */
struct snd_soc_pcm_stream capture;
struct snd_soc_pcm_stream playback;
unsigned int symmetric_rates:1;
unsigned int symmetric_channels:1;
unsigned int symmetric_samplebits:1;
unsigned int bus_control:1; /* DAI is also used for the control bus */

/* probe ordering - for components with runtime dependencies */
int probe_order;
int remove_order;
};
  • name: DAI 接口名称
  • probe:dai probe 函数,当 machine 驱动程序 probe 到该 dai 驱动程序所属的组件驱动程序时(实际是 machine 驱动程序向 ASOC core 注册 card 时)执行
  • ops:提供用于配置和控制 DAI 的回调函数
  • capture:它表示音频采集的硬件参数,包括通道数、rate、format 等
  • playback:它表示音频回放的硬件参数,包括通道数、rate、format 等
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
struct snd_soc_dai_ops {
/*
* DAI clocking configuration, all optional.
* Called by soc_card drivers, normally in their hw_params.
*/
int (*set_sysclk)(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir);
int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out);
int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);

/*
* DAI format configuration
* Called by soc_card drivers, normally in their hw_params.
*/
int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
int (*xlate_tdm_slot_mask)(unsigned int slots,
unsigned int *tx_mask, unsigned int *rx_mask);
int (*set_tdm_slot)(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width);
int (*set_channel_map)(struct snd_soc_dai *dai,
unsigned int tx_num, unsigned int *tx_slot,
unsigned int rx_num, unsigned int *rx_slot);
int (*get_channel_map)(struct snd_soc_dai *dai,
unsigned int *tx_num, unsigned int *tx_slot,
unsigned int *rx_num, unsigned int *rx_slot);
int (*set_tristate)(struct snd_soc_dai *dai, int tristate);

int (*set_sdw_stream)(struct snd_soc_dai *dai,
void *stream, int direction);
/*
* DAI digital mute - optional.
* Called by soc-core to minimise any pops.
*/
int (*digital_mute)(struct snd_soc_dai *dai, int mute);
int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);

/*
* ALSA PCM audio operations - all optional.
* Called by soc-core during audio PCM operations.
*/
int (*startup)(struct snd_pcm_substream *,
struct snd_soc_dai *);
void (*shutdown)(struct snd_pcm_substream *,
struct snd_soc_dai *);
int (*hw_params)(struct snd_pcm_substream *,
struct snd_pcm_hw_params *, struct snd_soc_dai *);
int (*hw_free)(struct snd_pcm_substream *,
struct snd_soc_dai *);
int (*prepare)(struct snd_pcm_substream *,
struct snd_soc_dai *);
/*
* NOTE: Commands passed to the trigger function are not necessarily
* compatible with the current state of the dai. For example this
* sequence of commands is possible: START STOP STOP.
* So do not unconditionally use refcounting functions in the trigger
* function, e.g. clk_enable/disable.
*/
int (*trigger)(struct snd_pcm_substream *, int,
struct snd_soc_dai *);
int (*bespoke_trigger)(struct snd_pcm_substream *, int,
struct snd_soc_dai *);
/*
* For hardware based FIFO caused delay reporting.
* Optional.
*/
snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
struct snd_soc_dai *);
};

第一类:时钟配置回调函数

  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct snd_kcontrol_new {
snd_ctl_elem_iface_t iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
const unsigned char *name; /* ASCII name of item */
unsigned int index; /* index of item */
unsigned int access; /* access rights */
unsigned int count; /* count of same elements */
snd_kcontrol_info_t *info;
snd_kcontrol_get_t *get;
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
};
  • 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
2
3
SOC_SINGLE("Input Switch", WM8960_SPEAKER_MIXER,7,1,0)
SOC_SINGLE("Output Switch", WM8960_SPEAKER_MIXER,3,1,0)
SOC_SINGLE("DAC Switch", WM8960_SPEAKER_MIXER,6,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
54
struct 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
11
struct 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 设备驱动开发-约翰-马德奥》