当前位置: 首页 > news >正文

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

本文主要分析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_outconst 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->dapmvoid *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_REGunsigned char shift;			/* bits to shift */          (add)--> .shift = 2unsigned int mask;			/* non-shifted mask */           (add)--> .mask = 1unsigned int on_val;			/* on state value */         (add)--> .on_val =  0unsigned int off_val;			/* off state value */        (add)--> .off_val =  0unsigned 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 */widgetint (*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_PMDint (*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;}// 首先尝试从路径缓存中查找源和目的widgetwsource = 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;  // 记录临时找到的目的widgetif (w->dapm == dapm) {wsink = w;  // 优先使用当前上下文中的widgetif (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;  // 记录临时找到的源widgetif (w->dapm == dapm) {wsource = w;  // 优先使用当前上下文中的widgetif (wsink)break;}// 更新源widget引用计数source_ref++;if (source_ref > 1)dev_warn(dapm->dev,"ASoC: source widget %s overwritten\n",w->name);}}// 如果当前上下文中未找到widget,则尝试使用其他上下文中的widgetif (!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);// 添加路径到DAPMret = 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];  // 用于保存源和目的widgetenum snd_soc_dapm_direction dir;  // 路径方向(输入/输出)struct snd_soc_dapm_path *path;  // 要创建的路径int ret;// 检查非法的路径连接:非供电widget不能连接到供电widgetif (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必须是供电widgetif (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指针,用于遍历所有widgetunsigned int val; // 存储从硬件寄存器读取的值// 锁定 dapm_mutex 以保证线程安全,防止在操作过程中其他线程对 DAPM 小部件进行修改mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);// 遍历当前音频卡中的所有widgetfor_each_card_widgets(card, w){// 如果当前widget已经是新的(即已被初始化),则跳过该widgetif (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 类型来初始化不同类型的widgetswitch (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 和返回值 retstruct 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.mrgr.cn/news/83478.html

相关文章:

  • 二级缓存(缓存到Redis)
  • 【opencv】第7章 图像变换
  • [Android]service命令的使用
  • 01 Oracle自学环境搭建(Windows系统)
  • FFmpeg音视频流媒体,视频编解码性能优化
  • 【免费开源】积木JimuBI大屏集成ruoyiVue
  • 单片机Day1
  • 代码随想录 字符串 test1
  • MathBuddyGUI:带控制系统仿真功能、积分运算的计算器,MATLAB课程设计
  • Vue3学习总结
  • Liunx-搭建安装VSOMEIP环境教程 执行 运行VSOMEIP示例demo
  • 李宏毅机器学习课程笔记02 | 机器学习任务攻略General Guide
  • week06_预训练语言模型—BERT
  • Android车机DIY开发之软件篇(八)单独编译
  • 全面教程:Nacos 2.3.2 启用鉴权与 MySQL 数据存储配置
  • Tkinter组件-Button按键
  • 《ROS2 机器人开发 从入门道实践》 鱼香ROS2——第6章内容
  • Windows 下Mamba2 / Vim / Vmamba 环境安装问题记录及解决方法终极版(无需绕过triton)
  • 攻防靶场(34):隐蔽的计划任务提权 Funbox1
  • 【云计算】OpenStack云计算平台
  • Qt 5.14.2 学习记录 —— 십일 QLCDNumber、ProgressBar、QCalendarWidget
  • 前端开发:Web前端和HTML
  • C++之函数提高
  • 国产编辑器EverEdit - 扩展脚本:新建同类型文件(避免编程学习者反复新建保存练习文件)
  • C语言 操作符_位操作符、赋值操作符、单目操作符
  • 仓颉笔记——写一个简易的web服务并用浏览器打开