alsa声卡驱动二:ASoC框架中的Codec

Codec简介

在移动设备中,Codec的作用可以归结为4种,分别是:
对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号
对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号
对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的
对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等;
(http://blog.csdn.net/droidphone/article/details/7283833)

ASoC对Codec数据的抽象

描述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编写的理解跟总结。

你可能感兴趣的:(linux驱动,音频子系统)