Linux ALSA 之十四:ASOC DAPM 之 Widget & Path & Route 分析


一、概述

在前面文章已经介绍了 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 的注册

上面小节的内容介绍了 Codec、Platform 以及 Machine 级别的显式 Widget 的注册方法,在 dapm 框架中,还有另外一种 widget,它代表了一个 dai(数字音频接口),dai 按所在位置又分为 cpu daicodec 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.

3.3 端点 Widgets

四、Path & Routes 的注册(完整路径)

参考 Link:linux-alsa详解13之DAPM详解6音频路径route

4.1 注册 “显式” Routes

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

5.1 dapm mixer kcontrol

5.2 dapm mux/demux kcontrol

5.3 dapm pga kcontrol

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值