概述
在ASOC小节中描述了整个ASOC的架构,其中Machine是ASOC架构中的关键部件,没有Machine部件,单独的Codec和Platform是无法工作的。因此本节则先从Machine部分开始,那应该如何开始呢? 答案当然是从代码入手,先进入ASOC在kernel中的位置: kernel/sound/soc下
- root@test:~/test/kernel/sound/soc$ ls
- adi au1x blackfin codecs dwc generic jz4740 kirkwood mxs omap rockchip sh soc-cache.c soc-core.c soc-devres.c soc-io.c soc-pcm.c spear txx9
- atmel bcm cirrus davinci fsl intel Kconfig Makefile nuc900 pxa samsung sirf soc-compress.c soc-dapm.c soc-generic-dmaengine-pcm.c soc-jack.c soc-utils.c tegra ux500
Machine代码分析
samsung平台的machine代码选择为: s3c24xx_uda134x.c
此代码先注册平台驱动s3c24xx_uda134x_driver, 当平台驱动和平台设备(以前在arch下,目前在dt中配置)的名字想匹配的时候,就会调用平台驱动中的probe函数s3c24xx_uda134x_probe。
- s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
- if (!s3c24xx_uda134x_snd_device) {
- printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
- "Unable to register\n");
- return -ENOMEM;
- }
- platform_set_drvdata(s3c24xx_uda134x_snd_device,&snd_soc_s3c24xx_uda134x);
既然此处注册"soc-audio"的设备,就会存在名字为"soc-audio"的驱动,搜索"soc-audio",就会发现在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);
- }
- static int soc_probe(struct platform_device *pdev)
- {
- struct snd_soc_card *card = platform_get_drvdata(pdev);
- /*
- * no card, so machine driver should be registering card
- * we should not be here in that case so ret error
- */
- if (!card)
- return -EINVAL;
- dev_warn(&pdev->dev,
- "ASoC: machine %s should use snd_soc_register_card()\n",
- card->name);
- /* Bodge while we unpick instantiation */
- card->dev = &pdev->dev;
- return snd_soc_register_card(card);
- }
- static struct snd_soc_ops s3c24xx_uda134x_ops = {
- .startup = s3c24xx_uda134x_startup,
- .shutdown = s3c24xx_uda134x_shutdown,
- .hw_params = s3c24xx_uda134x_hw_params,
- };
- static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
- .name = "UDA134X",
- .stream_name = "UDA134X",
- .codec_name = "uda134x-codec",
- .codec_dai_name = "uda134x-hifi",
- .cpu_dai_name = "s3c24xx-iis",
- .ops = &s3c24xx_uda134x_ops,
- .platform_name = "s3c24xx-iis",
- };
- static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
- .name = "S3C24XX_UDA134X",
- .owner = THIS_MODULE,
- .dai_link = &s3c24xx_uda134x_dai_link,
- .num_links = 1,
- };
- struct snd_soc_dai_link {
- /* config - must be set by machine driver */
- const char *name; /* Codec name */
- const char *stream_name; /* Stream name */
- /*
- * You MAY specify the link's CPU-side device, either by device name,
- * or by DT/OF node, but not both. If this information is omitted,
- * the CPU-side DAI is matched using .cpu_dai_name only, which hence
- * must be globally unique. These fields are currently typically used
- * only for codec to codec links, or systems using device tree.
- */
- const char *cpu_name;
- struct device_node *cpu_of_node;
- /*
- * You MAY specify the DAI name of the CPU DAI. If this information is
- * omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node
- * only, which only works well when that device exposes a single DAI.
- */
- const char *cpu_dai_name;
- /*
- * You MUST specify the link's codec, either by device name, or by
- * DT/OF node, but not both.
- */
- const char *codec_name;
- struct device_node *codec_of_node;
- /* You MUST specify the DAI name within the codec */
- const char *codec_dai_name;
- struct snd_soc_dai_link_component *codecs;
- unsigned int num_codecs;
- /*
- * You MAY specify the link's platform/PCM/DMA driver, either by
- * device name, or by DT/OF node, but not both. Some forms of link
- * do not need a platform.
- */
- const char *platform_name;
- struct device_node *platform_of_node;
- int be_id; /* optional ID for machine driver BE identification */
- const struct snd_soc_pcm_stream *params;
- unsigned int dai_fmt; /* format to set on init */
- enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */
- /* Keep DAI active over suspend */
- unsigned int ignore_suspend:1;
- /* Symmetry requirements */
- unsigned int symmetric_rates:1;
- unsigned int symmetric_channels:1;
- unsigned int symmetric_samplebits:1;
- /* Do not create a PCM for this DAI link (Backend link) */
- unsigned int no_pcm:1;
- /* This DAI link can route to other DAI links at runtime (Frontend)*/
- unsigned int dynamic:1;
- /* DPCM capture and Playback support */
- unsigned int dpcm_capture:1;
- unsigned int dpcm_playback: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);
- /* optional hw_params re-writing for BE and FE sync */
- int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
- struct snd_pcm_hw_params *params);
- /* machine stream operations */
- const struct snd_soc_ops *ops;
- const struct snd_soc_compr_ops *compr_ops;
- /* For unidirectional dai links */
- bool playback_only;
- bool capture_only;
- };
.cpu_dai_name: 用于指定cpu侧的dai名字,也就是所谓的cpu侧的数字音频接口,一般都是i2S接口。如果省略则会使用cpu_name/cou_of_name。
.codec_dai_name: 用于codec侧的dai名字,不可以省略。
.codec_name: 用于指定codec芯片。不可以省略。
.platform_name: 用于指定cpu侧平台驱动,通常都是DMA驱动,用于传输。
.ops: audio的相关操作函数集合。
再次回到
snd_soc_register_card函数中,继续分析Machine的作用。
1. 根据struct snd_soc_dai_link结构体的个数,此处是一个,检测下需要设置的name是否已经设置。
- if (link->platform_name && link->platform_of_node) {
- dev_err(card->dev,
- "ASoC: Both platform name/of_node are set for %s\n",link->name);
- return -EINVAL;
- }
- card->rtd = devm_kzalloc(card->dev,sizeof(struct snd_soc_pcm_runtime) *
- (card->num_links + card->num_aux_devs),GFP_KERNEL);
- if (card->rtd == NULL)
- return -ENOMEM;
- card->num_rtd = 0;
- card->rtd_aux = &card->rtd[card->num_links];
- for (i = 0; i < card->num_links; i++) {
- card->rtd[i].card = card;
- card->rtd[i].dai_link = &card->dai_link[i];
- card->rtd[i].codec_dais = devm_kzalloc(card->dev,
- sizeof(struct snd_soc_dai *) *
- (card->rtd[i].dai_link->num_codecs),
- GFP_KERNEL);
- if (card->rtd[i].codec_dais == NULL)
- return -ENOMEM;
- }
分析
snd_soc_instantiate_card函数的实际操作:
1. 根据num_links的值,进行DAIs的bind工作。第一步先bind cpu侧的dai
- cpu_dai_component.name = dai_link->cpu_name;
- cpu_dai_component.of_node = dai_link->cpu_of_node;
- cpu_dai_component.dai_name = dai_link->cpu_dai_name;
- rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);
- if (!rtd->cpu_dai) {
- dev_err(card->dev, "ASoC: CPU DAI %s not registered\n",
- dai_link->cpu_dai_name);
- return -EPROBE_DEFER;
- }
- static struct snd_soc_dai *snd_soc_find_dai(
- const struct snd_soc_dai_link_component *dlc)
- {
- struct snd_soc_component *component;
- struct snd_soc_dai *dai;
- /* Find CPU DAI from registered DAIs*/
- list_for_each_entry(component, &component_list, list) {
- if (dlc->of_node && component->dev->of_node != dlc->of_node)
- continue;
- if (dlc->name && strcmp(component->name, dlc->name))
- continue;
- list_for_each_entry(dai, &component->dai_list, list) {
- if (dlc->dai_name && strcmp(dai->name, dlc->dai_name))
- continue;
- return dai;
- }
- }
- return NULL;
- }
2. 然后根据codec的数据,寻找codec侧的dai。
- /* Find CODEC from registered CODECs */
- for (i = 0; i < rtd->num_codecs; i++) {
- codec_dais[i] = snd_soc_find_dai(&codecs[i]);
- if (!codec_dais[i]) {
- dev_err(card->dev, "ASoC: CODEC DAI %s not registered\n",
- codecs[i].dai_name);
- return -EPROBE_DEFER;
- }
- }
3. 在platform_list链表中查找platfrom,根据dai_link中的platform_name域。如果没有platform_name,则设置为"snd-soc-dummy"
- /* if there's no platform we match on the empty platform */
- platform_name = dai_link->platform_name;
- if (!platform_name && !dai_link->platform_of_node)
- platform_name = "snd-soc-dummy";
- /* find one from the set of registered platforms */
- list_for_each_entry(platform, &platform_list, list) {
- if (dai_link->platform_of_node) {
- if (platform->dev->of_node !=
- dai_link->platform_of_node)
- continue;
- } else {
- if (strcmp(platform->component.name, platform_name))
- continue;
- }
- rtd->platform = platform;
- }
这样查找完毕之后,snd_soc_pcm_runtime中存储了查找到的codec, dai, platform。
4. 接着初始化注册的codec cache,cache_init代表是否已经初始化过。
- /* initialize the register cache for each available codec */
- list_for_each_entry(codec, &codec_list, list) {
- if (codec->cache_init)
- continue;
- ret = snd_soc_init_codec_cache(codec);
- if (ret < 0)
- goto base_error;
- }
5. 然后调用ALSA中的创建card的函数: snd_card_new创建一个card
- /* card bind complete so register a sound card */
- ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
- card->owner, 0, &card->snd_card);
- if (ret < 0) {
- dev_err(card->dev,
- "ASoC: can't create sound card for card %s: %d\n",
- card->name, ret);
- goto base_error;
- }
- /* initialise the sound card only once */
- if (card->probe) {
- ret = card->probe(card);
- if (ret < 0)
- goto card_probe_error;
- }
- /* probe all components used by DAI links on this card */
- for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
- order++) {
- for (i = 0; i < card->num_links; i++) {
- ret = soc_probe_link_components(card, i, order);
- if (ret < 0) {
- dev_err(card->dev,
- "ASoC: failed to instantiate card %d\n",
- ret);
- goto probe_dai_err;
- }
- }
- }
- /* probe all DAI links on this card */
- for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
- order++) {
- for (i = 0; i < card->num_links; i++) {
- ret = soc_probe_link_dais(card, i, order);
- if (ret < 0) {
- dev_err(card->dev,
- "ASoC: failed to instantiate card %d\n",
- ret);
- goto probe_dai_err;
- }
- }
- }
- /* probe the cpu_dai */
- if (!cpu_dai->probed &&
- cpu_dai->driver->probe_order == order) {
- if (cpu_dai->driver->probe) {
- ret = cpu_dai->driver->probe(cpu_dai);
- if (ret < 0) {
- dev_err(cpu_dai->dev,
- "ASoC: failed to probe CPU DAI %s: %d\n",
- cpu_dai->name, ret);
- return ret;
- }
- }
- cpu_dai->probed = 1;
- }
- /* probe the CODEC DAI */
- for (i = 0; i < rtd->num_codecs; i++) {
- ret = soc_probe_codec_dai(card, rtd->codec_dais[i], order);
- if (ret)
- return ret;
- if (!dai_link->params) {
- /* create the pcm */
- ret = soc_new_pcm(rtd, num);
- if (ret < 0) {
- dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
- dai_link->stream_name, ret);
- return ret;
- }
最中此函数会调用ALSA的标准创建pcm设备的接口: snd_pcm_new,然后会设置pcm相应的ops操作函数集合。然后调用到platform->driver->pcm_new的函数。此处不帖函数了。
9. 接着会在dapm和dai widget做相应的操作,后期会设置control参数,最终会调用到ALSA的注册card的函数snd_card_register。
- ret = snd_card_register(card->snd_card);
- if (ret < 0) {
- dev_err(card->dev, "ASoC: failed to register soundcard %d\n",
- ret);
- goto probe_aux_dev_err;
- }
总结: 经过Machine的驱动的注册,Machine会根据注册以"soc_audio"为名字的平台设备,然后在同名的平台的驱动的probe函数中,会根据snd_soc_dai_link结构体中的name,进行匹配查找相应的codec, codec_dai,platform, cpu_dai。找到之后将这些值全部放入结构体snd_soc_pcm_runtime的相应位置,然后注册card,依次调用codec, platform,cpu_dai侧相应的probe函数进行初始化,接着创建pcm设备,注册card到系统中。其实ASOC也就是在ALSA的基础上又再次封装了一次,让写驱动更方便,简便。
这样封装之后,就可以大大简化驱动的编写,关于Machine驱动需要做的:
1. 注册名为"soc-audio"的平台设备。
2. 分配一个struct snd_soc_card结构体,然后设置其中的dai_link。对其中的dai_link再次设置。
3. 将struct snd_soc_card结构放入到平台设备的dev的私有数据中。
4. 注册平台设备。