Linux ALSA架构:DAPM初始化(六)

Linux ALSA架构:DAPM初始化(六)

一、dapm context

dapm context,直译过来的意思是dapm上下文。可以这么理解:dapm把整个音频系统,按照功能和偏置电压级别,划分为若干个电源域,每个域包含各自的widget,每个域中的所有widget通常都处于同一个偏置电压级别上,而一个电源域就是一个dapm context,通常会有以下几种dapm context:

1)属于codec中的widget位于一个dapm context中;

2)属于platform的widget位于一个dapm context中;

3)属于整个声卡的widget位于一个dapm context中。

对于音频系统的硬件来说,通常要提供合适的偏置电压才能正常地工作,有了dapm context这种组织方式,可以方便地对同一组widget进行统一的偏置电压管理,ASoc用snd_soc_dapm_context结构来表示一个dapm context:

/* DAPM context */
struct snd_soc_dapm_context {
    enum snd_soc_bias_level bias_level;
    unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */
    /* Go to BIAS_OFF in suspend if the DAPM context is idle */
    unsigned int suspend_bias_off:1;

    struct device *dev; /* from parent - for debug */
    struct snd_soc_component *component; /* parent component */
    struct snd_soc_card *card; /* parent card */

    /* used during DAPM updates */
    enum snd_soc_bias_level target_bias_level;
    struct list_head list;

    struct snd_soc_dapm_wcache path_sink_cache;
    struct snd_soc_dapm_wcache path_source_cache;

#ifdef CONFIG_DEBUG_FS
    struct dentry *debugfs_dapm;
#endif
};

枚举变量snd_soc_bias_level:

enum snd_soc_bias_level {
    SND_SOC_BIAS_OFF = 0,
    SND_SOC_BIAS_STANDBY = 1,
    SND_SOC_BIAS_PREPARE = 2,
    SND_SOC_BIAS_ON = 3,
};

snd_soc_dapm_context被内嵌到代表codec、platform和cpu中成员结构体snd_soc_component中 。card(machine)结构体中是直接内嵌的snd_soc_dapm_context。代表widget结构snd_soc_dapm_widget中,有一个snd_soc_dapm_context结构指针,指向所属的codec、platform、card、或dai的dapm结构。同时,所有的dapm结构,通过它的list字段,链接到代表声卡的snd_soc_card结构的dapm_list链表头字段。

二、创建和注册widget

2.1 dapm初始化

snd_soc_dapm_init函数初始化dapm结构体,并将其加入到card->dapm_list链表。

void snd_soc_dapm_init(struct snd_soc_dapm_context *dapm,
               struct snd_soc_card *card,
               struct snd_soc_component *component)
{
    dapm->card        = card;
    dapm->component        = component;
    dapm->bias_level    = SND_SOC_BIAS_OFF;

    if (component) {
        dapm->dev        = component->dev;
        dapm->idle_bias_off    = !component->driver->idle_bias_on,
        dapm->suspend_bias_off    = component->driver->suspend_bias_off;
    } else {
        dapm->dev        = card->dev;
    }

    INIT_LIST_HEAD(&dapm->list);
    /* see for_each_card_dapms */
    list_add(&dapm->list, &card->dapm_list);
}
2.2 注册widget

widget注册最终都调用了snd_soc_dapm_new_controls函数。

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);
            break;
        }
        widget++;
    }
    mutex_unlock(&dapm->card->dapm_mutex);
    return ret;
}

该函数只是简单的一个循环,为传入的widget模板数组依次调用snd_soc_dapm_new_control_unlocked函数,实际的工作由snd_soc_dapm_new_control_unlocked完成。

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;
    /* dapm_cnew_widget完成内存申请和拷贝模板的动作 */
    if ((w = dapm_cnew_widget(widget)) == NULL)
        return ERR_PTR(-ENOMEM);
    /* 根据widget的不同类型做不同的处理 */
    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);
            goto request_failed;
        }

        if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
            ret = regulator_allow_bypass(w->regulator, true);
            if (ret != 0)
                dev_warn(dapm->dev,
                     "ASoC: Failed to bypass %s: %d\n",
                     w->name, ret);
        }
        break;
    case snd_soc_dapm_pinctrl:
        w->pinctrl = devm_pinctrl_get(dapm->dev);
        if (IS_ERR(w->pinctrl)) {
            ret = PTR_ERR(w->pinctrl);
            goto request_failed;
        }

        /* set to sleep_state when initializing */
        dapm_pinctrl_event(w, NULL, SND_SOC_DAPM_POST_PMD);
        break;
    case snd_soc_dapm_clock_supply:
        w->clk = devm_clk_get(dapm->dev, w->name);
        if (IS_ERR(w->clk)) {
            ret = PTR_ERR(w->clk);
            goto request_failed;
        }
        break;
    default:
        break;
    }
    /* 根据需要,在widget的名称前加入必要的前缀 */
    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_const(w->sname);
        kfree(w);
        return ERR_PTR(-ENOMEM);
    }
    /* 为不同类型的widget设置合适的power_check电源状态回调函数 */
    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;
    ...

    default:
        w->power_check = dapm_always_on_check_power;
        break;
    }

    w->dapm = dapm;
    INIT_LIST_HEAD(&w->list);
    INIT_LIST_HEAD(&w->dirty);
    /* see for_each_card_widgets */
    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;  /* 把widget设置为connect状态 */
    return w;

request_failed:
    if (ret != -EPROBE_DEFER)
        dev_err(dapm->dev, "ASoC: Failed to request %s: %d\n",
            w->name, ret);

    kfree_const(w->sname);
    kfree(w);
    return ERR_PTR(ret);
}

connected字段代表着引脚的连接状态,只有以下这些widget使用connected字段:

snd_soc_dapm_output
snd_soc_dapm_input
snd_soc_dapm_hp
snd_soc_dapm_spk
snd_soc_dapm_line
snd_soc_dapm_vmid
snd_soc_dapm_mic
snd_soc_dapm_siggen

驱动程序可以使用以下这些api来设置引脚的连接状态:

snd_soc_dapm_enable_pin
snd_soc_dapm_force_enable_pin
snd_soc_dapm_disable_pin
snd_soc_dapm_nc_pin

到此,widget已经被正确地创建并初始化,而且被挂在声卡的widgets链表中,以后就可以通过声卡的widgets链表来遍历所有的widget,snd_soc_dapm_new_controls函数所完成的主要功能:

1)为widget分配内存,并拷贝参数中传入的在驱动中定义好的模板;

2)设置power_check回调函数,当音频路径发生变化时,power_check回调会被调用,用于检查该widget的电源状态是否需要更新;

3)把widget挂在声卡的widgets链表中。

2.3 Platform和Codec下widget注册

soc_probe_link_components函数依次调用soc_probe_component完成相关注册。

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 probed = 0;
    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;
    }

    ret = snd_soc_component_module_get_when_probe(component);
    if (ret < 0)
        return ret;

    component->card = card;
    soc_set_name_prefix(card, component);

    soc_init_component_debugfs(component);

    snd_soc_dapm_init(dapm, card, component);

    ret = snd_soc_dapm_new_controls(dapm,
                    component->driver->dapm_widgets,
                    component->driver->num_dapm_widgets);

    if (ret != 0) {
        dev_err(component->dev,
            "Failed to create new controls %d\n", ret);
        goto err_probe;
    }

    for_each_component_dais(component, dai) {
        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;
        }
    }

    ret = snd_soc_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);
    probed = 1;

    /*
     * machine specific init
     * see
     *    snd_soc_component_set_aux()
     */
    ret = snd_soc_component_init(component);
    if (ret < 0)
        goto err_probe;

    ret = snd_soc_add_component_controls(component,
                         component->driver->controls,
                         component->driver->num_controls);
    if (ret < 0)
        goto err_probe;

    ret = snd_soc_dapm_add_routes(dapm,
                      component->driver->dapm_routes,
                      component->driver->num_dapm_routes);
    if (ret < 0) {
        if (card->disable_route_checks) {
            dev_info(card->dev,
                 "%s: disable_route_checks set, ignoring errors on add_routes\n",
                 __func__);
        } else {
            dev_err(card->dev,
                "%s: snd_soc_dapm_add_routes failed: %d\n",
                __func__, ret);
            goto err_probe;
        }
    }

    /* see for_each_card_components */
    list_add(&component->card_list, &card->component_dev_list);

err_probe:
    if (ret < 0)
        soc_remove_component(component, probed);

    return ret;
}
2.4 dai注册widget

上面的内容介绍了codec、platform以及machine级别的widget的注册方法,在dapm框架中,还有另外一种widget,它代表了一个dai(数字音频接口),dai按所在的位置,又分为cpu dai和codec dai。在硬件上,通常一个cpu dai会连接一个codec dai,而在machine驱动中,我们要在snd_soc_card结构中指定一个叫做snd_soc_dai_link的结构,该结构定义了声卡使用哪一个cpu dai和codec dai进行连接。

soc_probe_component调用snd_soc_dapm_new_dai_widgets函数来生成该dai的播放流widget和录音流widget:

int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
                 struct snd_soc_dai *dai)
{
    struct snd_soc_dapm_widget template;
    struct snd_soc_dapm_widget *w;

    WARN_ON(dapm->dev != dai->dev);

    memset(&template, 0, sizeof(template));
    template.reg = SND_SOC_NOPM;

    if (dai->driver->playback.stream_name) {
        template.id = snd_soc_dapm_dai_in;
        template.name = dai->driver->playback.stream_name;
        template.sname = dai->driver->playback.stream_name;

        dev_dbg(dai->dev, "ASoC: adding %s widget\n",
            template.name);

        w = snd_soc_dapm_new_control_unlocked(dapm, &template);
        if (IS_ERR(w))
            return PTR_ERR(w);

        w->priv = dai;
        dai->playback_widget = w;
    }

    if (dai->driver->capture.stream_name) {
        template.id = snd_soc_dapm_dai_out;
        template.name = dai->driver->capture.stream_name;
        template.sname = dai->driver->capture.stream_name;

        dev_dbg(dai->dev, "ASoC: adding %s widget\n",
            template.name);

        w = snd_soc_dapm_new_control_unlocked(dapm, &template);
        if (IS_ERR(w))
            return PTR_ERR(w);

        w->priv = dai;
        dai->capture_widget = w;
    }

    return 0;
}

分别为Playback和Capture创建了一个widget,widget的priv字段指向了该dai,这样通过widget就可以找到相应的dai,并且widget的名字就是snd_soc_dai_driver结构的stream_name。

snd_soc_dapm_link_dai_widgets函数针对同一component下相同stream的dai widget连接:

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... */
    for_each_card_widgets(card, dai_w) {
        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 */
        for_each_card_widgets(card, w) {
            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;
}

snd_soc_dapm_connect_dai_link_widgets是针对BE DAI与Codec dai连接

void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card)
{
    struct snd_soc_pcm_runtime *rtd;
    struct snd_soc_dai *codec_dai;
    int i;

    /* for each BE DAI link... */
    for_each_card_rtds(card, rtd)  {
        /*
         * dynamic FE links have no fixed DAI mapping.
         * CODEC<->CODEC links have no direct connection.
         */
        if (rtd->dai_link->dynamic)
            continue;

        if (rtd->num_cpus == 1) {
            for_each_rtd_codec_dais(rtd, i, codec_dai)
                dapm_connect_dai_pair(card, rtd, codec_dai,
                              asoc_rtd_to_cpu(rtd, 0));
        } else if (rtd->num_codecs == rtd->num_cpus) {
            for_each_rtd_codec_dais(rtd, i, codec_dai)
                dapm_connect_dai_pair(card, rtd, codec_dai,
                              asoc_rtd_to_cpu(rtd, i));
        } else {
            dev_err(card->dev,
                "N cpus to M codecs link is not supported yet\n");
        }
    }
}

dapm_connect_dai_pair函数调用如下:

static void dapm_connect_dai_pair(struct snd_soc_card *card,
                  struct snd_soc_pcm_runtime *rtd,
                  struct snd_soc_dai *codec_dai,
                  struct snd_soc_dai *cpu_dai)
{
    struct snd_soc_dai_link *dai_link = rtd->dai_link;
    struct snd_soc_dapm_widget *dai, *codec, *playback_cpu, *capture_cpu;
    struct snd_pcm_substream *substream;
    struct snd_pcm_str *streams = rtd->pcm->streams;

    if (dai_link->params) {
        playback_cpu = cpu_dai->capture_widget;
        capture_cpu = cpu_dai->playback_widget;
    } else {
        playback_cpu = cpu_dai->playback_widget;
        capture_cpu = cpu_dai->capture_widget;
    }

    /* connect BE DAI playback if widgets are valid */
    codec = codec_dai->playback_widget;

    if (playback_cpu && codec) {
        if (dai_link->params && !rtd->playback_widget) {
            substream = streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
            dai = snd_soc_dapm_new_dai(card, substream, "playback");
            if (IS_ERR(dai))
                goto capture;
            rtd->playback_widget = dai;
        }

        dapm_connect_dai_routes(&card->dapm, cpu_dai, playback_cpu,
                    rtd->playback_widget,
                    codec_dai, codec);
    }

capture:
    /* connect BE DAI capture if widgets are valid */
    codec = codec_dai->capture_widget;

    if (codec && capture_cpu) {
        if (dai_link->params && !rtd->capture_widget) {
            substream = streams[SNDRV_PCM_STREAM_CAPTURE].substream;
            dai = snd_soc_dapm_new_dai(card, substream, "capture");
            if (IS_ERR(dai))
                return;
            rtd->capture_widget = dai;
        }

        dapm_connect_dai_routes(&card->dapm, codec_dai, codec,
                    rtd->capture_widget,
                    cpu_dai, capture_cpu);
    }
}

dapm_connect_dai_routes函数最终调用snd_soc_dapm_add_path连接音频路径。

static void dapm_connect_dai_routes(struct snd_soc_dapm_context *dapm,
                    struct snd_soc_dai *src_dai,
                    struct snd_soc_dapm_widget *src,
                    struct snd_soc_dapm_widget *dai,
                    struct snd_soc_dai *sink_dai,
                    struct snd_soc_dapm_widget *sink)
{
    dev_dbg(dapm->dev, "connected DAI link %s:%s -> %s:%s\n",
        src_dai->component->name, src->name,
        sink_dai->component->name, sink->name);

    if (dai) {
        snd_soc_dapm_add_path(dapm, src, dai, NULL, NULL);
        src = dai;
    }

    snd_soc_dapm_add_path(dapm, src, sink, NULL, NULL);
}

三、注册音频路径

如果widget之间没有连接关系,dapm就无法实现动态的电源管理工作,正是widget之间有了连结关系,这些连接关系形成了一条所谓的完成的音频路径,dapm可以顺着这条路径,统一控制路径上所有widget的电源状态,widget之间是使用snd_soc_path结构进行连接的,驱动要做的是定义一个snd_soc_route结构数组,该数组的每个条目描述了目的widget的和源widget的名称,以及控制这个连接的kcontrol的名称,最终,驱动程序使用api函数snd_soc_dapm_add_routes来注册这些连接信息,接下来要分析该函数的具体实现方式:

int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
                const struct snd_soc_dapm_route *route, int num)
{
    int i, r, ret = 0;

    mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
    for (i = 0; i < num; i++) {
        r = snd_soc_dapm_add_route(dapm, route);
        if (r < 0) {
            dev_err(dapm->dev, "ASoC: Failed to add route %s -> %s -> %s\n",
                route->source,
                route->control ? route->control : "direct",
                route->sink);
            ret = r;
        }
        route++;
    }
    mutex_unlock(&dapm->card->dapm_mutex);

    return ret;
}

该函数只是一个循环,依次对参数传入的数组调用snd_soc_dapm_add_route,主要的工作由snd_soc_dapm_add_route完成。

static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
                  const struct snd_soc_dapm_route *route)
{
    struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
    struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;
    const char *sink;
    const char *source;
    char prefixed_sink[80];
    char prefixed_source[80];
    const char *prefix;
    unsigned int sink_ref = 0;
    unsigned int source_ref = 0;
    int ret;
    /* 名称前缀的处理部分 */
    prefix = soc_dapm_prefix(dapm);
    if (prefix) {
        snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s",
             prefix, route->sink);
        sink = prefixed_sink;
        snprintf(prefixed_source, sizeof(prefixed_source), "%s %s",
             prefix, route->source);
        source = prefixed_source;
    } else {
        sink = route->sink;
        source = route->source;
    }

    wsource = dapm_wcache_lookup(&dapm->path_source_cache, source);
    wsink = dapm_wcache_lookup(&dapm->path_sink_cache, sink);

    if (wsink && wsource)
        goto skip_search;

    /*
     * find src and dest widgets over all widgets but favor a widget from
     * current DAPM context
     */
    for_each_card_widgets(dapm->card, w) {
        if (!wsink && !(strcmp(w->name, sink))) {
            wtsink = w;
            if (w->dapm == dapm) {
                wsink = w;
                if (wsource)
                    break;
            }
            sink_ref++;
            if (sink_ref > 1)
                dev_warn(dapm->dev,
                    "ASoC: sink widget %s overwritten\n",
                    w->name);
            continue;
        }
        if (!wsource && !(strcmp(w->name, source))) {
            wtsource = w;
            if (w->dapm == dapm) {
                wsource = w;
                if (wsink)
                    break;
            }
            source_ref++;
            if (source_ref > 1)
                dev_warn(dapm->dev,
                    "ASoC: source widget %s overwritten\n",
                    w->name);
        }
    }
    /* use widget from another DAPM context if not found from this */
    if (!wsink)
        wsink = wtsink;
    if (!wsource)
        wsource = wtsource;

    if (wsource == NULL) {
        dev_err(dapm->dev, "ASoC: no source widget found for %s\n",
            route->source);
        return -ENODEV;
    }
    if (wsink == NULL) {
        dev_err(dapm->dev, "ASoC: no sink widget found for %s\n",
            route->sink);
        return -ENODEV;
    }

skip_search:
    dapm_wcache_update(&dapm->path_sink_cache, wsink);
    dapm_wcache_update(&dapm->path_source_cache, wsource);
    /* 增加一条连接信息 */
    ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,
        route->connected);
    if (ret)
        goto err;

    return 0;
err:
    dev_warn(dapm->dev, "ASoC: no dapm match for %s --> %s --> %s\n",
         source, route->control, sink);
    return ret;
}

snd_soc_dapm_add_path函数是整个调用链条中的关键:

static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
    struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,
    const char *control,
    int (*connected)(struct snd_soc_dapm_widget *source,
             struct snd_soc_dapm_widget *sink))
{
    struct snd_soc_dapm_widget *widgets[2];
    enum snd_soc_dapm_direction dir;
    struct snd_soc_dapm_path *path;
    int ret;

    if (wsink->is_supply && !wsource->is_supply) {
        dev_err(dapm->dev,
            "Connecting non-supply widget to supply widget is not supported (%s -> %s)\n",
            wsource->name, wsink->name);
        return -EINVAL;
    }

    if (connected && !wsource->is_supply) {
        dev_err(dapm->dev,
            "connected() callback only supported for supply widgets (%s -> %s)\n",
            wsource->name, wsink->name);
        return -EINVAL;
    }

    if (wsource->is_supply && control) {
        dev_err(dapm->dev,
            "Conditional paths are not supported for supply widgets (%s -> [%s] -> %s)\n",
            wsource->name, control, wsink->name);
        return -EINVAL;
    }

    ret = snd_soc_dapm_check_dynamic_path(dapm, wsource, wsink, control);
    if (ret)
        return ret;
    /* 为这个连接分配了一个snd_soc_path结构 */
    path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
    if (!path)
        return -ENOMEM;
    /* path的source和sink字段分别指向源widget和目的widget */
    path->node[SND_SOC_DAPM_DIR_IN] = wsource;
    path->node[SND_SOC_DAPM_DIR_OUT] = wsink;
    widgets[SND_SOC_DAPM_DIR_IN] = wsource;
    widgets[SND_SOC_DAPM_DIR_OUT] = wsink;
    /* connected字段保存connected回调函数 */
    path->connected = connected;
    INIT_LIST_HEAD(&path->list);
    INIT_LIST_HEAD(&path->list_kcontrol);

    if (wsource->is_supply || wsink->is_supply)
        path->is_supply = 1;

    /* connect static paths */
    if (control == NULL) {
        path->connect = 1;
    } else {
        switch (wsource->id) {
        case snd_soc_dapm_demux:
            ret = dapm_connect_mux(dapm, path, control, wsource);
            if (ret)
                goto err;
            break;
        default:
            break;
        }
        /* 目的widget如果是mixer和mux类型,分别用dapm_connect_mixer和dapm_connect_mux函数完成连接工作 */
        switch (wsink->id) {
        case snd_soc_dapm_mux:
            ret = dapm_connect_mux(dapm, path, control, wsink);
            if (ret != 0)
                goto err;
            break;
        case snd_soc_dapm_switch:
        case snd_soc_dapm_mixer:
        case snd_soc_dapm_mixer_named_ctl:
            ret = dapm_connect_mixer(dapm, path, control);
            if (ret != 0)
                goto err;
            break;
        default:
            break;
        }
    }

    list_add(&path->list, &dapm->card->paths);
    snd_soc_dapm_for_each_direction(dir)
        list_add(&path->list_node[dir], &widgets[dir]->edges[dir]);

    snd_soc_dapm_for_each_direction(dir) {
        dapm_update_widget_flags(widgets[dir]);
        dapm_mark_dirty(widgets[dir], "Route added");
    }

    if (dapm->card->instantiated && path->connect)
        dapm_path_invalidate(path);

    return 0;
err:
    kfree(path);
    return ret;
}
2.3.1 函数dapm_connect_mixer

连接一个目的widget为mixer类型的所有输入端

/* connect mixer widget to its interconnecting audio paths */
static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm,
    struct snd_soc_dapm_path *path, const char *control_name)
{
    int i, nth_path = 0;

    /* search for mixer kcontrol */
    for (i = 0; i < path->sink->num_kcontrols; i++) {
        if (!strcmp(control_name, path->sink->kcontrol_news[i].name)) {
            path->name = path->sink->kcontrol_news[i].name;
            dapm_set_mixer_path_status(path, i, nth_path++);
            return 0;
        }
    }
    return -ENODEV;
}

用需要用来连接的kcontrol的名字,和目的widget中的kcontrol模板数组中的名字相比较,找出该kcontrol在widget中的编号,path的名字设置为该kcontrol的名字,然后用dapm_set_path_status函数来初始化该输入端的连接状态。连接两个widget的链表操作和其他widget是一样的。

2.3.2 函数dapm_set_mixer_path_status

根据传入widget中的kcontrol编号,读取实际寄存器的值,根据寄存器的值来初始化这个path是否处于连接状态。

/* set up initial codec paths */
static void dapm_set_mixer_path_status(struct snd_soc_dapm_path *p, int i,
                       int nth_path)
{
    struct soc_mixer_control *mc = (struct soc_mixer_control *)
        p->sink->kcontrol_news[i].private_value;
    unsigned int reg = mc->reg;
    unsigned int shift = mc->shift;
    unsigned int max = mc->max;
    unsigned int mask = (1 << fls(max)) - 1;
    unsigned int invert = mc->invert;
    unsigned int val;

    if (reg != SND_SOC_NOPM) {
        val = soc_dapm_read(p->sink->dapm, reg);
        /*
         * The nth_path argument allows this function to know
         * which path of a kcontrol it is setting the initial
         * status for. Ideally this would support any number
         * of paths and channels. But since kcontrols only come
         * in mono and stereo variants, we are limited to 2
         * channels.
         *
         * The following code assumes for stereo controls the
         * first path is the left channel, and all remaining
         * paths are the right channel.
         */
        if (snd_soc_volsw_is_stereo(mc) && nth_path > 0) {
            if (reg != mc->rreg)
                val = soc_dapm_read(p->sink->dapm, mc->rreg);
            val = (val >> mc->rshift) & mask;
        } else {
            val = (val >> shift) & mask;
        }
        if (invert)
            val = max - val;
        p->connect = !!val;
    } else {
        /* since a virtual mixer has no backing registers to
         * decide which path to connect, it will try to match
         * with initial state.  This is to ensure
         * that the default mixer choice will be
         * correctly powered up during initialization.
         */
        p->connect = invert;
    }
}
2.3.3 函数dapm_connect_mux

用该函数连接一个目的widget是mux类型的所有输入端。

/* connect mux widget to its interconnecting audio paths */
static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,
    struct snd_soc_dapm_path *path, const char *control_name,
    struct snd_soc_dapm_widget *w)
{
    const struct snd_kcontrol_new *kcontrol = &w->kcontrol_news[0];
    struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
    unsigned int val, item;
    int i;

    if (e->reg != SND_SOC_NOPM) {
        val = soc_dapm_read(dapm, e->reg);
        val = (val >> e->shift_l) & e->mask;
        item = snd_soc_enum_val_to_item(e, val);
    } else {
        /* since a virtual mux has no backing registers to
         * decide which path to connect, it will try to match
         * with the first enumeration.  This is to ensure
         * that the default mux choice (the first) will be
         * correctly powered up during initialization.
         */
        item = 0;
    }

    i = match_string(e->texts, e->items, control_name);
    if (i < 0)
        return -ENODEV;

    path->name = e->texts[i];
    path->connect = (i == item);
    return 0;

}

和mixer类型一样用名字进行匹配,只不过mux类型的kcontrol只需一个,所以要通过private_value字段所指向的soc_enum结构找出匹配的输入脚编号。初始化该输入端的连接状态。

当widget之间通过path进行连接之后,他们之间的关系就如下图所示:

四、注册dapm kcontrol

定义一个widget,我需要指定两个很重要的内容:一个是用于控制widget的电源状态的reg/shift等寄存器信息,另一个是用于控制音频路径切换的dapm kcontrol信息,这些dapm kcontrol有它们自己的reg/shift寄存器信息用于切换widget的路径连接方式。通过snd_soc_dapm_new_widgets函数,创建widget内包含的dapm kcontrol。

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);

    for_each_card_widgets(card, w)
    {   /* new字段用于判断该widget是否已经执行过snd_soc_dapm_new_widgets函数 */
        if (w->new)  
            continue;

        if (w->num_kcontrols) {
            w->kcontrols = kcalloc(w->num_kcontrols,
                        sizeof(struct snd_kcontrol *),
                        GFP_KERNEL);
            if (!w->kcontrols) {
                mutex_unlock(&card->dapm_mutex);
                return -ENOMEM;
            }
        }
        /* 对几种能影响音频路径的widget,创建并初始化它们所包含的dapm kcontrol */
        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_effect:
        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;
        }
        /* 根据widget寄存器的当前值,初始化widget的电源状态,并设置到power字段 */
        /* Read the initial power state from the device */
        if (w->reg >= 0) {
            val = soc_dapm_read(w->dapm, w->reg);
            val = val >> w->shift;
            val &= w->mask;
            if (val == w->on_val)
                w->power = 1;
        }
        /*接着,设置new字段,表明该widget已经初始化完成,我们还要吧该widget加入到声卡的dapm_dirty链表中,表明该widget的状态发生了变化,稍后在合适的时刻,dapm框架会扫描dapm_dirty链表,
统一处理所有已经变化的widget。为什么要统一处理?因为dapm要控制各种widget的上下电顺序,同时也是为了减少寄存器的读写次数(多个widget可能使用同一个寄存器)*/
        w->new = 1;

        dapm_mark_dirty(w, "new widget");
        dapm_debugfs_add_widget(w);
    }
    /*最后,通过dapm_power_widgets函数,统一处理所有位于dapm_dirty链表上的widget的状态改变*/
    dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
    mutex_unlock(&card->dapm_mutex);
    return 0;
}

对几种能影响音频路径的widget,创建并初始化它们所包含的dapm kcontrol,需要用到的创建函数分别是:

dapm mixer kcontrol

对于mixer类型的dapm kcontrol,我们会使用dapm_new_mixer来完成具体的创建工作,代码如下

/* create new dapm mixer control */
static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
{
    int i, ret;
    struct snd_soc_dapm_path *path;
    struct dapm_kcontrol_data *data;

    /* add kcontrol */
    for (i = 0; i < w->num_kcontrols; i++) {
        /* match name */
        snd_soc_dapm_widget_for_each_source_path(w, path) {
            /* mixer/mux paths name must match control name */
            if (path->name != (char *)w->kcontrol_news[i].name)
                continue;

            if (!w->kcontrols[i]) {
                ret = dapm_create_or_share_kcontrol(w, i);
                if (ret < 0)
                    return ret;
            }
            /*说明kcontrol已经在之前创建好了,增加一个虚拟的影子widget。则通过dapm_create_or_share_mixmux_kcontrol创建这个输入端的kcontrol,同理,kcontrol对应的影子widget也会通过dapm_kcontrol_add_path判断是否需要创建*/
            dapm_kcontrol_add_path(w->kcontrols[i], path);

            data = snd_kcontrol_chip(w->kcontrols[i]);
            if (data->widget)
                snd_soc_dapm_add_path(data->widget->dapm,
                              data->widget,
                              path->source,
                              NULL, NULL);
        }
    }

    return 0;
}

因为一个输入脚可能会连接多个输入源,所以可能在上一个输入源的path关联时已经创建了这个kcontrol,所以这里判断kcontrols指针数组中对应索引中的指针值,如果已经赋值,说明kcontrol已经在之前创建好了,所以我们只要简单地把连接该输入端的path加入到kcontrol的path_list链表中,并且增加一个虚拟的影子widget,该影子widget连接和输入端对应的源widget,因为使用了kcontrol本身的reg/shift等寄存器信息,所以实际上控制的是该kcontrol的开和关,这个影子widget只有在kcontrol的autodisable字段被设置的情况下才会被创建,该特性使得source的关闭时,与之连接的mixer的输入端也可以自动关闭,这个特性通过dapm_kcontrol_add_path来实现这一点:

static void dapm_kcontrol_add_path(const struct snd_kcontrol *kcontrol,
    struct snd_soc_dapm_path *path)
{
    struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol);

    list_add_tail(&path->list_kcontrol, &data->paths);
}

dapm mux kcontrol

因为一个widget最多只会包含一个mux类型的damp kcontrol,所以他的创建方法稍有不同,dapm框架使用dapm_new_mux函数来创建mux类型的dapm kcontrol:

/* create new dapm mux control */
static int dapm_new_mux(struct snd_soc_dapm_widget *w)
{
    struct snd_soc_dapm_context *dapm = w->dapm;
    enum snd_soc_dapm_direction dir;
    struct snd_soc_dapm_path *path;
    const char *type;
    int ret;

    switch (w->id) {
    case snd_soc_dapm_mux:
        dir = SND_SOC_DAPM_DIR_OUT;
        type = "mux";
        break;
    case snd_soc_dapm_demux:
        dir = SND_SOC_DAPM_DIR_IN;
        type = "demux";
        break;
    default:
        return -EINVAL;
    }

    if (w->num_kcontrols != 1) {
        dev_err(dapm->dev,
            "ASoC: %s %s has incorrect number of controls\n", type,
            w->name);
        return -EINVAL;
    }

    if (list_empty(&w->edges[dir])) {
        dev_err(dapm->dev, "ASoC: %s %s has no paths\n", type, w->name);
        return -EINVAL;
    }

    ret = dapm_create_or_share_kcontrol(w, 0);
    if (ret < 0)
        return ret;

    snd_soc_dapm_widget_for_each_path(w, dir, path) {
        if (path->name)
            dapm_kcontrol_add_path(w->kcontrols[0], path);
    }

    return 0;
}

dapm pga kcontrol

/* create new dapm volume control */
static int dapm_new_pga(struct snd_soc_dapm_widget *w)
{
    int i, ret;

    for (i = 0; i < w->num_kcontrols; i++) {
        ret = dapm_create_or_share_kcontrol(w, i);
        if (ret < 0)
            return ret;
    }

    return 0;
}

dapm_create_or_share_kcontrol

以上创建kcontrol 最终都call此函数,完成最后的创建,下面来看此函数定义:

static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w,
    int kci)
{
    struct snd_soc_dapm_context *dapm = w->dapm;
    struct snd_card *card = dapm->card->snd_card;
    const char *prefix;
    size_t prefix_len;
    int shared;
    struct snd_kcontrol *kcontrol;
    bool wname_in_long_name, kcname_in_long_name;
    char *long_name = NULL;
    const char *name;
    int ret = 0;

    prefix = soc_dapm_prefix(dapm);
    if (prefix)
        prefix_len = strlen(prefix) + 1;
    else
        prefix_len = 0;

    shared = dapm_is_shared_kcontrol(dapm, w, &w->kcontrol_news[kci],
                     &kcontrol);

    if (!kcontrol) {
        if (shared) {
            wname_in_long_name = false;
            kcname_in_long_name = true;
        } else {
            switch (w->id) {
            case snd_soc_dapm_switch:
            case snd_soc_dapm_mixer:
            case snd_soc_dapm_pga:
            case snd_soc_dapm_effect:
            case snd_soc_dapm_out_drv:
                wname_in_long_name = true;
                kcname_in_long_name = true;
                break;
            case snd_soc_dapm_mixer_named_ctl:
                wname_in_long_name = false;
                kcname_in_long_name = true;
                break;
            case snd_soc_dapm_demux:
            case snd_soc_dapm_mux:
                wname_in_long_name = true;
                kcname_in_long_name = false;
                break;
            default:
                return -EINVAL;
            }
        }

        if (wname_in_long_name && kcname_in_long_name) {
            /*
             * The control will get a prefix from the control
             * creation process but we're also using the same
             * prefix for widgets so cut the prefix off the
             * front of the widget name.
             */
            long_name = kasprintf(GFP_KERNEL, "%s %s",
                 w->name + prefix_len,
                 w->kcontrol_news[kci].name);
            if (long_name == NULL)
                return -ENOMEM;

            name = long_name;
        } else if (wname_in_long_name) {
            long_name = NULL;
            name = w->name + prefix_len;
        } else {
            long_name = NULL;
            name = w->kcontrol_news[kci].name;
        }

        kcontrol = snd_soc_cnew(&w->kcontrol_news[kci], NULL, name,
                    prefix);
        if (!kcontrol) {
            ret = -ENOMEM;
            goto exit_free;
        }

        kcontrol->private_free = dapm_kcontrol_free;

        ret = dapm_kcontrol_data_alloc(w, kcontrol, name);
        if (ret) {
            snd_ctl_free_one(kcontrol);
            goto exit_free;
        }

        ret = snd_ctl_add(card, kcontrol);
        if (ret < 0) {
            dev_err(dapm->dev,
                "ASoC: failed to add widget %s dapm kcontrol %s: %d\n",
                w->name, name, ret);
            goto exit_free;
        }
    }

    ret = dapm_kcontrol_add_widget(kcontrol, w);
    if (ret == 0)
        w->kcontrols[kci] = kcontrol;

exit_free:
    kfree(long_name);

    return ret;
}

需要注意的是,如果kcontol支持autodisable特性,一旦kcontrol由于source的关闭而被自动关闭,则用户空间只能操作该kcontrol的cache值,只有该kcontrol再次打开时,该cache值才会被真正地更新到寄存器中。
        总结一下,创建一个widget所包含的kcontrol所做的工作:
        1)循环每一个输入端,为每个输入端依次执行下面的一系列操作;
        2)为每个输入端创建一个kcontrol,能共享的则直接使用创建好的kcontrol;
        3)kcontrol的private_data字段保存着这些共享widget的信息;
        4)如果支持autodisable特性,每个输入端还要额外地创建一个虚拟的snd_soc_dapm_kcontrol类型的影子widget,该影子widget也记录在private_data字段中;

5)创建好的kcontrol会依次存放在widget的kcontrols数组中,供路径的控制和匹配之用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值