第一篇章中说过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进行交互。
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
该函数返回传送数据的当前位置。
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空间,以及参数设置,这一步很重要。