对技术执着
分类: LINUX
2015-03-14 15:27:07
原文地址:alsa声卡驱动分析小结 作者:nacichan
分析只列出部分重要代码,具体请参考linux3.0内核代码。
Alsa架构整体来说十分复杂,但对于驱动移植来说我们仅仅只需要关心ASOC就足够了。
在学习asoc之前我们先了解一些专业术语:
ASoC currently supportsthe three main Digital Audio Interfaces (DAI) found on
SoC controllers and portable audio CODECs today, namelyAC97, I2S and PCM.
ASoC现在支持如今的SoC控制器和便携音频解码器上的三个主要数字音频接口,即AC97,I2S,PCM(与pcm音频格式注意区分,前者是一种音频接口,后者是一种输入声卡的音频格式)。
AC97
AC97
====
AC97 is a five wire interface commonly found on many PC soundcards. It is
now also popular in many portable devices. This DAI has a reset line and time
multiplexes its data on its SDATA_OUT (playback) and SDATA_IN (capture) lines.
The bit clock (BCLK) is always driven by the CODEC (usually 12.288MHz) and the
frame (FRAME) (usually 48kHz) is always driven by the controller. Each AC97
frame is 21uS long and is divided into 13 time slots.
AC97是一种个人电脑声卡上常见的五线接口。现在在很多便携设备中也很流行。这个数字音频接口有一个复位线,分时在SDATA_OUT(回放)和SDATA_IN(捕获)线上传送数据。位时钟常由解码器驱动(通常是12.288MHz).帧时钟(通常48kHz)总是由控制器驱动。每个AC97帧21uS,并分为13个时间槽。
I2S
I2S
===
I2S is a common 4 wire DAI used in HiFi, STBand portable devices. The Tx and
Rx lines are used for audio transmission,whilst the bit clock (BCLK) and
left/right clock (LRC) synchronise the link.I2S is flexible in that either the
controller or CODEC can drive (master) theBCLK and LRC clock lines. Bit clock
usually varies depending on the sample rateand the master system clock
(SYSCLK). LRCLK is the same as the samplerate. A few devices support separate
ADC and DAC LRCLKs, this allows forsimultaneous capture and playback at
different sample rates.
I2S是一个4线数字音频接口,常用于HiFi,STB便携设备。Tx 和Rx信号线用于音频传输。而位时钟和左右时钟(LRC)用于同步链接。I2S具有灵活性,因为控制器和解码器都可以控制位时钟和左右时钟。位时钟因采样率和主系统时钟而有不同。LRCLK与采样率相同。少数设备支持独立的ADC和DAC的LRCLK。这使在不同采样率情况下同步捕获和回放成为可能。
I2S has several different operating modes:-
I2S有几个不同的操作模式:
o I2S - MSB is transmitted on the falling edgeof the first BCLK after LRC
transition.
I2S模式-MSB在LRC后的第一个位时钟的下降沿传送。
o Left Justified - MSB is transmitted ontransition of LRC.
左对齐模式:MSB在LRC传送时传送。
o Right Justified - MSB is transmitted samplesize BCLKs before LRC
transition.
右对齐模式:MSB在(此句不懂)
PCM
PCM
===
PCM is another 4 wire interface, very similarto I2S, which can support a more
flexible protocol. It has bit clock (BCLK) andsync (SYNC) lines that are used
to synchronise the link whilst the Tx and Rxlines are used to transmit and
receive the audio data. Bit clock usuallyvaries depending on sample rate
whilst sync runs at the sample rate. PCM alsosupports Time Division
Multiplexing (TDM) in that several devices canuse the bus simultaneously (this
is sometimes referred to as network mode).
PCM也是一种4线制接口。与I2S非常相像,但支持一个更灵活的协议。它有位时钟(BCLK)和同步时钟(SYNC)用来在Tx和Rx在传送和接收音频数据是同步连接。位时钟通常因采样率的不同而不同,然而同步时钟(SYNC)与采样频率相同。PCM同样支持时分复用,可以几个设备同时使用总线(这有时被称为network模式)。
Common PCM operating modes:-
常用的PCM操作模式:
o Mode A - MSB is transmitted on falling edgeof first BCLK after FRAME/SYNC.
模式A-MSB在FRAME/SYNC后第一个BCLK的下降沿传送。
o Mode B - MSB is transmitted on rising edgeof FRAME/SYNC.
模式B-MSB在FRAME/SYNC的上升沿传送。
Codec(解码器)
各解码器驱动必须提供如下特性:
1) Codec DAI and PCM configuration
2) Codec control IO - using I2C, 3 Wire(SPI)or both APIs
3) Mixers and audio controls
4) Codec audio operations
1)解码器数字音频接口和PCM配置。
2)解码器控制IO-使用I2C,3总线(SPI)或两个都有。
3)混音器和音频控制。
4)解码器音频操作。
Optionally, codec drivers can also provide:-
解码器驱动可以选择性提供:
5) DAPM description.
6) DAPM event handler.
7) DAC Digital mute control.
5)动态音频电源管理描述。
6)动态音频电源管理事件控制。
7)数模转换数字消音控制。
SoC DAI Drivers
板级DAI驱动
===============
Each SoC DAI driver must provide the followingfeatures:-
每个SoC DAI驱动都必须提供如下性能:
1) Digital audio interface (DAI) description
1)数字音频接口描述
2) Digital audio interface configuration
2)数字音频接口配置
3) PCM's description
3)PCM描述
4) SYSCLK configuration
4)系统时钟配置
5) Suspend and resume (optional)
5)挂起和恢复(可选的)
以上由君子翻译,本人实在没办法比他描述的更好了,所以把重要的部分提取出来直接copy。在这里对君子表示由衷感谢,赋上君子注。
君子注:
您现在所阅读的,是君子阅读Linux音频SoC驱动时,写下的文档译文。
君子写些译文,一方面是作为自己的笔记,帮助记忆,另一方面也希望能对他人有所帮助。
如果您能于君子的译文中有所收获,则吾心甚慰
现在我们开始分析ASOC:
ASoC被分为Machine、Platform和Codec三大部分。其中的Machine驱动负责Platform和Codec之间的耦合和设备或板子特定的代码。
看起来挺复杂,其实需要我们做的事情并不多,大部分内核已经完成。下面我们分析哪些是我们需要自己做的:
codec驱动:负责音频解码。这部分代码完全无平台无关,设备原厂提供,我们只需要把它加进内核编译就好了。
platform驱动:与处理器芯片相关,这部分代码在该芯片商用之前方案产商提供的demo板已完全确定了,也就是说我们只需要使用就可以了。
machine驱动:好了,到了最关键的地方了,machine驱动是耦合platform和codec驱动,同时与上层交互的代码。由于上层是标准的alsa架构,所以下层接口肯定要做了统一,所以我很负责的告诉你,这部分由machine本身的platform驱动和platform设备组成(请跟asoc的platform驱动区别),platform驱动内核帮我们完成了,所以你无须过多的关心你的驱动怎么跟上层alsa怎么衍接的问题,我们只需要注册一个machine的platform设备以及完成platform和codec耦合就ok
asoc的关系图如下:(以下适应于linux3.0。linux2.6会有所不同)
上图把asoc架构显示的淋漓尽致,如果你分析了asoc你就会发现上图描述的结构以及函数真的一个都跑不了。
Machie:
Machine platform device:(~/sound/soc/samsung/smdk_wm8994.c)
通过snd_soc_card结构,又引出了Machine驱动的另外两个个数据结构:
snd_soc_dai_link看名字就知道,很明显它是起耦合链接作用的。它指定了Platform、Codec、codec_dai、cpu_dai的名字,稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai。
snd_soc_ops连接Platform和Codec的dai_link对应的ops操作函数,本例就是smdk_ops,它只实现了hw_params函数:smdk_hw_params。
到此为止,最主要的部分machine的平台设备注册我们完成了。
下面我们关注machine平台驱动部分(这部分内核不需要我们实现,但我们需要知道它是怎么工作的)
ASoC的platform_driver在以下文件中定义:sound/soc/soc-core.c。
还是先从模块的入口看起:
[cpp] view plaincopy
soc_driver的定义如下:
[cpp] view plaincopy
初始化入口soc_probe()
soc_probe函数本身很简单,它先从platform_device参数中取出snd_soc_card,然后调用snd_soc_register_card,通过snd_soc_register_card,为snd_soc_pcm_runtime数组申请内存,每一个dai_link对应snd_soc_pcm_runtime数组的一个单元,然后把snd_soc_card中的dai_link配置复制到相应的snd_soc_pcm_runtime中,最后,大部分的工作都在snd_soc_instantiate_card中实现,下面就看看snd_soc_instantiate_card做了些什么:
该函数首先利用card->instantiated来判断该卡是否已经实例化,如果已经实例化则直接返回,否则遍历每一对dai_link,进行codec、platform、dai的绑定工作,下只是代码的部分选节,详细的代码请直接参考完整的代码树。
static int soc_probe(struct platform_device *pdev)
{
struct snd_soc_card *card =platform_get_drvdata(pdev);//别忘记了machine的platform_set_drvdata //取出snd_soc_card
.............
ret =snd_soc_register_card(card);//注册
............
}
下面我们看snd_soc_register_card()函数:
int snd_soc_register_card(struct snd_soc_card *card)
{
。。。。。。。。
card->rtd =kzalloc(sizeof(struct snd_soc_pcm_runtime) *
(card->num_links + card->num_aux_devs),
GFP_KERNEL);//为snd_soc_pcm_runtime数组申请内存,每一个dai_link对应一个snd_soc_pcm_runtime数组单元
。。。。。。。。
for (i = 0; i
card->rtd[i].dai_link= &card->dai_link[i];//把snd_soc_card中的dai_link复制到相应的snd_soc_pcm_runtime
。。。。。。。。
snd_soc_instantiate_cards();//将调用snd_soc_instantiate_card()//最为重要
}
下面我们分析snd_soc_instantiate_card()函数:
static void snd_soc_instantiate_card(struct snd_soc_card*card)
{
。。。。。。。。
if (card->instantiated) { //判断该卡是否已经实例化,如果是就返回
mutex_unlock(&card->mutex);
return;
}
/* bind DAIs */
for (i = 0; i
soc_bind_dai_link(card,i);
/*******************************************************************************************************************************************************************
ASoC定义了三个全局的链表头变量:codec_list、dai_list、platform_list,系统中所有的Codec、DAI、Platform都在注册时连接到这三个全局链表上。soc_bind_dai_link函数逐个扫描这三个链表,根据card->dai_link[]中的名称进行匹配,匹配后把相应的codec,dai和platform实例赋值到card->rtd[]中(snd_soc_pcm_runtime)。经过这个过程后,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驱动的信息。
*****************************************************************************************************************************************************************/
/* bind completed ? */
if (card->num_rtd !=card->num_links) {
mutex_unlock(&card->mutex);
return;
}
。。。。。。。。。
/* card bind complete soregister a sound card */
ret =snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner,0, &card->snd_card);
。。。。。。。。。
card->snd_card->dev =card->dev;
card->dapm.bias_level =SND_SOC_BIAS_OFF;
card->dapm.dev =card->dev;
card->dapm.card = card;
list_add(&card->dapm.list,&card->dapm_list);//初始化codec缓存,创建声卡实例
。。。。。。。。。。。。。
//下面是最重要的probe匹配工作
if (card->probe) {
ret =card->probe(card);
if (ret < 0)
gotocard_probe_error;
}
。。。。。。。。。
ret =soc_probe_dai_link(card, i, order);//主要的耦合链接工作在此函数完成
。。。。。。。。。
ret =soc_probe_aux_dev(card, i);
。。。。。。。。。
if (card->late_probe) {//最后的声卡初始化工作,
ret =card->late_probe(card);
}
。。。。。。。。。
ret =snd_card_register(card->snd_card);//然后调用标准的alsa驱动的声卡函数进行声卡注册
。。。。。。。。。
}
到这里声卡已经注册好了,声卡可以正常工作了,我们再分析最后一个函数,也就是它们是怎么匹配的 soc_probe_dai_link():
static int soc_probe_dai_link(struct snd_soc_card *card,int num, int order)
{
。。。。。。。。。。
/* 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);
returnret;
}
}
cpu_dai->probed = 1;
/* mark cpu_dai asprobed 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);
returnret;
}
}
/* mark codec_dai asprobed and add to card dai list */
codec_dai->probed =1;
list_add(&codec_dai->card_list,&card->dai_dev_list);
}
/* complete DAI probe duringlast probe */
if (order !=SND_SOC_COMP_ORDER_LAST)
return 0;
。。。。。。。。。。
/* create the pcm */
ret = soc_new_pcm(rtd, num);//如果上面都匹配成功将创建标准的alsa的pcm逻辑设备
。。。。。。。。。。
}
好了,到此为止我们最主要的部分machine部分分析完成了。
接着是codec驱动部分:
Codec简介
在移动设备中,Codec的作用可以归结为4种,分别是:
ASoC对Codec的这些功能都定义好了一些列相应的接口,以方便地对Codec进行控制。ASoC对Codec驱动的一个基本要求是:驱动程序的代码必须要做到平台无关性,以方便同一个Codec的代码不经修改即可用在不同的平台上。以下的讨论基于wolfson的Codec芯片WM8994,kernel的版本3.3.x。
描述Codec的最主要的几个数据结构分别是:snd_soc_codec,snd_soc_codec_driver,snd_soc_dai,snd_soc_dai_driver,其中的snd_soc_dai和snd_soc_dai_driver在ASoC的Platform驱动中也会使用到,Platform和Codec的DAI通过snd_soc_dai_link结构,在Machine驱动中进行绑定连接。
Codec的注册
因为Codec驱动的代码要做到平台无关性,要使得Machine驱动能够使用该Codec,Codec驱动的首要任务就是确定snd_soc_codec和snd_soc_dai的实例,并把它们注册到系统中,注册后的codec和dai才能为Machine驱动所用。以WM8994为例,对应的代码位置:/sound/soc/codecs/wm8994.c,模块的入口函数注册了一个platform driver:
[html] view plaincopy
1. static struct platform_driver wm8994_codec_driver = {
2. .driver = {
3. .name = "wm8994-codec", //注意machine device里面和这里保持一致
4. .owner = THIS_MODULE,
5. },
6. .probe = wm8994_probe,
7. .remove = __devexit_p(wm8994_remove),
8. };
9.
10. module_platform_driver(wm8994_codec_driver);
有platform driver,必定会有相应的platform device,platform device其实在我们之前讲过的machine device注册时已经引入了。
[html] view plaincopy
其中,soc_codec_dev_wm8994和wm8994_dai的定义如下(代码中定义了3个dai,这里只列出第一个):
[html] view plaincopy
[html] view plaincopy
可见,Codec驱动的第一个步骤就是定义snd_soc_codec_driver和snd_soc_dai_driver的实例,然后调用snd_soc_register_codec函数对Codec进行注册。
snd_soc_register_codec()函数是machine driver提供的,只要注册成功后codec提供的操作函数就能正常提供给machinedriver使用了。
int snd_soc_register_codec(struct device *dev,
const struct snd_soc_codec_driver *codec_drv,
struct snd_soc_dai_driver *dai_drv,
intnum_dai)
{
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
。。。。。。。。。。。
/* create CODEC component name */
codec->name = fmt_single_name(dev, &codec->id);/*Machine驱动定义的snd_soc_dai_link中会指定每个link的codec和dai的名字,进行匹配绑定时就是通过和这里的名字比较,从而找到该Codec的*/
// 然后初始化它的各个字段,多数字段的值来自上面定义的snd_soc_codec_driver的实例soc_codec_dev_wm8994:
codec->write = codec_drv->write;
codec->read = codec_drv->read;
codec->volatile_register = codec_drv->volatile_register;
codec->readable_register = codec_drv->readable_register;
codec->writable_register = codec_drv->writable_register;
codec->dapm.bias_level = SND_SOC_BIAS_OFF;
codec->dapm.dev = dev;
codec->dapm.codec = codec;
codec->dapm.seq_notifier = codec_drv->seq_notifier;
codec->dev = dev;
codec->driver = codec_drv;
codec->num_dai = num_dai;
mutex_init(&codec->mutex);
/* allocate CODEC register cache */
if (codec_drv->reg_cache_size && codec_drv->reg_word_size) {
reg_size = codec_drv->reg_cache_size *codec_drv->reg_word_size;
codec->reg_size = reg_size;
/* it is necessary to make a copy of the default registercache
* because in the case of using a compression type thatrequires
* the default register cache to be marked as__devinitconst the
* kernel might have freed the array by the time weinitialize
* the cache.
*/
if (codec_drv->reg_cache_default) {
codec->reg_def_copy =kmemdup(codec_drv->reg_cache_default,
reg_size, GFP_KERNEL);
if (!codec->reg_def_copy) {
ret = -ENOMEM;
goto fail;
}
}
}
。。。。。。。。。。。
/* register any DAIs */
if (num_dai) {
ret = snd_soc_register_dais(dev, dai_drv, num_dai);//通过snd_soc_register_dais函数对本Codec的dai进行注册
if (ret < 0)
goto fail;
}
mutex_lock(&client_mutex);
list_add(&codec->list, &codec_list);/*最后,它把codec实例链接到全局链表codec_list中,并且调用snd_soc_instantiate_cards是函数触发Machine驱动进行一次匹配绑定操作*/
snd_soc_instantiate_cards();
mutex_unlock(&client_mutex);
。。。。。。。。。
}
好了,在这里我们的codec驱动也分析完了,其实这部分都是与平台无关代码,一般也不需要改动,这部分我们从设备原厂拿到代码后丢上去就可以了,只是我们在写machine device的时候要注意和这里的名字匹配。
接下来是asoc的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的注册
通常,ASoC把snd_soc_platform_driver注册为一个系统的platform_driver,不要被这两个想像的术语所迷惑,前者只是针对ASoC子系统的,后者是来自Linux的设备驱动模型。我们要做的就是:
以kernel3.3中的/sound/soc/samsung/dma.c为例:
[cpp] view plaincopy
1. static struct snd_soc_platform_driver samsung_asoc_platform = {
2. .ops = &dma_ops,
3. .pcm_new = dma_new,
4. .pcm_free = dma_free_dma_buffers,
5. };
6.
7. static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
8. {
9. return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
10. }
11.
12. static int __devexit samsung_asoc_platform_remove(struct platform_device *pdev)
13. {
14. snd_soc_unregister_platform(&pdev->dev);
15. return 0;
16. }
17.
18. static struct platform_driver asoc_dma_driver = {
19. .driver = {
20. .name = "samsung-audio",
21. .owner = THIS_MODULE,
22. },
23.
24. .probe = samsung_asoc_platform_probe,
25. .remove = __devexit_p(samsung_asoc_platform_remove),
26. };
27.
28. module_platform_driver(asoc_dma_driver);
snd_soc_register_platform() 该函数用于注册一个snd_soc_platform,只有注册以后,它才可以被Machine驱动使用。它的代码已经清晰地表达了它的实现过程:
cpu的snd_soc_daidriver驱动的注册
dai驱动通常对应cpu的一个或几个I2S/PCM接口,与snd_soc_platform一样,dai驱动也是实现为一个platform driver,实现一个dai驱动大致可以分为以下几个步骤:
snd_soc_register_dai 这个函数在上一篇介绍codec驱动的博文中已有介绍
具体不再分析,这个驱动也不需要用户做任务修改,所以只要知道它的作用就已经够了。就像电话机一样,我们只要知道电话怎么打就够了,至于它怎么连接我们并不太需要关心。