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

Linux ALSA 之十:ALSA ASOC Machine Driver

ALSA ASOC Machine Driver

  • 一、Machine 简介
  • 二、ASoC Machine Driver
    • 2.1 Machine Driver 的 Platform Driver & Platform Device 驱动模型
    • 2.2 在 Probe() 中注册声卡
  • 三、snd_soc_register_card 函数
    • 3.1 bind DAIs
    • 3.2 New a sound card
    • 3.3 Create card new widgets
    • 3.4 Probe all component used by DAI links
    • 3.5 Probe all DAI links on this card
    • 3.6 DAPM 相关操作
    • 3.7 Register Sound Card
  • 四、创建 Pcm Device 节点


一、Machine 简介

在前面的章节中已经有提到,ASoC 被分为 Machine、Platform 和 Codec 三大部分,其中 Machine 负责 Platform 和 Codec 之间的耦合以及部分和设备或板子特定的代码。Machine 驱动负责处理机器特有的一些控件和音频事件,单独的 Platform 和 Codec 驱动是不能工作的,它必须由 Machine 驱动把它们结合在一起才能完成整个设备的音频处理工作。

在这里插入图片描述

二、ASoC Machine Driver

# Note下面均以 mt2701-wm8960.c 为例进行讲解

2.1 Machine Driver 的 Platform Driver & Platform Device 驱动模型

该部分其实就是 /sound/soc/mediate/mt2701/mt2701-wm8960.c 中的 platform driver 与 /arch/arm/boot/dts/mt7623a-rfb-emmc.dts 中的 platform device 进行匹配,匹配成功后调用 mt2701_wm8960_machine_probe() 函数。

1)mt2701-wm8960.c

#ifdef CONFIG_OF
static const struct of_device_id mt2701_wm8960_machine_dt_match[] = {{.compatible = "mediatek,mt2701-wm8960-machine",},{}
};
#endifstatic struct platform_driver mt2701_wm8960_machine = {.driver = {.name = "mt2701-wm8960",
#ifdef CONFIG_OF.of_match_table = mt2701_wm8960_machine_dt_match,
#endif},.probe = mt2701_wm8960_machine_probe,
};module_platform_driver(mt2701_wm8960_machine);

platform driver 中有注册名为 "ediatek,mt2701-wm8960-machine"

2)mt7623a-rfb-emmc.dts

...
sound {compatible = "mediatek,mt2701-wm8960-machine";mediatek,platform = <&afe>;audio-routing ="Headphone", "HP_L","Headphone", "HP_R","LINPUT1", "AMIC","RINPUT1", "AMIC";mediatek,audio-codec = <&wm8960>;pinctrl-names = "default";pinctrl-0 = <&i2s0_pins_a>;};
...

在设备树文件中有注册名为 "mediatek,mt2701-wm8960-machine"platform device,当 platform driver & platform device 匹配之后则会调用 platform_driver 下的 probe() 函数。

2.2 在 Probe() 中注册声卡

devm_snd_soc_register_card(&pdev->dev, card); // 注册声卡

ASoC 的一切都从 Machine 驱动开始,包括声卡的注册,绑定 Platform 和 Codec 驱动等。在 mt2701_wm8960_machine__probe() 函数会调用 devm_snd_soc_register_card() 函数在 ASoC Core 中注册一个 card,即 snd_soc_card,代码如下:

static struct snd_soc_card mt2701_wm8960_card = {.name = "mt2701-wm8960",.owner = THIS_MODULE,.dai_link = mt2701_wm8960_dai_links,.num_links = ARRAY_SIZE(mt2701_wm8960_dai_links),.controls = mt2701_wm8960_controls,.num_controls = ARRAY_SIZE(mt2701_wm8960_controls),.dapm_widgets = mt2701_wm8960_widgets,.num_dapm_widgets = ARRAY_SIZE(mt2701_wm8960_widgets),
};static int mt2701_wm8960_machine_probe(struct platform_device *pdev)
{struct snd_soc_card *card = &mt2701_wm8960_card;...ret = devm_snd_soc_register_card(&pdev->dev, card);if (ret)dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n",__func__, ret);...return ret;
}

如上代码所示,在 snd_soc_card 中会定义 num_links 个 snd_soc_dai_link 实例 dai_link,其中 dai_link 根据 DPCM 可以分为 Front End & Back End(当然也还有在两者之外的),并且会指定 cpu_name/cpu_of_node/cpu_dai_name、codec_name/codec_of_node/codec_dai_name、platform_name/platform_of_node 等,稍后 Machine 驱动将会利用这些属性去匹配系统中已经注册的 platform、cpu_dai、codec component。

三、snd_soc_register_card 函数

snd_soc_register_card() 函数中大部分工作都是在 snd_soc_instantiate_card() 函数中实现,其主要实现的内容如下:(涉及 DAPM 此处省略,待后面 DAPM 部分再解析)

3.1 bind DAIs

在 snd_soc_instantiate_card() 函数首先会进行 bind dais 操作,代码如下:

/* bind DAIs *///	Machine://		For FE dai_link: bind platform FE dai//		For BE dai_link: bind platform BE dai & Codec dai////	FE	--------  BE   -------//	  _|		|_   _|		  |_ HP//	  _|platform|_	  | Codec |//    _|		|_	 _| 	  |_ SPK//	   |		|	  |		  |//		--------	   -------//for_each_card_prelinks(card, i, dai_link) {ret = soc_bind_dai_link(card, dai_link);if (ret != 0)goto base_error;}

如前面所述,在定义 snd_soc_card 时有定义 dai_links,如上述代码则会循环为每个 dai_link 均调用 soc_bind_dai_link() 函数进行匹配绑定 DAI 操作,其中 soc_bind_dai_link() 函数定义如下:

static int soc_bind_dai_link(struct snd_soc_card *card,struct snd_soc_dai_link *dai_link)
{struct snd_soc_pcm_runtime *rtd;struct snd_soc_dai_link_component *codecs = dai_link->codecs;struct snd_soc_dai_link_component cpu_dai_component;struct snd_soc_component *component;struct snd_soc_dai **codec_dais;int i;if (dai_link->ignore)return 0;dev_dbg(card->dev, "ASoC: binding %s\n", dai_link->name);//3.1.1 Check the dai_link 是否已经被绑定if (soc_is_dai_link_bound(card, dai_link)) {dev_dbg(card->dev, "ASoC: dai link %s already bound\n",dai_link->name);return 0;}//3.1.2 为该 dai_link 分配一个 snd_soc_pcm_runtime 实例 rtdrtd = soc_new_pcm_runtime(card, dai_link);if (!rtd)return -ENOMEM;//3.1.3 Find cpu_dai from dai_link cpu params & component_list cpu paramscpu_dai_component.name = dai_link->cpu_name;cpu_dai_component.of_node = dai_link->cpu_of_node;cpu_dai_component.dai_name = dai_link->cpu_dai_name;rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);if (!rtd->cpu_dai) {dev_info(card->dev, "ASoC: CPU DAI %s not registered\n",dai_link->cpu_dai_name);goto _err_defer;}snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);//3.1.4 FInd codec_dais[] from dai_link codec params & component_list codec paramsrtd->num_codecs = dai_link->num_codecs;/* Find CODEC from registered CODECs *//* we can use for_each_rtd_codec_dai() after this */codec_dais = rtd->codec_dais;for (i = 0; i < rtd->num_codecs; i++) {codec_dais[i] = snd_soc_find_dai(&codecs[i]);if (!codec_dais[i]) {dev_err(card->dev, "ASoC: CODEC DAI %s not registered\n",codecs[i].dai_name);goto _err_defer;}snd_soc_rtdcom_add(rtd, codec_dais[i]->component);}/* Single codec links expect codec and codec_dai in runtime data */rtd->codec_dai = codec_dais[0];//3.1.5 Find platform component from dai_link platform params & component_list platform params/* find one from the set of registered platforms */for_each_component(component) {if (!snd_soc_is_matching_component(dai_link->platform,component))continue;snd_soc_rtdcom_add(rtd, component);}//3.1.6 Add rtd -> card->rtd_listsoc_add_pcm_runtime(card, rtd);return 0;_err_defer:soc_free_pcm_runtime(rtd);return -EPROBE_DEFER;
}

如代码所示,该函数首先会为 dai_link 分配一个 snd_soc_pcm_runtime 实例 rtd,在前面小节中已经介绍 platform & codec 驱动最终都会创建相应的 component 实例并插入到全局链表 component_list 中,此处则会根据 dai_link 参数匹配相应的 platform & codec,

根据 cpu_name/cpu_of_node/cpu_dai_name 从 component(cpu_dai_component) 中找到匹配的 snd_soc_dai cpu_dai(rtd->cpu_dai),并将该 cpu_dai 对应的 component 插入到 rtd->component_list 链表中;
根据 codec_name/codec_of_node/codec_dai_name 从 component(codec_dai component) 中找到匹配的 snd_soc_dai codec_dais(rtd->codec_dais[]),并将该 codec_dais 对应的 component 插入到 rtd->component_list 链表中;
根据 platform_name/platform_of_node 从 component(platform driver component) 【例如前面的 mtk_afe_pcm_platform】中找到匹配的 component,并将该 component 插入到 rtd->component_list 链表中。

最后调用 soc_add_pcm_runtime() 函数将该 rtd 插入到 card->rtd_list 中。

3.2 New a sound card

紧接着调用标准 alsa core 函数创建声卡实例,代码如下:

/* card bind complete so register a sound card */ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);if (ret < 0) {dev_err(card->dev,"ASoC: can't create sound card for card %s: %d\n",card->name, ret);goto base_error;}

3.3 Create card new widgets

紧接着调用 snd_soc_dapm_new_controls() 函数创建 card->dapm_widgets,如下代码可知,定义该 widget 可以通知直接在代码中定义 dapm_widgets,也可以通过在 dts 文件中创建(该函数详解解析属于 DAPM,见后面章节)

if (card->dapm_widgets)snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,card->num_dapm_widgets);if (card->of_dapm_widgets)snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets,card->num_of_dapm_widgets);

3.4 Probe all component used by DAI links

紧接着遍历 card->rtd_list 中的所有 rtd 调用 soc_probe_link_components() 函数,代码如下:

/* probe all components used by DAI links on this card */for_each_comp_order(order) {for_each_card_rtds(card, rtd) {ret = soc_probe_link_components(card, rtd, order);if (ret < 0) {dev_err(card->dev,"ASoC: failed to instantiate card %d\n",ret);goto probe_dai_err;}}}

其中 soc_probe_link_components() 函数定义如下:

static int soc_probe_link_components(struct snd_soc_card *card,struct snd_soc_pcm_runtime *rtd, int order)
{struct snd_soc_component *component;struct snd_soc_rtdcom_list *rtdcom;int ret;// 3.4.1 遍历该 rtd->component_list, 为所有用到的 components 调用 soc_probe_component() 函数for_each_rtdcom(rtd, rtdcom) {component = rtdcom->component;if (component->driver->probe_order == order) {ret = soc_probe_component(card, component);if (ret < 0)return ret;}}return 0;
}

如上代码所示,该函数会遍历该 rtd->component_list, 为所有用到的 components 调用 soc_probe_component() 函数,定义如下:

static int soc_probe_component(struct snd_soc_card *card,struct snd_soc_component *component)
{// dapm context 详见后面章节struct snd_soc_dapm_context *dapm =snd_soc_component_get_dapm(component);struct snd_soc_dai *dai;int ret;if (!strcmp(component->name, "snd-soc-dummy"))return 0;...if (!try_module_get(component->dev->driver->owner))return -ENODEV;component->card = card;dapm->card = card;soc_set_name_prefix(card, component);soc_init_component_debugfs(component);// 3.4.1.1 创建 component->driver->dai_widgetsif (component->driver->dapm_widgets) {ret = snd_soc_dapm_new_controls(dapm,component->driver->dapm_widgets,component->driver->num_dapm_widgets);if (ret != 0) {dev_err(component->dev,"Failed to create new controls %d\n", ret);goto err_probe;}}// 3.4.1.2 遍历 component->dai_list 创建 dai widgetsfor_each_component_dais(component, dai) {ret = snd_soc_dapm_new_dai_widgets(dapm, dai);if (ret != 0) {dev_err(component->dev,"Failed to create DAI widgets %d\n", ret);goto err_probe;}}// 3.4.1.3 调用 component->driver->probe() 函数if (component->driver->probe) {ret = component->driver->probe(component);if (ret < 0) {dev_err(component->dev,"ASoC: failed to probe component %d\n", ret);goto err_probe;}WARN(dapm->idle_bias_off &&dapm->bias_level != SND_SOC_BIAS_OFF,"codec %s can not start from non-off bias with idle_bias_off==1\n",component->name);}...// 3.4.1.4 创建 component->driver->controls & dapm_routesif (component->driver->controls)snd_soc_add_component_controls(component,component->driver->controls,component->driver->num_controls);if (component->driver->dapm_routes)snd_soc_dapm_add_routes(dapm,component->driver->dapm_routes,component->driver->num_dapm_routes);// 3.4.1.5 将该 dapm text(即 component dapm text) 插入到 card->dapm_list 中list_add(&dapm->list, &card->dapm_list);/* see for_each_card_components */list_add(&component->card_list, &card->component_dev_list);return 0;...return ret;
}

如代码所示,该函数主要是实现对匹配的 platform/codec component 涉及的 DAPM、kcontrol 相关操作(详见后面 DAPM 章节),并调用 component->driver->probe(),该 probe() 函数主要也是涉及 DAPM、kcontrol 操作等。

3.5 Probe all DAI links on this card

紧接着遍历 card->rtd_list 中的所有 rtd 调用 soc_probe_link_dais() 函数,代码如下:

/* probe all DAI links on this card */for_each_comp_order(order) {for_each_card_rtds(card, rtd) {ret = soc_probe_link_dais(card, rtd, order);if (ret < 0) {dev_err(card->dev,"ASoC: failed to instantiate card %d\n",ret);goto probe_dai_err;}}}

其中 soc_probe_link_dais() 函数定义如下:

static int soc_probe_link_dais(struct snd_soc_card *card,struct snd_soc_pcm_runtime *rtd, int order)
{struct snd_soc_dai_link *dai_link = rtd->dai_link;struct snd_soc_dai *cpu_dai = rtd->cpu_dai;struct snd_soc_rtdcom_list *rtdcom;struct snd_soc_component *component;struct snd_soc_dai *codec_dai;int i, ret, num;...// 3.5.1 调用 rtd->cpu_dai->driver->probe() 函数ret = soc_probe_dai(cpu_dai, order);if (ret)return ret;// 3.5.2 调用 rtd->codec_dais[]->driver->probe() 函数/* probe the CODEC DAI */for_each_rtd_codec_dai(rtd, i, codec_dai) {ret = soc_probe_dai(codec_dai, order);if (ret)return ret;}...// 3.5.3 创建 pcm deviceif (!dai_link->params) {/* create the pcm */ret = soc_new_pcm(rtd, num);if (ret < 0) {dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",dai_link->stream_name, ret);return ret;}// 3.5.4 调用 cpu_dai/codec_dais[]->driver->pcm_new() 函数ret = soc_link_dai_pcm_new(&cpu_dai, 1, rtd);if (ret < 0)return ret;ret = soc_link_dai_pcm_new(rtd->codec_dais,rtd->num_codecs, rtd);if (ret < 0)return ret;} else {INIT_DELAYED_WORK(&rtd->delayed_work,codec2codec_close_delayed_work);}return 0;
}

如上述函数定义所示,该函数首先会调用各个 rtd->cpu_dai/codec_dais[]->driver->probe() 函数,接着调用 soc_new_pcm() 函数用于 create the pcm,该函数定义如下:

/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{struct snd_soc_dai *codec_dai;struct snd_soc_dai *cpu_dai = rtd->cpu_dai;struct snd_soc_component *component;struct snd_soc_rtdcom_list *rtdcom;struct snd_pcm *pcm;char new_name[64];int ret = 0, playback = 0, capture = 0;int i;// 3.5.3.1 Check rtd 是否支持 playback & capture// DPCM://		FE: rtd->dai_link->dynamic//		BE: rtd->dai_link->no_pcm// No DPCM://		codec_dai&cpu_daiif (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {playback = rtd->dai_link->dpcm_playback;capture = rtd->dai_link->dpcm_capture;} else {for_each_rtd_codec_dai(rtd, i, codec_dai) {if (codec_dai->driver->playback.channels_min)playback = 1;if (codec_dai->driver->capture.channels_min)capture = 1;}capture = capture && cpu_dai->driver->capture.channels_min;playback = playback && cpu_dai->driver->playback.channels_min;}if (rtd->dai_link->playback_only) {playback = 1;capture = 0;}if (rtd->dai_link->capture_only) {playback = 0;capture = 1;}// 3.5.3.2 create the pcm// 由于 FE->BE ==> BE 调用 snd_pcm_new_internal() 不会创建 user device, 自然后面也就不需要填充 ops 操作集给到 user/* create the PCM */if (rtd->dai_link->no_pcm) {snprintf(new_name, sizeof(new_name), "(%s)",rtd->dai_link->stream_name);ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,playback, capture, &pcm);} else {if (rtd->dai_link->dynamic)snprintf(new_name, sizeof(new_name), "%s (*)",rtd->dai_link->stream_name);elsesnprintf(new_name, sizeof(new_name), "%s %s-%d",rtd->dai_link->stream_name,(rtd->num_codecs > 1) ?"multicodec" : rtd->codec_dai->name, num);ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,capture, &pcm);}if (ret < 0) {dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\n",rtd->dai_link->name);return ret;}...pcm->nonatomic = rtd->dai_link->nonatomic;rtd->pcm = pcm;pcm->private_data = rtd;if (rtd->dai_link->no_pcm) {if (playback)pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;if (capture)pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;goto out;}// 3.5.1.3 for BE/ No DPCM 设置 ops 操作集/* ASoC PCM operations */if (rtd->dai_link->dynamic) {rtd->ops.open		= dpcm_fe_dai_open;rtd->ops.hw_params	= dpcm_fe_dai_hw_params;rtd->ops.prepare	= dpcm_fe_dai_prepare;rtd->ops.trigger	= dpcm_fe_dai_trigger;rtd->ops.hw_free	= dpcm_fe_dai_hw_free;rtd->ops.close		= dpcm_fe_dai_close;rtd->ops.pointer	= soc_pcm_pointer;rtd->ops.ioctl		= soc_pcm_ioctl;} else {rtd->ops.open		= soc_pcm_open;rtd->ops.hw_params	= soc_pcm_hw_params;rtd->ops.prepare	= soc_pcm_prepare;rtd->ops.trigger	= soc_pcm_trigger;rtd->ops.hw_free	= soc_pcm_hw_free;rtd->ops.close		= soc_pcm_close;rtd->ops.pointer	= soc_pcm_pointer;rtd->ops.ioctl		= soc_pcm_ioctl;}for_each_rtdcom(rtd, rtdcom) {const struct snd_pcm_ops *ops = rtdcom->component->driver->ops;if (!ops)continue;if (ops->ack)rtd->ops.ack		= soc_rtdcom_ack;if (ops->copy_user)rtd->ops.copy_user	= soc_rtdcom_copy_user;if (ops->copy_kernel)rtd->ops.copy_kernel	= soc_rtdcom_copy_kernel;if (ops->fill_silence)rtd->ops.fill_silence	= soc_rtdcom_fill_silence;if (ops->page)rtd->ops.page		= soc_rtdcom_page;if (ops->mmap)rtd->ops.mmap		= soc_rtdcom_mmap;}if (playback)snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);if (capture)snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);//3.5.1.4 调用 rtd 下所有 component->driver_pcm_new()for_each_rtdcom(rtd, rtdcom) {component = rtdcom->component;if (!component->driver->pcm_new)continue;ret = component->driver->pcm_new(rtd);if (ret < 0) {dev_err(component->dev,"ASoC: pcm constructor failed: %d\n",ret);return ret;}}pcm->private_free = soc_pcm_private_free;
out:dev_info(rtd->card->dev, "%s <-> %s mapping ok\n",(rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name,cpu_dai->name);return ret;
}

如上所示该函数首先会调用标准 alsa core 函数,如下:

  • DPCM BE:由于 FE->BE,在操作 FE 时会同时操作与之 connect 的 BE,故不会对 BE 创建 user pcm device,即调用 snd_pcm_new_internal() 函数;
  • DPCM FE:调用 snd_pcm_new() 创建声卡 pcm device,并且 FE Device Name:("%s(*)", dai_link->stream_name) (故有 '*' 代表为 FE 可以动态连接 BE);
  • No DPCM:调用 snd_pcm_new() 创建声卡 pcm device,并且 No DPCM Device Name:("%s %s-%d", dai_link->stream, "multicodec"/codec_dai->name, num)(故无 '*' 代表 No NDPCM)

从上述代码中可以看出,对于 DPCM FE 和 No DPCM 对应 rtd->ops(snd_soc_ops) 字段赋值也不一样,由于 FE 的操作函数中需要涉及对 Connect’s BE 的操作;

在初始化 rtd->ops 时还会根据 platform driver 中的 snd_pcm_ops 填充相应的字段,如 ack、copy_user 等,最终会调用标准 alsa core 函数 snd_pcm_set_ops() 将 rtd->ops 设置为 substream->ops.

# Note:在 rtd->ops 字段中,如 open、hw_params、prepare 等回调函数中最终都会调用到 platform_driver & cpu_dai driver 以及 connect BE 对应的 codec driver 对应的 ops 字段相应的回调函数。

最后,该函数还会调用 rtd 下 component->driver->pcm_new() (主要是 platform driver pcm_new())函数,如前面 platform driver 小节讲解,此函数主要是预分配 DMA Memory,最后真正分配 DMA Memory 是在 Platform Driver/Cpu Dai Driver hw_params 中。

3.6 DAPM 相关操作

接着会调用 card dapm、route 相关操作(详细见后面章节)

	snd_soc_dapm_link_dai_widgets(card);snd_soc_dapm_connect_dai_link_widgets(card);if (card->controls)snd_soc_add_card_controls(card, card->controls,card->num_controls);if (card->dapm_routes)snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,card->num_dapm_routes);if (card->of_dapm_routes)snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,card->num_of_dapm_routes);...snd_soc_dapm_new_widgets(card);

3.7 Register Sound Card

snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),"%s", card->name);snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),"%s", card->long_name ? card->long_name : card->name);snprintf(card->snd_card->driver, sizeof(card->snd_card->driver),"%s", card->driver_name ? card->driver_name : card->name);for (i = 0; i < ARRAY_SIZE(card->snd_card->driver); i++) {switch (card->snd_card->driver[i]) {case '_':case '-':case '\0':break;default:if (!isalnum(card->snd_card->driver[i]))card->snd_card->driver[i] = '_';break;}}...ret = snd_card_register(card->snd_card);if (ret < 0) {dev_err(card->dev, "ASoC: failed to register soundcard %d\n",ret);goto probe_aux_dev_err;}card->instantiated = 1;

如上代码所示,在该函数最后先填充 snd_card 的重要参数,如 shortname、longname、driver 等,最后调用标准 alsa core 函数 snd_card_register() 来注册声卡以及注册该声卡下的所有 snd_devices.

至此,整个 Machine 驱动的初始化已经完成,通过各个子结构 probe 调用等,实际上,也完成了部分 Platform 驱动和 Codec 驱动的初始化工作,整个过程可以用以下的序列图表示:
在这里插入图片描述

四、创建 Pcm Device 节点

snd_soc_dai_link mt2701_wm8960_dai_link 定义如下:

static struct snd_soc_dai_link mt2701_wm8960_dai_links[] = {/* FE */{.name = "wm8960-playback",.stream_name = "wm8960-playback",.cpu_dai_name = "PCMO0",.codec_name = "snd-soc-dummy",.codec_dai_name = "snd-soc-dummy-dai",.trigger = {SND_SOC_DPCM_TRIGGER_POST,SND_SOC_DPCM_TRIGGER_POST},.dynamic = 1,.dpcm_playback = 1,},{.name = "wm8960-capture",.stream_name = "wm8960-capture",.cpu_dai_name = "PCM0",.codec_name = "snd-soc-dummy",.codec_dai_name = "snd-soc-dummy-dai",.trigger = {SND_SOC_DPCM_TRIGGER_POST,SND_SOC_DPCM_TRIGGER_POST},.dynamic = 1,.dpcm_capture = 1,},/* BE */{.name = "wm8960-codec",.cpu_dai_name = "I2S0",.no_pcm = 1,.codec_dai_name = "wm8960-hifi",.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS| SND_SOC_DAIFMT_GATED,.ops = &mt2701_wm8960_be_ops,.dpcm_playback = 1,.dpcm_capture = 1,},
};

根据上面 Machine 驱动中调用 soc_new_pcm() 创建 pcm device 节点后如下:
在这里插入图片描述
参考链接:
linux-alsa详解7 ASOC-machine

相关文章:

Linux ALSA 之十:ALSA ASOC Machine Driver

ALSA ASOC Machine Driver一、Machine 简介二、ASoC Machine Driver2.1 Machine Driver 的 Platform Driver & Platform Device 驱动模型2.2 在 Probe() 中注册声卡三、snd_soc_register_card 函数3.1 bind DAIs3.2 New a sound card3.3 Create card new widgets3.4 Probe …...

Spring 面试题(一):Spring 如何处理全局异常?

❤️ 博客首页&#xff1a;水滴技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; &#x1f338; 订阅专栏&#xff1a;Spring 教程&#xff1a;从入门到精通 文章目录1、如何处理全局异常2、代码示例2.1、定义统一的“响应结果对象”2.2、…...

Threadlocal为何引发内存泄漏问题

首先我们要先了解什么是泄漏问题和什么是内存溢出 内存泄漏表示程序员申请了内存&#xff0c;但是该内存一直无法被释放 内存溢出表示申请内存不足&#xff0c;就会报错 为何引发内存泄漏问题 因为每个线程都有自己独立的ThreadLocalMap对象&#xff0c;key为ThreadLocal&…...

如何写好 Python 的 Lambda 函数?

当你需要完成一件小工作时&#xff0c;在本地环境中使用这个函数&#xff0c;可以让工作如此得心应手&#xff0c;它就是 Lambda 函数。 Lambda 函数是 Python 中的匿名函数。有些人将它们简称为lambdas&#xff0c;它们的语法如下&#xff1a; lambda arguments: expression…...

大数据技术架构(组件)32——Spark:Spark SQL--Execute Engine

2.2、Spark SQL2.2.1、Execute EngineSparkSql的整体提交执行流程和Hive的执行流程基本上一致。站在通用的角度&#xff0c;对于SparkSql来说&#xff0c;从Sql到Spark的RDD执行需要经历两个大的阶段&#xff1a;逻辑计划和物理计划逻辑计划层面会把用户提交的sql转换成树型结构…...

Leetcode.1138 字母板上的路径

题目链接 Leetcode.1138 字母板上的路径 Rating &#xff1a; 1411 题目描述 我们从一块字母板上的位置 (0, 0)出发&#xff0c;该坐标对应的字符为 board[0][0]。 在本题里&#xff0c;字母板为board ["abcde", "fghij", "klmno", "pqr…...

一个自动配置 opengrok 多项目的脚本

前段时间在服务器上配置 opengrok 阅读代码&#xff0c;项目有很多个&#xff0c;一个一个手动配置比较繁琐。 我从搭建 tomcat 和 opengrok&#xff0c;到配置和索引完 5 个 Android 项目&#xff0c;用了差不多一整天。 要是再让我手动配置几个项目&#xff0c;估计真要崩溃…...

JAVA同步代码块 同步方法

JAVA同步代码块 & 同步方法 为了解决多线程操作共享数据时产生的安全问题 例如以下代码 if (ticket < 0) {// 卖完了break; } else {ticket--;System.out.println(Thread.currentThread().getName() "在卖票&#xff0c;还剩下" ticket "张")…...

分享111个助理类简历模板,总有一款适合您

分享111个助理类简历模板&#xff0c;总有一款适合您 111个助理类简历模板下载链接&#xff1a;https://pan.baidu.com/s/1JafYuLPQMmq37K4V0wiqWA?pwd8y54 提取码&#xff1a;8y54 Python采集代码下载链接&#xff1a;https://wwgn.lanzoul.com/iKGwb0kye3wj 设计师助理…...

Allegro如何更改临时高亮的颜色设置操作指导

Allegro如何更改临时高亮的颜色设置操作指导 在用Allegro做PCB设计的时候,当移动或者高亮某个对象之前,会被临时高亮一个颜色,方便查看,类似下图 运行高亮命令的时候,器件被临时高亮成了白色 软件默认的是白色,如何更改成其它颜色? 具体操作如下 点击Display选择Color…...

知识图谱嵌入技术研究综述

作者 张天成 1 , * 田 雪 1 , * 孙相会 1 , * 于明鹤 2 , * 孙艳红 1 , * 于 戈 摘要 知识图谱 是一种用图模型来描述知识和建模事物之间的关联关系的技术。 知识图谱嵌入 作为一种被广泛采用的知识表示方法。 主要思想是将知识图谱中的实体和关系嵌入到连续的向量空间中…...

Scratch少儿编程案例-水果忍者-超完整

专栏分享 点击跳转=>Unity3D特效百例点击跳转=>案例项目实战源码点击跳转=>游戏脚本-辅助自动化点击跳转=>Android控件全解手册点击跳转=>Scratch编程案例👉关于作者...

练 习

1.判断三个中最重的//依次输入相应的人的体重double people1, people2, people3;cout << "请输入第一个人体重" << endl;cin >> people1;cout << "请输入第二个人体重" << endl;cin >> people2;cout << "请…...

Urho3D整体结构

Urho3D引擎编译成一个库。从概念上讲&#xff0c;它由几个代表不同子系统或功能的“子库”组成。其中每个都位于Source/Urho3D目录下的子目录中&#xff1a; 容器:提供STL替换类和共享指针。数学:提供相交测试中使用的矢量、四元数和矩阵类型以及几何形状。Core:提供执行上下文…...

大数据技术之Hudi

Hudi概述 1.1 Hudi简介 Apache Hudi&#xff08;Hadoop Upserts Delete and Incremental&#xff09;是下一代流数据湖平台。Apache Hudi将核心仓库和数据库功能直接引入数据湖。Hudi提供了表、事务、高效的upserts/delete、高级索引、流摄取服务、数据集群/压缩优化和并发&a…...

libxlsxwriter条件格式

今天来看一个libxlsxwriter的高级用法&#xff1a;一个条件格式的示例。 说它“高级”&#xff0c;也是基于非Excel专家的小白们的视角。对&#xff0c;没错&#xff0c;本小白正是这样的小白。 1 一个简单的问题 来看我们今天的场景问题&#xff1a;有一列数据&#xff0c;有…...

nodejs+vue+elementui在线求助系统vscode

目 录 摘 要 1 前 言 3 第1章 概述 4 1.1 研究背景 4 1.2 研究目的 4 1.3 研究内容 4 第二章 开发技术介绍 5 前端技术&#xff1a;nodejsvueelementui,视图层其实质就是vue页面&#xff0c;通过编写vue页面从而展示在浏览器中&#xff0c;编写完成的vue页面要能够和控制器类进…...

电子技术——BJT差分输入对

电子技术——BJT差分输入对 本节我们来讨论BJT差分输入对。 共模输入 下图是BJT差分输入对的基本原理图&#xff1a; 首先我们考虑两端输入共模信号 VCMV_{CM}VCM​ &#xff1a; 此时 vB1vB2VCMv_{B1} v_{B2} V_{CM}vB1​vB2​VCM​ 因为电路的对称结构&#xff0c;所以 i…...

[MySQL教程②] - MySQL介绍和发展史

目录 ❤ MySQL介绍 ❤ 什么是数据库 ❤ 什么是数据 ❤ 数据库管理系统 ❤ NoSQL特性总览 ❤ NoSQL的分类、特点、典型产品 ❤ 常见的数据库产品有哪些&#xff1f; ❤ Oracle公司产品介绍 Oracle数据库版本介绍 Oracle的市场应用 MySQL数据库版本介绍 MyS…...

多表查询--实例

1 创建student和score表 CREATE TABLE student ( id INT(10) NOT NULL UNIQUE PRIMARY KEY , name VARCHAR(20) NOT NULL , sex VARCHAR(4) , birth YEAR, department VARCHAR(20) , address VARCHAR(50) ); 创建score表。SQL代码如下&#xff1a; CREATE TABLE score ( id INT…...

Differentially Private Grids for Geospatial Data

文章目录abstractintroabstract 在本文中&#xff0c;我们解决了为二维数据集(如地理空间数据集)构建差异私有概要的问题。目前最先进的方法通过执行数据域的递归二进制分区和构造分区的层次结构来工作。我们表明&#xff0c;基于分区的概要方法的关键挑战在于选择正确的分区粒…...

Java学习记录day8

类与对象 继承例题 https://www.bilibili.com/video/BV1PU4y1E7nX?p55&vd_source8f80327daa664c039f5c342a25bcbbae&#xff08;B站千峰马剑威Java基础入门视频第P55&#xff0c;记录的重要学习内容之一&#xff09; final关键字 作用&#xff1a; 声明一个常量&…...

Solon2 开发之容器,三、注入或手动获取 Bean

1、如何注入Bean&#xff1f; 先了解一下Bean生命周期的简化版&#xff1a; 运行构建函数尝试字段注入&#xff08;有时同步注入&#xff0c;没时订阅注入。不会有相互依赖而卡住的问题&#xff09;Init 函数&#xff08;是在容器初始化完成后才执行&#xff09;…释放&#…...

微信小程序_调用openAi搭建虚拟伙伴聊天

微信小程序_调用openAi搭建虚拟伙伴聊天背景效果关于账号注册接口实现8行python搞定小程序实现页面结构数据逻辑结速背景 从2022年的年底&#xff0c;网上都是chagpt的传说&#xff0c;个人理解这个chatgpt是模型优化训练&#xff0c;我们在用chatgpt的时候就在优化这个模型&a…...

硬件工程师入门基础知识(一)基础元器件认识(一)

硬件工程师入门基础知识 &#xff08;一&#xff09;基础元器件认识&#xff08;一&#xff09; 今天水一篇hhh。介绍点基础但是实用的东西。 tips&#xff1a;学习资料和数据来自《硬件工程师炼成之路》、百度百科、网上资料。 1.贴片电阻 2.电容 3.电感 4.磁珠 1.贴片电…...

TCP的运输连接管理

TCP的运输连接管理 文章目录TCP的运输连接管理TCP报文格式简介首部各个字段的含义控制位(flags)TCP的连接建立抓包验证一些细节及解答TCP连接释放抓包验证一些细节及解答参考TCP是面向连接的协议。运输连接是用来传送TCP报文的。TCP运输连接的建立和释放时每一次面向连接的通信…...

地级市用电、用水、用气数据指标

用电用水量和煤气及液化石油气供应及利用情况可以反映出城市基础设施的建设情况&#xff01;之前我们基于历年的《中国城市统计年鉴》整理了1999—2020年的人口数量数据指标、人口变动数据指标、用地相关数据指标、污染物排放和环境治理相关数据指标、地区生产总值及一二三产构…...

安装deepinlinuxV20.8配置docker和vscode开发c语言

# 重装的原因 某个开发任务时&#xff0c;发现需要glibc2.25,本机版本比较低&#xff0c;就下载源码configure make makeinstall&#xff0c;结果失败了&#xff0c; 看来与系统用的glibc有冲突&#xff0c;造成部分库版本不一致&#xff0c;打开终端出现段错误&#x…...

java08-面向对象3

一&#xff1a;static 关键字&#xff1a;静态的 1.可以用来修饰的结构:主要用来修饰类的内部结构 属性、方法、代码块、内部类 2. static 修饰属性&#xff1a;静态变量&#xff08;或类变量&#xff09; 2.1 属性&#xff0c;是否使用static修饰&#xff0c;又分为静态属…...

【Spark分布式内存计算框架——Spark Core】8. 共享变量

第七章 共享变量 在默认情况下&#xff0c;当Spark在集群的多个不同节点的多个任务上并行运行一个函数时&#xff0c;它会把函数中涉及到的每个变量&#xff0c;在每个任务上都生成一个副本。但是&#xff0c;有时候需要在多个任务之间共享变量&#xff0c;或者在任务(Task)和…...

网站开发移动端多少钱/网站平台如何推广

帧中继&#xff08; Frame Relay&#xff09;是一种用于连接计算机系统的面向分组的通信方法。它主要用在公共或专用网上的局域网互联以及广域网连接。大多数公共电信局都提供帧中继服务&#xff0c;把它作为建立高性能的虚拟广域连接的一种途径。1.实验器材3台思科路由器 3台…...

wordpress js插件开发教程视频/广州权威发布

前缀表示法 前缀表示法又叫波兰表示法&#xff0c;他的操作符置于操作数的前面&#xff08;例&#xff1a; 1 2&#xff09;&#xff0c;是波兰数学家扬武卡谢维奇1920年代引入的&#xff0c;用于简化命题逻辑。因为我们一般认为操作符是在操作数中间的&#xff0c;所以在日常…...

网站运行费用一般多少/百度指数行业排行

码云静态网页1 介绍2 搭建2.1 建仓库2.2 开启Gitee Pages功能3 图片3.1 头像参考1 介绍 码云是开源中国社区2013年推出的基于 Git 的代码托管服务&#xff0c;目前已经成为国内最大的代码托管平台&#xff0c;致力于为国内开发者提供优质稳定的托管服务。码云 Pages 是一个免费…...

本地江苏网站建设/24小时自助下单平台网站便宜

更新&#xff08;Update&#xff09; 在ThinkPHP中使用save方法更新数据库&#xff0c;并且也支持连贯操作的使用。 save 更新数据到数据库用法 save($data,$optionsarray())参数 data&#xff1a;要保存的数据&#xff0c;如果为空&#xff0c;则取当前的数据对象。 option…...

网站源代码上传都需要怎么做/搜外滴滴友链

华为OD机试题 最近更新的博客华为 OD 机试 300 题大纲解压缩算法题目描述输入描述输出描述说明示例一输入输出说明示例二输入输出说明代码编写思路Python 代码实现最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单...

微信导购网站怎么做视频教学/磁力搜索

OSI model&#xff08;open system interconnection&#xff09;存在的原因&#xff1a; 网络模型建立是为了是网络的建造者可以建造出可以相互交流和一起工作的网络&#xff0c;并且描述了从一个电脑上通过网络传数据到另一个网络。 1.physical层 定义了对终端系统之间的连接的…...