在移动设备中,Codec的作用可以归结为4种,分别是:
对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号
对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号
对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的
对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等;
(http://blog.csdn.net/droidphone/article/details/7283833)
描述Codec最主要的数据结构有四个:snd_soc_codec,snd_soc_codec_driver,snd_soc_dai,snd_soc_dai_driver,接下来的内容最主要也是找得这几个结构体的实现跟需要做的工作。
一下讨论基于讨论基于Codec芯片tlv320aic3x,kernel的版本3.3版本。
到文件/sound/soc/codecs/tlv320aic3x.c中
static struct i2c_driver aic3x_i2c_driver = {
.driver = {
.name = "tlv320aic3x-codec",
.owner = THIS_MODULE,
},
.probe = aic3x_i2c_probe,
.remove = aic3x_i2c_remove,
.id_table = aic3x_i2c_id,
};
static int __init aic3x_modinit(void)
{
int ret = 0;
ret = i2c_add_driver(&aic3x_i2c_driver);
if (ret != 0) {
printk(KERN_ERR "Failed to register TLV320AIC3x I2C driver: %d\n",
ret);
}
return ret;
}
module_init(aic3x_modinit);
static void __exit aic3x_exit(void)
{
i2c_del_driver(&aic3x_i2c_driver);
}
module_exit(aic3x_exit);
这里实现了一个i2c驱动,这部分的原理可以查看i2c子系统学习总结,将tlv320aic挂载在i2c总线上。进入.probe = aic3x_i2c_probe函数看一下。
static int aic3x_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct aic3x_pdata *pdata = i2c->dev.platform_data;
struct aic3x_priv *aic3x;
int ret;
aic3x = devm_kzalloc(&i2c->dev, sizeof(struct aic3x_priv), GFP_KERNEL);
if (aic3x == NULL) {
dev_err(&i2c->dev, "failed to create private data\n");
return -ENOMEM;
}
aic3x->control_type = SND_SOC_I2C;
i2c_set_clientdata(i2c, aic3x);
...
ret = snd_soc_register_codec(&i2c->dev,
&soc_codec_dev_aic3x, &aic3x_dai, 1);
return ret;
}
ret = snd_soc_register_codec(&i2c->dev,&soc_codec_dev_aic3x, &aic3x_dai, 1);
1、aic3x_dai是snd_soc_dai_driver
static struct snd_soc_dai_driver aic3x_dai = {
.name = "tlv320aic3x-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = AIC3X_RATES,
.formats = AIC3X_FORMATS,},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = AIC3X_RATES,
.formats = AIC3X_FORMATS,},
.ops = &aic3x_dai_ops,
.symmetric_rates = 1,
};
其中.name跟Machine中snd_soc_dai_link da850_evm_dai结构里边的.codec_dai_name = “tlv320aic3x-hifi”匹配。
2、soc_codec_dev_aic3x是snd_soc_codec_driver
static struct snd_soc_codec_driver soc_codec_dev_aic3x = {
.set_bias_level = aic3x_set_bias_level,
.reg_cache_size = ARRAY_SIZE(aic3x_reg),
.reg_word_size = sizeof(u8),
.reg_cache_default = aic3x_reg,
.probe = aic3x_probe,
.remove = aic3x_remove,
.suspend = aic3x_suspend,
.resume = aic3x_resume,
};
进入codec的注册函数 snd_soc_register_codec
int snd_soc_register_codec(struct device *dev,
const struct snd_soc_codec_driver *codec_drv,
struct snd_soc_dai_driver *dai_drv,
int num_dai)
{
size_t reg_size;
struct snd_soc_codec *codec;
int ret, i;
dev_dbg(dev, "codec register %s\n", dev_name(dev));
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
if (codec == NULL)
return -ENOMEM;
/* create CODEC component name */
codec->name = fmt_single_name(dev, &codec->id);
if (codec->name == NULL) {
kfree(codec);
return -ENOMEM;
}
if (codec_drv->compress_type)
codec->compress_type = codec_drv->compress_type;
else
codec->compress_type = SND_SOC_FLAT_COMPRESSION;
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->dapm.stream_event = codec_drv->stream_event;
codec->dev = dev;
codec->driver = codec_drv;
codec->num_dai = num_dai;
mutex_init(&codec->mutex);
...
if (num_dai) {
ret = snd_soc_register_dais(dev, dai_drv, num_dai);
if (ret < 0)
goto fail;
}
mutex_lock(&client_mutex);
list_add(&codec->list, &codec_list);
snd_soc_instantiate_cards();
mutex_unlock(&client_mutex);
pr_debug("Registered codec '%s'\n", codec->name);
return 0;
fail:
kfree(codec->reg_def_copy);
codec->reg_def_copy = NULL;
kfree(codec->name);
kfree(codec);
return ret;
}
函数里边主要是snd_soc_codec 的实现。
1.codec->name = fmt_single_name(dev, &codec->id);这个名字的赋值是跟dev->driver->name(“tlv320aic3x-codec”)的名字有关的,也是可以跟我们Machine的struct snd_soc_dai_link da850_evm_dai成员.codec_name = “tlv320aic3x-codec.1-0018”匹配的,到此我们可以知道snd_soc_codec跟snd_soc_dai 跟Machine的名字匹配了。
2.将codec添加到全局的codec_list列表中,调用snd_soc_instantiate_cards();触发一次绑定,这个在驱动一我们已经了解过了,就不再讨论。
list_add(&codec->list, &codec_list);
snd_soc_instantiate_cards();
3.对codec_dai即snd_soc_dai 进行注册
ret = snd_soc_register_dais(dev, dai_drv, num_dai);
进入函数
int snd_soc_register_dais(struct device *dev,
struct snd_soc_dai_driver *dai_drv, size_t count)
{
struct snd_soc_dai *dai;
int i, ret = 0;
dev_dbg(dev, "dai register %s #%Zu\n", dev_name(dev), count);
for (i = 0; i < count; i++) {
dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
if (dai == NULL) {
ret = -ENOMEM;
goto err;
}
/* create DAI component name */
dai->name = fmt_multiple_name(dev, &dai_drv[i]);
if (dai->name == NULL) {
kfree(dai);
ret = -EINVAL;
goto err;
}
dai->dev = dev;
dai->driver = &dai_drv[i];
if (dai->driver->id)
dai->id = dai->driver->id;
else
dai->id = i;
if (!dai->driver->ops)
dai->driver->ops = &null_dai_ops;
mutex_lock(&client_mutex);
list_add(&dai->list, &dai_list);
mutex_unlock(&client_mutex);
pr_debug("Registered DAI '%s'\n", dai->name);
}
mutex_lock(&client_mutex);
snd_soc_instantiate_cards();
mutex_unlock(&client_mutex);
return 0;
err:
for (i--; i >= 0; i--)
snd_soc_unregister_dai(dev);
return ret;
}
同样的主要工作是实现snd_soc_dai 成员变量,并使用list_add(&dai->list, &dai_list);将snd_soc_dai 添加到全局链表dai_list中,通过函数snd_soc_instantiate_cards()触发一次绑定。
接下来看一下下面的两个数据结构
//这部分主要跟tlv320aic3x的初始化以及一下配置有关,都是通过i2c进行操作的。
static const struct snd_soc_dai_ops aic3x_dai_ops = {
.hw_params = aic3x_hw_params,//跟codec的一些参数配置相关,数字字长,时钟频率等
.digital_mute = aic3x_mute,//由名字知道数字静音
.set_sysclk = aic3x_set_dai_sysclk,//设置dai系统时钟
.set_fmt = aic3x_set_dai_fmt,//跟数据时钟和同步的主从模式有关
};
static struct snd_soc_dai_driver aic3x_dai = {
.name = "tlv320aic3x-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = AIC3X_RATES,
.formats = AIC3X_FORMATS,},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = AIC3X_RATES,
.formats = AIC3X_FORMATS,},
.ops = &aic3x_dai_ops,
.symmetric_rates = 1,
};
static struct snd_soc_codec_driver soc_codec_dev_aic3x = {
.set_bias_level = aic3x_set_bias_level,
.reg_cache_size = ARRAY_SIZE(aic3x_reg),//元素个数
.reg_word_size = sizeof(u8),//word的长度
.reg_cache_default = aic3x_reg,
.probe = aic3x_probe,//跟tlv320aic3x初始化有关
.remove = aic3x_remove,
.suspend = aic3x_suspend,
.resume = aic3x_resume,
};
这部分数据结构也是音频驱动主要完成的工作,不作纤细了解,只是一些注释,根据个人需求做了解。
以上是个人对codec编写的理解跟总结。