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

本文详细解析了Linux音频子系统中snd_soc_instantiate_card函数的内部实现,包括Dai_link绑定、辅助设备绑定、Dai_link动态添加、Codec寄存器缓存初始化、Soundcard创建、Debugfs初始化、DAPM控制初始化、组件探测、PCM创建流程及DAPM Widget链接,是深入理解Linux音频架构不可或缺的内容。

上篇说到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> 创建一个card专用的debugfs,关于debugfs可以看这篇文章: [Linux]使用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);
<think>我们正在讨论如何编写Linux ALSA音频驱动程序。根据用户的问题和之前的引用,我们需要提供一份清晰的开发指南。 引用内容中提到了ALSA SoC框架,因此我们将重点放在ALSA SoC(ASoC)驱动开发上,因为这是嵌入式系统中常用的框架。 步骤: 1. 介绍ALSA驱动的基本结构 2. 详细说明ASoC驱动的三个组成部分:Codec驱动、Platform驱动Machine驱动 3. 提供代码示例(注意:由于内核版本不同,示例代码可能需要调整) 4. 说明如何注册和测试驱动 注意:由于用户问题比较宽泛,我们将从基础开始,逐步深入。 根据引用[1]:本书介绍了如何开发和调试ALSA编解码器和平台类驱动程序,并探讨了如何利用DAPM进行数字音频电源管理。因此,我们也会提到DAPM。 由于用户要求生成相关问题,我们将在最后生成相关问题。 开始回答:</think>### Linux ALSA音频驱动开发指南 ALSA(Advanced Linux Sound Architecture)是Linux内核的标准音频子系统。开发ALSA驱动涉及以下关键步骤: #### 1. **驱动框架组成** ALSA SoC驱动采用分层设计: - **Codec驱动**:管理音频编解码器硬件(如DA/AD转换、混音器) - **Platform驱动**:处理SoC平台相关组件(DMA、I2S接口) - **Machine驱动**:描述Codec与Platform的连接配置(时钟、数据格式) - **DAPM(动态音频电源管理)**:自动管理电源状态以降低功耗[^1] #### 2. **关键数据结构** ```c struct snd_soc_dai_driver { // 描述DAI(数字音频接口) const char *name; int (*probe)(...); // 设备探测函数 struct snd_soc_pcm_stream capture; struct snd_soc_pcm_stream playback; }; struct snd_soc_component_driver { // 编解码器操作集 const char *name; struct snd_kcontrol_new *controls; // 混音器控件 const struct snd_soc_dapm_route *dapm_routes; // DAPM路由 }; ``` #### 3. **驱动开发步骤** **(1) 初始化编解码器驱动** ```c static struct snd_soc_component_driver my_codec = { .name = "my_codec", .controls = my_controls, // 定义音量等控件 .dapm_routes = my_routes, // 定义信号路径 }; static int my_codec_probe(struct platform_device *pdev) { return devm_snd_soc_register_component(&pdev->dev, &my_codec, NULL, 0); } ``` **(2) 配置平台DMA** ```c static struct snd_soc_platform_driver my_platform = { .ops = &my_dma_ops, // 实现DMA传输回调 }; static int my_dma_probe(struct platform_device *pdev) { return snd_soc_register_platform(&pdev->dev, &my_platform); } ``` **(3) 定义Machine连接** ```c static struct snd_soc_dai_link my_dai_link = { .name = "MyAudioSystem", .cpu_dai_name = "soc-audio.0", // Platform端DAI .codec_dai_name = "my_codec", // Codec端DAI .platform_name = "soc-audio", .codec_name = "my_codec", }; static struct snd_soc_card my_card = { .dai_link = &my_dai_link, .num_links = 1, }; ``` #### 4. **DAPM配置示例** ```c static const struct snd_soc_dapm_route my_routes[] = { {"Headphone", NULL, "HP_L"}, // 信号路径定义 {"Headphone", NULL, "HP_R"}, {"MIC", NULL, "Microphone"}, }; static const struct snd_kcontrol_new my_controls[] = { SOC_DAPM_SINGLE("Playback Switch", MY_REG, 5, 1, 0), // 控件定义 }; ``` #### 5. **调试与测试** - 加载驱动:`insmod snd_my_driver.ko` - 查看设备节点:`ls /dev/snd/` - 使用工具: ```bash alsamixer # 调节音量 aplay test.wav # 播放测试 arecord -d 5 test.wav # 录音测试 ``` #### 6. **电源管理** 通过DAPM自动关闭未使用模块的电源: ```c static const struct snd_soc_dapm_widget my_widgets[] = { SND_SOC_DAPM_OUTPUT("Headphone"), SND_SOC_DAPM_INPUT("Microphone"), SND_SOC_DAPM_PGA("HP Amp", SND_SOC_NOPM, 0, 0, NULL, 0), }; ``` [^1]: 引用自《嵌入式系统开发》第2部分,详细描述了ALSA SoC框架和DAPM电源管理机制。 --- ### 相关问题 1. ALSA驱动中如何实现低延迟音频传输? 2. DAPM如何动态管理音频组件的电源状态? 3. 如何为自定义I2S接口编写Platform驱动? 4. ALSA驱动调试常用工具有哪些? 5. 如何处理多声道音频(如5.1环绕声)的配置?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山猫Show

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值