alsa声卡驱动分析总结 (三)
2016-02-26 17:48
405 查看
Codec简介
在移动设备中,Codec的作用可以归结为4种,分别是:
对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号
对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号
对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的
对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等
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
static int __devinit wm8994_probe(struct platform_device *pdev)
{
return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994,
wm8994_dai, ARRAY_SIZE(wm8994_dai));
}
其中,soc_codec_dev_wm8994和wm8994_dai的定义如下(代码中定义了3个dai,这里只列出第一个):
[html] view plaincopy
static struct snd_soc_codec_driver soc_codec_dev_wm8994 = {
.probe = wm8994_codec_probe,
.remove = wm8994_codec_remove,
.suspend = wm8994_suspend,
.resume = wm8994_resume,
.set_bias_level = wm8994_set_bias_level,
.reg_cache_size = WM8994_MAX_REGISTER,
.volatile_register = wm8994_soc_volatile,
};
[html] view plaincopy
static struct snd_soc_dai_driver wm8994_dai[] = {
{
.name = "wm8994-aif1",
.id = 1,
.playback = {
.stream_name = "AIF1 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = WM8994_RATES,
.formats = WM8994_FORMATS,
},
.capture = {
.stream_name = "AIF1 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = WM8994_RATES,
.formats = WM8994_FORMATS,
},
.ops = &wm8994_aif1_dai_ops,
},
......
}
可见,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的设备驱动模型。我们要做的就是:
定义一个snd_soc_platform_driver结构的实例;
在platform_driver的probe回调中利用ASoC的API:snd_soc_register_platform()注册上面定义的实例;
实现snd_soc_platform_driver中的各个回调函数;
以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驱动使用。它的代码已经清晰地表达了它的实现过程:
为snd_soc_platform实例申请内存;
从platform_device中获得它的名字,用于Machine驱动的匹配工作;
初始化snd_soc_platform的字段;
把snd_soc_platform实例连接到全局链表platform_list中;
调用snd_soc_instantiate_cards,触发声卡的machine、platform、codec、dai等的匹配工作;
cpu的snd_soc_daidriver驱动的注册
dai驱动通常对应cpu的一个或几个I2S/PCM接口,与snd_soc_platform一样,dai驱动也是实现为一个platform
driver,实现一个dai驱动大致可以分为以下几个步骤:
定义一个snd_soc_dai_driver结构的实例;
在对应的platform_driver中的probe回调中通过API:snd_soc_register_dai或者snd_soc_register_dais,注册snd_soc_dai实例;
实现snd_soc_dai_driver结构中的probe、suspend等回调;
实现snd_soc_dai_driver结构中的snd_soc_dai_ops字段中的回调函数;
snd_soc_register_dai 这个函数在上一篇介绍codec驱动的博文中已有介绍
具体不再分析,这个驱动也不需要用户做任务修改,所以只要知道它的作用就已经够了。就像电话机一样,我们只要知道电话怎么打就够了,至于它怎么连接我们并不太需要关心。
在移动设备中,Codec的作用可以归结为4种,分别是:
对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号
对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号
对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的
对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等
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
static int __devinit wm8994_probe(struct platform_device *pdev)
{
return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994,
wm8994_dai, ARRAY_SIZE(wm8994_dai));
}
其中,soc_codec_dev_wm8994和wm8994_dai的定义如下(代码中定义了3个dai,这里只列出第一个):
[html] view plaincopy
static struct snd_soc_codec_driver soc_codec_dev_wm8994 = {
.probe = wm8994_codec_probe,
.remove = wm8994_codec_remove,
.suspend = wm8994_suspend,
.resume = wm8994_resume,
.set_bias_level = wm8994_set_bias_level,
.reg_cache_size = WM8994_MAX_REGISTER,
.volatile_register = wm8994_soc_volatile,
};
[html] view plaincopy
static struct snd_soc_dai_driver wm8994_dai[] = {
{
.name = "wm8994-aif1",
.id = 1,
.playback = {
.stream_name = "AIF1 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = WM8994_RATES,
.formats = WM8994_FORMATS,
},
.capture = {
.stream_name = "AIF1 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = WM8994_RATES,
.formats = WM8994_FORMATS,
},
.ops = &wm8994_aif1_dai_ops,
},
......
}
可见,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的设备驱动模型。我们要做的就是:
定义一个snd_soc_platform_driver结构的实例;
在platform_driver的probe回调中利用ASoC的API:snd_soc_register_platform()注册上面定义的实例;
实现snd_soc_platform_driver中的各个回调函数;
以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驱动使用。它的代码已经清晰地表达了它的实现过程:
为snd_soc_platform实例申请内存;
从platform_device中获得它的名字,用于Machine驱动的匹配工作;
初始化snd_soc_platform的字段;
把snd_soc_platform实例连接到全局链表platform_list中;
调用snd_soc_instantiate_cards,触发声卡的machine、platform、codec、dai等的匹配工作;
cpu的snd_soc_daidriver驱动的注册
dai驱动通常对应cpu的一个或几个I2S/PCM接口,与snd_soc_platform一样,dai驱动也是实现为一个platform
driver,实现一个dai驱动大致可以分为以下几个步骤:
定义一个snd_soc_dai_driver结构的实例;
在对应的platform_driver中的probe回调中通过API:snd_soc_register_dai或者snd_soc_register_dais,注册snd_soc_dai实例;
实现snd_soc_dai_driver结构中的probe、suspend等回调;
实现snd_soc_dai_driver结构中的snd_soc_dai_ops字段中的回调函数;
snd_soc_register_dai 这个函数在上一篇介绍codec驱动的博文中已有介绍
具体不再分析,这个驱动也不需要用户做任务修改,所以只要知道它的作用就已经够了。就像电话机一样,我们只要知道电话怎么打就够了,至于它怎么连接我们并不太需要关心。
相关文章推荐
- 音乐播放器
- lamp lnmp lnamp区别
- [Linux]./configure 编译调试信息
- Linux中Apache安装、配置、加为服务
- 如何学好图像处理——从小白到大神?
- xml增强学习笔记
- 使用jquey的css()方法改变样式,
- How to make workflow chart using several tools in Linux?
- SQL语句详细汇总
- 年度十佳 DevOps 博客文章(后篇)
- ioS开发知识(三十一)
- 做飞行器的规划及想法
- Yii2 framework学习笔记(八) -- 整合blueimp的jquery-file-upload插件
- Git学习笔记总结和注意事项
- Android Process 详解
- chrome dev 4000 tools
- springmvc讲解
- Android插件化开发 第五篇 [360 Droid Plugin]
- Sublime Text3中配置Java环境
- iOS闭包循环引用精讲