【DAPM杂谈之三】DAPM的初始化流程

news2025/1/13 11:11:48

本文主要分析DAPM的设计与实现

内核的版本是:linux-5.15.164,下载链接:Linux内核下载

主要讲解有关于DAPM相关的知识,会给出一些例程并分析内核如何去实现的

/*****************************************************************************************************************/

声明: 本博客内容均由芯心智库-CSDN博客原创,转载or引用请注明出处,谢谢!

创作不易,如果文章对你有帮助,麻烦点赞 收藏支持~感谢

/*****************************************************************************************************************/

目录

零、本文主要内容

一、注册DAPM的另外一种写法

二、dapm widget的初始化:snd_soc_dapm_new_controls

三、route的添加:snd_soc_dapm_add_routes

四、更新 DAPM 系统的状态:snd_soc_dapm_new_widgets


零、本文主要内容

前面的文章描述了DAPM的官方文档并且实践了一下DAPM的驱动程序应该如何写,这篇文章主要是沿着上一章节的DAPM驱动程序的线索,讲一下DAPM的初始化流程。
在Linux内核文档中直接指出了三个函数:snd_soc_dapm_new_widgets()、snd_soc_dapm_connect_input()、snd_soc_dapm_new_control(),并给出了他们的作用,原话是这样的:
  1. After all the widgets have been defined, they can then be added to the DAPM subsystem individually with a call to snd_soc_dapm_new_control().
  2. Interconnections are created with a call to: snd_soc_dapm_connect_input(codec, sink, path, source);
  3. Finally, snd_soc_dapm_new_widgets(codec) must be called after all widgets and interconnections have been registered with the core. This causes the core to scan the codec and machine so that the internal DAPM state matches the physical state of the machine.
也就是说:
  1. snd_soc_dapm_new_widgets函数需要在我们驱动中调用的进行widgets初始化,这里仅是进行简单初始化,例如申请一块内存去存储我们定义的widgets和简单的赋值,其并没有进行更深层次的操作,比如始化与硬件对应的 DAPM 状态
  2. snd_soc_dapm_connect_input函数在内核文档的描述中是用作route的创建和链接的,但是实际上,我们用更多的函数应该是:snd_soc_dapm_add_routes
  3. snd_soc_dapm_new_widgets函数的作用是进行更深层次的初始化,通过调用这个函数,系统会扫描已注册的部件和连接,并且初始化与硬件对应的 DAPM 状态

本文所有提交的代码可以见平台:https://github.com/xiaoWEN7/ASOC_DAPM/tree/master

一、注册DAPM的另外一种写法

在上一篇的文章中,我们是直接以下面的格式去注册DAPM的:
.controls		    = my_controls,
.num_controls		= ARRAY_SIZE(my_controls),
.dapm_widgets		= vcodec_dapm_widgets,
.num_dapm_widgets	= ARRAY_SIZE(vcodec_dapm_widgets),
.dapm_routes		= vcodec_dapm_routes,
.num_dapm_routes	= ARRAY_SIZE(vcodec_dapm_routes),
};

这种方式是我们经常使用的,但是其实我们也可以按照内核文档给我们的方式进行注册,例如:

--- a/codec.c
+++ b/codec.c
@@ -128,9 +128,15 @@ static const struct snd_kcontrol_new my_controls[] = {

 static int my_codec_probe(struct snd_soc_component *codec)
 {
-    //int ret;
+       //Changes to DAPM registration method^M
+       struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(codec);
+       snd_soc_dapm_new_controls(dapm, vcodec_dapm_widgets,
+               ARRAY_SIZE(vcodec_dapm_widgets));
+       snd_soc_dapm_add_routes(dapm, vcodec_dapm_routes,
+               ARRAY_SIZE(vcodec_dapm_routes));
+    
+       //int ret;
     printk("-----%s----\n",__func__);
-
     return 0;
 }

@@ -146,10 +152,10 @@ static struct snd_soc_component_driver soc_my_codec_drv = {
     .write = virtual_reg_write,
     .controls              = my_controls,
        .num_controls           = ARRAY_SIZE(my_controls),
-    .dapm_widgets              = vcodec_dapm_widgets,
-       .num_dapm_widgets       = ARRAY_SIZE(vcodec_dapm_widgets),
-       .dapm_routes            = vcodec_dapm_routes,
-       .num_dapm_routes        = ARRAY_SIZE(vcodec_dapm_routes),
+//    .dapm_widgets            = vcodec_dapm_widgets,
+//     .num_dapm_widgets       = ARRAY_SIZE(vcodec_dapm_widgets),
+//     .dapm_routes            = vcodec_dapm_routes,
+//     .num_dapm_routes        = ARRAY_SIZE(vcodec_dapm_routes),
 };

这种方式在内核源码中也有相关codec在使用:

那么这两种注册方式有何差异呢?

这要从声卡的注册流程说起了,对于不直接调用API而是定义在结构体struct snd_soc_component_driver中这种方式最终是由声卡的注册流程调用API去注册,写法参考我的这篇文章:【ASOC全解析(三)】machine原理和实战-CSDN博客。我们使用的注册声卡的函数:snd_soc_register_card(),里面就会调用到我们上面的三个API,声卡注册这里还是比较多内容的,后续我再出文章说明一下注册的流程,大致流程如下:

snd_soc_register_card()
   --> snd_soc_bind_card()
      --> snd_soc_dapm_new_controls()
      --> snd_soc_dapm_add_routes()
      --> snd_soc_dapm_new_widgets()

而如果在snd_soc_component_driver结构体的probe中调用的话流程是这样:

snd_soc_register_card()
   --> snd_soc_bind_card()
      --> snd_soc_card_probe()
         --> card->probe(card)
      --> snd_soc_dapm_new_controls()
      --> snd_soc_dapm_add_routes()
      --> snd_soc_dapm_new_widgets()

从上面我们不难看出,其实两种写法基本是差不多的,一个是依赖于声卡注册中的调用,一个则是自行在probe上加载,后面均会调用“snd_soc_dapm_new_widgets”进行进一步的初始化。

二、dapm widget的初始化:snd_soc_dapm_new_controls

这个函数的作用主要是进行widget的简单初始化,主要包括:

1、为结构体申请空间

2、根据结构体控件id设置相关的属性

整体的流程图如下:

左下角的图可能看不清,这里放大一下:

内容其实不难,可以直接看源码,我举个例子,在上个文章中的源码:

SND_SOC_DAPM_AIF_OUT_E("ADCL", "Capture", 0, VCODEC_ADCL_REG,
    2, 0, vcodec_capture_event,
    SND_SOC_DAPM_WILL_PMU | SND_SOC_DAPM_WILL_PMD),

经过snd_soc_dapm_new_controls函数的处理后,结构体snd_soc_dapm_widget的内容会变成下面:

struct snd_soc_dapm_widget {
	enum snd_soc_dapm_type id;                 (add)--> .id = snd_soc_dapm_aif_out
	const char *name;		/* widget name */   (add)--> .name = "ADCL"
	const char *sname;	/* stream name */       (add)--> .sname = "Capture"
	struct list_head list;                      (add)--> list_add_tail(&w->list, &dapm->card->widgets);
	struct snd_soc_dapm_context *dapm;          (add)--> .dapm = &component->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 */  (add)--> .reg = VCODEC_ADCL_REG
	unsigned char shift;			/* bits to shift */          (add)--> .shift = 2
	unsigned int mask;			/* non-shifted mask */           (add)--> .mask = 1
	unsigned int on_val;			/* on state value */         (add)--> .on_val =  0
	unsigned int off_val;			/* off state value */        (add)--> .off_val =  0
	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  */
	int subseq;				/* sort within widget type */widget

	int (*power_check)(struct snd_soc_dapm_widget *w);      (add)--> dapm_generic_check_power

	/* external events */
	unsigned short event_flags;		/* flags to specify event types */  (add)--> .event_flags = SND_SOC_DAPM_WILL_PMU | SND_SOC_DAPM_WILL_PMD
	int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int); (add)--> .event = vcodec_capture_event

	/* 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;                            (add)--> .channel=0
};

这每个结构体其实就是代表一个dapm_widget,代码中注册的dapm最终都会加入到“component->dapm->card->widgets”这个list中。

	struct list_head list;                      (add)--> list_add_tail(&w->list, &dapm->card->widgets);
	struct snd_soc_dapm_context *dapm;          (add)--> .dapm = &component->dapm

三、route的添加:snd_soc_dapm_add_routes

route的作用是描述各个DAPM widget的关系,比如上一个章节中,我们给出的连接关系是:

/* 定义音频路径(路由) */
static const struct snd_soc_dapm_route vcodec_dapm_routes[] = {
    /* MIC 输入路径 */
    {"ADCL Input", "MIC1 Boost Switch", "MIC1"},
    {"ADCL", NULL, "ADCL Input"},
	{"Output Switch", "Output_mixer", "ADCL"},
    /* 如果有其他输入输出路径,在此添加 */
    {"ADC Output", NULL, "Output Switch"},
};

这里首先看两个结构体:

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;
};

结构体snd_soc_dapm_route是我们在定义route的时候所使用的,它比较简洁,仅仅是把我们定义的route进行简单的赋值,但是在音频的内核调度的时候,如果要用这个结构体直接去找相应的widget势必会造成大量重复的代码,在内核中,结构体snd_soc_dapm_route通过函数snd_soc_dapm_add_routes生成了一个带有更多详细信息的结构体snd_soc_dapm_path,如下:

struct snd_soc_dapm_path {
	const char *name;

	/*
	 * source (input) and sink (output) widgets
	 * The union is for convience, since it is a lot nicer to type
	 * p->source, rather than p->node[SND_SOC_DAPM_DIR_IN]
	 */
	union {
		struct {
			struct snd_soc_dapm_widget *source;
			struct snd_soc_dapm_widget *sink;
		};
		struct snd_soc_dapm_widget *node[2];
	};

	/* status */
	u32 connect:1;	/* source and sink widgets are connected */
	u32 walking:1;  /* path is in the process of being walked */
	u32 weak:1;	/* path ignored for power management */
	u32 is_supply:1;	/* At least one of the connected widgets is a supply */

	int (*connected)(struct snd_soc_dapm_widget *source,
			 struct snd_soc_dapm_widget *sink);

	struct list_head list_node[2];
	struct list_head list_kcontrol;
	struct list_head list;
};

因此函数snd_soc_dapm_add_routes的作用其实可以归纳如下:

1、主要将简单的结构体snd_soc_dapm_route构建出具备更多信息的结构体snd_soc_dapm_path,以方便控制和减少非必要的代码

2、进行一些简单的初始化,如读取寄存器的数值进行初始化widget的状态

这个函数的流程如下:

可以看出,其实并不是非常的复杂,这里直接附上分析的代码:

/**
 * snd_soc_dapm_add_route - 添加一条DAPM路径
 * @dapm: DAPM上下文
 * @route: 描述路径的路由信息
 *
 * 此函数用于在音频系统中添加一条DAPM路径(从一个源widget到一个目的widget)。
 * DAPM(动态音频功耗管理)是ALSA系统的一个子系统,用于动态管理音频部件的功耗。
 */
static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
				  const struct snd_soc_dapm_route *route)
{
	// 定义widget指针,用于保存源和目的widget以及临时变量
	struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
	struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;
	const char *sink;  // 目的widget名称
	const char *source;  // 源widget名称
	char prefixed_sink[80];  // 带前缀的目的widget名称
	char prefixed_source[80];  // 带前缀的源widget名称
	const char *prefix;  // 前缀字符串
	unsigned int sink_ref = 0;  // 目的widget引用计数
	unsigned int source_ref = 0;  // 源widget引用计数
	int ret;

	// 获取DAPM前缀(如果存在)
	prefix = soc_dapm_prefix(dapm);
	if (prefix) {
		// 为源和目的widget添加前缀
		snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s",
			 prefix, route->sink);
		sink = prefixed_sink;
		snprintf(prefixed_source, sizeof(prefixed_source), "%s %s",
			 prefix, route->source);
		source = prefixed_source;
	} else {
		// 如果没有前缀,直接使用路由中提供的名称
		sink = route->sink;
		source = route->source;
	}

	// 首先尝试从路径缓存中查找源和目的widget
	wsource = dapm_wcache_lookup(&dapm->path_source_cache, source);
	wsink = dapm_wcache_lookup(&dapm->path_sink_cache, sink);

	// 如果源和目的widget都已找到,则跳过查找
	if (wsink && wsource)
		goto skip_search;

	/*
	 * 遍历整个声卡的widget列表,寻找源和目的widget。
	 * 优先使用当前DAPM上下文中的widget。
	 */
	for_each_card_widgets(dapm->card, w) {
		if (!wsink && !(strcmp(w->name, sink))) {
			wtsink = w;  // 记录临时找到的目的widget
			if (w->dapm == dapm) {
				wsink = w;  // 优先使用当前上下文中的widget
				if (wsource)
					break;
			}
			// 更新目的widget引用计数
			sink_ref++;
			if (sink_ref > 1)
				dev_warn(dapm->dev,
					"ASoC: sink widget %s overwritten\n",
					w->name);
			continue;
		}
		if (!wsource && !(strcmp(w->name, source))) {
			wtsource = w;  // 记录临时找到的源widget
			if (w->dapm == dapm) {
				wsource = w;  // 优先使用当前上下文中的widget
				if (wsink)
					break;
			}
			// 更新源widget引用计数
			source_ref++;
			if (source_ref > 1)
				dev_warn(dapm->dev,
					"ASoC: source widget %s overwritten\n",
					w->name);
		}
	}

	// 如果当前上下文中未找到widget,则尝试使用其他上下文中的widget
	if (!wsink)
		wsink = wtsink;
	if (!wsource)
		wsource = wtsource;

	// 如果仍未找到源widget,返回错误
	if (wsource == NULL) {
		dev_err(dapm->dev, "ASoC: no source widget found for %s\n",
			route->source);
		return -ENODEV;
	}
	// 如果仍未找到目的widget,返回错误
	if (wsink == NULL) {
		dev_err(dapm->dev, "ASoC: no sink widget found for %s\n",
			route->sink);
		return -ENODEV;
	}

skip_search:
	// 更新路径缓存,以加快后续查找
	dapm_wcache_update(&dapm->path_sink_cache, wsink);
	dapm_wcache_update(&dapm->path_source_cache, wsource);

	// 添加路径到DAPM
	ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,
		route->connected);
	if (ret)
		goto err;

	return 0;

err:
	// 如果添加路径失败,记录警告日志
	dev_warn(dapm->dev, "ASoC: no dapm match for %s --> %s --> %s\n",
		 source, route->control, sink);
	return ret;
}
其实也就是:
  • 解析路径源和目的: 根据提供的route结构体确定源和目的widget的名称,并处理可能的前缀。
  • 查找widget: 优先从路径缓存中查找,如果未找到,则在整个声卡的widget列表中查找。
  • 路径添加: 调用dapm_add_path函数将路径添加到DAPM系统中。
这里为啥没有看到我们说的结构体snd_soc_dapm_path呢?别着急,往下看:

/**
 * snd_soc_dapm_add_path - 添加一条DAPM路径
 * @dapm: DAPM上下文
 * @wsource: 源widget
 * @wsink: 目的widget
 * @control: 控制路径的名称(可选)
 * @connected: 回调函数,用于动态判断路径是否连接
 *
 * 此函数用于在DAPM系统中添加路径,并更新相关widget和路径的状态。
 */
static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
	struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,
	const char *control,
	int (*connected)(struct snd_soc_dapm_widget *source,
			 struct snd_soc_dapm_widget *sink))
{
	struct snd_soc_dapm_widget *widgets[2];  // 用于保存源和目的widget
	enum snd_soc_dapm_direction dir;  // 路径方向(输入/输出)
	struct snd_soc_dapm_path *path;  // 要创建的路径
	int ret;

	// 检查非法的路径连接:非供电widget不能连接到供电widget
	if (wsink->is_supply && !wsource->is_supply) {
		dev_err(dapm->dev,
			"Connecting non-supply widget to supply widget is not supported (%s -> %s)\n",
			wsource->name, wsink->name);
		return -EINVAL;
	}

	// 如果提供了connected回调,则源widget必须是供电widget
	if (connected && !wsource->is_supply) {
		dev_err(dapm->dev,
			"connected() callback only supported for supply widgets (%s -> %s)\n",
			wsource->name, wsink->name);
		return -EINVAL;
	}

	// 供电widget不支持带条件的路径
	if (wsource->is_supply && control) {
		dev_err(dapm->dev,
			"Conditional paths are not supported for supply widgets (%s -> [%s] -> %s)\n",
			wsource->name, control, wsink->name);
		return -EINVAL;
	}

	// 检查是否有动态路径冲突
	ret = snd_soc_dapm_check_dynamic_path(dapm, wsource, wsink, control);
	if (ret)
		return ret;

	// 为路径分配内存
	path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
	if (!path)
		return -ENOMEM;

	// 初始化路径结构
	path->node[SND_SOC_DAPM_DIR_IN] = wsource;  // 源节点
	path->node[SND_SOC_DAPM_DIR_OUT] = wsink;  // 目的节点
	widgets[SND_SOC_DAPM_DIR_IN] = wsource;
	widgets[SND_SOC_DAPM_DIR_OUT] = wsink;

	path->connected = connected;  // 设置动态连接回调
	INIT_LIST_HEAD(&path->list);  // 初始化路径链表
	INIT_LIST_HEAD(&path->list_kcontrol);  // 初始化控制链表

	// 如果源或目的widget是供电widget,标记路径为供电路径
	if (wsource->is_supply || wsink->is_supply)
		path->is_supply = 1;

	/* 处理静态路径 */
	if (control == NULL) {
		// 如果路径没有条件控制,直接标记为连接状态
		path->connect = 1;
	} else {
		// 根据widget的类型处理条件控制
		switch (wsource->id) {
		case snd_soc_dapm_demux:  // 处理多路分解器
			ret = dapm_connect_mux(dapm, path, control, wsource);
			if (ret)
				goto err;
			break;
		default:
			break;
		}

		switch (wsink->id) {
		case snd_soc_dapm_mux:  // 处理多路复用器
			ret = dapm_connect_mux(dapm, path, control, wsink);
			if (ret != 0)
				goto err;
			break;
		case snd_soc_dapm_switch:  // 处理开关
		case snd_soc_dapm_mixer:  // 处理混音器
		case snd_soc_dapm_mixer_named_ctl:  // 处理带名称的混音器
			ret = dapm_connect_mixer(dapm, path, control);
			if (ret != 0)
				goto err;
			break;
		default:
			break;
		}
	}

	// 将路径添加到DAPM的全局路径列表
	list_add(&path->list, &dapm->card->paths);

	// 将路径添加到源和目的widget的边缘列表
	snd_soc_dapm_for_each_direction(dir)
		list_add(&path->list_node[dir], &widgets[dir]->edges[dir]);

	// 更新每个widget的标志,并标记为脏以触发更新
	snd_soc_dapm_for_each_direction(dir) {
		dapm_update_widget_flags(widgets[dir]);
		dapm_mark_dirty(widgets[dir], "Route added");
	}

	// 如果声卡已实例化并且路径已连接,则使路径失效以触发重新评估
	if (dapm->card->instantiated && path->connect)
		dapm_path_invalidate(path);

	return 0;

err:
	// 如果添加路径失败,释放分配的内存
	kfree(path);
	return ret;
}
这里就通过开始实例化一个结构体snd_soc_dapm_path了:kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
然后对这个结构体的成员进行赋值,注意这两个函数:dapm_connect_mux、dapm_connect_mixer。
他们是特别一点的widget,普通的widget是用于描述自身的,而对于mux和mixer他们比较特殊:
  • mux 是一种选择器,允许从多个输入源中选择一个作为输出,比如在音频路径中选择使用麦克风输入还是线路输入。
  • mixer 用于将多个音频信号混合成一个信号,比如在耳机输出上同时播放来自麦克风和音乐播放器的音频。
所以他们是有多个选择的,这个选择是通过kcontrol来选择,因此指定一下是哪个kcontrol进行控制,以dapm_connect_mixer函数为例,
/* connect mixer widget to its interconnecting audio paths */
static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm,
	struct snd_soc_dapm_path *path, const char *control_name)
{
	int i, nth_path = 0;

	/* search for mixer kcontrol */
	for (i = 0; i < path->sink->num_kcontrols; i++) {
		if (!strcmp(control_name, path->sink->kcontrol_news[i].name)) {
			path->name = path->sink->kcontrol_news[i].name;
			dapm_set_mixer_path_status(path, i, nth_path++);
			return 0;
		}
	}
	return -ENODEV;
}

也就是以名字进行匹配的,比如上一小节的源码:

{"Output Switch", "Output_mixer", "ADCL"},

control_name变量的数值就是“Output_mixer”,其先遍历path->sink->num_kcontrols,找到相同名字的widgets,把这个名字赋值给path->name,然后调用dapm_set_mixer_path_status函数再去填充和初始化结构体snd_soc_dapm_route。

Tip :这个path->sink其实就是path->node[1],仔细看这个成员的实现:

	union {
		struct {
			struct snd_soc_dapm_widget *source;
			struct snd_soc_dapm_widget *sink;
		};
		struct snd_soc_dapm_widget *node[2];
	};

再翻一下代码,其来源就是:

path->sink   即   dapm->card->widgets

 前面第二章节分析过,这个list中有我们所有的DAPM widgets信息:

struct list_head list;                      (add)--> list_add_tail(&w->list, &dapm->card->widgets);

以route: {"ADC Output", NULL, "Output Switch"}为例:最终被填充的结构体snd_soc_dapm_route的内容如下(注意不同的route填充的结果是不太一样的):

struct snd_soc_dapm_path {
	const char *name;    如果是mixer则path->name = path->sink->kcontrol_news[i].name;

	/*
	 * source (input) and sink (output) widgets
	 * The union is for convience, since it is a lot nicer to type
	 * p->source, rather than p->node[SND_SOC_DAPM_DIR_IN]
	 */
	union {
		struct {
			struct snd_soc_dapm_widget *source;
			struct snd_soc_dapm_widget *sink;
		};
		struct snd_soc_dapm_widget *node[2];    --> path->node[SND_SOC_DAPM_DIR_IN] = wsource;path->node[SND_SOC_DAPM_DIR_OUT] = wsink;
	};

	/* status */
	u32 connect:1;	/* source and sink widgets are connected */    --> if (control == NULL) {path->connect = 1;}
	u32 walking:1;  /* path is in the process of being walked */
	u32 weak:1;	/* path ignored for power management */
	u32 is_supply:1;	/* At least one of the connected widgets is a supply */

	int (*connected)(struct snd_soc_dapm_widget *source,    --> path->connected = connected;(初始为空)
			 struct snd_soc_dapm_widget *sink);

	struct list_head list_node[2];  --> list_add(&path->list_node[0], &widgets[0]->edges[0]); list_add(&path->list_node[1], &widgets[1]->edges[1]);
	struct list_head list_kcontrol;
	struct list_head list;          --> list_add(&path->list, &dapm->card->paths);
};

以上个章节的route为例,经过了snd_soc_dapm_new_controls函数和snd_soc_dapm_new_controls函数,最终我们会得到是下面的结构体组织(图是自制的,可能会不够美观,但是你看连接和代码应该能悟到我意思):

四、更新 DAPM 系统的状态:snd_soc_dapm_new_widgets

直接贴出含函数的内容并附上分析:

int snd_soc_dapm_new_widgets(struct snd_soc_card *card)
{
    struct snd_soc_dapm_widget *w; // widget指针,用于遍历所有widget
    unsigned int val; // 存储从硬件寄存器读取的值

    // 锁定 dapm_mutex 以保证线程安全,防止在操作过程中其他线程对 DAPM 小部件进行修改
    mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);

    // 遍历当前音频卡中的所有widget
    for_each_card_widgets(card, w)
    {
        // 如果当前widget已经是新的(即已被初始化),则跳过该widget
        if (w->new)
            continue;

        // 如果当前widget有控制项(kcontrol),为其分配内存
        if (w->num_kcontrols) {
            // 为 kcontrols 数组分配内存,用于存储与该小部件相关的所有控制
            w->kcontrols = kcalloc(w->num_kcontrols,
                                    sizeof(struct snd_kcontrol *),
                                    GFP_KERNEL);
            // 如果内存分配失败,则释放锁并返回错误代码
            if (!w->kcontrols) {
                mutex_unlock(&card->dapm_mutex);
                return -ENOMEM;
            }
        }

        // 根据widget的 ID 类型来初始化不同类型的widget
        switch (w->id) {
        case snd_soc_dapm_switch:
        case snd_soc_dapm_mixer:
        case snd_soc_dapm_mixer_named_ctl:
            // 如果是开关或混音器widget,调用 dapm_new_mixer 来初始化它
            dapm_new_mixer(w);
            break;
        case snd_soc_dapm_mux:
        case snd_soc_dapm_demux:
            // 如果是复用器或解复用器,调用 dapm_new_mux 来初始化它
            dapm_new_mux(w);
            break;
        case snd_soc_dapm_pga:
        case snd_soc_dapm_effect:
        case snd_soc_dapm_out_drv:
            // 如果是放大器或效果驱动widget,调用 dapm_new_pga 来初始化它
            dapm_new_pga(w);
            break;
        case snd_soc_dapm_dai_link:
            // 如果是 DAI 链接widget,调用 dapm_new_dai_link 来初始化它
            dapm_new_dai_link(w);
            break;
        default:
            // 对于其他类型的widget,默认不做任何处理
            break;
        }

        // 如果widget有寄存器设置(即 reg >= 0),则读取初始的电源状态
        if (w->reg >= 0) {
            // 读取widget所在的寄存器值
            val = soc_dapm_read(w->dapm, w->reg);
            // 将寄存器值右移到适当的位,按掩码过滤得到有效值
            val = val >> w->shift;
            val &= w->mask;
            // 如果读取的值与 on_val 相等,则将widget的电源状态设置为开启
            if (val == w->on_val)
                w->power = 1;
        }

        // 将widget标记为已初始化
        w->new = 1;

        // 标记当前widget为“dirty”,表示它已被修改,需要重新处理
        dapm_mark_dirty(w, "new widget");

        // 将该widget添加到 DAPM 的调试文件系统(需要启用debugfs)
        dapm_debugfs_add_widget(w);
    }

    // 调用 dapm_power_widgets 来处理widget的电源管理
    dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);

    // 解锁 dapm_mutex,允许其他线程访问 DAPM 资源
    mutex_unlock(&card->dapm_mutex);

    // 返回 0,表示函数执行成功
    return 0;
}

也就是对于个别widget进行详细的初始化,例如上一章的mixier:

/* create new dapm mixer control */
static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
{
    int i, ret; // 定义循环计数器 i 和返回值 ret
    struct snd_soc_dapm_path *path; // 定义 dapm 路径结构体指针,用于遍历widget的路径
    struct dapm_kcontrol_data *data; // 定义控制数据结构体指针,用于存储控制项的信息

    /* add kcontrol */
    // 遍历当前widget的所有控制项(kcontrols)
    for (i = 0; i < w->num_kcontrols; i++) {
        
        /* match name */
        // 遍历当前widget的所有源路径(通过宏 snd_soc_dapm_widget_for_each_source_path)
        snd_soc_dapm_widget_for_each_source_path(w, path) {
            // 如果路径的名称与控制项的名称不匹配,则跳过
            if (path->name != (char *)w->kcontrol_news[i].name)
                continue;

            // 如果当前控制项未创建,则调用 dapm_create_or_share_kcontrol 创建该控制项
            if (!w->kcontrols[i]) {
                ret = dapm_create_or_share_kcontrol(w, i);
                // 如果控制项创建失败,返回错误码
                if (ret < 0)
                    return ret;
            }

            // 将当前路径添加到对应控制项的路径列表中
            dapm_kcontrol_add_path(w->kcontrols[i], path);

            // 获取当前控制项的私有数据(用于控制的硬件数据)
            data = snd_kcontrol_chip(w->kcontrols[i]);
            // 如果控制项的数据中有widget,则为该widget添加新的路径
            if (data->widget)
                snd_soc_dapm_add_path(data->widget->dapm,
                                      data->widget,
                                      path->source,
                                      NULL, NULL);
        }
    }

    // 返回 0,表示函数执行成功
    return 0;
}

即创建一个widget控制项了,这个会生成一个mixer的控制项,随后使用amixer或者tinymix工具就可以看到这个控制项了。

接下来函数snd_soc_dapm_new_widgets会调用dapm_power_widgets函数去触发一次DAPM!

这个dapm_power_widgets函数内容挺重要的,后续文章进行深入分析一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2275935.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HarmonyOS:@LocalBuilder装饰器: 维持组件父子关系

一、前言 当开发者使用Builder做引用数据传递时&#xff0c;会考虑组件的父子关系&#xff0c;使用了bind(this)之后&#xff0c;组件的父子关系和状态管理的父子关系并不一致。为了解决组件的父子关系和状态管理的父子关系保持一致的问题&#xff0c;引入LocalBuilder装饰器。…

Pytorch导出onnx模型并在C++环境中调用(含python和C++工程)

Pytorch导出onnx模型并在C环境中调用&#xff08;含python和C工程&#xff09; 工程下载链接&#xff1a;Pytorch导出onnx模型并在C环境中调用&#xff08;python和C工程&#xff09; 机器学习多层感知机MLP的Pytorch实现-以表格数据为例-含数据集和PyCharm工程中简单介绍了在…

git打补丁

1、应用场景 跨仓库升级 开发项目B使用的是开源项目A。开源项目A发现漏洞&#xff0c;作者进行了修复&#xff0c;我们可以通过使用git补丁的方式&#xff0c;将作者修改的内容复制到我 们的项目B中。 2、TortoiseGit方式 源仓库 格式化补丁 根据提交数量&#xff0c;生成…

计算机网络 (34)可靠传输的工作原理

前言 计算机网络可靠传输的工作原理主要依赖于一系列协议和机制&#xff0c;以确保数据在传输过程中能够准确无误地到达目的地。 一、基本概念 可靠传输指的是数据链路层的发送端发送什么&#xff0c;在接收端就收到什么&#xff0c;即保证数据的完整性、正确性和顺序性。由于网…

基于ADAS 与关键点特征金字塔网络融合的3D LiDAR目标检测原理与算法实现

一、概述 3D LiDAR目标检测是一种在三维空间中识别和定位感兴趣目标的技术。在自动驾驶系统和先进的空间分析中&#xff0c;目标检测方法的不断演进至关重要。3D LiDAR目标检测作为一种变革性的技术&#xff0c;在环境感知方面提供了前所未有的准确性和深度信息. 在这里&…

Vue3初学之常用的指令

v-bind&#xff1a;动态绑定属性 v-bind 用于动态绑定一个或多个属性&#xff0c;或一个组件 prop 到表达式的值。 v-model&#xff1a;双向数据绑定 见上篇 https://editor.csdn.net/md/?articleId145022994 v-if、v-else-if、v-else&#xff1a;条件渲染 v-show&…

docker中jenkins流水线式部署GitLab中springboot项目

本质就是将java项目拉取下来&#xff0c;并自动打包成docker镜像&#xff0c;运行 首先启动一个docker的jenkins 如果没有镜像使用我的镜像 通过网盘分享的文件&#xff1a;jenkins.tar 链接: https://pan.baidu.com/s/1VJOMf6RSIQbvW_V1zFD7eQ?pwd6666 提取码: 6666 放入服…

在ubuntu下对NFS做性能测试

安装NFS 首先&#xff0c;安装服务 sudo apt update sudo apt install nfs-kernel-server然后创建共享文件夹 # 请自定义你自己的共享目录 sudo mkdir -p /exports/nfs4/homes sudo chmod -R 777 /exports/nfs4/homes# 这个可以根据no_root_squash标致选择设置。 # 如果不设…

Open FPV VTX开源之默认MAVLink设置

Open FPV VTX开源之默认MAVLink设置 1. 源由2. 准备3. 连接4. 安装5. 配置6. 测试6.1 启动wfb-ng服务6.2 启动wfb-ng监测6.3 启动QGroundControl6.4 观察测试结果 7. 总结8. 参考资料9. 补充9.1 telemetry_tx异常9.2 DEBUG串口部分乱码9.3 PixelPilot软件问题 1. 源由 飞控图传…

26个开源Agent开发框架调研总结(2)

根据Markets & Markets的预测&#xff0c;到2030年&#xff0c;AI Agent的市场规模将从2024年的50亿美元激增至470亿美元&#xff0c;年均复合增长率为44.8%。 Gartner预计到2028年&#xff0c;至少15%的日常工作决策将由AI Agent自主完成&#xff0c;AI Agent在企业应用中…

mark 一下conductor github

Netflix 关闭conductor 后&#xff0c;后续https://orkes.io/content/ 继续在维护&#xff0c;github地址如下 https://github.com/conductor-oss/conductor 最新release为3.21.11

PyCharm文档管理

背景&#xff1a;使用PyCharmgit做文档管理 需求&#xff1a;需要PyCharm自动识别docx/xslx/vsdx等文件类型&#xff0c;并在PyCharm内点击文档时唤起系统内关联应用(如word、excel、visio) 设置步骤&#xff1a; 1、file -》 settings -》file types 2、在Files opened i…

嘉立创画原理图和PCB

一、环境 进入立创EDA官网 注册登录的环节就不介绍了。 登录账号后&#xff0c;选择专业版 二、原理图 工程中&#xff0c;有原理图和PCB&#xff0c;这里选择原理图 那么接下来就是进行绘制 元器件在如下区域搜索使用。 双击进行放置&#xff0c;也可以左键提前预览。 网…

科创驱动 | 华望系统科技荣膺西湖区年度前沿创新新锐企业

2025年1月3日&#xff0c;由中共西湖区党委、西湖区人民政府主办的“新年第一会”—西湖区科技创新大会在杭州隆重举行。大会现场揭晓了西湖区年度科技创新团队与项目&#xff0c;并发布了“2024西湖区科技十大事件”与“西湖区五大年度科技榜单”。杭州华望系统科技有限公司榜…

Monorepo设置:新手指南

Monorepo是一种项目代码管理方法&#xff0c;指在单个代码仓库中管理多个项目&#xff0c;有助于简化代码共享、版本控制、构建和部署的复杂性&#xff0c;并提供更好的可重用性和协作性。 简单理解&#xff1a;所有项目都在一个代码仓库中 &#x1f4e6;&#xff0c;但这并不意…

[Python学习日记-75] 计算机基础与网络

[Python学习日记-75] 计算机基础与网络 简介 计算机基础 什么是网络编程 计算机网络 简介 本篇主要介绍的计算机基础是浓缩的&#xff0c;这是因为我们主要学习的是 Python&#xff0c;而 Python 主要是为了开发应用程序的&#xff0c;并不会用它来开发操作系统和嵌入式程序…

1. Doris分布式环境搭建

一. 环境准备 本次测试集群采用3台机器hadoop1、hadoop2、hadoop3, Frontend和Backend部署在同一台机器上&#xff0c;Frontend部署3台组成高可用&#xff0c;Backend部署3个节点&#xff0c;组成3副本存储。 主机IP操作系统FrontendBackendhadoop1192.168.47.128Centos7Foll…

【Java】-- 利用 jar 命令将配置文件添加到 jar 中

目录 1、准备 2、目标 3、步骤 3.1、安装 jdk 3.2、添加配置文件 3.3、校验 1、准备 java 环境hadoop-core-1.2.1.jar 和 core-site.xml 2、目标 将 core-site.xml 添加到 hadoop-core-1.2.1.jar 中。 3、步骤 3.1、安装 jdk 3.2、添加配置文件 jar -cvf hadoop-core-…

day14-Linux系统基础权限知识精讲

1. 给文件加特殊属性 1.1 chattr a:只能追加内容&#xff0c;不能删除 i:不能修改&#xff0c;不能删除;保护关键文件&#xff0c;防止非法写入 [rootoldboy ~]# chattr a test.txt [rootoldboy ~]# chattr i test.txt [rootoldboy ~]# echo 123 >> test.txt -bash: t…

Android使用系统消息与定时器实现霓虹灯效果

演示效果: 界面设计: 在帧布局FrameLayout中添加6个TextView 依次设置这6个TextView的宽&#xff0c;高&#xff0c;权重 也可在XML中直接设置 添加自定义颜色 关联自定义颜色到数组变量 关联6个TextView控件到数组变量 处理自定义系统消息 Handler _sysHandler new Han…