通过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 ", 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 "); 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 "); 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 ", 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 ", 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 ", 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 ", 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 ", 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 ", 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 ", 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 ", 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 '