Linux ALSA音频驱动二:ALSA驱动注册

在系统/dev/snd下可查看注册成功的声卡信息,如下所示。

ubuntu@ubuntu:~$ ls -l /dev/snd
total 0
drwxr-xr-x  2 root root       60 4月   7 09:22 by-path
crw-rw----+ 1 root audio 116,  2 4月   7 09:22 controlC0   // 通路控制
crw-rw----+ 1 root audio 116,  4 4月   7 09:23 pcmC0D0c    // 录音设备0
crw-rw----+ 1 root audio 116,  3 4月   7 09:23 pcmC0D0p    // 播放设备
crw-rw----+ 1 root audio 116,  5 4月   7 09:22 pcmC0D1c    // 录音设备1
crw-rw----+ 1 root audio 116,  1 4月   7 09:22 seq
crw-rw----+ 1 root audio 116, 33 4月   7 09:22 timer

下面分别分析machine, platform, codec驱动注册过程。

2.1、Machine驱动注册

参考sound\soc\pxa\mioa701_wm9713.c,注册snd_soc_card同时会填充dai_link,这里还注册了widgetsroutes(下面章节分析)。

static struct snd_soc_card mioa701 = {
    .name = "MioA701",
    .owner = THIS_MODULE,
    .dai_link = mioa701_dai,
    .num_links = ARRAY_SIZE(mioa701_dai),

    .dapm_widgets = mioa701_dapm_widgets,
    .num_dapm_widgets = ARRAY_SIZE(mioa701_dapm_widgets),
    .dapm_routes = audio_map,
    .num_dapm_routes = ARRAY_SIZE(audio_map),
};

snd_soc_dai_link定义如下,这里用到了SND_SOC_DAILINK_DEFS宏和SND_SOC_DAILINK_REG

SND_SOC_DAILINK_DEFS(ac97,
    DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97")),
    DAILINK_COMP_ARRAY(COMP_CODEC("wm9713-codec", "wm9713-hifi")),
    DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));

SND_SOC_DAILINK_DEFS(ac97_aux,
    DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97-aux")),
    DAILINK_COMP_ARRAY(COMP_CODEC("wm9713-codec", "wm9713-aux")),
    DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));

static struct snd_soc_dai_link mioa701_dai[] = {
    {
        .name = "AC97",
        .stream_name = "AC97 HiFi",
        .init = mioa701_wm9713_init,
        .ops = &mioa701_ops,
        SND_SOC_DAILINK_REG(ac97),
    },
    {
        .name = "AC97 Aux",
        .stream_name = "AC97 Aux",
        .ops = &mioa701_ops,
        SND_SOC_DAILINK_REG(ac97_aux),
    },
};

SND_SOC_DAILINK_DEFS用于定义compoment,而SND_SOC_DAILINK_REG则是引用compoment,宏展开后如下。

static struct snd_soc_dai_link_component ac97_cpus[] = { { .dai_name = "pxa2xx-ac97", } };
static struct snd_soc_dai_link_component ac97_codecs[] = { { .name = "wm9713-codec", .dai_name = "wm9713-hifi", } };
static struct snd_soc_dai_link_component ac97_platforms[] = { { .name = "pxa-pcm-audio" } };
static struct snd_soc_dai_link_component ac97_aux_cpus[] = { { .dai_name = "pxa2xx-ac97-aux", } };
static struct snd_soc_dai_link_component ac97_aux_codecs[] = { { .name = "wm9713-codec", .dai_name = "wm9713-aux", } };
static struct snd_soc_dai_link_component ac97_aux_platforms[] = { { .name = "pxa-pcm-audio" } };

static struct snd_soc_dai_link mioa701_dai[] = {
    {
        .name = "AC97",
        .stream_name = "AC97 HiFi",
        .init = mioa701_wm9713_init,
        .ops = &mioa701_ops,
        .cpus       = ac97_cpus,                \
        .num_cpus   = ARRAY_SIZE(ac97_cpus),    \
        .codecs     = ac97_codecs,              \
        .num_codecs = ARRAY_SIZE(ac97_codecs),  \
        .platforms  = ac97_platforms,           \
        .num_platforms  = ARRAY_SIZE(ac97_platforms)
    },
    {
        .name = "AC97 Aux",
        .stream_name = "AC97 Aux",
        .ops = &mioa701_ops,
        .cpus       = ac97_aux_cpus,                \
        .num_cpus   = ARRAY_SIZE(ac97_aux_cpus),    \
        .codecs     = ac97_aux_codecs,              \
        .num_codecs = ARRAY_SIZE(ac97_aux_codecs),  \
        .platforms  = ac97_aux_platforms,           \
        .num_platforms  = ARRAY_SIZE(ac97_aux_platforms)
    },
};

这里指定codec_name为"wm9713-codec",因此挂载的是wm9713声卡。

// sound\soc\codecs\wm9713.c
static struct platform_driver wm9713_codec_driver = {
    .driver = {
            .name = "wm9713-codec",
    },

    .probe = wm9713_probe,
};

module_platform_driver(wm9713_codec_driver);

platform_name为"pxa-pcm-audio",指定platform驱动为pxa2xx-pcm.c。

// sound\soc\pxa\pxa2xx-pcm.c
static struct platform_driver pxa_pcm_driver = {
    .driver = {
        .name = "pxa-pcm-audio",
    },

    .probe = pxa2xx_soc_platform_probe,
};

module_platform_driver(pxa_pcm_driver);

Machine驱动注册通过devm_snd_soc_register_card实现,PCM创建部分查看下图蓝色字体,红色字体为DAPM部分后面分析。

  图2.1 devm_snd_soc_register_card注册snd_soc_card流程

snd_soc_bind_card会为每个dai_link调用snd_soc_add_pcm_runtime完成dai_link的初始化。

    for_each_card_prelinks(card, i, dai_link) {
        ret = snd_soc_add_pcm_runtime(card, dai_link);
        if (ret < 0)
            goto probe_end;
    }
int snd_soc_add_pcm_runtime(struct snd_soc_card *card,
                struct snd_soc_dai_link *dai_link)
{
    struct snd_soc_pcm_runtime *rtd;
    struct snd_soc_dai_link_component *codec, *platform, *cpu;
    struct snd_soc_component *component;

	……

    if (dai_link->ignore)
        return 0;

    dev_dbg(card->dev, "ASoC: binding %s\n", dai_link->name);

    ret = soc_dai_link_sanity_check(card, dai_link);
    if (ret < 0)
        return ret;

    rtd = soc_new_pcm_runtime(card, dai_link);
    if (!rtd)
        return -ENOMEM;

    for_each_link_cpus(dai_link, i, cpu) {
        asoc_rtd_to_cpu(rtd, i) = snd_soc_find_dai(cpu);
        if (!asoc_rtd_to_cpu(rtd, i)) {
            dev_info(card->dev, "ASoC: CPU DAI %s not registered\n",
                 cpu->dai_name);
            goto _err_defer;
        }
        snd_soc_rtd_add_component(rtd, asoc_rtd_to_cpu(rtd, i)->component);
    }

    /* Find CODEC from registered CODECs */
    for_each_link_codecs(dai_link, i, codec) {
        asoc_rtd_to_codec(rtd, i) = snd_soc_find_dai(codec);
        if (!asoc_rtd_to_codec(rtd, i)) {
            dev_info(card->dev, "ASoC: CODEC DAI %s not registered\n",
                 codec->dai_name);
            goto _err_defer;
        }

        snd_soc_rtd_add_component(rtd, asoc_rtd_to_codec(rtd, i)->component);
    }

    /* Find PLATFORM from registered PLATFORMs */
    for_each_link_platforms(dai_link, i, platform) {
        for_each_component(component) {
            if (!snd_soc_is_matching_component(platform, component))
                continue;

            snd_soc_rtd_add_component(rtd, component);
        }
}

……
}

snd_soc_add_pcm_runtime主要完成以下几项工作

  1. 检查dai_link是否有匹配的设备;
  2. 创建runtime
  3. 绑定componentdai
static struct snd_soc_pcm_runtime *soc_new_pcm_runtime(
    struct snd_soc_card *card, struct snd_soc_dai_link *dai_link)
{
    struct snd_soc_pcm_runtime *rtd;
    struct snd_soc_component *component;

    /*
     * for rtd
     */
    rtd = devm_kzalloc(dev,
               sizeof(*rtd) +
               sizeof(*component) * (dai_link->num_cpus +
                         dai_link->num_codecs +
                         dai_link->num_platforms),
               GFP_KERNEL);
    if (!rtd)
        goto free_rtd;

    rtd->dev = dev;
    INIT_LIST_HEAD(&rtd->list);
    for_each_pcm_streams(stream) {
        INIT_LIST_HEAD(&rtd->dpcm[stream].be_clients);
        INIT_LIST_HEAD(&rtd->dpcm[stream].fe_clients);
    }
    dev_set_drvdata(dev, rtd);
    INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);

    /*
     * for rtd->dais
     */
    rtd->dais = devm_kcalloc(dev, dai_link->num_cpus + dai_link->num_codecs,
                    sizeof(struct snd_soc_dai *),
                    GFP_KERNEL);
    if (!rtd->dais)
        goto free_rtd;

    rtd->num_cpus   = dai_link->num_cpus;
    rtd->num_codecs = dai_link->num_codecs;
    rtd->card   = card;
    rtd->dai_link   = dai_link;
    rtd->num    = card->num_rtd++;

    /* see for_each_card_rtds */
    list_add_tail(&rtd->list, &card->rtd_list);

……
}

在驱动匹配时会用到snd_soc_find_dai,该函数会遍历component_list,并比较name字答串完成匹配。

struct snd_soc_dai *snd_soc_find_dai(
    const struct snd_soc_dai_link_component *dlc)
{
    struct snd_soc_component *component;
    struct snd_soc_dai *dai;

    lockdep_assert_held(&client_mutex);

    /* Find CPU DAI from registered DAIs */
    for_each_component(component) {
        if (!snd_soc_is_matching_component(dlc, component))
            continue;
        for_each_component_dais(component, dai) {
            if (dlc->dai_name && strcmp(dai->name, dlc->dai_name)
                && (!dai->driver->name
                || strcmp(dai->driver->name, dlc->dai_name)))
                continue;

            return dai;
        }
    }

    return NULL;
}

for_each_component即遍历component_list

static LIST_HEAD(component_list);
#define for_each_component(component)           \
    list_for_each_entry(component, &component_list, list)

component_list填充的操作则是在codec驱动,platform驱动里完成的。

soc_init_pcm_runtime函数实现PCM设备的创建与初始化,生成的设备用于PCM数据流控制,分析如图2.2.

 图2.2 soc_init_pcm_runtime流程分析

 2.2、CODEC驱动注册

Codec驱动负责声卡通路管理,电源控制等。需要实现snd_soc_component_driver结构体, 并在probe初始化时通过devm_snd_soc_register_component向ALSA注册自己的能力。

static const struct snd_soc_component_driver soc_component_dev_wm9713 = {
    .probe          = wm9713_soc_probe,
    .remove         = wm9713_soc_remove,
    .suspend        = wm9713_soc_suspend,
    .resume         = wm9713_soc_resume,
    .set_bias_level     = wm9713_set_bias_level,
    .controls       = wm9713_snd_ac97_controls,
    .num_controls       = ARRAY_SIZE(wm9713_snd_ac97_controls),
    .dapm_widgets       = wm9713_dapm_widgets,
    .num_dapm_widgets   = ARRAY_SIZE(wm9713_dapm_widgets),
    .dapm_routes        = wm9713_audio_map,
    .num_dapm_routes    = ARRAY_SIZE(wm9713_audio_map),
    .idle_bias_on       = 1,
    .use_pmdown_time    = 1,
    .endianness     = 1,
    .non_legacy_dai_naming  = 1,
};
static int wm9713_probe(struct platform_device *pdev)
{
	……
    return devm_snd_soc_register_component(&pdev->dev,
            &soc_component_dev_wm9713, wm9713_dai, ARRAY_SIZE(wm9713_dai));
}

devm_snd_soc_register_component第三个参数是snd_soc_dai_driver结构,主要描述codec支持的通路声道数据,采样率等信息,其snd_soc_dai_ops成员可感知音频播放事件,如startup, prepare, trigger等。

还有一个很重要的地方是snd_soc_dai_driverstream_name字段,ALSA会为每个dai创建一个widget,类型为snd_soc_dapm_dai_insnd_soc_dapm_dai_out,用于音频流控制,后面dapm章节会分析。

soc_component_dev_wm9713也会填充上自己的kcontrol, widget以及routes等信息。

 图2.3 snd_soc_register_component函数流程

 上面只是将codec注册到全局链表,后面还要完成匹配的工作参见Machine驱动注册部分。

2.4、ALSA结构体关系图

ALSA涉及很多结构体,大致梳理了其中的关系如图2.4所示。Machine驱动起总领作用,通过搜索component_list并比较name/dai_name与codec/platform驱动进行匹配。

 图2.4 ALSA结构体关系图

 图2.5 PCM流数据结构

  • 6
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值