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