通过snd_soc_register_card来注册card,即注册整个machine driver.
此函数接收一个参数 snd_soc_card:
/* SoC card */ struct snd_soc_card { const char *name; const char *long_name; const char *driver_name; struct device *dev; struct snd_card *snd_card; struct module *owner; struct mutex mutex; struct mutex dapm_mutex; bool instantiated; int (*probe)(struct snd_soc_card *card); int (*late_probe)(struct snd_soc_card *card); int (*remove)(struct snd_soc_card *card); /* the pre and post PM functions are used to do any PM work before and * after the codec and DAI's do any PM work. */ int (*suspend_pre)(struct snd_soc_card *card); int (*suspend_post)(struct snd_soc_card *card); int (*resume_pre)(struct snd_soc_card *card); int (*resume_post)(struct snd_soc_card *card); /* callbacks */ int (*set_bias_level)(struct snd_soc_card *, struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level); int (*set_bias_level_post)(struct snd_soc_card *, struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level); int (*add_dai_link)(struct snd_soc_card *, struct snd_soc_dai_link *link); void (*remove_dai_link)(struct snd_soc_card *, struct snd_soc_dai_link *link); long pmdown_time; /* CPU <--> Codec DAI links */ struct snd_soc_dai_link *dai_link; /* predefined links only */ int num_links; /* predefined links only */ struct list_head dai_link_list; /* all links */ int num_dai_links; struct list_head rtd_list; int num_rtd; /* optional codec specific configuration */ struct snd_soc_codec_conf *codec_conf; int num_configs; /* * optional auxiliary devices such as amplifiers or codecs with DAI * link unused */ struct snd_soc_aux_dev *aux_dev; int num_aux_devs; struct list_head aux_comp_list; const struct snd_kcontrol_new *controls; int num_controls; /* * Card-specific routes and widgets. * Note: of_dapm_xxx for Device Tree; Otherwise for driver build-in. */ const struct snd_soc_dapm_widget *dapm_widgets; int num_dapm_widgets; const struct snd_soc_dapm_route *dapm_routes; int num_dapm_routes; const struct snd_soc_dapm_widget *of_dapm_widgets; int num_of_dapm_widgets; const struct snd_soc_dapm_route *of_dapm_routes; int num_of_dapm_routes; bool fully_routed; struct work_struct deferred_resume_work; /* lists of probed devices belonging to this card */ struct list_head codec_dev_list; struct list_head widgets; struct list_head paths; struct list_head dapm_list; struct list_head dapm_dirty; /* attached dynamic objects */ struct list_head dobj_list; /* Generic DAPM context for the card */ struct snd_soc_dapm_context dapm; struct snd_soc_dapm_stats dapm_stats; struct snd_soc_dapm_update *update; #ifdef CONFIG_DEBUG_FS struct dentry *debugfs_card_root; struct dentry *debugfs_pop_time; #endif u32 pop_time; void *drvdata; }
snd_soc_card结构体成员非常多,但是在定义一个snd_soc_card时,大部分machine driver只需要定义dai_link, controls, machine level的dapm widget和dapm route.其他成员在register card时,在匹配到已经注册的platform和codec时填充。
一个典型的card定义如下:
static struct snd_soc_dai_link mt2701_cs42448_dai_links[] = { /* FE */ [DAI_LINK_FE_MULTI_CH_OUT] = { .name = "mt2701-cs42448-multi-ch-out", .stream_name = "mt2701-cs42448-multi-ch-out", .cpu_dai_name = "PCM_multi", .codec_name = "snd-soc-dummy", .codec_dai_name = "snd-soc-dummy-dai", .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .ops = &mt2701_cs42448_48k_fe_ops, .dynamic = 1, .dpcm_playback = 1, }, ... /* BE */ [DAI_LINK_BE_I2S0] = { .name = "mt2701-cs42448-I2S0", .cpu_dai_name = "I2S0", .no_pcm = 1, .codec_dai_name = "cs42448", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_GATED, .ops = &mt2701_cs42448_be_ops, .dpcm_playback = 1, .dpcm_capture = 1, }, ... }; static struct snd_soc_card mt2701_cs42448_soc_card = { .name = "mt2701-cs42448", .owner = THIS_MODULE, .dai_link = mt2701_cs42448_dai_links, .num_links = ARRAY_SIZE(mt2701_cs42448_dai_links), .controls = mt2701_cs42448_controls, .num_controls = ARRAY_SIZE(mt2701_cs42448_controls), .dapm_widgets = mt2701_cs42448_asoc_card_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(mt2701_cs42448_asoc_card_dapm_widgets), };
snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字,稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的,这样看来,Machine驱动的设备初始化代码无非就是选择合适Platform和Codec以及dai,用他们填充以上几个数据结构,然后注册Platform设备即可。
struct snd_soc_dai_link { /* config - must be set by machine driver */ const char *name; /* Codec name */ const char *stream_name; /* Stream name */ /* * You MAY specify the link's CPU-side device, either by device name, * or by DT/OF node, but not both. If this information is omitted, * the CPU-side DAI is matched using .cpu_dai_name only, which hence * must be globally unique. These fields are currently typically used * only for codec to codec links, or systems using device tree. */ const char *cpu_name; struct device_node *cpu_of_node; /* * You MAY specify the DAI name of the CPU DAI. If this information is * omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node * only, which only works well when that device exposes a single DAI. */ const char *cpu_dai_name; /* * You MUST specify the link's codec, either by device name, or by * DT/OF node, but not both. */ const char *codec_name; struct device_node *codec_of_node; /* You MUST specify the DAI name within the codec */ const char *codec_dai_name; struct snd_soc_dai_link_component *codecs; unsigned int num_codecs; /* * You MAY specify the link's platform/PCM/DMA driver, either by * device name, or by DT/OF node, but not both. Some forms of link * do not need a platform. */ const char *platform_name; struct device_node *platform_of_node; int id; /* optional ID for machine driver link identification */ const struct snd_soc_pcm_stream *params; unsigned int num_params; unsigned int dai_fmt; /* format to set on init */ enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */ /* codec/machine specific init - e.g. add machine controls */ int (*init)(struct snd_soc_pcm_runtime *rtd); /* optional hw_params re-writing for BE and FE sync */ int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params); /* machine stream operations */ const struct snd_soc_ops *ops; const struct snd_soc_compr_ops *compr_ops; /* For unidirectional dai links */ bool playback_only; bool capture_only; /* Mark this pcm with non atomic ops */ bool nonatomic; /* Keep DAI active over suspend */ unsigned int ignore_suspend:1; /* Symmetry requirements */ unsigned int symmetric_rates:1; unsigned int symmetric_channels:1; unsigned int symmetric_samplebits:1; /* Do not create a PCM for this DAI link (Backend link) */ unsigned int no_pcm:1; /* This DAI link can route to other DAI links at runtime (Frontend)*/ unsigned int dynamic:1; /* DPCM capture and Playback support */ unsigned int dpcm_capture:1; unsigned int dpcm_playback:1; /* DPCM used FE & BE merged format */ unsigned int dpcm_merged_format:1; /* pmdown_time is ignored at stop */ unsigned int ignore_pmdown_time:1; struct list_head list; /* DAI link list of the soc card */ struct snd_soc_dobj dobj; /* For topology */ };
进入正题,调用snd_soc_register_card来注册card.
/** * snd_soc_register_card - Register a card with the ASoC core * * @card: Card to register * */ int snd_soc_register_card(struct snd_soc_card *card) { int i, ret; struct snd_soc_pcm_runtime *rtd; if (!card->name || !card->dev) return -EINVAL; for (i = 0; i < card->num_links; i++) { struct snd_soc_dai_link *link = &card->dai_link[i]; ret = soc_init_dai_link(card, link); if (ret) { dev_err(card->dev, "ASoC: failed to init link %s\n", link->name); return ret; } } dev_set_drvdata(card->dev, card); snd_soc_initialize_card_lists(card); INIT_LIST_HEAD(&card->dai_link_list); card->num_dai_links = 0; INIT_LIST_HEAD(&card->rtd_list); card->num_rtd = 0; INIT_LIST_HEAD(&card->dapm_dirty); INIT_LIST_HEAD(&card->dobj_list); card->instantiated = 0; mutex_init(&card->mutex); mutex_init(&card->dapm_mutex); ret = snd_soc_instantiate_card(card); if (ret != 0) return ret; /* deactivate pins to sleep state */ list_for_each_entry(rtd, &card->rtd_list, list) { struct snd_soc_dai *cpu_dai = rtd->cpu_dai; int j; for (j = 0; j < rtd->num_codecs; j++) { struct snd_soc_dai *codec_dai = rtd->codec_dais[j]; if (!codec_dai->active) pinctrl_pm_select_sleep_state(codec_dai->dev); } if (!cpu_dai->active) pinctrl_pm_select_sleep_state(cpu_dai->dev); } return ret; }
1.遍历card->dai_link,调用soc_init_dai_link()来初始化dai_link.
在soc_init_dai_link()中,主要调用snd_soc_init_multicodec初始化该dail_link上的codec.并检查该dai_link上是否有codec_name, codec_dai_name,platform_name, cpu_name,cpu_dai_name.
static int snd_soc_init_multicodec(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) { /* Legacy codec/codec_dai link is a single entry in multicodec */ if (dai_link->codec_name || dai_link->codec_of_node || dai_link->codec_dai_name) { dai_link->num_codecs = 1; dai_link->codecs = devm_kzalloc(card->dev, sizeof(struct snd_soc_dai_link_component), GFP_KERNEL); if (!dai_link->codecs) return -ENOMEM; dai_link->codecs[0].name = dai_link->codec_name; dai_link->codecs[0].of_node = dai_link->codec_of_node; dai_link->codecs[0].dai_name = dai_link->codec_dai_name; } if (!dai_link->codecs) { dev_err(card->dev, "ASoC: DAI link has no CODECs\n"); return -EINVAL; } return 0; } static int soc_init_dai_link(struct snd_soc_card *card, struct snd_soc_dai_link *link) { int i, ret; ret = snd_soc_init_multicodec(card, link); if (ret) { dev_err(card->dev, "ASoC: failed to init multicodec\n"); return ret; } for (i = 0; i < link->num_codecs; i++) { /* * Codec must be specified by 1 of name or OF node, * not both or neither. */ if (!!link->codecs[i].name == !!link->codecs[i].of_node) { dev_err(card->dev, "ASoC: Neither/both codec name/of_node are set for %s\n", link->name); return -EINVAL; } /* Codec DAI name must be specified */ if (!link->codecs[i].dai_name) { dev_err(card->dev, "ASoC: codec_dai_name not set for %s\n", link->name); return -EINVAL; } } /* * Platform may be specified by either name or OF node, but * can be left unspecified, and a dummy platform will be used. */ if (link->platform_name && link->platform_of_node) { dev_err(card->dev, "ASoC: Both platform name/of_node are set for %s\n", link->name); return -EINVAL; } /* * CPU device may be specified by either name or OF node, but * can be left unspecified, and will be matched based on DAI * name alone.. */ if (link->cpu_name && link->cpu_of_node) { dev_err(card->dev, "ASoC: Neither/both cpu name/of_node are set for %s\n", link->name); return -EINVAL; } /* * At least one of CPU DAI name or CPU device name/node must be * specified */ if (!link->cpu_dai_name && !(link->cpu_name || link->cpu_of_node)) { dev_err(card->dev, "ASoC: Neither cpu_dai_name nor cpu_name/of_node are set for %s\n", link->name); return -EINVAL; } return 0; }
2.初始化card的一些list,包括widgets, path, dapm_list, dai_link_list, rtd_list, dapm_dirty.
3.调用snd_soc_instantiate_card来实例化card,大部分注册工作都在此函数内完成。
static int snd_soc_instantiate_card(struct snd_soc_card *card) { struct snd_soc_codec *codec; struct snd_soc_pcm_runtime *rtd; struct snd_soc_dai_link *dai_link; int ret, i, order; mutex_lock(&client_mutex); mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_INIT); /* bind DAIs */ for (i = 0; i < card->num_links; i++) { ret = soc_bind_dai_link(card, &card->dai_link[i]); if (ret != 0) goto base_error; } /* bind aux_devs too */ for (i = 0; i < card->num_aux_devs; i++) { ret = soc_bind_aux_dev(card, i); if (ret != 0) goto base_error; } /* add predefined DAI links to the list */ for (i = 0; i < card->num_links; i++) snd_soc_add_dai_link(card, card->dai_link+i); /* initialize the register cache for each available codec */ list_for_each_entry(codec, &codec_list, list) { if (codec->cache_init) continue; ret = snd_soc_init_codec_cache(codec); if (ret < 0) goto base_error; } /* 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; } soc_init_card_debugfs(card); card->dapm.bias_level = SND_SOC_BIAS_OFF; card->dapm.dev = card->dev; card->dapm.card = card; list_add(&card->dapm.list, &card->dapm_list); #ifdef CONFIG_DEBUG_FS snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root); #endif #ifdef CONFIG_PM_SLEEP /* deferred resume work */ INIT_WORK(&card->deferred_resume_work, soc_resume_deferred); #endif 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); /* initialise the sound card only once */ if (card->probe) { ret = card->probe(card); if (ret < 0) goto card_probe_error; } /* probe all components used by DAI links on this card */ for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; order++) { list_for_each_entry(rtd, &card->rtd_list, list) { 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; } } } /* probe auxiliary components */ ret = soc_probe_aux_devices(card); if (ret < 0) goto probe_dai_err; /* Find new DAI links added during probing components and bind them. * Components with topology may bring new DAIs and DAI links. */ list_for_each_entry(dai_link, &card->dai_link_list, list) { if (soc_is_dai_link_bound(card, dai_link)) continue; ret = soc_init_dai_link(card, dai_link); if (ret) goto probe_dai_err; ret = soc_bind_dai_link(card, dai_link); if (ret) goto probe_dai_err; } /* probe all DAI links on this card */ for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; order++) { list_for_each_entry(rtd, &card->rtd_list, list) { 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; } } } 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); 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; } } if (card->late_probe) { ret = card->late_probe(card); if (ret < 0) { dev_err(card->dev, "ASoC: %s late_probe() failed: %d\n", card->name, ret); goto probe_aux_dev_err; } } snd_soc_dapm_new_widgets(card); 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; dapm_mark_endpoints_dirty(card); snd_soc_dapm_sync(&card->dapm); mutex_unlock(&card->mutex); mutex_unlock(&client_mutex); return 0; probe_aux_dev_err: soc_remove_aux_devices(card); probe_dai_err: soc_remove_dai_links(card); card_probe_error: if (card->remove) card->remove(card); snd_soc_dapm_free(&card->dapm); soc_cleanup_card_debugfs(card); snd_card_free(card->snd_card); base_error: soc_remove_pcm_runtimes(card); mutex_unlock(&card->mutex); mutex_unlock(&client_mutex); return ret; }
3.1遍历每一对dai_link,调用soc_bind_dai_link对codec、platform、dai进行bind。
ASoC定义了三个全局的链表头变量:codec_list、dai_list、platform_list,系统中所有的Codec、DAI、Platform都在注册时连接到这三个全局链表上。soc_bind_dai_link函数逐个扫描这三个链表,根据card->dai_link[]中的名称进行匹配,匹配后把相应的codec,dai和platform实例赋值到card->rtd[]中(snd_soc_pcm_runtime)。经过这个过程后,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驱动的信息。
在soc_bind_dai_link中,对每个dai_link都通过soc_new_pcm_rutime()创建一个rtd,在函数最后通过调用soc_add_pcm_rutime()将rtd加到card->rtd_list链表中。
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_dai **codec_dais; struct snd_soc_platform *platform; const char *platform_name; int i; dev_dbg(card->dev, "ASoC: binding %s\n", dai_link->name); 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; } rtd = soc_new_pcm_runtime(card, dai_link); if (!rtd) return -ENOMEM; cpu_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_err(card->dev, "ASoC: CPU DAI %s not registered\n", dai_link->cpu_dai_name); goto _err_defer; } rtd->num_codecs = dai_link->num_codecs; /* Find CODEC from registered CODECs */ 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; } } /* Single codec links expect codec and codec_dai in runtime data */ rtd->codec_dai = codec_dais[0]; rtd->codec = rtd->codec_dai->codec; /* if there's no platform we match on the empty platform */ platform_name = dai_link->platform_name; if (!platform_name && !dai_link->platform_of_node) platform_name = "snd-soc-dummy"; /* find one from the set of registered platforms */ list_for_each_entry(platform, &platform_list, list) { if (dai_link->platform_of_node) { if (platform->dev->of_node != dai_link->platform_of_node) continue; } else { if (strcmp(platform->component.name, platform_name)) continue; } rtd->platform = platform; } if (!rtd->platform) { dev_err(card->dev, "ASoC: platform %s not registered\n", dai_link->platform_name); goto _err_defer; } soc_add_pcm_runtime(card, rtd); return 0; _err_defer: soc_free_pcm_runtime(rtd); return -EPROBE_DEFER; }
3.2调用snd_soc_add_dai_link将dai_link添加到card->dai_link_list中。
3.3调用snd_card_new创建card->snd_card.
3.4调用snd_soc_dapm_new_controls创建card->dapm_widgets.
在snd_soc_dapm_new_controls遍历card->dapm_widgets的所有的widgets,通过调用snd_soc_dapm_new_control_unlock()来创建widget,设定每个widget的power_check()函数,并将创建的widget加到card->widget链表中。
/** * snd_soc_dapm_new_controls - create new dapm controls * @dapm: DAPM context * @widget: widget array * @num: number of widgets * * Creates new DAPM controls based upon the templates. * * Returns 0 for success else error. */ int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_widget *widget, int num) { struct snd_soc_dapm_widget *w; int i; int ret = 0; mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT); for (i = 0; i < num; i++) { w = snd_soc_dapm_new_control_unlocked(dapm, widget); if (IS_ERR(w)) { ret = PTR_ERR(w); /* Do not nag about probe deferrals */ if (ret == -EPROBE_DEFER) break; dev_err(dapm->dev, "ASoC: Failed to create DAPM control %s (%d)\n", widget->name, ret); break; } if (!w) { dev_err(dapm->dev, "ASoC: Failed to create DAPM control %s\n", widget->name); ret = -ENOMEM; break; } widget++; } mutex_unlock(&dapm->card->dapm_mutex); return ret; } struct snd_soc_dapm_widget * snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_widget *widget) { enum snd_soc_dapm_direction dir; struct snd_soc_dapm_widget *w; const char *prefix; int ret; if ((w = dapm_cnew_widget(widget)) == NULL) return NULL; switch (w->id) { case snd_soc_dapm_regulator_supply: w->regulator = devm_regulator_get(dapm->dev, w->name); if (IS_ERR(w->regulator)) { ret = PTR_ERR(w->regulator); if (ret == -EPROBE_DEFER) return ERR_PTR(ret); dev_err(dapm->dev, "ASoC: Failed to request %s: %d\n", w->name, ret); return NULL; } if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) { ret = regulator_allow_bypass(w->regulator, true); if (ret != 0) dev_warn(w->dapm->dev, "ASoC: Failed to bypass %s: %d\n", w->name, ret); } break; case snd_soc_dapm_clock_supply: #ifdef CONFIG_CLKDEV_LOOKUP w->clk = devm_clk_get(dapm->dev, w->name); if (IS_ERR(w->clk)) { ret = PTR_ERR(w->clk); if (ret == -EPROBE_DEFER) return ERR_PTR(ret); dev_err(dapm->dev, "ASoC: Failed to request %s: %d\n", w->name, ret); return NULL; } #else return NULL; #endif break; default: break; } prefix = soc_dapm_prefix(dapm); if (prefix) w->name = kasprintf(GFP_KERNEL, "%s %s", prefix, widget->name); else w->name = kstrdup_const(widget->name, GFP_KERNEL); if (w->name == NULL) { kfree(w); return NULL; } switch (w->id) { case snd_soc_dapm_mic: w->is_ep = SND_SOC_DAPM_EP_SOURCE; w->power_check = dapm_generic_check_power; break; case snd_soc_dapm_input: if (!dapm->card->fully_routed) w->is_ep = SND_SOC_DAPM_EP_SOURCE; w->power_check = dapm_generic_check_power; break; case snd_soc_dapm_spk: case snd_soc_dapm_hp: w->is_ep = SND_SOC_DAPM_EP_SINK; w->power_check = dapm_generic_check_power; break; case snd_soc_dapm_output: if (!dapm->card->fully_routed) w->is_ep = SND_SOC_DAPM_EP_SINK; w->power_check = dapm_generic_check_power; break; case snd_soc_dapm_vmid: case snd_soc_dapm_siggen: w->is_ep = SND_SOC_DAPM_EP_SOURCE; w->power_check = dapm_always_on_check_power; break; case snd_soc_dapm_sink: w->is_ep = SND_SOC_DAPM_EP_SINK; w->power_check = dapm_always_on_check_power; break; case snd_soc_dapm_mux: case snd_soc_dapm_demux: case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: case snd_soc_dapm_adc: case snd_soc_dapm_aif_out: case snd_soc_dapm_dac: case snd_soc_dapm_aif_in: case snd_soc_dapm_pga: case snd_soc_dapm_out_drv: case snd_soc_dapm_micbias: case snd_soc_dapm_line: case snd_soc_dapm_dai_link: case snd_soc_dapm_dai_out: case snd_soc_dapm_dai_in: w->power_check = dapm_generic_check_power; break; case snd_soc_dapm_supply: case snd_soc_dapm_regulator_supply: case snd_soc_dapm_clock_supply: case snd_soc_dapm_kcontrol: w->is_supply = 1; w->power_check = dapm_supply_check_power; break; default: w->power_check = dapm_always_on_check_power; break; } w->dapm = dapm; INIT_LIST_HEAD(&w->list); INIT_LIST_HEAD(&w->dirty); list_add_tail(&w->list, &dapm->card->widgets); snd_soc_dapm_for_each_direction(dir) { INIT_LIST_HEAD(&w->edges[dir]); w->endpoints[dir] = -1; } /* machine layer sets up unconnected pins and insertions */ w->connected = 1; return w; }
3.5 如果card有定义probe函数,调用probe
3.6遍历card->rtd_list,调用soc_probe_link_components来probe每个rtd上的component(paltform, cpu_dai, codec_dai的component).
static int soc_probe_link_components(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd, int order) { struct snd_soc_platform *platform = rtd->platform; struct snd_soc_component *component; int i, ret; /* probe the CPU-side component, if it is a CODEC */ component = rtd->cpu_dai->component; if (component->driver->probe_order == order) { ret = soc_probe_component(card, component); if (ret < 0) return ret; } /* probe the CODEC-side components */ for (i = 0; i < rtd->num_codecs; i++) { component = rtd->codec_dais[i]->component; if (component->driver->probe_order == order) { ret = soc_probe_component(card, component); if (ret < 0) return ret; } } /* probe the platform */ if (platform->component.driver->probe_order == order) { ret = soc_probe_component(card, &platform->component); if (ret < 0) return ret; } return 0; }
在soc_probe_component()里面,基于该component->dapm_widgets创建widget,将widget加到card->widgets链表中。基于component->dai_list中的dai创建dai widget,也将此widget加到card->widgets链表中。基于component->controls创建kcontrol,将此kcontrol加到card->snd_card->controls链表中。基于component->dapm_routes创建dapm path,并将创建的path加到card->paths.
static int soc_probe_component(struct snd_soc_card *card, struct snd_soc_component *component) { 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 (component->card) { if (component->card != card) { dev_err(component->dev, "Trying to bind component to card \"%s\" but is already bound to card \"%s\"\n", card->name, component->card->name); return -ENODEV; } 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); if (component->dapm_widgets) { ret = snd_soc_dapm_new_controls(dapm, component->dapm_widgets, component->num_dapm_widgets); if (ret != 0) { dev_err(component->dev, "Failed to create new controls %d\n", ret); goto err_probe; } } list_for_each_entry(dai, &component->dai_list, list) { 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; } } if (component->probe) { ret = component->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); } /* machine specific init */ if (component->init) { ret = component->init(component); if (ret < 0) { dev_err(component->dev, "Failed to do machine specific init %d\n", ret); goto err_probe; } } if (component->controls) snd_soc_add_component_controls(component, component->controls, component->num_controls); if (component->dapm_routes) snd_soc_dapm_add_routes(dapm, component->dapm_routes, component->num_dapm_routes); list_add(&dapm->list, &card->dapm_list); /* This is a HACK and will be removed soon */ if (component->codec) list_add(&component->codec->card_list, &card->codec_dev_list); return 0; err_probe: soc_cleanup_component_debugfs(component); component->card = NULL; module_put(component->dev->driver->owner); return ret; }
3.7遍历card->rtd_list, 调用soc_probe_link_dais来probe每个rtd上的dais(cpu_dai, codec_dai).
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; int i, ret; dev_dbg(card->dev, "ASoC: probe %s dai link %d late %d\n", card->name, rtd->num, order); /* set default power off timeout */ rtd->pmdown_time = pmdown_time; ret = soc_probe_dai(cpu_dai, order); if (ret) return ret; /* probe the CODEC DAI */ for (i = 0; i < rtd->num_codecs; i++) { ret = soc_probe_dai(rtd->codec_dais[i], order); if (ret) return ret; } /* complete DAI probe during last probe */ if (order != SND_SOC_COMP_ORDER_LAST) return 0; /* do machine specific initialization */ if (dai_link->init) { ret = dai_link->init(rtd); if (ret < 0) { dev_err(card->dev, "ASoC: failed to init %s: %d\n", dai_link->name, ret); return ret; } } if (dai_link->dai_fmt) snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt); ret = soc_post_component_init(rtd, dai_link->name); if (ret) return ret; #ifdef CONFIG_DEBUG_FS /* add DPCM sysfs entries */ if (dai_link->dynamic) soc_dpcm_debugfs_add(rtd); #endif if (cpu_dai->driver->compress_new) { /*create compress_device"*/ ret = cpu_dai->driver->compress_new(rtd, rtd->num); if (ret < 0) { dev_err(card->dev, "ASoC: can't create compress %s\n", dai_link->stream_name); return ret; } } else { if (!dai_link->params) { /* create the pcm */ ret = soc_new_pcm(rtd, rtd->num); if (ret < 0) { dev_err(card->dev, "ASoC: can't create pcm %s :%d\n", dai_link->stream_name, ret); return ret; } } else { INIT_DELAYED_WORK(&rtd->delayed_work, codec2codec_close_delayed_work); /* link the DAI widgets */ ret = soc_link_dai_widgets(card, dai_link, rtd); if (ret) return ret; } } return 0; }
在soc_probe_link_dais()所做的事情如下:
a)首先调用soc_probe_dai()来probe cpu_dai/codec_dai.在soc_probe_dai()中是调用dai driver的probe()函数.
b)如果dai_link定义dai_fmt,则调用snd_soc_runtime_set_dai_fmt将format设置到cpu_dai和codec_dai,即调用dai->driver->ops->set_fmt()。
c)如果dai_link没有定义params,则调用soc_pcm_new来创建pcm device.
在soc_pcm_new中根据dai_link的dynamic(FE), no_pcm(BE)成员来用不同的函数创建pcm,并设置不同的ops.
/* create a new pcm */ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) { struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_pcm *pcm; char new_name[64]; int ret = 0, playback = 0, capture = 0; int i; if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) { playback = rtd->dai_link->dpcm_playback; capture = rtd->dai_link->dpcm_capture; } else { for (i = 0; i < rtd->num_codecs; i++) { codec_dai = rtd->codec_dais[i]; 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; } /* 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); else snprintf(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; } dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name); /* DAPM dai link stream work */ INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work); 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; } /* 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; } if (platform->driver->ops) { rtd->ops.ack = platform->driver->ops->ack; rtd->ops.copy = platform->driver->ops->copy; rtd->ops.silence = platform->driver->ops->silence; rtd->ops.page = platform->driver->ops->page; rtd->ops.mmap = platform->driver->ops->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); if (platform->driver->pcm_new) { ret = platform->driver->pcm_new(rtd); if (ret < 0) { dev_err(platform->dev, "ASoC: pcm constructor failed: %d\n", ret); return ret; } } pcm->private_free = platform->driver->pcm_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; }
3.8调用snd_soc_dapm_link_dai_widgets来link dai widget和非dai widget.
snd_soc_dapm_link_dai_widgets函数会去遍历每一个dai widgets,然后遍历所有的非dai widgets,如果非dai widgets的stream name与dai widgets的name相同,则把两个widgets进行链接。这也是为什么创建dai widgets时name一定要是stream name的原因之一了。
int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card) { struct snd_soc_dapm_widget *dai_w, *w; struct snd_soc_dapm_widget *src, *sink; struct snd_soc_dai *dai; /* For each DAI widget... */ list_for_each_entry(dai_w, &card->widgets, list) { switch (dai_w->id) { case snd_soc_dapm_dai_in: case snd_soc_dapm_dai_out: break; default: continue; } /* let users know there is no DAI to link */ if (!dai_w->priv) { dev_dbg(card->dev, "dai widget %s has no DAI\n", dai_w->name); continue; } dai = dai_w->priv; /* ...find all widgets with the same stream and link them */ list_for_each_entry(w, &card->widgets, list) { if (w->dapm != dai_w->dapm) continue; switch (w->id) { case snd_soc_dapm_dai_in: case snd_soc_dapm_dai_out: continue; default: break; } if (!w->sname || !strstr(w->sname, dai_w->sname)) continue; if (dai_w->id == snd_soc_dapm_dai_in) { src = dai_w; sink = w; } else { src = w; sink = dai_w; } dev_dbg(dai->dev, "%s -> %s\n", src->name, sink->name); snd_soc_dapm_add_path(w->dapm, src, sink, NULL, NULL); } } return 0; }
3.9调用snd_soc_dapm_connect_dai_link_widgets() link CPU BE DAI widget 和 codec DAI widget.
void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *rtd; /* for each BE DAI link... */ list_for_each_entry(rtd, &card->rtd_list, list) { /* * dynamic FE links have no fixed DAI mapping. * CODEC<->CODEC links have no direct connection. */ if (rtd->dai_link->dynamic || rtd->dai_link->params) continue; dapm_connect_dai_link_widgets(card, rtd); } } static void dapm_connect_dai_link_widgets(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_soc_dapm_widget *sink, *source; int i; for (i = 0; i < rtd->num_codecs; i++) { struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; /* connect BE DAI playback if widgets are valid */ if (codec_dai->playback_widget && cpu_dai->playback_widget) { source = cpu_dai->playback_widget; sink = codec_dai->playback_widget; dev_dbg(rtd->dev, "connected DAI link %s:%s -> %s:%s\n", cpu_dai->component->name, source->name, codec_dai->component->name, sink->name); snd_soc_dapm_add_path(&card->dapm, source, sink, NULL, NULL); } /* connect BE DAI capture if widgets are valid */ if (codec_dai->capture_widget && cpu_dai->capture_widget) { source = codec_dai->capture_widget; sink = cpu_dai->capture_widget; dev_dbg(rtd->dev, "connected DAI link %s:%s -> %s:%s\n", codec_dai->component->name, source->name, cpu_dai->component->name, sink->name); snd_soc_dapm_add_path(&card->dapm, source, sink, NULL, NULL); } } }
3.10如果存在card->controls,调用snd_soc_add_card_controls()创建kcontrol,并添加kcontrol到card->snd_card->controls链表中。
3.11如果存在card->dapm_routes,调用snd_soc_dapm_add_routes()创建dapm path,并将path加到card->paths链表中。
3.12调用snd_soc_dapm_new_widgets,遍历card->widgets链表,对带有control的widget(mixer/mux/pga)创建kcontrol,并添加到card->snd_card->controls链表中。读取widget 当前寄存器的值,初始化widget的power状态,将widget加到card->dapm_dirty链表中,最后调用dapm_power_widget() 统一处理card->dapm_dirty链表里所有widget的power状态的变化。
/** * snd_soc_dapm_new_widgets - add new dapm widgets * @card: card to be checked for new dapm widgets * * Checks the codec for any new dapm widgets and creates them if found. * * Returns 0 for success. */ int snd_soc_dapm_new_widgets(struct snd_soc_card *card) { struct snd_soc_dapm_widget *w; unsigned int val; mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT); list_for_each_entry(w, &card->widgets, list) { if (w->new) continue; if (w->num_kcontrols) { w->kcontrols = kzalloc(w->num_kcontrols * sizeof(struct snd_kcontrol *), GFP_KERNEL); if (!w->kcontrols) { mutex_unlock(&card->dapm_mutex); return -ENOMEM; } } switch(w->id) { case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: dapm_new_mixer(w); break; case snd_soc_dapm_mux: case snd_soc_dapm_demux: dapm_new_mux(w); break; case snd_soc_dapm_pga: case snd_soc_dapm_out_drv: dapm_new_pga(w); break; case snd_soc_dapm_dai_link: dapm_new_dai_link(w); break; default: break; } /* Read the initial power state from the device */ if (w->reg >= 0) { soc_dapm_read(w->dapm, w->reg, &val); val = val >> w->shift; val &= w->mask; if (val == w->on_val) w->power = 1; } w->new = 1; dapm_mark_dirty(w, "new widget"); dapm_debugfs_add_widget(w); } dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP); mutex_unlock(&card->dapm_mutex); return 0; }
3.13调用snd_card_register()注册card->snd_card.