ALSA driver---register card_snd_soc_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\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.

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/mahao1107/article/details/105615009

智能推荐

稀疏编码的数学基础与理论分析-程序员宅基地

文章浏览阅读290次,点赞8次,收藏10次。1.背景介绍稀疏编码是一种用于处理稀疏数据的编码技术,其主要应用于信息传输、存储和处理等领域。稀疏数据是指数据中大部分元素为零或近似于零的数据,例如文本、图像、音频、视频等。稀疏编码的核心思想是将稀疏数据表示为非零元素和它们对应的位置信息,从而减少存储空间和计算复杂度。稀疏编码的研究起源于1990年代,随着大数据时代的到来,稀疏编码技术的应用范围和影响力不断扩大。目前,稀疏编码已经成为计算...

EasyGBS国标流媒体服务器GB28181国标方案安装使用文档-程序员宅基地

文章浏览阅读217次。EasyGBS - GB28181 国标方案安装使用文档下载安装包下载,正式使用需商业授权, 功能一致在线演示在线API架构图EasySIPCMSSIP 中心信令服务, 单节点, 自带一个 Redis Server, 随 EasySIPCMS 自启动, 不需要手动运行EasySIPSMSSIP 流媒体服务, 根..._easygbs-windows-2.6.0-23042316使用文档

【Web】记录巅峰极客2023 BabyURL题目复现——Jackson原生链_原生jackson 反序列化链子-程序员宅基地

文章浏览阅读1.2k次,点赞27次,收藏7次。2023巅峰极客 BabyURL之前AliyunCTF Bypassit I这题考查了这样一条链子:其实就是Jackson的原生反序列化利用今天复现的这题也是大同小异,一起来整一下。_原生jackson 反序列化链子

一文搞懂SpringCloud,详解干货,做好笔记_spring cloud-程序员宅基地

文章浏览阅读734次,点赞9次,收藏7次。微服务架构简单的说就是将单体应用进一步拆分,拆分成更小的服务,每个服务都是一个可以独立运行的项目。这么多小服务,如何管理他们?(服务治理 注册中心[服务注册 发现 剔除])这么多小服务,他们之间如何通讯?这么多小服务,客户端怎么访问他们?(网关)这么多小服务,一旦出现问题了,应该如何自处理?(容错)这么多小服务,一旦出现问题了,应该如何排错?(链路追踪)对于上面的问题,是任何一个微服务设计者都不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。_spring cloud

Js实现图片点击切换与轮播-程序员宅基地

文章浏览阅读5.9k次,点赞6次,收藏20次。Js实现图片点击切换与轮播图片点击切换<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title></title> <script type="text/ja..._点击图片进行轮播图切换

tensorflow-gpu版本安装教程(过程详细)_tensorflow gpu版本安装-程序员宅基地

文章浏览阅读10w+次,点赞245次,收藏1.5k次。在开始安装前,如果你的电脑装过tensorflow,请先把他们卸载干净,包括依赖的包(tensorflow-estimator、tensorboard、tensorflow、keras-applications、keras-preprocessing),不然后续安装了tensorflow-gpu可能会出现找不到cuda的问题。cuda、cudnn。..._tensorflow gpu版本安装

随便推点

物联网时代 权限滥用漏洞的攻击及防御-程序员宅基地

文章浏览阅读243次。0x00 简介权限滥用漏洞一般归类于逻辑问题,是指服务端功能开放过多或权限限制不严格,导致攻击者可以通过直接或间接调用的方式达到攻击效果。随着物联网时代的到来,这种漏洞已经屡见不鲜,各种漏洞组合利用也是千奇百怪、五花八门,这里总结漏洞是为了更好地应对和预防,如有不妥之处还请业内人士多多指教。0x01 背景2014年4月,在比特币飞涨的时代某网站曾经..._使用物联网漏洞的使用者

Visual Odometry and Depth Calculation--Epipolar Geometry--Direct Method--PnP_normalized plane coordinates-程序员宅基地

文章浏览阅读786次。A. Epipolar geometry and triangulationThe epipolar geometry mainly adopts the feature point method, such as SIFT, SURF and ORB, etc. to obtain the feature points corresponding to two frames of images. As shown in Figure 1, let the first image be ​ and th_normalized plane coordinates

开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先抽取关系)_语义角色增强的关系抽取-程序员宅基地

文章浏览阅读708次,点赞2次,收藏3次。开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先关系再实体)一.第二代开放信息抽取系统背景​ 第一代开放信息抽取系统(Open Information Extraction, OIE, learning-based, 自学习, 先抽取实体)通常抽取大量冗余信息,为了消除这些冗余信息,诞生了第二代开放信息抽取系统。二.第二代开放信息抽取系统历史第二代开放信息抽取系统着眼于解决第一代系统的三大问题: 大量非信息性提取(即省略关键信息的提取)、_语义角色增强的关系抽取

10个顶尖响应式HTML5网页_html欢迎页面-程序员宅基地

文章浏览阅读1.1w次,点赞6次,收藏51次。快速完成网页设计,10个顶尖响应式HTML5网页模板助你一臂之力为了寻找一个优质的网页模板,网页设计师和开发者往往可能会花上大半天的时间。不过幸运的是,现在的网页设计师和开发人员已经开始共享HTML5,Bootstrap和CSS3中的免费网页模板资源。鉴于网站模板的灵活性和强大的功能,现在广大设计师和开发者对html5网站的实际需求日益增长。为了造福大众,Mockplus的小伙伴整理了2018年最..._html欢迎页面

计算机二级 考试科目,2018全国计算机等级考试调整,一、二级都增加了考试科目...-程序员宅基地

文章浏览阅读282次。原标题:2018全国计算机等级考试调整,一、二级都增加了考试科目全国计算机等级考试将于9月15-17日举行。在备考的最后冲刺阶段,小编为大家整理了今年新公布的全国计算机等级考试调整方案,希望对备考的小伙伴有所帮助,快随小编往下看吧!从2018年3月开始,全国计算机等级考试实施2018版考试大纲,并按新体系开考各个考试级别。具体调整内容如下:一、考试级别及科目1.一级新增“网络安全素质教育”科目(代..._计算机二级增报科目什么意思

conan简单使用_apt install conan-程序员宅基地

文章浏览阅读240次。conan简单使用。_apt install conan