[Alsa]9, Machine驱动的编写(2)

版权声明:本文为博主原创文章,欢迎转载,转载请注明出处。 https://blog.csdn.net/wangyijieonline/article/details/88357354

上篇说到Machine驱动中的snd_soc_register_card函数初始化到了最核心的snd_soc_instantiate_card函数,可以说这才是重中之重,本篇分三部分来讲这个内容。

Linux 4.9.123 可从以下地址获得
https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/

本文Codec基于wm8994。


1 soc_bind_dai_link

static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
  struct snd_soc_codec *codec;
  struct snd_soc_pcm_runtime *rtd;
  struct snd_soc_dai_link *dai_link;
  int ret, i, order;

...

  /* bind DAIs */
  for (i = 0; i < card->num_links; i++) {
    ret = soc_bind_dai_link(card, &card->dai_link[i]);	//绑定
    if (ret != 0)
      goto base_error;
  }

...
}

其函数定义如下:

static int soc_bind_dai_link(struct snd_soc_card *card,
  struct snd_soc_dai_link *dai_link)
{
  struct snd_soc_pcm_runtime *rtd;
  struct snd_soc_dai_link_component *codecs = dai_link->codecs;
  struct snd_soc_dai_link_component cpu_dai_component;
  struct snd_soc_dai **codec_dais;
  struct snd_soc_platform *platform;
  const char *platform_name;
  int i;

  dev_dbg(card->dev, "ASoC: binding %s\n", dai_link->name);

  if (soc_is_dai_link_bound(card, dai_link)) {  //判断是否已经绑定
    dev_dbg(card->dev, "ASoC: dai link %s already bound\n",
      dai_link->name);
    return 0;
  }

  rtd = soc_new_pcm_runtime(card, dai_link);
  if (!rtd)
    return -ENOMEM;

  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) {	//如果cpu_dai没有初始化过,其实就是dai_link的cpu_*几个成员
    dev_err(card->dev, "ASoC: CPU DAI %s not registered\n",
    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;
  }
  if (!rtd->platform) {	//如果platform没有初始化过
    dev_err(card->dev, "ASoC: platform %s not registered\n",
      dai_link->platform_name);
    goto _err_defer;
  }

  soc_add_pcm_runtime(card, rtd);
  return 0;

_err_defer:
  soc_free_pcm_runtime(rtd);
  return  -EPROBE_DEFER;
}

首先,绑定每一个dai_link到card: ASoC定义了三个全局的链表头变量:codec_list、dai_list、platform_list,系统中所有的Codec、DAI、Platform都在注册时连接到这三个全局链表上。soc_bind_dai_link函数逐个扫描这三个链表,根据card->dai_link[]中的名称进行匹配,匹配后把相应的codec,dai和platform实例赋值到card->rtd[]中(snd_soc_pcm_runtime)。经过这个过程后,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驱动的信息。

  /* bind aux_devs too */
  for (i = 0; i < card->num_aux_devs; i++) {
    ret = soc_bind_aux_dev(card, i);
    if (ret != 0)
      goto base_error;
  }
  
 /* add predefined DAI links to the list */
  for (i = 0; i < card->num_links; i++)
    snd_soc_add_dai_link(card, card->dai_link+i);

  /* 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;
  }
  
  /* 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;
  }

然后绑定aux_dev;动态地添加dai_link到链表里,这样,就可以从链表里找到这些dai_link;为每个codec初始化寄存器缓存,基本上该绑定地都已经绑定到struct snd_soc_card card;里了,现在可以添加这个声卡了。

<2> 创建一个debugfs???这是什么鬼???一个专用文件系统???不是太了解。。。

  soc_init_card_debugfs(card);

<3> 声卡创建好了,下面开始进行dapm的工作,毕竟这部分工作堪称alsa的点睛之笔。

if (card->dapm_widgets)
    snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
            card->num_dapm_widgets);

  if (card->of_dapm_widgets)
    snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets,
            card->num_of_dapm_widgets);

添加snd_soc_dapm_new_controls,调用probe函数初始化card,仅一次

/* initialise the sound card only once */
  if (card->probe) {
    ret = card->probe(card);
    if (ret < 0)
      goto card_probe_error;
  }

对于之前维护的rtd_list链表,probe 所有 这个card上DAI links用到的components。

  /* probe all components used by DAI links on this card */
  for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; order++) {
    list_for_each_entry(rtd, &card->rtd_list, list) {
      ret = soc_probe_link_components(card, rtd, order);
      if (ret < 0) {
        dev_err(card->dev,
          "ASoC: failed to instantiate card %d\n",
          ret);
        goto probe_dai_err;
      }
    }
  }

/* 对于每个card->aux_comp_list链表,probe 辅助 components */

  ret = soc_probe_aux_devices(card);
  if (ret < 0)
    goto probe_dai_err;

查找在探测components期间添加的新DAI链接并绑定它们,因为具有拓扑结构的组件可能会带来新的DAI和DAI链接。

  list_for_each_entry(dai_link, &card->dai_link_list, list) {
    if (soc_is_dai_link_bound(card, dai_link))
      continue;

    ret = soc_init_dai_link(card, dai_link);
    if (ret)
      goto probe_dai_err;
    ret = soc_bind_dai_link(card, dai_link);
    if (ret)
      goto probe_dai_err;
  }

/* probe the platform */

接下来soc_probe_link_dais,在soc_probe_link_dais函数里挨个调用了cpu-dai, codec-dai(可能有n个)的probe函数,然后调用了dai_link的init函数,在这里我们看到一条注释/* do machine specific initialization */,也就是说dai_link就是machine。在snd_soc_runtime_set_dai_fmt中更新连接到指定运行时的DAI link的所有DAI的DAI链接格式。然后使用snd_soc_pcm_runtimename再次进行component的初始化,然后创建cpu-dai端的compress_device,最后调用soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备。

  for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; order++) {
    list_for_each_entry(rtd, &card->rtd_list, list) {
      ret = soc_probe_link_dais(card, rtd, order);
      if (ret < 0) {
        dev_err(card->dev,
          "ASoC: failed to instantiate card %d\n",
          ret);
        goto probe_dai_err;
      }
    }
  }

soc_new_pcm函数首先初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成员,例如open,close,hw_params等,紧接着调用标准alsa驱动中的创建pcm的函数snd_pcm_new()创建声卡的pcm实例,pcm的private_data字段设置为该runtime变量rtd,然后用platform驱动中的snd_pcm_ops替换部分pcm中的snd_pcm_ops字段,最后,调用platform驱动的pcm_new回调,该回调实现该platform下的dma内存申请和dma初始化等相关工作。到这里,声卡和他的pcm实例创建完成。

/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
	struct snd_soc_platform *platform = rtd->platform;
	struct snd_soc_dai *codec_dai;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	struct snd_pcm *pcm;
	char new_name[64];
	int ret = 0, playback = 0, capture = 0;
	int i;

	if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {
		playback = rtd->dai_link->dpcm_playback;
		capture = rtd->dai_link->dpcm_capture;
	} else {
		for (i = 0; i < rtd->num_codecs; i++) {
			codec_dai = rtd->codec_dais[i];
			if (codec_dai->driver->playback.channels_min)
				playback = 1;
			if (codec_dai->driver->capture.channels_min)
				capture = 1;
		}

		capture = capture && cpu_dai->driver->capture.channels_min;
		playback = playback && cpu_dai->driver->playback.channels_min;
	}

	if (rtd->dai_link->playback_only) {
		playback = 1;
		capture = 0;
	}

	if (rtd->dai_link->capture_only) {
		playback = 0;
		capture = 1;
	}

	/* create the PCM */
	if (rtd->dai_link->no_pcm) {
		snprintf(new_name, sizeof(new_name), "(%s)",
			rtd->dai_link->stream_name);

		ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,
				playback, capture, &pcm);
	} else {
		if (rtd->dai_link->dynamic)
			snprintf(new_name, sizeof(new_name), "%s (*)",
				rtd->dai_link->stream_name);
		else
			snprintf(new_name, sizeof(new_name), "%s %s-%d",
				rtd->dai_link->stream_name,
				(rtd->num_codecs > 1) ?
				"multicodec" : rtd->codec_dai->name, num);

		ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,
			capture, &pcm);
	}
	if (ret < 0) {
		dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\n",
			rtd->dai_link->name);
		return ret;
	}
	dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name);

	/* DAPM dai link stream work */
	INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);

	pcm->nonatomic = rtd->dai_link->nonatomic;
	rtd->pcm = pcm;
	pcm->private_data = rtd;

	if (rtd->dai_link->no_pcm) {
		if (playback)
			pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;
		if (capture)
			pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;
		goto out;
	}

	/* ASoC PCM operations */
	if (rtd->dai_link->dynamic) {
		rtd->ops.open		= dpcm_fe_dai_open;
		rtd->ops.hw_params	= dpcm_fe_dai_hw_params;
		rtd->ops.prepare	= dpcm_fe_dai_prepare;
		rtd->ops.trigger	= dpcm_fe_dai_trigger;
		rtd->ops.hw_free	= dpcm_fe_dai_hw_free;
		rtd->ops.close		= dpcm_fe_dai_close;
		rtd->ops.pointer	= soc_pcm_pointer;
		rtd->ops.ioctl		= soc_pcm_ioctl;
	} else {
		rtd->ops.open		= soc_pcm_open;
		rtd->ops.hw_params	= soc_pcm_hw_params;
		rtd->ops.prepare	= soc_pcm_prepare;
		rtd->ops.trigger	= soc_pcm_trigger;
		rtd->ops.hw_free	= soc_pcm_hw_free;
		rtd->ops.close		= soc_pcm_close;
		rtd->ops.pointer	= soc_pcm_pointer;
		rtd->ops.ioctl		= soc_pcm_ioctl;
	}

	if (platform->driver->ops) {
		rtd->ops.ack		= platform->driver->ops->ack;
		rtd->ops.copy		= platform->driver->ops->copy;
		rtd->ops.silence	= platform->driver->ops->silence;
		rtd->ops.page		= platform->driver->ops->page;
		rtd->ops.mmap		= platform->driver->ops->mmap;
	}

	if (playback)
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);

	if (capture)
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);

	if (platform->driver->pcm_new) {
		ret = platform->driver->pcm_new(rtd);
		if (ret < 0) {
			dev_err(platform->dev,
				"ASoC: pcm constructor failed: %d\n",
				ret);
			return ret;
		}
	}

	pcm->private_free = platform->driver->pcm_free;
out:
	dev_info(rtd->card->dev, "%s <-> %s mapping ok\n",
		 (rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name,
		 cpu_dai->name);
	return ret;
}

接下来snd_soc_dapm_link_dai_widgets找到具有相同stream的所有widgets并链接它们

  snd_soc_dapm_link_dai_widgets(card);
  snd_soc_dapm_connect_dai_link_widgets(card);
	if (card->controls)
		snd_soc_add_card_controls(card, card->controls, card->num_controls);

	if (card->dapm_routes)
		snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
					card->num_dapm_routes);

	if (card->of_dapm_routes)
		snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,
					card->num_of_dapm_routes);

执行late_probe函数,这个函数对应于在imx-wm8524.c中定义的imx_wm8524_late_probe

	if (card->late_probe) {
		ret = card->late_probe(card);
		if (ret < 0) {
			dev_err(card->dev, "ASoC: %s late_probe() failed: %d\n",
				card->name, ret);
			goto probe_aux_dev_err;
		}
	}

检查codec是否有任何新的dapm widget,如果找到则创建它们。

	snd_soc_dapm_new_widgets(card);

最后,调用snd_card_register函数,注册分配给声卡的所有设备。在调用此函数之前,ALSA控制接口将被阻止从外部访问。 因此,应该在声卡初始化结束时调用此函数。

int snd_card_register(struct snd_card *card)
{
	int err;

	if (snd_BUG_ON(!card))
		return -EINVAL;

	if (!card->registered) {
		err = device_add(&card->card_dev);
		if (err < 0)
			return err;
		card->registered = true;
	}

	if ((err = snd_device_register_all(card)) < 0)
		return err;
	mutex_lock(&snd_card_mutex);
	if (snd_cards[card->number]) {
		/* already registered */
		mutex_unlock(&snd_card_mutex);
		return snd_info_card_register(card); /* register pending info */
	}
	if (*card->id) {
		/* make a unique id name from the given string */
		char tmpid[sizeof(card->id)];
		memcpy(tmpid, card->id, sizeof(card->id));
		snd_card_set_id_no_lock(card, tmpid, tmpid);
	} else {
		/* create an id from either shortname or longname */
		const char *src;
		src = *card->shortname ? card->shortname : card->longname;
		snd_card_set_id_no_lock(card, src,
					retrieve_id_from_card_name(src));
	}
	snd_cards[card->number] = card;
	mutex_unlock(&snd_card_mutex);
	init_info_for_card(card);
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
	if (snd_mixer_oss_notify_callback)
		snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
#endif
	return 0;
}

EXPORT_SYMBOL(snd_card_register);

猜你喜欢

转载自blog.csdn.net/wangyijieonline/article/details/88357354