现在的位置: 首页 > 综合 > 正文

PCM data flow之五:ASoC中的machine_drv

2013年12月22日 ⁄ 综合 ⁄ 共 6508字 ⁄ 字号 评论关闭

章节ASoC中的codec_drvASoC中的platform_drv介绍了codec、platform(I2S、pcm_dma)驱动,但仅有codec、platform驱动是不能工作的,需要一个角色把codec、codec
dai、cpu dai、pcm dma给链结起来才能组成一个完整的音频回路,这个角色就由machine_drv承担了。

struct snd_soc_dai_link {
    /* config - must be set by machine driver */
    const char *name;           /* Codec name */
    const char *stream_name;        /* Stream name */
    const char *codec_name;     /* for multi-codec */
    const struct device_node *codec_of_node;
    const char *platform_name;  /* for multi-platform */
    const struct device_node *platform_of_node;
    const char *cpu_dai_name;
    const struct device_node *cpu_dai_of_node;
    const char *codec_dai_name;

    unsigned int dai_fmt;           /* format to set on init */

    /* Keep DAI active over suspend */
    unsigned int ignore_suspend:1;

    /* Symmetry requirements */
    unsigned int symmetric_rates:1;

    /* pmdown_time is ignored at stop */
    unsigned int ignore_pmdown_time:1;

    /* codec/machine specific init - e.g. add machine controls */
    int (*init)(struct snd_soc_pcm_runtime *rtd);

    /* machine stream operations */
    struct snd_soc_ops *ops;
};

注释比较详细,重点介绍如下几个字段:

·          codec_name:该音频链路需要绑定的codec名称标识,soc-core中会遍历codec_list,匹配同名的codec并绑定。

·          platform_name:该音频链路需要绑定的platform名称标识,soc-core中会遍历platform_list,匹配同名的platform并绑定。

·          cpu_dai_name:该音频链路需要绑定的cpu_dai名称标识,soc-core中会遍历dai_list,匹配同名的dai并绑定。

·          codec_dai_name:该音频链路需要绑定的codec_dai名称标识,soc-core中会遍历dai_list,匹配同名的dai并绑定。

·          ops:重点留意hw_params回调,一般来说这个回调是要实现的,用于配置codec、cpu_dai的时钟、格式。在Codec的音频操作接口小节中有简单的描述。

goni_wm8994.c中的dai_link定义,两个音频链路,分别用于Media和Voice,可以看到用于Voice链路不须指定platform_name的:

static struct snd_soc_dai_link goni_dai[] = {
{
    .name = "WM8994",
    .stream_name = "WM8994 HiFi",
    .cpu_dai_name = "samsung-i2s.0",
    .codec_dai_name = "wm8994-aif1",
    .platform_name = "samsung-audio",
    .codec_name = "wm8994-codec.0-001a",
    .init = goni_wm8994_init,
    .ops = &goni_hifi_ops,
}, {
    .name = "WM8994 Voice",
    .stream_name = "Voice",
    .cpu_dai_name = "goni-voice-dai",
    .codec_dai_name = "wm8994-aif2",
    .codec_name = "wm8994-codec.0-001a",
    .ops = &goni_voice_ops,
},
};

除了dai_link,机器中一些特定的音频控件和音频事件也可以在machine_drv定义,如耳机插拔检测、外部功放打开关闭等。

我们再分析machine_drv初始化过程:

static struct snd_soc_card goni = {
    .name = "goni",
    .owner = THIS_MODULE,
    .dai_link = goni_dai,
    .num_links = ARRAY_SIZE(goni_dai),

    .dapm_widgets = goni_dapm_widgets,
    .num_dapm_widgets = ARRAY_SIZE(goni_dapm_widgets),
    .dapm_routes = goni_dapm_routes,
    .num_dapm_routes = ARRAY_SIZE(goni_dapm_routes),
};

static int __init goni_init(void)
{
    int ret;

    goni_snd_device = platform_device_alloc("soc-audio", -1);
    if (!goni_snd_device)
        return -ENOMEM;

    /* register voice DAI here */
    ret = snd_soc_register_dai(&goni_snd_device->dev, &voice_dai);
    if (ret) {
        platform_device_put(goni_snd_device);
        return ret;
    }

    platform_set_drvdata(goni_snd_device, &goni);
    ret = platform_device_add(goni_snd_device);

    if (ret) {
        snd_soc_unregister_dai(&goni_snd_device->dev);
        platform_device_put(goni_snd_device);
    }

    return ret;
}

分配一个name="soc-audio"的platform_device实例;
为该platform_device设定私有数据指向snd_soc_card;
然后注册该platform_device到系统中;
再然后呢?好像没有了...

但是真的没有了吗?别忘了,platform_device还得有个好伙伴platform_driver跟它配对。而name="soc-audio"的platform_driver定义在soc-core.c中:

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

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

当两者匹配后,soc_probe()会被调用,继而调用snd_soc_register_card()注册声卡。由于该过程很冗长,这里不一一贴代码分析了,但整个流程是比较简单的,流程图如下:

·          取出platform_device的私有数据,该私有数据保存的是machine_drv中的snd_soc_card。

·          snd_soc_register_card()为每个dai_link分配一个snd_soc_pcm_runtime实例,别忘了之前提过snd_soc_pcm_runtime是ASoC core的桥梁,它将保存codec、codec_dai、platform、cpu_dai、pcm_dma等runtime devices。每个snd_soc_pcm_runtime都对应着各自的dai_link。

随后的工作都在snd_soc_instantiate_card()进行:

·          遍历dai_list、codec_list、platform_list链表,为每个音频链路匹配cpu_dai、codec_dai、codec、platform;找到的cpu_dai、codec_dai、codec、platform实例保存到dai_link对应的snd_soc_pcm_runtime中,完成dai_link的runtime devices绑定工作。

·          初始化codec的寄存器缓存,调用snd_card_create()创建声卡实例。

·          soc_probe_dai_link()依次回调cpu_dai、codec、platform、codec_dai的probe,完成各个子设备的初始化,随后调用soc_new_pcm()创建pcm实例(因为涉及到本系列的重点内容,后面具体分析这个函数)。

·          最后调用snd_card_register()注册声卡。

回过头详细分析soc_new_pcm函数:

/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
    struct snd_soc_codec *codec = rtd->codec;
    struct snd_soc_platform *platform = rtd->platform;
    struct snd_soc_dai *codec_dai = rtd->codec_dai;
    struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
    struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;
    struct snd_pcm *pcm;
    char new_name[64];
    int ret = 0, playback = 0, capture = 0;

    // 初始化snd_soc_pcm_runtime的ops字段,soc_pcm_XXX这些函数其实依次调用machine、codec_dai、cpu_dai、platform的回调;
    // 如soc_pcm_hw_params:|-> rtd->dai_link->ops->hw_params() 
    //                          |-> codec_dai->driver->ops->hw_params() 
    //                          |-> cpu_dai->driver->ops->hw_params()
    //                          |-> platform->driver->ops->hw_params()
    soc_pcm_ops->open   = soc_pcm_open;
    soc_pcm_ops->close  = soc_pcm_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->trigger    = soc_pcm_trigger;
    soc_pcm_ops->pointer    = soc_pcm_pointer;

    /* check client and interface hw capabilities */
    snprintf(new_name, sizeof(new_name), "%s %s-%d",
            rtd->dai_link->stream_name, codec_dai->name, num);

    if (codec_dai->driver->playback.channels_min)
        playback = 1;
    if (codec_dai->driver->capture.channels_min)
        capture = 1;

    // 创建pcm实例
    ret = snd_pcm_new(rtd->card->snd_card, new_name,
            num, playback, capture, &pcm);
    if (ret < 0) {
        printk(KERN_ERR "asoc: can't create pcm for codec %s\n", codec->name);
        return ret;
    }

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

    rtd->pcm = pcm;
    pcm->private_data = rtd; // pcm的私有数据指向snd_soc_pcm_runtime
    if (platform->driver->ops) {
        // 初始化snd_soc_pcm_runtime的ops字段,这些与pcm dma操作相关,一般我们只用留意pointer回调
        soc_pcm_ops->mmap = platform->driver->ops->mmap;
        soc_pcm_ops->pointer = platform->driver->ops->pointer;
        soc_pcm_ops->ioctl = platform->driver->ops->ioctl;
        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;
    }

    if (playback)
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops); // 把soc_pcm_ops赋给playback substream的ops字段

    if (capture)
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops); // 把soc_pcm_ops赋给capture substream的ops字段

    // 回调platform驱动的pcm_new(),分配dma buffer和dma设备的初始化工作
    if (platform->driver->pcm_new) {
        ret = platform->driver->pcm_new(rtd);
        if (ret < 0) {
            pr_err("asoc: platform pcm constructor failed\n");
            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;
}

可见soc_new_pcm()最主要的工作是创建playback/capture substream实例,并初始化playback/capture substreams的pcm操作函数,pcm数据搬运时,需要触发这些操作函数来控制codec、cpu_dai、pcm_dma硬件工作。

抱歉!评论已关闭.