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

2019-07-12 18:38发布

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

欢迎和我一起交流。


上一节说了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 "); 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