PCM data flow - 5 - ASoC machine driver

来源:互联网 发布:外汇分析软件 编辑:程序博客网 时间:2024/05/22 04:35

章节ASoC codec driverASoC platform driver介绍了codec、platform驱动,但仅有codec、platform驱动是不能工作的,需要一个角色把codec、codec_dai、cpu_dai、platform给链结起来才能构成一个完整的音频回路,这个角色就由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 audio operations小节中有描述。

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的桥梁,保存着codec、codec_dai、cpu_dai、platform等硬件设备实例;

随后的工作都在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中,完成音频设备的绑定工作;

·          初始化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()最主要的工作是创建pcm逻辑设备,创建回放子流和录制子流实例,并初始化回放子流和录制子流的pcm操作函数,数据搬运时,需要调用这些操作函数来触发codec、cpu_dai、pcm_dma硬件工作。


1 0
原创粉丝点击