alsa声卡驱动一:声卡的创建

对于Alsa的介绍跟大概框架我这里就不做纤细的总结了,需要了解可以去看
http://blog.csdn.net/droidphone/article/details/6271122
这个博客的文章,写得很好,我也是在对他文章的阅读跟源代码的阅读做出属于自己的总结。

声卡的创建流程

这里写图片描述
图片是一个声卡的简单的创建流程,最主要要关心的是snd_card跟PCM的创建跟注册。
由PCM的创建,我们可以看出PCM是挂在声卡snd_card下的,用下图来看一下snd_card的框架。
这里写图片描述
由图片我们可以很清楚看出来,一个声卡驱动主要需要实现的,就做snd_card、pcm、挂在pcm下的plaback跟capture。
接下来以ASOC的框架来了解一下声卡创建的过程。

ASOC

ASoC–ALSA System on Chip ,是建立在标准ALSA驱动层上,为了更好地支持嵌入式处理器和移动设备中的音频Codec的一套软件体系。在ASoc出现之前,内核对于SoC中的音频已经有部分的支持,不过会有一些局限性:
1、Codec驱动与SoC CPU的底层耦合过于紧密,这种不理想会导致代码的重复,例如,仅是wm8731的驱动,当时Linux中有分别针对4个平台的驱动代码。
2、音频事件没有标准的方法来通知用户,例如耳机、麦克风的插拔和检测,这些事件在移动设备中是非常普通的,而且通常都需要特定于机器的代码进行重新对音频路劲进行配置。
3、当进行播放或录音时,驱动会让整个codec处于上电状态,这对于PC没问题,但对于移动设备来说,这意味着浪费大量的电量。同时也不支持通过改变过取样频率和偏置电流来达到省电的目的。
ASoC正是为了解决上述种种问题而提出的,目前已经被整合至内核的代码树中:sound/soc。ASoC不能单独存在,他只是建立在标准ALSA驱动上的一个它必须和标准的ALSA驱动框架相结合才能工作。(来至http://blog.csdn.net/droidphone/article/details/7165482

硬件架构

音频系统可以被划分为三个部分:1.板载硬件(Machine),2.Soc(Platform),3Codec;
1.Machine:指具体的某款开发板或者开发板,未platform以及code提供载体。
2.platform:指具体的某个Soc平台,主要跟音频驱动中,需要用的Soc的硬件资源有关,比如DMA,I2S,时钟等。将platform抽象出来的好处是,这样可以在不同的machine中使用而不需要改动。
3.codec:根据字面不难理解就是一个解码器,包含了I2S接口,A/D,D/A转化等。codec跟platform一样都是重用的部件,可以在不同的machine中使用不用改动。
这里写图片描述
图片为machine、platform、codec之间的关系。
我们以omapl138开发板为例,解码芯片tlv320aic3x。
我们先从裸机驱动方向思考:
soc(omapl138):需要做的工作EDMA、MCASP、时钟这方面的初始化,这部分工作在ASOC相当于platform,platform主要工作也是做这方面的初始化跟一些操作,其中还包含一个cpu_dai结构跟codec进行数据交互。
codec(tlv320aic3x):主要工作是通过i2c对芯片进行初始化跟配置,这部分工作也是相当于ASOC中codec的工作,对解码芯片进行初始化配置以及一些相对于的操作,以及包含一个codec_dai跟soc进行数据交互。
现在我们来思考这两部分是怎么关联起来的,其实这部分是在Machine完成的了。以我用的开发板为例子,machine在文件/sound/soc/davinci的davinci-evm.c中。

static int __init evm_init(void)
{
    struct snd_soc_card *evm_snd_dev_data;
    int index;
    int ret;

    if (machine_is_davinci_evm()) {
        evm_snd_dev_data = &dm6446_snd_soc_card_evm;
        index = 0;
    } else if (machine_is_davinci_dm355_evm()) {
        evm_snd_dev_data = &dm355_snd_soc_card_evm;
        index = 1;
    } else if (machine_is_davinci_dm365_evm()) {
        evm_snd_dev_data = &dm365_snd_soc_card_evm;
        index = 0;
    } else if (machine_is_davinci_dm6467_evm()) {
        evm_snd_dev_data = &dm6467_snd_soc_card_evm;
        index = 0;
    } else if (machine_is_davinci_da830_evm()) {
        evm_snd_dev_data = &da830_snd_soc_card;
        index = 1;
    } else if (machine_is_davinci_da850_evm()) {
        evm_snd_dev_data = &da850_snd_soc_card;
        index = 0;
    } else if (machine_is_davinci_da850_sdi()) {
#ifdef CONFIG_MACH_DAVINCI_DA850_SDI
        evm_snd_dev_data = &da850_sdi_snd_soc_card;
        index = 0;
#endif
    } else if (machine_is_omapl138_lcdkboard()) {
        evm_snd_dev_data = &da850_snd_soc_card;
        index = 0;
    } else
        return -EINVAL;

    evm_snd_device = platform_device_alloc("soc-audio", index);
    if (!evm_snd_device)
        return -ENOMEM;

    platform_set_drvdata(evm_snd_device, evm_snd_dev_data);
    ret = platform_device_add(evm_snd_device);
    if (ret)
        platform_device_put(evm_snd_device);

    return ret;
}

static void __exit evm_exit(void)
{
    platform_device_unregister(evm_snd_device);
}

module_init(evm_init);
module_exit(evm_exit);

ret = platform_device_add(evm_snd_device);添加了一个platform_device,就一定会有platform_driver的存在,对应的platform_driver在/sound/soc/soc_core.c中。
platform_set_drvdata(evm_snd_device, evm_snd_dev_data);给evm_snd_device添加 了一个专用数据evm_snd_dev_data,用的开发板是davinci_da850,进入看,存在这样一个数据结构:

static struct snd_soc_card da850_snd_soc_card = {
    .name = "DA850/OMAP-L138 EVM",
    .owner = THIS_MODULE,
    .dai_link = &da850_evm_dai,
    .num_links = 1,
};

其中.dai_link = &da850_evm_dai就是我们machine跟据名字将platform跟codec联系起来的,进入&da850_evm_dai中

static struct snd_soc_dai_link da850_evm_dai = {
    .name = "TLV320AIC3X",
    .stream_name = "AIC3X",
    .cpu_dai_name= "davinci-mcasp.0",
    .codec_dai_name = "tlv320aic3x-hifi",
    .codec_name = "tlv320aic3x-codec.1-0018",
    .platform_name = "davinci-pcm-audio",
    .init = evm_aic3x_init,
    .ops = &evm_ops,
};

由结构体成员我们可以看到,我们上面提到的几个部分,codec、codec_dai、platform、cpu_dai的名字都在上面,就是通过名字的方式将这四个部分整合到machine中的。
接下来我们去看一下machine文件的platform_device对应的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,
};

进入.probe = soc_probe回调函数,

static int soc_probe(struct platform_device *pdev)
{
    struct snd_soc_card *card = platform_get_drvdata(pdev);
    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);
    if (ret != 0) {
        dev_err(&pdev->dev, "Failed to register card\n");
        return ret;
    }

    return 0;
}

card->dev = &pdev->dev;将card->dev 指向platf_device的私有数据,即上面提到的struct snd_soc_dai_link da850_evm_dai这个结构体,后面的声卡创建跟PCM创建跟这个结构体有这密切关系。
我们进入声卡的注册函数snd_soc_register_card(card);但其他的部分我们先不进行了解直接进入snd_soc_instantiate_cards()再进入snd_soc_instantiate_card(card)中:
这个函数的第一步是通过函数soc_bind_dai_link对card_link进行名字的关联

    struct snd_soc_dai_link *dai_link = &card->dai_link[num];
    struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
    struct snd_soc_codec *codec;
    struct snd_soc_platform *platform;
    struct snd_soc_dai *codec_dai, *cpu_dai;
    const char *platform_name;

    if (rtd->complete)
        return 1;
    dev_dbg(card->dev, "binding %s at idx %d\n", dai_link->name, num);

    /* do we already have the CPU DAI for this link ? */
    if (rtd->cpu_dai) {
        goto find_codec;
    }
    /* no, then find CPU DAI from registered DAIs*/
    list_for_each_entry(cpu_dai, &dai_list, list) {
        if (dai_link->cpu_dai_of_node) {
            if (cpu_dai->dev->of_node != dai_link->cpu_dai_of_node)
                continue;
        } else {
            if (strcmp(cpu_dai->name, dai_link->cpu_dai_name))
                continue;
        }

        rtd->cpu_dai = cpu_dai;
        goto find_codec;
    }

从list_for_each_entry(cpu_dai, &dai_list, list)取出cpu_dai,再跟card_link的cpu_dai_name进行匹配,如果匹配上将cpu_dai放入rtd->cpu_dai,既将匹配到的cpu_dai赋值给card的成员snd_soc_pcm_runtime的cpu_dai。后面的codec、codec_dai、platform是通过一样的方式进行匹配的。经过这个过程,card的成员snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驱动的信息。
返回snd_soc_instantiate_card中
第二步、通过

ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
            card->owner, 0, &card->snd_card);

进行声卡的创建,对这个函数是不是很熟悉,就是最前面提到的声卡创建步骤的第一步;
第三步:调用各个子结构的probe函数,对soc跟cedec进行相对应的初始化

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

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

static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order)
{
    struct snd_soc_dai_link *dai_link = &card->dai_link[num];
    struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
    struct snd_soc_codec *codec = rtd->codec;
    struct snd_soc_platform *platform = rtd->platform;
    struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai;
    int ret;

    dev_dbg(card->dev, "probe %s dai link %d late %d\n",
            card->name, num, order);

    /* config components */
    codec_dai->codec = codec;
    cpu_dai->platform = platform;
    codec_dai->card = card;
    cpu_dai->card = card;

    /* set default power off timeout */
    rtd->pmdown_time = pmdown_time;

    /* probe the cpu_dai */
    if (!cpu_dai->probed &&
            cpu_dai->driver->probe_order == order) {
        if (!try_module_get(cpu_dai->dev->driver->owner))
            return -ENODEV;

        if (cpu_dai->driver->probe) {
            ret = cpu_dai->driver->probe(cpu_dai);
            if (ret < 0) {
                printk(KERN_ERR "asoc: failed to probe CPU DAI %s\n",
                        cpu_dai->name);
                module_put(cpu_dai->dev->driver->owner);
                return ret;
            }
        }
        cpu_dai->probed = 1;
        /* mark cpu_dai as probed and add to card dai list */
        list_add(&cpu_dai->card_list, &card->dai_dev_list);
    }

    /* probe the CODEC */
    if (!codec->probed &&
            codec->driver->probe_order == order) {
        ret = soc_probe_codec(card, codec);
        if (ret < 0)
            return ret;
    }

    /* probe the platform */
    if (!platform->probed &&
            platform->driver->probe_order == order) {
        ret = soc_probe_platform(card, platform);
        if (ret < 0)
            return ret;
    }

    /* probe the CODEC DAI */
    if (!codec_dai->probed && codec_dai->driver->probe_order == order) {
        if (codec_dai->driver->probe) {
            ret = codec_dai->driver->probe(codec_dai);
            if (ret < 0) {
                printk(KERN_ERR "asoc: failed to probe CODEC DAI %s\n",
                        codec_dai->name);
                return ret;
            }
        }

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

    /* complete DAI probe during last probe */
    if (order != SND_SOC_COMP_ORDER_LAST)
        return 0;

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

    ret = device_create_file(rtd->dev, &dev_attr_pmdown_time);
    if (ret < 0)
        printk(KERN_WARNING "asoc: failed to add pmdown_time sysfs\n");

    /* create the pcm */
    ret = soc_new_pcm(rtd, num);
    if (ret < 0) {
        printk(KERN_ERR "asoc: can't create pcm %s\n", dai_link->stream_name);
        return ret;
    }

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

分别调用cpu_dai、codec、platform、codec_dai的probe函数,这些函数主要工作也是进行相对于的初始化,这里不作探讨,留到后面章节的总结再说。函数最后ret = soc_new_pcm(rtd, num),进行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;

    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;

    dev_dbg(rtd->card->dev, "registered pcm #%d %s\n",num,new_name);
    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;
    if (platform->driver->ops) {
        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);

    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);
        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_pcm_ops成员函数

    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;

创建一个PCM:

ret = snd_pcm_new(rtd->card->snd_card, new_name,
            num, playback, capture, &pcm);

这一步就是最早说的声卡驱动创建步骤的第四步。
回到snd_soc_init_codec_cache中
第四步:对声卡进行注册

ret = snd_card_register(card->snd_card);

到此我们队ASOC框架下的声卡创建过程也大概探讨完了,这对我们对于我们做一个声卡驱动需要做的工作内容理解有很大帮助。下面是Asoc数据结构的静态关系图。
这里写图片描述
来自于:http://blog.csdn.net/droidphone/article/details/7165482
根据上面所说,我们可以大概知道,一个声卡的驱动主要工作是:
1.实现codec实例
2.实现codec_dai实例
3.实现platform实例
4.实现cpu_dai实例
5.machine通过名字对各个部分进行关联。
后面内容对这几个部分再进行了解。

有任何错误的地方,还望指出。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值