dai link代码分析
- snd_soc_dapm_link_dai_widgets 函数连接 codec 的 widget 与其他的 widget。
// msm_tx_cdc_dma_be_dai_links(sun.c)staticstructsnd_soc_dai_linkmsm_rx_tx_cdc_dma_be_dai_links[]={{.name=LPASS_BE_TX_CDC_DMA_TX_4,// "CODEC_DMA-LPAIF_RXTX-TX-4".stream_name=LPASS_BE_TX_CDC_DMA_TX_4,// "CODEC_DMA-LPAIF_RXTX-TX-4".capture_only=1,.trigger={SND_SOC_DPCM_TRIGGER_POST,SND_SOC_DPCM_TRIGGER_POST},.ignore_suspend=1,.ops=&msm_common_be_ops,SND_SOC_DAILINK_REG(tx_dma_tx4),},}SND_SOC_DAILINK_DEFS(tx_dma_tx4,DAILINK_COMP_ARRAY(COMP_CPU("snd-soc-dummy-dai")),DAILINK_COMP_ARRAY(COMP_CODEC("lpass-cdc","tx_macro_tx2"),COMP_CODEC("wcd937x_codec","wcd937x_cdc"),COMP_CODEC("wcd938x_codec","wcd938x_cdc"),COMP_CODEC("wcd939x_codec","wcd939x_cdc"),COMP_CODEC("swr-dmic.01","swr_dmic_tx0"),COMP_CODEC("swr-dmic.02","swr_dmic_tx1"),COMP_CODEC("swr-dmic.03","swr_dmic_tx2"),COMP_CODEC("swr-dmic.04","swr_dmic_tx3")),DAILINK_COMP_ARRAY(COMP_PLATFORM("snd-soc-dummy")));staticstructsnd_soc_dai_driverdummy_dai={.name="snd-soc-dummy-dai",.playback={.stream_name="Playback",.channels_min=1,.channels_max=384,.rates=STUB_RATES,.formats=STUB_FORMATS,},.capture={.stream_name="Capture",.channels_min=1,.channels_max=384,.rates=STUB_RATES,.formats=STUB_FORMATS,},.ops=&dummy_dai_ops,};// 最终会创建一个 name和sname 都为 "TX_AIF2 Capture" 的widget// 具体代码可查看 snd_soc_dapm_new_dai_widgets() 函数staticstructsnd_soc_dai_driverlpass_cdc_tx_macro_dai[]={{.name="tx_macro_tx2",.id=LPASS_CDC_TX_MACRO_AIF2_CAP,.capture={.stream_name="TX_AIF2 Capture",.rates=LPASS_CDC_TX_MACRO_RATES,.formats=LPASS_CDC_TX_MACRO_FORMATS,.rate_max=192000,.rate_min=8000,.channels_min=1,.channels_max=8,},.ops=&lpass_cdc_tx_macro_dai_ops,},}staticconststructsnd_soc_dapm_widgetlpass_cdc_tx_macro_dapm_widgets[]={SND_SOC_DAPM_AIF_OUT("TX_AIF1 CAP","TX_AIF1 Capture",0,SND_SOC_NOPM,LPASS_CDC_TX_MACRO_AIF1_CAP,0),SND_SOC_DAPM_AIF_OUT("TX_AIF2 CAP","TX_AIF2 Capture",0,SND_SOC_NOPM,LPASS_CDC_TX_MACRO_AIF2_CAP,0),}// 创建dai widgetsoc_probe_component()for_each_component_dais()snd_soc_dapm_new_dai_widgets()// 连接dai widget与其他的 widget(根据sname)snd_soc_dapm_link_dai_widgets()简单梳理下流程:
- 在调用 snd_soc_bind_card 函数之前,会调用 populate_snd_card_dailinks 填充所有要使用的 dai link。
- soc_probe_component 函数会遍历 component 中的 dai,为每个 dai 创建对应的 widget,widget 的 name 与 sname 是 dai 中 stream 的 name
- snd_soc_dapm_link_dai_widgets 函数会根据 sname 连接 dai widget 与普通的 widget。
比如上述的过程就会根据lpass_cdc_tx_macro_dai创建一个名为TX_AIF2 Capture的dai widget,并根据它的sname连接TX_AIF2 CAP这个widget。
函数 snd_soc_dapm_connect_dai_link_widgets 负责连接 cpu dai 与 codec dai。
snd_soc_bind_card()soc_probe_link_components()soc_probe_link_dais()// 以下的两个函数都在前面函数的调用之后,因为前面两个函数会将对应的widget注册到声卡之中,没有注册后面的连接就无法进行snd_soc_dapm_link_dai_widgets()// 连接codec dai widget与 普通的 widgetsnd_soc_dapm_connect_dai_link_widgets()// 连接cpu dai widget与codec dai widget每个dai link会对应一个pcm runtime,通过下面的调用在声卡中注册对应的pcm设备
snd_soc_bind_card()for_each_card_rtds(card,rtd)soc_init_pcm_runtime(card,rtd);// 自动寻找空闲的次设备号,然后设置对应的snd_card_register中使用的回调函数soc_new_pcm()snd_card_register()// 创建一个名为 "CODEC_DMA-LPAIF_RXTX-TX-4" 的 rtdsoc_new_pcm_runtime()dev=kzalloc(sizeof(structdevice),GFP_KERNEL);// 这里会创建一个名为"CODEC_DMA-LPAIF_RXTX-TX-4"的devicedev_set_name(dev,"%s",dai_link->name);// 注册设备ret=device_register(dev);// 创建pcm设备,并添加到card的device中的详细调用栈如下:for_each_card_rtds(card,rtd)// 这个函数会将rtd中的pcm初始化,包括各种操作函数soc_init_pcm_runtime(card,rtd)soc_new_pcm()soc_create_pcm()snd_pcm_new()_snd_pcm_new()// 将创建的pcm设备添加到card的device链表之中snd_device_new()
component probe函数调用
注册component(以wcd938x.c为例子)
staticstructsnd_soc_component_driversoc_codec_dev_wcd938x={.name=WCD938X_DRV_NAME,.probe=wcd938x_soc_codec_probe,.remove=wcd938x_soc_codec_remove,.controls=wcd938x_snd_controls,.num_controls=ARRAY_SIZE(wcd938x_snd_controls),.dapm_widgets=wcd938x_dapm_widgets,.num_dapm_widgets=ARRAY_SIZE(wcd938x_dapm_widgets),.dapm_routes=wcd938x_audio_map,.num_dapm_routes=ARRAY_SIZE(wcd938x_audio_map),.suspend=wcd938x_soc_codec_suspend,.resume=wcd938x_soc_codec_resume,};wcd938x_bind(){// 注册component到全局的component_list链表之中snd_soc_register_component(dev,&soc_codec_dev_wcd938x,wcd938x_dai,ARRAY_SIZE(wcd938x_dai))}寻找component
populate_snd_card_dailinks(){// 拷贝dai—link数据到msm_kalama_dai_links数组中memcpy(msm_kalama_dai_links+total_links,msm_rx_tx_cdc_dma_be_dai_links,sizeof(msm_rx_tx_cdc_dma_be_dai_links));}// 遍历msm_kalama_dai_links数组,创建对应的pcm_runtime结构for_each_card_prelinks(card,i,dai_link)// 里面会找cpu、codec、platform对应的dai和component,然后添加到rtd的components数组中snd_soc_add_pcm_runtime(card,dai_link);// 根据dai link中的dai link component找到对应的component,然后找到component对应的daisnd_soc_find_dai()调用component 的 probe函数: 一般的功能就是注册widget,比如tfa9874这个smart pa的中定义的widget,将其注册到声卡之中。
// 调用component driver中的probe函数soc_probe_link_components()snd_soc_component_probe()// 调用component driver中的probe函数staticintsoc_probe_link_components(structsnd_soc_card*card){structsnd_soc_component*component;structsnd_soc_pcm_runtime*rtd;inti,ret,order;for_each_comp_order(order){for_each_card_rtds(card,rtd){for_each_rtd_components(rtd,i,component){// 按照顺序执行probe函数if(component->driver->probe_order!=order)continue;// 根据rtd得到对应的componentret=soc_probe_component(card,component);if(ret<0)returnret;}}}return0;}soc_probe_link_dais()// 根据rtd中的dai,调用对应的dai probe函数snd_soc_pcm_dai_probe()intsnd_soc_pcm_dai_probe(structsnd_soc_pcm_runtime*rtd,intorder){structsnd_soc_dai*dai;inti;for_each_rtd_dais(rtd,i,dai){if(dai->driver->probe_order!=order)continue;// 现在已经改版为dai->driver->ops->probeif(dai->driver->probe){intret=dai->driver->probe(dai);if(ret<0)returnsoc_dai_ret(dai,ret);}dai->probed=1;}return0;}为什么component的probe函数还需要在声卡注册阶段主动的调用,而不是直接驱动自动触发的probe调用
他也会注册为平台设备,但是平台设备的probe函数的功能是去注册component组件。后续由声卡从component_list中找到组件,然后进行调用
- 第一次probe,平台设备驱动的probe函数,它负责注册component到compnent_list中
- 第二次probe,声卡的驱动程序从component_list中找到对应的数据,调用其component driver的probe函数与dai driver的probe函数
staticconststructdev_pm_opswcd938x_dev_pm_ops={.suspend_late=wcd938x_suspend,.resume_early=wcd938x_resume,};#endifstaticstructplatform_driverwcd938x_codec_driver={.probe=wcd938x_probe,.remove=wcd938x_remove,.driver={.name="wcd938x_codec",.owner=THIS_MODULE,.of_match_table=of_match_ptr(wcd938x_dt_match),#ifdefCONFIG_PM_SLEEP.pm=&wcd938x_dev_pm_ops,#endif.suppress_bind_attrs=true,},};staticconststructof_device_idwcd938x_dt_match[]={{.compatible="qcom,wcd938x-codec",.data="wcd938x"},{}};// 注册为平台设备驱动程序module_platform_driver(wcd938x_codec_driver);// 平台驱动对应的probe函数wcd938x_probe()wcd938x_bind()snd_soc_register_component(dev,&soc_codec_dev_wcd938x,wcd938x_dai,ARRAY_SIZE(wcd938x_dai));声卡注册为平台设备,声卡的驱动程序在匹配到对应的设备时,就会自动调用probe函数。后续的component的probe与dai的probe函数都是在声卡驱动程序中调用的。如下:
staticconststructof_device_idkalama_asoc_machine_of_match[]={{.compatible="qcom,kalama-asoc-snd",.data="codec"},{.compatible="qcom,kalama-asoc-snd-stub",.data="stub_codec"},{},};staticstructplatform_driverkalama_asoc_machine_driver={.driver={.name=DRV_NAME,.owner=THIS_MODULE,.pm=&snd_soc_pm_ops,.of_match_table=kalama_asoc_machine_of_match,.suppress_bind_attrs=true,},.probe=msm_asoc_machine_probe,.remove=msm_asoc_machine_remove,};staticint__initmsm_asoc_machine_init(void){snd_card_sysfs_init();returnplatform_driver_register(&kalama_asoc_machine_driver);}