章节ASoC codec driver和ASoC platform driver介绍了codec、platform驱动,但仅有codec、platform驱动是不能工作的,需要一个角色把codec、codec_dai、cpu_dai、platform给链结起来才能构成一个完整的音频回路,这个角色就由machine_drv承担了。
struct snd_soc_dai_link {
/* config - must be set by machine driver */
const char *name; /* Codec name */
const char *stream_name; /* Stream name */
const char *codec_name; /* for multi-codec */
const struct device_node *codec_of_node;
const char *platform_name; /* for multi-platform */
const struct device_node *platform_of_node;
const char *cpu_dai_name;
const struct device_node *cpu_dai_of_node;
const char *codec_dai_name;
unsigned int dai_fmt; /* format to set on init */
/* Keep DAI active over suspend */
unsigned int ignore_suspend:1;
/* Symmetry requirements */
unsigned int symmetric_rates:1;
/* pmdown_time is ignored at stop */
unsigned int ignore_pmdown_time:1;
/* codec/machine specific init - e.g. add machine controls */
int (*init)(struct snd_soc_pcm_runtime *rtd);
/* machine stream operations */
struct snd_soc_ops *ops;
};
注释比较详细,重点介绍如下几个字段:
· codec_name:音频链路需要绑定的codec名称标识,soc-core中会遍历codec_list,找到同名的codec并绑定;
· platform_name:音频链路需要绑定的platform名称标识,soc-core中会遍历platform_list,找到同名的platform并绑定;
· cpu_dai_name:音频链路需要绑定的cpu_dai名称标识,soc-core中会遍历dai_list,找到同名的dai并绑定;
· codec_dai_name:音频链路需要绑定的codec_dai名称标识,soc-core中会遍历dai_list,找到同名的dai并绑定;
· ops:重点留意hw_params回调,一般来说这个回调是要实现的,用于配置codec、cpu_dai的时钟、格式。在Codec audio operations小节中有描述。
goni_wm8994.c中的dai_link定义,两个音频链路,分别用于Media和Voice,可以看到用于Voice链路不须指定platform_name的:
static struct snd_soc_dai_link goni_dai[] = {
{
.name = "WM8994",
.stream_name = "WM8994 HiFi",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-audio",
.codec_name = "wm8994-codec.0-001a",
.init = goni_wm8994_init,
.ops = &goni_hifi_ops,
}, {
.name = "WM8994 Voice",
.stream_name = "Voice",
.cpu_dai_name = "goni-voice-dai",
.codec_dai_name = "wm8994-aif2",
.codec_name = "wm8994-codec.0-001a",
.ops = &goni_voice_ops,
},
};
除了dai_link,机器中一些特定的音频控件和音频事件也可以在machine_drv定义,如耳机插拔检测、外部功放打开关闭等。
我们再分析machine_drv初始化过程:
static struct snd_soc_card goni = {
.name = "goni",
.owner = THIS_MODULE,
.dai_link = goni_dai,
.num_links = ARRAY_SIZE(goni_dai),
.dapm_widgets = goni_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(goni_dapm_widgets),
.dapm_routes = goni_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(goni_dapm_routes),
};
static int __init goni_init(void)
{
int ret;
goni_snd_device = platform_device_alloc("soc-audio", -1);
if (!goni_snd_device)
return -ENOMEM;
/* register voice DAI here */
ret = snd_soc_register_dai(&goni_snd_device->dev, &voice_dai);
if (ret) {
platform_device_put(goni_snd_device);
return ret;
}
platform_set_drvdata(goni_snd_device, &goni);
ret = platform_device_add(goni_snd_device);
if (ret) {
snd_soc_unregister_dai(&goni_snd_device->dev);
platform_device_put(goni_snd_device);
}
return ret;
}
· 分配一个name="soc-audio"的platform_device实例;
· 设定platform_device的私有数据指向snd_soc_card;
· 然后注册platform_device实例到系统中;
· 再然后呢?好像没有了...
但是真的没有了吗?别忘了,platform_device还得有个好伙伴platform_driver跟它配对。而name="soc-audio"的platform_driver定义在soc-core.c中:
/* ASoC platform driver */
static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
static int __init snd_soc_init(void)
{
// ...
snd_soc_util_init();
return platform_driver_register(&soc_driver);
}
module_init(snd_soc_init);
当两者匹配后,soc_probe()会被调用,继而调用snd_soc_register_card()注册声卡。由于该过程很冗长,这里不一一贴代码分析了,但整个流程是比较简单的,流程图如下:
· 取出platform_device的私有数据,该私有数据保存的是machine_drv中的snd_soc_card实例;
· snd_soc_register_card()为每个dai_link分配一个snd_soc_pcm_runtime实例,别忘了之前提过snd_soc_pcm_runtime是ASoC的桥梁,保存着codec、codec_dai、cpu_dai、platform等硬件设备实例;
随后的工作都在snd_soc_instantiate_card()进行:
· 遍历dai_list、codec_list、platform_list链表,为每个音频链路找到cpu_dai、codec_dai、codec、platform;找到的cpu_dai、codec_dai、codec、platform实例保存到dai_link对应的snd_soc_pcm_runtime中,完成音频设备的绑定工作;
· 初始化codec的寄存器缓存,调用snd_card_create()创建声卡;
· soc_probe_dai_link()依次回调cpu_dai、codec、platform、codec_dai的probe()函数,完成各个音频设备的初始化,随后调用soc_new_pcm()创建pcm逻辑设备(因为涉及到本系列的重点内容,后面具体分析这个函数);
· 最后调用snd_card_register()注册声卡。
soc_new_pcm源码分析:
/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;
struct snd_pcm *pcm;
char new_name[64];
int ret = 0, playback = 0, capture = 0;
// 初始化snd_soc_pcm_runtime的ops字段,soc_pcm_XXX这些函数其实依次调用machine、codec_dai、cpu_dai、platform的回调;
// 如soc_pcm_hw_params:|-> rtd->dai_link->ops->hw_params()
// |-> codec_dai->driver->ops->hw_params()
// |-> cpu_dai->driver->ops->hw_params()
// |-> platform->driver->ops->hw_params()
soc_pcm_ops->open = soc_pcm_open;
soc_pcm_ops->close = soc_pcm_close;
soc_pcm_ops->hw_params = soc_pcm_hw_params;
soc_pcm_ops->hw_free = soc_pcm_hw_free;
soc_pcm_ops->prepare = soc_pcm_prepare;
soc_pcm_ops->trigger = soc_pcm_trigger;
soc_pcm_ops->pointer = soc_pcm_pointer;
/* check client and interface hw capabilities */
snprintf(new_name, sizeof(new_name), "%s %s-%d",
rtd->dai_link->stream_name, codec_dai->name, num);
if (codec_dai->driver->playback.channels_min)
playback = 1;
if (codec_dai->driver->capture.channels_min)
capture = 1;
// 创建pcm逻辑设备
ret = snd_pcm_new(rtd->card->snd_card, new_name,
num, playback, capture, &pcm);
if (ret < 0) {
printk(KERN_ERR "asoc: can't create pcm for codec %s\n", codec->name);
return ret;
}
/* DAPM dai link stream work */
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
rtd->pcm = pcm;
pcm->private_data = rtd; // pcm的私有数据指向snd_soc_pcm_runtime
if (platform->driver->ops) {
// 初始化snd_soc_pcm_runtime的ops字段,这些与pcm dma操作相关,一般我们只用留意pointer回调
soc_pcm_ops->mmap = platform->driver->ops->mmap;
soc_pcm_ops->pointer = platform->driver->ops->pointer;
soc_pcm_ops->ioctl = platform->driver->ops->ioctl;
soc_pcm_ops->copy = platform->driver->ops->copy;
soc_pcm_ops->silence = platform->driver->ops->silence;
soc_pcm_ops->ack = platform->driver->ops->ack;
soc_pcm_ops->page = platform->driver->ops->page;
}
if (playback)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops); // 把soc_pcm_ops赋给playback substream的ops字段
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops); // 把soc_pcm_ops赋给capture substream的ops字段
// 回调platform驱动的pcm_new(),完成dma buffer和dma设备的初始化工作
if (platform->driver->pcm_new) {
ret = platform->driver->pcm_new(rtd);
if (ret < 0) {
pr_err("asoc: platform pcm constructor failed\n");
return ret;
}
}
pcm->private_free = platform->driver->pcm_free;
printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name,
cpu_dai->name);
return ret;
}
可见soc_new_pcm()最主要的工作是创建pcm逻辑设备,创建回放子流和录制子流实例,并初始化回放子流和录制子流的pcm操作函数,数据搬运时,需要调用这些操作函数来触发codec、cpu_dai、pcm_dma硬件工作。