PCM data flow - 5 - ASoC machine driver

章节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硬件工作。


评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值