一、概述
在前面文章已经介绍了 DAPM 基本知识,了解了代表音频控件的 widget,并且系统中注册的各种 widget 需要互相连接在一起才能协调工作,故对于 ASOC Driver 中当使用到 DAPM 时需要定义 Widget 以及连接路径的 routes(ASOC DAPM Core 注册 Route 时会对连接的两个 widget 建立 path)。在本节中,将会从代码分析 DAPM Core 是如何注册 widget 以及注册 routes,主要有以下几个方面:
(1)如何注册 widget;(Including DAI Widget)
(2)如何连接两个 widget;
(3)一个 widget 的状态变化如何传递到整个音频路径中。
二、DAPM Context 介绍
参考 Link:linux-alsa详解11之DAPM详解4驱动中widget初始化
在讨论 widget 的注册之前,我们先了解另外一个概念:dapm context,直译过来的意思是 dapm 上下文,这个好像不好理解,其实我们可以这样理解:dapm 把整个音频系统,按照功能和偏置电压级别,划分为若干个电源域,每个域包含各自的 widget,每个域中的所有 widget 通常都处于同一个偏置电压级别上,而一个电源域就是一个 dapm context,通常会有以下几种 dapm context:
(1)属于 Codec 的 widget 位于一个 dapm context 中;
(2)属于 Platform 的 widget 位于一个 dapm context 中;
(3)属于整个声卡的 widget 位于一个 dapm context 中。
对于音频系统的硬件来说,通常要提供合适的偏置电压才能正常地工作,有了 dapm context 这种组织方式,我们可以方便地对于同一组 widget 进行统一的偏置电压管理,ASoc 用 snd_soc_dapm_context 结构来表示一个 dapm context:
/* DAPM context */
struct snd_soc_dapm_context {
enum snd_soc_bias_level bias_level;
unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */
/* Go to BIAS_OFF in suspend if the DAPM context is idle */
unsigned int suspend_bias_off:1;
void (*seq_notifier)(struct snd_soc_dapm_context *,
enum snd_soc_dapm_type, int);
struct device *dev; /* from parent - for debug */
struct snd_soc_component *component; /* parent component */
struct snd_soc_card *card; /* parent card */
/* used during DAPM updates */
enum snd_soc_bias_level target_bias_level;
struct list_head list;
int (*stream_event)(struct snd_soc_dapm_context *dapm, int event);
int (*set_bias_level)(struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level);
struct snd_soc_dapm_wcache path_sink_cache;
struct snd_soc_dapm_wcache path_source_cache;
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs_dapm;
#endif
};
在 snd_soc_dapm_context 中用于设置偏置电压的重要成员为 set_bias_level 函数指针,一般 Codec 会在 snd_soc_component_driver 下定义 set_bias_level 函数,最后会添加到 Component->dapm->set_bias_level 中。其中 snd_soc_bias_level 枚举变量定义如下:
/*
* Bias levels
*
* @ON: Bias is fully on for audio playback and capture operations.
* @PREPARE: Prepare for audio operations. Called before DAPM switching for
* stream start and stop operations.
* @STANDBY: Low power standby state when no playback/capture operations are
* in progress. NOTE: The transition time between STANDBY and ON
* should be as fast as possible and no longer than 10ms.
* @OFF: Power Off. No restrictions on transition times.
*/
enum snd_soc_bias_level {
SND_SOC_BIAS_OFF = 0,
SND_SOC_BIAS_STANDBY = 1,
SND_SOC_BIAS_PREPARE = 2,
SND_SOC_BIAS_ON = 3,
};
(1)For Platform & Codec,snd_soc_dapm_context 是有直接内嵌在 snd_soc_component 结构体中;
(2)For Machine,snd_soc_dapm_context 是有直接内嵌在 snd_soc_card 结构体中。
代表 widget 结构 snd_soc_dapm_widget 中,有一个 snd_soc_dapm_context 结构指针,指向所属的 codec、platform、card 结构。同时,所有 dapm 结构,通过它的 list 字段,链接到代表声卡的 snd_soc_card 结构的 dapm_list 链表头字段。
三、Widget 的注册
参考链接同 “二、DAPM Context 介绍”
我们已经知道,一个 widget 用 snd_soc_dapm_widget 结构体来描述,通常,我们会根据音频硬件的组成,分别在声卡的 codec 驱动、platform 驱动和 machine 驱动定义一组 widget,这些 widget 用数组进行组织,我们一般会使用 dapm 框架提供的大量的辅助宏来定义这些 widget 数组,辅助宏的说明参与前一篇文章 “ASOC DAPM 简介 & Widget/Kcontrol 定义”。
3.1 Codec & Platform & Machine 中"显式" Widget 的注册
根据音频硬件的组成,通常在 Codec & Platform & Machine Driver 中都会各自定义一个 snd_soc_dapm_widget 数组,并且最终都会用过 snd_soc_dapm_new_controls(dapm,widgets,num_widgets)
进行注册显式 Widgets. (显式 Widget 指的是在 Driver 中直接定义的 Widget)
(1)For Platform
:在定义 snd_soc_component_driver 时其 dapm_widgets 成员会指向定义的 Widgets 数组,在 Machine Driver 匹配成功后会在 soc_probe_link_components()
中调用 snd_soc_dapm_new_controls(dapm,conponent->driver->widgets,num_widgets) 来注册 Platform 显式 Widgets;
(2)For Codec
:有两种方式注册显式 Widgets:
① 和 Platform 一样,将定义的 Widgets 填充到 Component 中;
② 第二种是在 snd_component_driver->probe 函数中会调用 snd_soc_dapm_new_controls(dapm,widgets,num_widgets) 来注册 Widgets,而在 Machine Driver 中匹配成功后同样是在 soc_probe_link_components()
中会调用 component->driver->probe(component) 函数,从而完成 Codec 显式 Widget 的注册;
(3)For Machine:在创建 snd_card_device 后紧接着会直接调用 snd_soc_dapm_new_controls(&card->dapm,card->dapm_widgets,num_widgets)
来注册 Machine 显式 Widgets。
如上所述,对于 Codec & Platform & Machine 注册 Widgets 最终都是通过 snd_soc_dapm_new_controls() 函数进行注册显式 Widgets,定义位于:/sound/soc/soc-dapm.c
/**
* snd_soc_dapm_new_controls - create new dapm controls
* @dapm: DAPM context
* @widget: widget array
* @num: number of widgets
*
* Creates new DAPM controls based upon the templates.
*
* Returns 0 for success else error.
*/
int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_widget *widget,
int num)
{
struct snd_soc_dapm_widget *w;
int i;
int ret = 0;
mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
for (i = 0; i < num; i++) {
w = snd_soc_dapm_new_control_unlocked(dapm, widget);
if (IS_ERR(w)) {
ret = PTR_ERR(w);
break;
}
widget++;
}
mutex_unlock(&dapm->card->dapm_mutex);
return ret;
}
如上述代码所示,该函数实际上最终就是为每个 Widget 调用 snd_soc_dapm_new_control_unlocked() 函数来完成真正的注册,如下:
struct snd_soc_dapm_widget *
snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_widget *widget)
{
enum snd_soc_dapm_direction dir;
struct snd_soc_dapm_widget *w;
const char *prefix;
int ret;
//(1) 为 Widget 分配内存并拷贝内容
if ((w = dapm_cnew_widget(widget)) == NULL)
return ERR_PTR(-ENOMEM);
//(2) 当 widget 为 regulator_supply/pinctrl/clock_supply 则填充特定字段
switch (w->id) {
case snd_soc_dapm_regulator_supply:
w->regulator = devm_regulator_get(dapm->dev, w->name);
if (IS_ERR(w->regulator)) {
ret = PTR_ERR(w->regulator);
goto request_failed;
}
if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
ret = regulator_allow_bypass(w->regulator, true);
if (ret != 0)
dev_warn(dapm->dev,
"ASoC: Failed to bypass %s: %d\n",
w->name, ret);
}
break;
case snd_soc_dapm_pinctrl:
w->pinctrl = devm_pinctrl_get(dapm->dev);
if (IS_ERR(w->pinctrl)) {
ret = PTR_ERR(w->pinctrl);
goto request_failed;
}
break;
case snd_soc_dapm_clock_supply:
w->clk = devm_clk_get(dapm->dev, w->name);
if (IS_ERR(w->clk)) {
ret = PTR_ERR(w->clk);
goto request_failed;
}
break;
default:
break;
}
//(3) Widget Name 赋值
prefix = soc_dapm_prefix(dapm);
if (prefix)
w->name = kasprintf(GFP_KERNEL, "%s %s", prefix, widget->name);
else
w->name = kstrdup_const(widget->name, GFP_KERNEL);
if (w->name == NULL) {
kfree(w);
return ERR_PTR(-ENOMEM);
}
//(4) 根据 widget->id 填充 is_ep/power_check 等字段
switch (w->id) {
case snd_soc_dapm_mic:
w->is_ep = SND_SOC_DAPM_EP_SOURCE;
w->power_check = dapm_generic_check_power;
break;
case snd_soc_dapm_input:
if (!dapm->card->fully_routed)
w->is_ep = SND_SOC_DAPM_EP_SOURCE;
w->power_check = dapm_generic_check_power;
break;
case snd_soc_dapm_spk:
case snd_soc_dapm_hp:
w->is_ep = SND_SOC_DAPM_EP_SINK;
w->power_check = dapm_generic_check_power;
break;
case snd_soc_dapm_output:
if (!dapm->card->fully_routed)
w->is_ep = SND_SOC_DAPM_EP_SINK;
w->power_check = dapm_generic_check_power;
break;
case snd_soc_dapm_vmid:
case snd_soc_dapm_siggen:
w->is_ep = SND_SOC_DAPM_EP_SOURCE;
w->power_check = dapm_always_on_check_power;
break;
case snd_soc_dapm_sink:
w->is_ep = SND_SOC_DAPM_EP_SINK;
w->power_check = dapm_always_on_check_power;
break;
case snd_soc_dapm_mux:
case snd_soc_dapm_demux:
case snd_soc_dapm_switch:
case snd_soc_dapm_mixer:
case snd_soc_dapm_mixer_named_ctl:
case snd_soc_dapm_adc:
case snd_soc_dapm_aif_out:
case snd_soc_dapm_dac:
case snd_soc_dapm_aif_in:
case snd_soc_dapm_pga:
case snd_soc_dapm_out_drv:
case snd_soc_dapm_micbias:
case snd_soc_dapm_line:
case snd_soc_dapm_dai_link:
case snd_soc_dapm_dai_out:
case snd_soc_dapm_dai_in:
w->power_check = dapm_generic_check_power;
break;
case snd_soc_dapm_supply:
case snd_soc_dapm_regulator_supply:
case snd_soc_dapm_pinctrl:
case snd_soc_dapm_clock_supply:
case snd_soc_dapm_kcontrol:
w->is_supply = 1;
w->power_check = dapm_supply_check_power;
break;
default:
w->power_check = dapm_always_on_check_power;
break;
}
//(5) 初始化 widget dirty 等链表头,并将 widget 挂在 card->widgets list 中
w->dapm = dapm;
INIT_LIST_HEAD(&w->list);
INIT_LIST_HEAD(&w->dirty);
list_add_tail(&w->list, &dapm->card->widgets);
snd_soc_dapm_for_each_direction(dir) {
INIT_LIST_HEAD(&w->edges[dir]);
w->endpoints[dir] = -1;
}
/* machine layer sets up unconnected pins and insertions */
w->connected = 1;
return w;
request_failed:
if (ret != -EPROBE_DEFER)
dev_err(dapm->dev, "ASoC: Failed to request %s: %d\n",
w->name, ret);
return ERR_PTR(ret);
}
驱动中定义的 Widget 只是模板,该函数首先为该 Widget 重新分配内存,并将模板的内容拷贝过来,然后根据 widget->id 初始化一些重要字段,如 is_ep(如 mic/input/vmid/siggen=>EP_SOURCE,spk/hp/output/sink=>EP_SINK)
[Note:在后面 route 时会根据情况修改 is_ep 参数(详见后面)]、power_check 函数指针,填充 snd_soc_dapm_context 代表该 widget 属于哪一个域,以及初始化 list、dirty list、edges[2] list、endpoint[2] 等,然后将 widget 挂在 card->widgets list 中,最终将 w->connected=1.
Widget 类型和对应的 power_check 回调函数设置如下:
综上,显式 Widgets 的注册函数 snd_soc_dapm_new_controls() 主要完成功能如下:
- 为每个 Widget 分配内存,并将 Driver 中定义的 Widget 拷贝到其参数;(重新分配并拷贝 widget 参数)
- 设置 power_check 回调函数,
当音频路径发生变化时,power_check 回调会被调用,用于检查该 widget 的电源状态是否需要更新
; - 将每个 widget 挂在 card->widgets list 中。
至此,widget 已经正确地被创建并初始化,而且被挂在声卡的 widgets 链表中,以后我们就可以通过 card->widgets 链表来遍历所有的 widgets.
3.2 DAI Widgets 的注册
3.3 端点 Widgets
四、Path & Routes 的注册(完整路径)
参考 Link:linux-alsa详解13之DAPM详解6音频路径route
4.1 注册 “显式” Routes
上面小节的内容介绍了 Codec、Platform 以及 Machine 级别的显式 Widget 的注册方法,在 dapm 框架中,还有另外一种 widget,它代表了一个 dai(数字音频接口),dai 按所在位置又分为 cpu dai
和 codec dai
.对于 cpu dai 经常会区分 FE & BE
,其中 FE 不会连接 codec dai,而 BE 则会连接 codec dai,而在 Machine Driver 中,我们要在 snd_soc_card 结构中指定一个叫做 snd_soc_dai_link 的结构,其定义了声卡使用哪一个 cpu dai 和 codec dai 进行连接
。
在 Platform Driver & Codec Driver 中,当调用 snd_soc_register_component(dai) 时会将其所定义的所有 dai 注册到 component 中,即定义一个对应的 snd_soc_dai,并将这些 dai 都挂在 component->dai_list
中。在 Machine 驱动调用声卡初始化函数 snd_soc_instantiate_card() 中,Platform & Codec Component 被 Machine 匹配后,后面会调用 soc_probe_component()
函数,最后会遍历 component->dai_list 中所有的 dai 去调用 snd_soc_dapm_new_dai_widgets(dapm,dai) 进行注册 cpu & codec dai widgets
.文章来源:https://www.toymoban.com/news/detail-460995.html
4.2 DAI Widgets Connect Internal Widgets
4.3 CPU DAI Widgets Connect Codec DAI Widgets
五、DAPM Kcontrol 的创建
参考 Link:linux-alsa详解12之DAPM详解5dapm kcontrol文章来源地址https://www.toymoban.com/news/detail-460995.html
5.1 dapm mixer kcontrol
5.2 dapm mux/demux kcontrol
5.3 dapm pga kcontrol
到了这里,关于Linux ALSA 之十四:ASOC DAPM 之 Widget & Path & Route 分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!