0%

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设备驱动开发-约翰-马德奥》