嵌入式Linux驱动笔记(二十)------音频子系统(ASOC框架)之Codec

你好!这里是风筝的博客,

欢迎和我一起交流。

上一节说了Machine部分,嵌入式Linux驱动笔记(十九)——音频子系统(ASOC框架)之Machine
现在可以看看Codec部分:
对于一块嵌入式设备的主板来说,一般会集成一颗音频CODEC芯片。 ASoC架构下的CODEC设备功能和物理CODEC对应, 其在machine的控制下和platform设备连接,负责音频的实际处理,如声音播放(D/A)、声音采集(A/D) 和各种音频control控件的设置。
我们继续是uad1341为例,Linux4.8.17:
uda134x.c

static int uda134x_codec_probe(struct platform_device *pdev)
{
    struct uda134x_platform_data *pd = pdev->dev.platform_data;
    struct uda134x_priv *uda134x;

    if (!pd) {
        dev_err(&pdev->dev, "Missing L3 bitbang function\n");
        return -ENODEV;
    }

    uda134x = devm_kzalloc(&pdev->dev, sizeof(*uda134x), GFP_KERNEL);
    if (!uda134x)
        return -ENOMEM;

    uda134x->pd = pd;
    platform_set_drvdata(pdev, uda134x);

    uda134x->regmap = devm_regmap_init(&pdev->dev, NULL, pd,
        &uda134x_regmap_config);
    if (IS_ERR(uda134x->regmap))
        return PTR_ERR(uda134x->regmap);

    return snd_soc_register_codec(&pdev->dev,
            &soc_codec_dev_uda134x, &uda134x_dai, 1);//定义snd_soc_codec_driver和snd_soc_dai_driver的实例
}
static struct platform_driver uda134x_codec_driver = {
    .driver = {
        .name = "uda134x-codec",
    },
    .probe = uda134x_codec_probe,
    .remove = uda134x_codec_remove,
};

这里先申请了空间作为uda1341的私有数据,然后使用regmap机制,使得控制接口抽象化,codec_drv不用操心当前控制方式是什么;
regmap 机制是在 Linux 3.1 加入进来的特性。主要目的是减少慢速 I/O 驱动上的重复逻辑,提供一种通用的接口来操作底层硬件上的寄存器。
接着设置私有数据,
最后调用snd_soc_register_codec函数对codec进行注册。
注册时涉及两个数据结构:snd_soc_codec_driver和snd_soc_dai_driver:

static struct snd_soc_codec_driver soc_codec_dev_uda134x = {//描述codec的控制接口
    .probe =        uda134x_soc_probe,
    .set_bias_level = uda134x_set_bias_level,
    .suspend_bias_off = true,

    .dapm_widgets = uda134x_dapm_widgets,
    .num_dapm_widgets = ARRAY_SIZE(uda134x_dapm_widgets),
    .dapm_routes = uda134x_dapm_routes,
    .num_dapm_routes = ARRAY_SIZE(uda134x_dapm_routes),
};

codec_dai和pcm配置信息通过结构体snd_soc_dai_driver描述,包括dai的能力描述和操作接口,snd_soc_dai_driver最终会被注册到asoc-core中。

static struct snd_soc_dai_driver uda134x_dai = {
    .name = "uda134x-hifi",
    /* playback capabilities */
    .playback = {//回放能力描述信息,如所支持的声道数、采样率、音频格式;
        .stream_name = "Playback",
        .channels_min = 1,
        .channels_max = 2,
        .rates = UDA134X_RATES,
        .formats = UDA134X_FORMATS,
    },
    /* capture capabilities */
    .capture = {//录制能力描述信息,如所支持声道数、采样率、音频格式;
        .stream_name = "Capture",
        .channels_min = 1,
        .channels_max = 2,
        .rates = UDA134X_RATES,
        .formats = UDA134X_FORMATS,
    },
    /* pcm operations */
    .ops = &uda134x_dai_ops,
};

snd_soc_dai_driver里描述了他的dai接口,其中.ops 字段很重要,它指向codec_dai的操作函数集,定义了dai的时钟配置、格式配置、硬件参数配置等回调。

static const struct snd_soc_dai_ops uda134x_dai_ops = {
    .startup    = uda134x_startup,
    .shutdown   = uda134x_shutdown,
    .hw_params  = uda134x_hw_params,//codec_dai的硬件参数设置,根据上层设定的声道数、采样率、数据格式,来设置codec_dai相关寄存器。
    .digital_mute   = uda134x_mute,
    .set_sysclk = uda134x_set_dai_sysclk,
    .set_fmt    = uda134x_set_dai_fmt,
};

我们的重点在注册函数:

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)
{
    struct snd_soc_dapm_context *dapm;
    struct snd_soc_codec *codec;
    struct snd_soc_dai *dai;

    codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);//申请空间,分配一个snd_soc_codec结构
    codec->component.codec = codec;//设置component参数

    ret = snd_soc_component_initialize(&codec->component,
            &codec_drv->component_driver, dev);//【1】
    ......
    if (codec_drv->controls) {//然后初始化它的各个字段,多数字段的值来自上面定义的snd_soc_codec_driver的实例
        codec->component.controls = codec_drv->controls;////【2】
        codec->component.num_controls = codec_drv->num_controls;
    }
    if (codec_drv->dapm_widgets) {
        codec->component.dapm_widgets = codec_drv->dapm_widgets;
        codec->component.num_dapm_widgets = codec_drv->num_dapm_widgets;
    }
    if (codec_drv->dapm_routes) {
        codec->component.dapm_routes = codec_drv->dapm_routes;
        codec->component.num_dapm_routes = codec_drv->num_dapm_routes;
    }

    if (codec_drv->probe)
        codec->component.probe = snd_soc_codec_drv_probe;//其实就是codec->driver->probe
    if (codec_drv->remove)
        codec->component.remove = snd_soc_codec_drv_remove;
    if (codec_drv->write)
        codec->component.write = snd_soc_codec_drv_write;
    if (codec_drv->read)
        codec->component.read = snd_soc_codec_drv_read;
    codec->component.ignore_pmdown_time = codec_drv->ignore_pmdown_time;
    ......
    if (codec_drv->get_regmap)
        codec->component.regmap = codec_drv->get_regmap(dev);//使用了remap

    for (i = 0; i < num_dai; i++) {//snd_soc_dai_driver中的参数设置Format
        fixup_codec_formats(&dai_drv[i].playback);//【3】 fixup_codec_formats(&dai_drv[i].capture); } ret = snd_soc_register_dais(&codec->component, dai_drv, num_dai, false);//【4】 list_for_each_entry(dai, &codec->component.dai_list, list) dai->codec = codec;//【5】 mutex_lock(&client_mutex); snd_soc_component_add_unlocked(&codec->component);//【6】 list_add(&codec->list, &codec_list);//【7】codec_list链表 mutex_unlock(&client_mutex); }

注释如上
【1】snd_soc_component_initialize里面设置名字和component字段填充
【2】设置codec->component的各个字段
【3】fixup_codec_formats设置Format
【4】snd_soc_register_dais对本Codec的dai进行注册
【5】codec下的dai全部存放入code->component.dai_list中
【6】codec下的component组件codec->component添加到component_list链表上
【7】把codec实例链接到全局链表codec_list中,Machine驱动初始化时会遍历该链表,以匹配并绑定dai_link上的codec

看下具体分析:
【1】snd_soc_component_initialize

static int snd_soc_component_initialize(struct snd_soc_component *component,
    const struct snd_soc_component_driver *driver, struct device *dev)
{
    component->name = fmt_single_name(dev, &component->id);
    component->dev = dev;
    component->driver = driver;
    component->probe = component->driver->probe;
    component->remove = component->driver->remove;

    dapm = &component->dapm;
    dapm->dev = dev;
    dapm->component = component;
    dapm->bias_level = SND_SOC_BIAS_OFF;
    dapm->idle_bias_off = true;
    if (driver->seq_notifier)
        dapm->seq_notifier = snd_soc_component_seq_notifier;
    if (driver->stream_event)
        dapm->stream_event = snd_soc_component_stream_event;

    component->controls = driver->controls;//多数字段的值来自上面定义的snd_soc_codec_driver->component_driver的实例
    component->num_controls = driver->num_controls;
    component->dapm_widgets = driver->dapm_widgets;
    component->num_dapm_widgets = driver->num_dapm_widgets;
    component->dapm_routes = driver->dapm_routes;
    component->num_dapm_routes = driver->num_dapm_routes;

    INIT_LIST_HEAD(&component->dai_list);
    mutex_init(&component->io_mutex);
}

这里面fmt_single_name函数就是确定codec的名字,这个名字很重要,Machine驱动定义的snd_soc_dai_link中会指定每个link的codec和dai的名字,进行匹配绑定时就是通过和这里的名字比较,从而找到该Codec的!
接着设置componment组件。
【4】snd_soc_register_dais函数

static int snd_soc_register_dais(struct snd_soc_component *component,
    struct snd_soc_dai_driver *dai_drv, size_t count,
    bool legacy_dai_naming)
{
    struct device *dev = component->dev;
    struct snd_soc_dai *dai;
    unsigned int i;

    component->dai_drv = dai_drv;

    for (i = 0; i < count; i++) {
        dai = soc_add_dai(component, dai_drv + i,
                count == 1 && legacy_dai_naming);
        if (dai == NULL) {
            ret = -ENOMEM;
            goto err;
        }
    }
}

里面就是调用soc_add_dai函数,分配内存,链接到component->dai_list中。

最后附上一位博主的话:

“最后顺便提下dai_link中的codec和codec_dai的区别:codec指音频codec共有的部分,包括codec初始化函数、控制接口、寄存器缓存、控件列表、dapm部件列表、音频路由列表、偏置电压设置函数等描述信息;而codec_dai指codec上的音频接口驱动描述,各个接口的描述信息不一定都是一致的,所以各个音频接口都有它自身的驱动描述,包括音频接口的初始化函数、操作函数集、能力描述等。
我开始时认为:codec_dai从属于codec,dai_link没有必要同时声明codec和codec_dai,应该可以实现codec_dai就能找到它对应的父设备codec的方法。后来想到系统上如果有两个以上的codec,而恰好不同codec上的codec_dai有重名的话,此时就必须同时声明codec和codec_dai才能找到正确的音频接口了。”

参考:https://blog.csdn.net/azloong/article/details/17252843
参考:https://blog.csdn.net/RadianceBlau/article/details/79432661

你可能感兴趣的:(Linux驱动)