• ALSA driver---register card


    通过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 '':
                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
    ",
                    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
    ",
                    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
    ", dai_link->name);
    
        if (soc_is_dai_link_bound(card, dai_link)) {
            dev_dbg(card->dev, "ASoC: dai link %s already bound
    ",
                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
    ",
                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
    ",
                    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
    ",
                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)
    ",
                    widget->name, ret);
                break;
            }
            if (!w) {
                dev_err(dapm->dev,
                    "ASoC: Failed to create DAPM control %s
    ",
                    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
    ",
                    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
    ",
                         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
    ",
                    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"
    ",
                    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
    ", 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
    ", ret);
                goto err_probe;
            }
        }
    
        if (component->probe) {
            ret = component->probe(component);
            if (ret < 0) {
                dev_err(component->dev,
                    "ASoC: failed to probe component %d
    ", 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
    ",
                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
    ", 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
    ",
                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
    ",
                    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
    ",
                         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
    ",
                           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
    ",
                rtd->dai_link->name);
            return ret;
        }
        dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s
    ",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
    ",
                    ret);
                return ret;
            }
        }
    
        pcm->private_free = platform->driver->pcm_free;
    out:
        dev_info(rtd->card->dev, "%s <-> %s mapping ok
    ",
             (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
    ",
                    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
    ", 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
    ",
                    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
    ",
                    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.

  • 相关阅读:
    物理课件开发记录之一2013年10月25日
    以as中的泛型数组举一例看帮助手册优化的好处
    flash的显示列表的机制探索一
    组合模式
    actionscript中重写注意事项
    用adobe air写一个金鱼监控程序
    adobe air桌面应用程序在前端显示,类似于暴风的总是在桌面的最上方
    windows7下的cmd命令之powercfg命令,不常用的
    设置默认访问项目的客户端的浏览器版本(IE版本)
    防火墙设置对外开放端口
  • 原文地址:https://www.cnblogs.com/fellow1988/p/12642521.html
Copyright © 2020-2023  润新知