章节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硬件工作。