1. 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。
2. ASoC中对Codec的数据抽象
描述Codec的{BANNED}最佳主要的几个数据结构分别是: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驱动中进行绑定连接。下面我们先看看这几个结构的定义,这里我只贴出我要关注的字段,详细的定义请参照:/include/sound/soc.h。
snd_soc_codec:
-
-
-
-
-
const struct snd_soc_codec_driver *driver;
-
struct snd_soc_card *card;
-
-
int (*volatile_register)(...);
-
int (*readable_register)(...);
-
int (*writable_register)(...);
-
-
-
-
-
-
enum snd_soc_control_type control_type;
-
unsigned int (*read)(struct snd_soc_codec *, unsigned int);
-
int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
-
-
struct snd_soc_dapm_context dapm;
-
snd_soc_codec_driver:
-
-
struct snd_soc_codec_driver {
-
-
int (*probe)(struct snd_soc_codec *);
-
int (*remove)(struct snd_soc_codec *);
-
int (*suspend)(struct snd_soc_codec *);
-
int (*resume)(struct snd_soc_codec *);
-
-
-
const struct snd_kcontrol_new *controls;
-
const struct snd_soc_dapm_widget *dapm_widgets;
-
const struct snd_soc_dapm_route *dapm_routes;
-
-
-
-
-
-
-
unsigned int (*read)(...);
-
-
int (*volatile_register)(...);
-
int (*readable_register)(...);
-
int (*writable_register)(...);
-
-
-
int (*set_bias_level)(...);
-
-
snd_soc_dai:
-
-
-
-
-
-
-
-
-
-
-
struct snd_soc_dai_driver *driver;
-
-
-
unsigned int capture_active:1;
-
unsigned int playback_active:1;
-
-
-
-
-
-
-
-
struct snd_soc_platform *platform;
-
struct snd_soc_codec *codec;
-
-
struct snd_soc_card *card;
-
snd_soc_dai_driver:
-
-
-
-
-
-
-
-
-
-
-
struct snd_soc_dai_driver {
-
-
-
-
-
int (*probe)(struct snd_soc_dai *dai);
-
int (*remove)(struct snd_soc_dai *dai);
-
int (*suspend)(struct snd_soc_dai *dai);
-
int (*resume)(struct snd_soc_dai *dai);
-
-
-
const struct snd_soc_dai_ops *ops;
-
-
-
struct snd_soc_pcm_stream capture;
-
struct snd_soc_pcm_stream playback;
-
snd_soc_dai_ops用于实现该dai的控制盒参数配置:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
int (*set_tdm_slot)(...);
-
int (*set_channel_map)(...);
-
int (*set_tristate)(...);
-
-
-
-
-
int (*digital_mute)(...);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
snd_pcm_sframes_t (*delay)(...);
-
3. Codec的注册
因为Codec驱动的代码要做到平台无关性,要使得Machine驱动能够使用该Codec,Codec驱动的首要任务就是确定snd_soc_codec和snd_soc_dai的实例,并把它们注册到系统中,注册后的codec和dai才能为Machine驱动所用。以WM8994为例,对应的代码位置:/sound/soc/codecs/wm8994.c,模块的入口函数注册了一个platform driver:
-
static struct platform_driver wm8994_codec_driver = {
-
-
-
-
-
-
.remove = __devexit_p(wm8994_remove),
-
module_platform_driver(wm8994_codec_driver);
有platform driver,必定会有相应的platform device,这个platform device的来源后面再说,显然,platform driver注册后,probe回调将会被调用,这里是wm8994_probe函数:
-
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,这里只列出{BANNED}中国第一个):
-
static struct snd_soc_codec_driver soc_codec_dev_wm8994 = {
-
.probe = wm8994_codec_probe,
-
.remove = wm8994_codec_remove,
-
.suspend = wm8994_suspend,
-
-
.set_bias_level = wm8994_set_bias_level,
-
.reg_cache_size = WM8994_MAX_REGISTER,
-
.volatile_register = wm8994_soc_volatile,
-
-
static struct snd_soc_dai_driver wm8994_dai[] = {
-
-
-
-
-
.stream_name = "AIF1 Playback",
-
-
-
-
.formats = WM8994_FORMATS,
-
-
-
.stream_name = "AIF1 Capture",
-
-
-
-
.formats = WM8994_FORMATS,
-
-
.ops = &wm8994_aif1_dai_ops,
-
-
-
可见,Codec驱动的{BANNED}中国第一个步骤就是定义snd_soc_codec_driver和snd_soc_dai_driver的实例,然后调用snd_soc_register_codec函数对Codec进行注册。进入snd_soc_register_codec函数看看:
首先,它申请了一个snd_soc_codec结构的实例:
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
确定codec的名字,这个名字很重要,Machine驱动定义的snd_soc_dai_link中会指定每个link的codec和dai的名字,进行匹配绑定时就是通过和这里的名字比较,从而找到该Codec的!
-
-
codec->name = fmt_single_name(dev, &codec->id);
然后初始化它的各个字段,多数字段的值来自上面定义的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.codec = codec;
-
codec->dapm.seq_notifier = codec_drv->seq_notifier;
-
codec->dapm.stream_event = codec_drv->stream_event;
-
-
codec->driver = codec_drv;
-
codec->num_dai = num_dai;
在做了一些寄存器缓存的初始化和配置工作后,通过snd_soc_register_dais函数对本Codec的dai进行注册:
-
-
-
ret = snd_soc_register_dais(dev, dai_drv, num_dai);
-
-
-
{BANNED}最佳后,它把codec实例链接到全局链表codec_list中,并且调用snd_soc_instantiate_cards是函数触发Machine驱动进行一次匹配绑定操作:
-
list_add(&codec->list, &codec_list);
-
snd_soc_instantiate_cards();
上面的snd_soc_register_dais函数其实也是和snd_soc_register_codec类似,显示为每个snd_soc_dai实例分配内存,确定dai的名字,用snd_soc_dai_driver实例的字段对它进行必要初始化,{BANNED}最佳后把该dai链接到全局链表dai_list中,和Codec一样,{BANNED}最佳后也会调用snd_soc_instantiate_cards函数触发一次匹配绑定的操作。
图3.1 dai的注册
4. mfd设备
前面已经提到,codec驱动把自己注册为一个platform driver,那对应的platform device在哪里定义?答案是在以下代码文件中:/drivers/mfd/wm8994-core.c。