Yuhang Zheng

基于i.MX6X的音频驱动分析(三)音频驱动ASoC的机器层

N 人看过

前面一节的内容我们提到,ASoC被分为Machine、Platform和Codec三大部分。

其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,再次引用上一节的内容:

Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。

ASoC的一切都从Machine驱动开始,包括声卡的注册,绑定Platform和Codec驱动等等,下面就让我们从Machine驱动开始讨论吧

注册Platform Device

ASoC把声卡注册为Platform Device,以WM8960为例 ,涉及文件:sound/soc/imx/imx-wm8960.c

其模块初始化函数为:module_init(imx_asoc_init);

static int __init imx_asoc_init(void)
{
        int ret;

        ret = platform_driver_register(&imx_wm8960_driver);
        if (ret < 0)
                goto exit;

        if (machine_is_mx6q_sabresd())
                imx_dai[0].codec_name = "wm8960-codec.0-001a";
        else if (machine_is_mx6sl_arm2()  machine_is_mx6sl_evk())
                imx_dai[0].codec_name = "wm8960.1-001a";

        imx_snd_device = platform_device_alloc("soc-audio", 6);
        if (!imx_snd_device)
                goto err_device_alloc;

        platform_set_drvdata(imx_snd_device, &snd_soc_card_imx);

        ret = platform_device_add(imx_snd_device);

        if (0 == ret)
                goto exit;

        platform_device_put(imx_snd_device);

err_device_alloc:
        platform_driver_unregister(&imx_wm8960_driver);
exit:
        return ret;
}

由此可见,模块初始化时,注册了一个名为soc-audio的Platform设备,同时把snd_soc_card_imx设到platform_device结构的dev.drvdata字段中。

这里引出了第一个数据结构snd_soc_card的实例snd_soc_card_imx,它的定义如下(数据结构snd_soc_card的定义在include/sound/soc.h):

static struct snd_soc_dai_link imx_dai[] = {
        {
                .name = "HiFi",
                .stream_name = "HiFi",
                .codec_dai_name = "wm8960-hifi",//指定codec dai的名字
                .codec_name     = "wm8960-codec.0-001a",//指定codec的名字
                .cpu_dai_name   = "imx-ssi.1", //指定cpu dai的名字
                .platform_name  = "imx-pcm-audio.1",//指定platform的名字
                .init           = imx_wm8960_init,
                .ops            = &imx_hifi_ops,
        },
};

static struct snd_soc_card snd_soc_card_imx = {
        .name           = "wm8960-audio",
        .dai_link       = imx_dai,
        .num_links      = ARRAY_SIZE(imx_dai),
};

通过snd_soc_card结构,又引出了Machine驱动的另外两个数据结构:

  • snd_soc_dai_link(实例:imx_dai[] )
  • snd_soc_ops(实例:imx_hifi_ops )

snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字,稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的,这样看来,Machine驱动的设备初始化代码无非就是选择合适Platform和Codec以及dai,用他们填充以上几个数据结构,然后注册Platform设备即可。当然还要实现连接Platform和Codec的dai_link对应的ops实现,本例中就是imx_hifi_ops:

static struct snd_soc_ops imx_hifi_ops = {
        .startup = imx_hifi_startup,
        .shutdown = imx_hifi_shutdown,
        .hw_params = imx_hifi_hw_params,
        .hw_free = imx_hifi_hw_free,
        .trigger = imx_hifi_trigger,
};

注册Platform Driver

按照Linux的设备模型,有platform_device,就一定会有platform_driver。ASoC的platform_driver在以下文件中定义:sound/soc/soc-core.c。

还是先从模块的入口看起:

static int __init snd_soc_init(void)
{
        ...
        return platform_driver_register(&soc_driver);
}
module_init(snd_soc_init);

soc_driver的定义如下:

/* ASoC platform driver */
static struct platform_driver soc_driver = {
        .driver         = {
                .name           = "soc-audio",
                .owner          = THIS_MODULE,
                .pm             = &snd_soc_pm_ops,
        },
        .probe          = soc_probe,
        .remove         = soc_remove,
};

我们看到platform_driver的name字段为soc-audio,正好与platform_device中的名字相同,按照Linux的设备模型,platform总线会匹配这两个名字相同的device和driver,同时会触发soc_probe的调用,它正是整个ASoC驱动初始化的入口。

初始化入口soc_probe()

soc_probe函数本身很简单,它先从platform_device参数中取出snd_soc_card,然后调用snd_soc_register_card,通过snd_soc_register_card,为snd_soc_pcm_runtime数组申请内存,每一个dai_link对应snd_soc_pcm_runtime数组的一个单元,然后把snd_soc_card中的dai_link配置复制到相应的snd_soc_pcm_runtime中。

/* probes a new socdev */
static int soc_probe(struct platform_device *pdev)
{
        struct snd_soc_card *card = platform_get_drvdata(pdev);//从platform_device参数中取出snd_soc_card
        int ret = 0;

        /*
         * no card, so machine driver should be registering card
         * we should not be here in that case so ret error
         */
        if (!card)
                return -EINVAL;

        /* Bodge while we unpick instantiation */
        card->dev = &pdev->dev;

        ret = snd_soc_register_card(card);//为snd_soc_pcm_runtime数组申请内存
        if (ret != 0) {
                dev_err(&pdev->dev, "Failed to register card\n");
                return ret;
        }

        return 0;
}
/**
 * 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;

        if (!card->name  !card->dev)
                return -EINVAL;

        dev_set_drvdata(card->dev, card);

        snd_soc_initialize_card_lists(card);

        soc_init_card_debugfs(card);

        card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) *
                            (card->num_links + card->num_aux_devs),
                            GFP_KERNEL);
        if (card->rtd == NULL)
                return -ENOMEM;
        card->rtd_aux = &card->rtd[card->num_links];

        for (i = 0; i < card->num_links; i++)
                card->rtd[i].dai_link = &card->dai_link[i];

        INIT_LIST_HEAD(&card->list);
        card->instantiated = 0;
        mutex_init(&card->mutex);

        mutex_lock(&client_mutex);
        list_add(&card->list, &card_list);
        snd_soc_instantiate_cards();
        mutex_unlock(&client_mutex);

        dev_dbg(card->dev, "Registered card '%s'\n", card->name);

        return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_register_card);
/*
 * Attempt to initialise any uninitialised cards.  Must be called with
 * client_mutex.
 */
static void snd_soc_instantiate_cards(void)
{
        struct snd_soc_card *card;
        list_for_each_entry(card, &card_list, list)
                snd_soc_instantiate_card(card);
}

最后,大部分的工作都在snd_soc_instantiate_card中实现,下面就看看snd_soc_instantiate_card做了些什么:

static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
        struct snd_soc_codec *codec;
        struct snd_soc_codec_conf *codec_conf;
        enum snd_soc_compress_type compress_type;
        int ret, i;

        mutex_lock(&card->mutex);

        if (card->instantiated) {
                mutex_unlock(&card->mutex);
                return;
        }

        /* bind DAIs */
        for (i = 0; i < card->num_links; i++)
                soc_bind_dai_link(card, i);

        /* bind completed ? */
        if (card->num_rtd != card->num_links) {
                mutex_unlock(&card->mutex);
                return;
        }

        /* initialize the register cache for each available codec */
        list_for_each_entry(codec, &codec_list, list) {
                if (codec->cache_init)
                        continue;
                /* by default we don't override the compress_type */
                compress_type = 0;
                /* check to see if we need to override the compress_type */
                for (i = 0; i < card->num_configs; ++i) {
                        codec_conf = &card->codec_conf[i];
                        if (!strcmp(codec->name, codec_conf->dev_name)) {
                                compress_type = codec_conf->compress_type;
                                if (compress_type && compress_type
                                    != codec->compress_type)
                                        break;
                        }
                }
                ret = snd_soc_init_codec_cache(codec, compress_type);
                if (ret < 0) {
                        mutex_unlock(&card->mutex);
                        return;
                }
        }

        /* card bind complete so register a sound card */
        ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
                        card->owner, 0, &card->snd_card);
        if (ret < 0) {
                printk(KERN_ERR "asoc: can't create sound card for card %s\n",
                        card->name);
                mutex_unlock(&card->mutex);
                return;
        }
        card->snd_card->dev = card->dev;

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

        /* initialise the sound card only once */
        if (card->probe) {
                ret = card->probe(card);
                if (ret < 0)
                        goto card_probe_error;
        }

        for (i = 0; i < card->num_links; i++) {
                ret = soc_probe_dai_link(card, i);
                if (ret < 0) {
                        pr_err("asoc: failed to instantiate card %s: %d\n",
                               card->name, ret);
                        goto probe_dai_err;
                }
        }

        for (i = 0; i < card->num_aux_devs; i++) {
                ret = soc_probe_aux_dev(card, i);
                if (ret < 0) {
                        pr_err("asoc: failed to add auxiliary devices %s: %d\n",
                               card->name, ret);
                        goto probe_aux_dev_err;
                }
        }

        /* We should have a non-codec control add function but we don't */
        if (card->controls)
                snd_soc_add_controls(list_first_entry(&card->codec_dev_list,
                                                      struct snd_soc_codec,
                                                      card_list),
                                     card->controls,
                                     card->num_controls);

        if (card->dapm_routes)
                snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
                                        card->num_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, "%s late_probe() failed: %d\n",
                                card->name, ret);
                        goto probe_aux_dev_err;
                }
        }

        ret = snd_card_register(card->snd_card);
        if (ret < 0) {
                printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
                goto probe_aux_dev_err;
        }

#ifdef CONFIG_SND_SOC_AC97_BUS
        /* register any AC97 codecs */
        for (i = 0; i < card->num_rtd; i++) {
                ret = soc_register_ac97_dai_link(&card->rtd[i]);
                if (ret < 0) {
                        printk(KERN_ERR "asoc: failed to register AC97 %s\n", card->name);
                        while (--i >= 0)
                                soc_unregister_ac97_dai_link(card->rtd[i].codec);
                        goto probe_aux_dev_err;
                }
        }
#endif

        card->instantiated = 1;
        mutex_unlock(&card->mutex);
        return;

probe_aux_dev_err:
        for (i = 0; i < card->num_aux_devs; i++)
                soc_remove_aux_dev(card, i);

probe_dai_err:
        soc_remove_dai_links(card);

card_probe_error:
        if (card->remove)
                card->remove(card);

        snd_card_free(card->snd_card);

        mutex_unlock(&card->mutex);
}

该函数首先利用card->instantiated来判断该卡是否已经实例化,如果已经实例化则直接返回,否则遍历每一对dai_link,进行codec、platform、dai的绑定工作,下只是代码的部分选节,详细的代码请直接参考完整的代码树。

        /* bind DAIs */
        for (i = 0; i < card->num_links; i++)
                soc_bind_dai_link(card, i);

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驱动的信息。

snd_soc_instantiate_card接着初始化Codec的寄存器缓存,然后调用标准的alsa函数创建声卡实例:

        /* card bind complete so register a sound card */
        ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
                        card->owner, 0, &card->snd_card);
        if (ret < 0) {
                printk(KERN_ERR "asoc: can't create sound card for card %s\n",
                        card->name);
                mutex_unlock(&card->mutex);
                return;
        }
        card->snd_card->dev = card->dev;

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

然后,依次调用各个子结构的probe函数:

        /* initialise the sound card only once */
        if (card->probe) {
                ret = card->probe(card);
                if (ret < 0)
                        goto card_probe_error;
        }

        for (i = 0; i < card->num_links; i++) {
                ret = soc_probe_dai_link(card, i);
                if (ret < 0) {
                        pr_err("asoc: failed to instantiate card %s: %d\n",
                               card->name, ret);
                        goto probe_dai_err;
                }
        }

        for (i = 0; i < card->num_aux_devs; i++) {
                ret = soc_probe_aux_dev(card, i);
                if (ret < 0) {
                        pr_err("asoc: failed to add auxiliary devices %s: %d\n",
                               card->name, ret);
                        goto probe_aux_dev_err;
                }
        }

在上面的soc_probe_dai_link()函数中做了比较多的事情,把他展开继续讨论:

static int soc_probe_dai_link(struct snd_soc_card *card, int num)
{
        ...
        /* set default power off timeout */
        rtd->pmdown_time = pmdown_time;

        /* probe the cpu_dai */
        if (!cpu_dai->probed) {
                if (cpu_dai->driver->probe) {
                        ret = cpu_dai->driver->probe(cpu_dai);
                }
                cpu_dai->probed = 1;
                /* mark cpu_dai as probed and add to card cpu_dai list */
                list_add(&cpu_dai->card_list, &card->dai_dev_list);
        }

        /* probe the CODEC */
        if (!codec->probed) {
                ret = soc_probe_codec(card, codec);
        }

        /* probe the platform */
        if (!platform->probed) {
                if (platform->driver->probe) {
                        ret = platform->driver->probe(platform);
                }
                /* mark platform as probed and add to card platform list */
                platform->probed = 1;
                list_add(&platform->card_list, &card->platform_dev_list);
        }

        /* probe the CODEC DAI */
        if (!codec_dai->probed) {
                if (codec_dai->driver->probe) {
                        ret = codec_dai->driver->probe(codec_dai);
                }

                /* mark cpu_dai as probed and add to card cpu_dai list */
                codec_dai->probed = 1;
                list_add(&codec_dai->card_list, &card->dai_dev_list);
        }

        /* DAPM dai link stream work */
        INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);

        ret = soc_post_component_init(card, codec, num, 0);
        if (ret)
                return ret;

        ret = device_create_file(&rtd->dev, &dev_attr_pmdown_time);

        /* create the pcm */
        ret = soc_new_pcm(rtd, num);

        /* add platform data for AC97 devices */
        if (rtd->codec_dai->driver->ac97_control)
                snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata);

        return 0;
}

该函数出了挨个调用了codec,dai和platform驱动的probe函数外,在最后还调用了soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备。现在把该函数的部分代码也贴出来:

/* create a new pcm */
static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
        struct snd_pcm_ops *soc_pcm_ops;

        rtd->pcm = pcm;
        pcm->private_data = rtd;

        soc_pcm_ops->open = soc_pcm_open;
        soc_pcm_ops->close = soc_codec_close;
        soc_pcm_ops->hw_params = soc_pcm_hw_params;
        soc_pcm_ops->hw_free = soc_pcm_hw_free;
        soc_pcm_ops->prepare = soc_pcm_prepare;
        soc_pcm_ops->pointer = soc_pcm_pointer;
        soc_pcm_ops->trigger = soc_pcm_trigger;
        soc_pcm_ops->ioctl = soc_pcm_ioctl;

        if (platform->driver->ops) {
                soc_pcm_ops->mmap = platform->driver->ops->mmap;
                soc_pcm_ops->copy = platform->driver->ops->copy;
                soc_pcm_ops->silence = platform->driver->ops->silence;
                soc_pcm_ops->ack = platform->driver->ops->ack;
                soc_pcm_ops->page = platform->driver->ops->page;
        }

        rtd->ops = soc_pcm_ops;

        if (playback)
                snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops);

        if (capture)
                snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops);

        if (platform->driver->pcm_new) {
                ret = platform->driver->pcm_new(rtd->card->snd_card,
                                                codec_dai, pcm);
                if (ret < 0) {
                        pr_err("asoc: platform pcm constructor failed\n");
                        kfree(rtd->ops);
                        return ret;
                }
        }

        pcm->private_free = platform->driver->pcm_free;
        printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name,
                cpu_dai->name);
        return ret;
}

该函数首先初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成员,例如open,close,hw_params等,紧接着调用标准alsa驱动中的创建pcm的函数snd_pcm_new()创建声卡的pcm实例,pcm的private_data字段设置为该runtime变量rtd,然后用platform驱动中的snd_pcm_ops替换部分pcm中的snd_pcm_ops字段,最后,调用platform驱动的pcm_new回调,该回调实现该platform下的dma内存申请和dma初始化等相关工作。到这里,声卡和他的pcm实例创建完成。

回到snd_soc_instantiate_card函数,完成snd_card和snd_pcm的创建后,接着对dapm和dai支持的格式做出一些初始化合设置工作后,调用了 card->late_probe(card)进行一些最后的初始化合设置工作,最后则是调用标准alsa驱动的声卡注册函数对声卡进行注册:

    if (card->late_probe) {
            ret = card->late_probe(card);
            if (ret < 0) {
                    dev_err(card->dev, "%s late_probe() failed: %d\n",
                            card->name, ret);
                    goto probe_aux_dev_err;
            }
    }

    ret = snd_card_register(card->snd_card);
    if (ret < 0) {
            printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
            goto probe_aux_dev_err;
    }

至此,整个Machine驱动的初始化已经完成,通过各个子结构的probe调用,实际上,也完成了部分Platfrom驱动和Codec驱动的初始化工作,整个过程可以用以下的序列图表示:

基于3.0内核的soc probe序列图