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 如何处理全局异常?
❤️ 博客首页:水滴技术 🚀 支持水滴:点赞👍 收藏⭐ 留言💬 🌸 订阅专栏:Spring 教程:从入门到精通 文章目录1、如何处理全局异常2、代码示例2.1、定义统一的“响应结果对象”2.2、…...

Threadlocal为何引发内存泄漏问题
首先我们要先了解什么是泄漏问题和什么是内存溢出 内存泄漏表示程序员申请了内存,但是该内存一直无法被释放 内存溢出表示申请内存不足,就会报错 为何引发内存泄漏问题 因为每个线程都有自己独立的ThreadLocalMap对象,key为ThreadLocal&…...
如何写好 Python 的 Lambda 函数?
当你需要完成一件小工作时,在本地环境中使用这个函数,可以让工作如此得心应手,它就是 Lambda 函数。 Lambda 函数是 Python 中的匿名函数。有些人将它们简称为lambdas,它们的语法如下: lambda arguments: expression…...

大数据技术架构(组件)32——Spark:Spark SQL--Execute Engine
2.2、Spark SQL2.2.1、Execute EngineSparkSql的整体提交执行流程和Hive的执行流程基本上一致。站在通用的角度,对于SparkSql来说,从Sql到Spark的RDD执行需要经历两个大的阶段:逻辑计划和物理计划逻辑计划层面会把用户提交的sql转换成树型结构…...

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

一个自动配置 opengrok 多项目的脚本
前段时间在服务器上配置 opengrok 阅读代码,项目有很多个,一个一个手动配置比较繁琐。 我从搭建 tomcat 和 opengrok,到配置和索引完 5 个 Android 项目,用了差不多一整天。 要是再让我手动配置几个项目,估计真要崩溃…...
JAVA同步代码块 同步方法
JAVA同步代码块 & 同步方法 为了解决多线程操作共享数据时产生的安全问题 例如以下代码 if (ticket < 0) {// 卖完了break; } else {ticket--;System.out.println(Thread.currentThread().getName() "在卖票,还剩下" ticket "张")…...

分享111个助理类简历模板,总有一款适合您
分享111个助理类简历模板,总有一款适合您 111个助理类简历模板下载链接:https://pan.baidu.com/s/1JafYuLPQMmq37K4V0wiqWA?pwd8y54 提取码:8y54 Python采集代码下载链接: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引擎编译成一个库。从概念上讲,它由几个代表不同子系统或功能的“子库”组成。其中每个都位于Source/Urho3D目录下的子目录中: 容器:提供STL替换类和共享指针。数学:提供相交测试中使用的矢量、四元数和矩阵类型以及几何形状。Core:提供执行上下文…...

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

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

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

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

[MySQL教程②] - MySQL介绍和发展史
目录 ❤ MySQL介绍 ❤ 什么是数据库 ❤ 什么是数据 ❤ 数据库管理系统 ❤ NoSQL特性总览 ❤ NoSQL的分类、特点、典型产品 ❤ 常见的数据库产品有哪些? ❤ 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代码如下: CREATE TABLE score ( id INT…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...