alsa声卡驱动之三:ASoC框架中的platform

第一篇章中说过ASoC分为:Machine、Codec、Platform三个部分,其中Machine主要起到匹配Codec跟Platform,Codec主要是音频解码芯片的初始化配置跟一些相对应的控制,比如第二章节说的tlv320aic3x.c文件的内容。Platform主要是初始化配置跟操作cpu中跟音频驱动相关的一些硬件资源,我们知道在跟音频解码芯片信息交互的主要资源是omapl138的mcasp和edma相关,所以Platform也是主要跟这部分硬件打交道了。
Platform驱动的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口(DAI)把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音信信号。
在具体实现上,ASoC有把Platform驱动分为两个部分:snd_soc_platform_driver和snd_soc_dai_driver。其中,platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中,dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。

snd_soc_platform_driver的注册

1.定义一个snd_soc_platform_driver结构的实例;
2.在platform_driver的probe回调中利用ASoC的API:snd_soc_register_platform()注册上面定义的实例;
3.实现snd_soc_platform_driver中的各个回调函数;
进入/sound/soc/daivinc/daicinc-pcm.c

static struct snd_soc_platform_driver davinci_soc_platform = {
    .ops =      &davinci_pcm_ops,
    .pcm_new =  davinci_pcm_new,
    .pcm_free =     davinci_pcm_free,
};
static int __devinit davinci_soc_platform_probe(struct platform_device *pdev)
{
    return snd_soc_register_platform(&pdev->dev, &davinci_soc_platform);
}

static int __devexit davinci_soc_platform_remove(struct platform_device *pdev)
{
    snd_soc_unregister_platform(&pdev->dev);
    return 0;
}

static struct platform_driver davinci_pcm_driver = {
    .driver = {
            .name = "davinci-pcm-audio",
            .owner = THIS_MODULE,
    },

    .probe = davinci_soc_platform_probe,
    .remove = __devexit_p(davinci_soc_platform_remove),
};

很平常的platfrom_driver驱动,所以我们移植的时候需要注册相对应的platform_device的。platform_device就不做了解。
进入.prode函数里,可以看到进行snd_soc_register_platform注册一个snd_soc_platform_drive实例。
snd_soc_register_platform(&pdev->dev, &davinci_soc_platform);

int snd_soc_register_platform(struct device *dev,
        struct snd_soc_platform_driver *platform_drv)
{
    struct snd_soc_platform *platform;

    dev_dbg(dev, "platform register %s\n", dev_name(dev));

    platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);
    if (platform == NULL)
        return -ENOMEM;

    /* create platform component name */
    platform->name = fmt_single_name(dev, &platform->id);
    if (platform->name == NULL) {
        kfree(platform);
        return -ENOMEM;
    }

    platform->dev = dev;
    platform->driver = platform_drv;
    platform->dapm.dev = dev;
    platform->dapm.platform = platform;
    platform->dapm.stream_event = platform_drv->stream_event;

    mutex_lock(&client_mutex);
    list_add(&platform->list, &platform_list);
    snd_soc_instantiate_cards();
    mutex_unlock(&client_mutex);

    pr_debug("Registered platform '%s'\n", platform->name);

    return 0;
}

snd_soc_register_platform() 该函数用于注册一个snd_soc_platform,只有注册以后,它才可以被Machine驱动使用。它的代码已经清晰地表达了它的实现过程:
1.为snd_soc_platform实例申请内存;
2.从platform_device中获得它的名字,用于Machine驱动的匹配工作;其实也可以看到platform_devicen的name跟Machine中的.platform_name = “davinci-pcm-audio”是一样的。
3.初始化snd_soc_platform的字段;
4.把snd_soc_platform实例连接到全局链表platform_list中;
5.调用snd_soc_instantiate_cards,触发声卡的machine、platform、codec、dai等的匹配工作;
进入snd_soc_platform_driver的实例看

static struct snd_soc_platform_driver davinci_soc_platform = {
    .ops =      &davinci_pcm_ops,
    .pcm_new =  davinci_pcm_new,
    .pcm_free =     davinci_pcm_free,
};

.pcm_new = davinci_pcm_new,通过davinci_pcm_preallocate_dma_buffer给playback跟capture调用dma_alloc_writecombine分配dma缓存。
.pcm_free = davinci_pcm_free,跟.pcm_new做相反的工作,是否缓存。
.ops = &davinci_pcm_ops,是一些接口函数,进入看看,

static struct snd_pcm_ops davinci_pcm_ops = {
    .open =     davinci_pcm_open,
    .close =    davinci_pcm_close,
    .ioctl =    snd_pcm_lib_ioctl,
    .hw_params =    davinci_pcm_hw_params,
    .hw_free =  davinci_pcm_hw_free,
    .prepare =  davinci_pcm_prepare,
    .trigger =  davinci_pcm_trigger,
    .pointer =  davinci_pcm_pointer,
    .mmap =     davinci_pcm_mmap,
};

.open = davinci_pcm_open
主要是通过snd_soc_dai_get_dma_data(rtd->cpu_dai, substream),获得cpu_dai中的dma参数,然后给davinci_runtime_data申请内存,保存在davinci_runtime_data字段。最后用ret =davinci_pcm_dma_request(substream);分配asp的dma通道跟link通道。
.close = davinci_pcm_close
所做的是跟.open相反的工作。
.hw_params = davinci_pcm_hw_params
这个函数调用了snd_pcm_lib_malloc_pages这个函数,通过snd_pcm_set_runtime_buffer函数设置snd_pcm_runtime结构中的dma buffer的地址和大小等参数。其中size参数是从params_buffer_bytes(hw_params),这里获得,其余的device、type等参数都从snd_pcm_substream->dma_buffer中获得,而snd_pcm_substream->dma_buffer是在.pcm_new实现的。
ops.prepare
正式开始数据传送之前会调用该函数,该函数通常会完成dma操作的必要准备工作。
ops.trigger
数据传送的开始,暂停,恢复和停止时,该函数会被调用。
ops.pointer
该函数返回传送数据的当前位置。

cpu的snd_soc_dai driver驱动的注册

1.定义一个snd_soc_dai_driver结构的实例;
2.在对应的platform_driver中的probe回调中通过API:snd_soc_register_dai或者3.snd_soc_register_dais,注册snd_soc_dai实例;
4.实现snd_soc_dai_driver结构中的probe、suspend等回调;
5.实现snd_soc_dai_driver结构中的snd_soc_dai_ops字段中的回调函数;

static struct platform_driver davinci_mcasp_driver = {
    .probe      = davinci_mcasp_probe,
    .remove     = davinci_mcasp_remove,
    .driver     = {
        .name   = "davinci-mcasp",
        .owner  = THIS_MODULE,
    },
};

module_platform_driver(davinci_mcasp_driver);

进入.probe函数,主要是获取内存,进行内存映射等,以及初始化davinci_pcm_dma_params结构,这个结构很重要,在platform会经常出现,比如在platform的driver中的ops里边的回调函数open中会将此结构保存在snd_pcm_runtime的私有数据中,davinci_pcm_dma_request会根据参数进行dma通道申请。
最后调用snd_soc_register_dai(&pdev->dev, &davinci_mcasp_dai[pdata->op_mode]);注册snd_soc_dai实例,snd_soc_register_dai参考codec中dai注册过程。
进入davinci_mcasp_dai

static struct snd_soc_dai_driver davinci_mcasp_dai[] = {
    {
        .name       = "davinci-mcasp.0",
        .playback   = {
            .channels_min   = 2,
            .channels_max   = 2,
            .rates      = DAVINCI_MCASP_RATES,
            .formats    = DAVINCI_MCASP_PCM_FMTS,
        },
        .capture    = {
            .channels_min   = 2,
            .channels_max   = 2,
            .rates      = DAVINCI_MCASP_RATES,
            .formats    = DAVINCI_MCASP_PCM_FMTS,
        },
        .ops        = &davinci_mcasp_dai_ops,

    },

.name跟Machine中.cpu_dai_name= “davinci-mcasp.0”名字匹配。
进入.ops = &davinci_mcasp_dai_ops函数

static const struct snd_soc_dai_ops davinci_mcasp_dai_ops = {
    .startup    = davinci_mcasp_startup,
    .trigger    = davinci_mcasp_trigger,
    .hw_params  = davinci_mcasp_hw_params,
    .set_fmt    = davinci_mcasp_set_dai_fmt,

};

.startup = davinci_mcasp_startup
数据发送前进行数据准备;
.trigger = davinci_mcasp_trigger
数据传送的开始,暂停,恢复和停止时,该函数会被调用。
.hw_params = davinci_mcasp_hw_params
主要对mcasp硬件资源进行初始化跟配置;
.set_fmt = davinci_mcasp_set_dai_fmt
设置codec时钟的主从模式等。

从上面的探讨,我们可以清楚知道,dai跟platform直接的dma参数主要是依据dai->dev的私有数据进行传递的,在snd_soc_dai_driver的probe函数里,将描述masap的结构体保存在dai->dev的私有数据中,platform中通过snd_soc_dai_get_dma_data来获得,再申请dma_buffer空间,以及参数设置,这一步很重要。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值