Linux ALSA 之十:ALSA ASOC Machine Driver

2023-05-16

ALSA ASOC Machine Driver

  • 一、Machine 简介
  • 二、ASoC Machine Driver
    • 2.1 Machine Driver 的 Platform Driver & Platform Device 驱动模型
    • 2.2 在 Probe() 中注册声卡
  • 三、snd_soc_register_card 函数
    • 3.1 bind DAIs
    • 3.2 New a sound card
    • 3.3 Create card new widgets
    • 3.4 Probe all component used by DAI links
    • 3.5 Probe all DAI links on this card
    • 3.6 DAPM 相关操作
    • 3.7 Register Sound Card
  • 四、创建 Pcm Device 节点


一、Machine 简介

在前面的章节中已经有提到,ASoC 被分为 Machine、Platform 和 Codec 三大部分,其中 Machine 负责 Platform 和 Codec 之间的耦合以及部分和设备或板子特定的代码。Machine 驱动负责处理机器特有的一些控件和音频事件,单独的 Platform 和 Codec 驱动是不能工作的,它必须由 Machine 驱动把它们结合在一起才能完成整个设备的音频处理工作。

在这里插入图片描述

二、ASoC Machine Driver

# Note下面均以 mt2701-wm8960.c 为例进行讲解

2.1 Machine Driver 的 Platform Driver & Platform Device 驱动模型

该部分其实就是 /sound/soc/mediate/mt2701/mt2701-wm8960.c 中的 platform driver 与 /arch/arm/boot/dts/mt7623a-rfb-emmc.dts 中的 platform device 进行匹配,匹配成功后调用 mt2701_wm8960_machine_probe() 函数。

1)mt2701-wm8960.c

#ifdef CONFIG_OF
static const struct of_device_id mt2701_wm8960_machine_dt_match[] = {
	{.compatible = "mediatek,mt2701-wm8960-machine",},
	{}
};
#endif

static struct platform_driver mt2701_wm8960_machine = {
	.driver = {
		.name = "mt2701-wm8960",
#ifdef CONFIG_OF
		.of_match_table = mt2701_wm8960_machine_dt_match,
#endif
	},
	.probe = mt2701_wm8960_machine_probe,
};

module_platform_driver(mt2701_wm8960_machine);

platform driver 中有注册名为 "ediatek,mt2701-wm8960-machine"

2)mt7623a-rfb-emmc.dts

...
sound {
		compatible = "mediatek,mt2701-wm8960-machine";
		mediatek,platform = <&afe>;
		audio-routing =
			"Headphone", "HP_L",
			"Headphone", "HP_R",
			"LINPUT1", "AMIC",
			"RINPUT1", "AMIC";
		mediatek,audio-codec = <&wm8960>;
		pinctrl-names = "default";
		pinctrl-0 = <&i2s0_pins_a>;
	};
...

在设备树文件中有注册名为 "mediatek,mt2701-wm8960-machine"platform device,当 platform driver & platform device 匹配之后则会调用 platform_driver 下的 probe() 函数。

2.2 在 Probe() 中注册声卡

devm_snd_soc_register_card(&pdev->dev, card); // 注册声卡

ASoC 的一切都从 Machine 驱动开始,包括声卡的注册,绑定 Platform 和 Codec 驱动等。在 mt2701_wm8960_machine__probe() 函数会调用 devm_snd_soc_register_card() 函数在 ASoC Core 中注册一个 card,即 snd_soc_card,代码如下:

static struct snd_soc_card mt2701_wm8960_card = {
	.name = "mt2701-wm8960",
	.owner = THIS_MODULE,
	.dai_link = mt2701_wm8960_dai_links,
	.num_links = ARRAY_SIZE(mt2701_wm8960_dai_links),
	.controls = mt2701_wm8960_controls,
	.num_controls = ARRAY_SIZE(mt2701_wm8960_controls),
	.dapm_widgets = mt2701_wm8960_widgets,
	.num_dapm_widgets = ARRAY_SIZE(mt2701_wm8960_widgets),
};

static int mt2701_wm8960_machine_probe(struct platform_device *pdev)
{
	struct snd_soc_card *card = &mt2701_wm8960_card;
	...
	ret = devm_snd_soc_register_card(&pdev->dev, card);
	if (ret)
		dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n",
			__func__, ret);
	...
	return ret;
}

如上代码所示,在 snd_soc_card 中会定义 num_links 个 snd_soc_dai_link 实例 dai_link,其中 dai_link 根据 DPCM 可以分为 Front End & Back End(当然也还有在两者之外的),并且会指定 cpu_name/cpu_of_node/cpu_dai_name、codec_name/codec_of_node/codec_dai_name、platform_name/platform_of_node 等,稍后 Machine 驱动将会利用这些属性去匹配系统中已经注册的 platform、cpu_dai、codec component。

三、snd_soc_register_card 函数

snd_soc_register_card() 函数中大部分工作都是在 snd_soc_instantiate_card() 函数中实现,其主要实现的内容如下:(涉及 DAPM 此处省略,待后面 DAPM 部分再解析)

3.1 bind DAIs

在 snd_soc_instantiate_card() 函数首先会进行 bind dais 操作,代码如下:

/* bind DAIs */
	//	Machine:
	//		For FE dai_link: bind platform FE dai
	//		For BE dai_link: bind platform BE dai & Codec dai
	//
	//	FE	--------  BE   -------
	//	  _|		|_   _|		  |_ HP
	//	  _|platform|_	  | Codec |
	//    _|		|_	 _| 	  |_ SPK
	//	   |		|	  |		  |
	//		--------	   -------
	//
	for_each_card_prelinks(card, i, dai_link) {
		ret = soc_bind_dai_link(card, dai_link);
		if (ret != 0)
			goto base_error;
	}

如前面所述,在定义 snd_soc_card 时有定义 dai_links,如上述代码则会循环为每个 dai_link 均调用 soc_bind_dai_link() 函数进行匹配绑定 DAI 操作,其中 soc_bind_dai_link() 函数定义如下:

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_component *component;
	struct snd_soc_dai **codec_dais;
	int i;

	if (dai_link->ignore)
		return 0;

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

	//3.1.1 Check the dai_link 是否已经被绑定
	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;
	}

	//3.1.2 为该 dai_link 分配一个 snd_soc_pcm_runtime 实例 rtd
	rtd = soc_new_pcm_runtime(card, dai_link);
	if (!rtd)
		return -ENOMEM;

	//3.1.3 Find cpu_dai from dai_link cpu params & component_list cpu params
	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_info(card->dev, "ASoC: CPU DAI %s not registered\n",
			 dai_link->cpu_dai_name);
		goto _err_defer;
	}
	snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);

	//3.1.4 FInd codec_dais[] from dai_link codec params & component_list codec params
	rtd->num_codecs = dai_link->num_codecs;

	/* Find CODEC from registered CODECs */
	/* we can use for_each_rtd_codec_dai() after this */
	codec_dais = rtd->codec_dais;
	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);
			goto _err_defer;
		}
		snd_soc_rtdcom_add(rtd, codec_dais[i]->component);
	}

	/* Single codec links expect codec and codec_dai in runtime data */
	rtd->codec_dai = codec_dais[0];

	//3.1.5 Find platform component from dai_link platform params & component_list platform params
	/* find one from the set of registered platforms */
	for_each_component(component) {
		if (!snd_soc_is_matching_component(dai_link->platform,
						   component))
			continue;

		snd_soc_rtdcom_add(rtd, component);
	}

	//3.1.6 Add rtd -> card->rtd_list
	soc_add_pcm_runtime(card, rtd);
	return 0;

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

如代码所示,该函数首先会为 dai_link 分配一个 snd_soc_pcm_runtime 实例 rtd,在前面小节中已经介绍 platform & codec 驱动最终都会创建相应的 component 实例并插入到全局链表 component_list 中,此处则会根据 dai_link 参数匹配相应的 platform & codec,

根据 cpu_name/cpu_of_node/cpu_dai_name 从 component(cpu_dai_component) 中找到匹配的 snd_soc_dai cpu_dai(rtd->cpu_dai),并将该 cpu_dai 对应的 component 插入到 rtd->component_list 链表中;
根据 codec_name/codec_of_node/codec_dai_name 从 component(codec_dai component) 中找到匹配的 snd_soc_dai codec_dais(rtd->codec_dais[]),并将该 codec_dais 对应的 component 插入到 rtd->component_list 链表中;
根据 platform_name/platform_of_node 从 component(platform driver component) 【例如前面的 mtk_afe_pcm_platform】中找到匹配的 component,并将该 component 插入到 rtd->component_list 链表中。

最后调用 soc_add_pcm_runtime() 函数将该 rtd 插入到 card->rtd_list 中。

3.2 New a sound card

紧接着调用标准 alsa core 函数创建声卡实例,代码如下:

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

3.3 Create card new widgets

紧接着调用 snd_soc_dapm_new_controls() 函数创建 card->dapm_widgets,如下代码可知,定义该 widget 可以通知直接在代码中定义 dapm_widgets,也可以通过在 dts 文件中创建(该函数详解解析属于 DAPM,见后面章节)

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);

3.4 Probe all component used by DAI links

紧接着遍历 card->rtd_list 中的所有 rtd 调用 soc_probe_link_components() 函数,代码如下:

/* probe all components used by DAI links on this card */
	for_each_comp_order(order) {
		for_each_card_rtds(card, rtd) {
			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;
			}
		}
	}

其中 soc_probe_link_components() 函数定义如下:

static int soc_probe_link_components(struct snd_soc_card *card,
				     struct snd_soc_pcm_runtime *rtd, int order)
{
	struct snd_soc_component *component;
	struct snd_soc_rtdcom_list *rtdcom;
	int ret;

	// 3.4.1 遍历该 rtd->component_list, 为所有用到的 components 调用 soc_probe_component() 函数
	for_each_rtdcom(rtd, rtdcom) {
		component = rtdcom->component;

		if (component->driver->probe_order == order) {
			ret = soc_probe_component(card, component);
			if (ret < 0)
				return ret;
		}
	}

	return 0;
}

如上代码所示,该函数会遍历该 rtd->component_list, 为所有用到的 components 调用 soc_probe_component() 函数,定义如下:

static int soc_probe_component(struct snd_soc_card *card,
	struct snd_soc_component *component)
{
	// dapm context 详见后面章节
	struct snd_soc_dapm_context *dapm =
			snd_soc_component_get_dapm(component);
	struct snd_soc_dai *dai;
	int ret;

	if (!strcmp(component->name, "snd-soc-dummy"))
		return 0;

	...

	if (!try_module_get(component->dev->driver->owner))
		return -ENODEV;

	component->card = card;
	dapm->card = card;
	soc_set_name_prefix(card, component);

	soc_init_component_debugfs(component);

	// 3.4.1.1 创建 component->driver->dai_widgets
	if (component->driver->dapm_widgets) {
		ret = snd_soc_dapm_new_controls(dapm,
					component->driver->dapm_widgets,
					component->driver->num_dapm_widgets);

		if (ret != 0) {
			dev_err(component->dev,
				"Failed to create new controls %d\n", ret);
			goto err_probe;
		}
	}

	// 3.4.1.2 遍历 component->dai_list 创建 dai widgets
	for_each_component_dais(component, dai) {
		ret = snd_soc_dapm_new_dai_widgets(dapm, dai);
		if (ret != 0) {
			dev_err(component->dev,
				"Failed to create DAI widgets %d\n", ret);
			goto err_probe;
		}
	}

	// 3.4.1.3 调用 component->driver->probe() 函数
	if (component->driver->probe) {
		ret = component->driver->probe(component);
		if (ret < 0) {
			dev_err(component->dev,
				"ASoC: failed to probe component %d\n", ret);
			goto err_probe;
		}

		WARN(dapm->idle_bias_off &&
			dapm->bias_level != SND_SOC_BIAS_OFF,
			"codec %s can not start from non-off bias with idle_bias_off==1\n",
			component->name);
	}

	...

	// 3.4.1.4 创建 component->driver->controls & dapm_routes
	if (component->driver->controls)
		snd_soc_add_component_controls(component,
					       component->driver->controls,
					       component->driver->num_controls);
	if (component->driver->dapm_routes)
		snd_soc_dapm_add_routes(dapm,
					component->driver->dapm_routes,
					component->driver->num_dapm_routes);

	// 3.4.1.5 将该 dapm text(即 component dapm text) 插入到 card->dapm_list 中
	list_add(&dapm->list, &card->dapm_list);
	/* see for_each_card_components */
	list_add(&component->card_list, &card->component_dev_list);

	return 0;

...

	return ret;
}

如代码所示,该函数主要是实现对匹配的 platform/codec component 涉及的 DAPM、kcontrol 相关操作(详见后面 DAPM 章节),并调用 component->driver->probe(),该 probe() 函数主要也是涉及 DAPM、kcontrol 操作等。

3.5 Probe all DAI links on this card

紧接着遍历 card->rtd_list 中的所有 rtd 调用 soc_probe_link_dais() 函数,代码如下:

/* probe all DAI links on this card */
	for_each_comp_order(order) {
		for_each_card_rtds(card, rtd) {
			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_probe_link_dais() 函数定义如下:

static int soc_probe_link_dais(struct snd_soc_card *card,
		struct snd_soc_pcm_runtime *rtd, int order)
{
	struct snd_soc_dai_link *dai_link = rtd->dai_link;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	struct snd_soc_rtdcom_list *rtdcom;
	struct snd_soc_component *component;
	struct snd_soc_dai *codec_dai;
	int i, ret, num;

	...

	// 3.5.1 调用 rtd->cpu_dai->driver->probe() 函数
	ret = soc_probe_dai(cpu_dai, order);
	if (ret)
		return ret;

	// 3.5.2 调用 rtd->codec_dais[]->driver->probe() 函数
	/* probe the CODEC DAI */
	for_each_rtd_codec_dai(rtd, i, codec_dai) {
		ret = soc_probe_dai(codec_dai, order);
		if (ret)
			return ret;
	}
	...

	// 3.5.3 创建 pcm device
	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;
		}
	
		// 3.5.4 调用 cpu_dai/codec_dais[]->driver->pcm_new() 函数
		ret = soc_link_dai_pcm_new(&cpu_dai, 1, rtd);
		if (ret < 0)
			return ret;
		ret = soc_link_dai_pcm_new(rtd->codec_dais,
					   rtd->num_codecs, rtd);
		if (ret < 0)
			return ret;
	} else {
		INIT_DELAYED_WORK(&rtd->delayed_work,
					codec2codec_close_delayed_work);
	}

	return 0;
}

如上述函数定义所示,该函数首先会调用各个 rtd->cpu_dai/codec_dais[]->driver->probe() 函数,接着调用 soc_new_pcm() 函数用于 create the pcm,该函数定义如下:

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

	// 3.5.3.1 Check rtd 是否支持 playback & capture
	// DPCM:
	//		FE: rtd->dai_link->dynamic
	//		BE: rtd->dai_link->no_pcm
	// No DPCM:
	//		codec_dai&cpu_dai
	if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {
		playback = rtd->dai_link->dpcm_playback;
		capture = rtd->dai_link->dpcm_capture;
	} else {
		for_each_rtd_codec_dai(rtd, i, codec_dai) {
			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;
	}

	// 3.5.3.2 create the pcm
	// 由于 FE->BE ==> BE 调用 snd_pcm_new_internal() 不会创建 user device, 自然后面也就不需要填充 ops 操作集给到 user
	/* 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;
	}
	
	...

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

	// 3.5.1.3 for BE/ No DPCM 设置 ops 操作集
	/* 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;
	}

	for_each_rtdcom(rtd, rtdcom) {
		const struct snd_pcm_ops *ops = rtdcom->component->driver->ops;

		if (!ops)
			continue;

		if (ops->ack)
			rtd->ops.ack		= soc_rtdcom_ack;
		if (ops->copy_user)
			rtd->ops.copy_user	= soc_rtdcom_copy_user;
		if (ops->copy_kernel)
			rtd->ops.copy_kernel	= soc_rtdcom_copy_kernel;
		if (ops->fill_silence)
			rtd->ops.fill_silence	= soc_rtdcom_fill_silence;
		if (ops->page)
			rtd->ops.page		= soc_rtdcom_page;
		if (ops->mmap)
			rtd->ops.mmap		= soc_rtdcom_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);

	//3.5.1.4 调用 rtd 下所有 component->driver_pcm_new()
	for_each_rtdcom(rtd, rtdcom) {
		component = rtdcom->component;

		if (!component->driver->pcm_new)
			continue;

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

	pcm->private_free = soc_pcm_private_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;
}

如上所示该函数首先会调用标准 alsa core 函数,如下:

  • DPCM BE:由于 FE->BE,在操作 FE 时会同时操作与之 connect 的 BE,故不会对 BE 创建 user pcm device,即调用 snd_pcm_new_internal() 函数;
  • DPCM FE:调用 snd_pcm_new() 创建声卡 pcm device,并且 FE Device Name:("%s(*)", dai_link->stream_name) (故有 '*' 代表为 FE 可以动态连接 BE);
  • No DPCM:调用 snd_pcm_new() 创建声卡 pcm device,并且 No DPCM Device Name:("%s %s-%d", dai_link->stream, "multicodec"/codec_dai->name, num)(故无 '*' 代表 No NDPCM)

从上述代码中可以看出,对于 DPCM FE 和 No DPCM 对应 rtd->ops(snd_soc_ops) 字段赋值也不一样,由于 FE 的操作函数中需要涉及对 Connect’s BE 的操作;

在初始化 rtd->ops 时还会根据 platform driver 中的 snd_pcm_ops 填充相应的字段,如 ack、copy_user 等,最终会调用标准 alsa core 函数 snd_pcm_set_ops() 将 rtd->ops 设置为 substream->ops.

# Note:在 rtd->ops 字段中,如 open、hw_params、prepare 等回调函数中最终都会调用到 platform_driver & cpu_dai driver 以及 connect BE 对应的 codec driver 对应的 ops 字段相应的回调函数。

最后,该函数还会调用 rtd 下 component->driver->pcm_new() (主要是 platform driver pcm_new())函数,如前面 platform driver 小节讲解,此函数主要是预分配 DMA Memory,最后真正分配 DMA Memory 是在 Platform Driver/Cpu Dai Driver hw_params 中。

3.6 DAPM 相关操作

接着会调用 card dapm、route 相关操作(详细见后面章节)

	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);
	...
	snd_soc_dapm_new_widgets(card);

3.7 Register Sound Card

snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
		 "%s", card->name);
	snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
		 "%s", card->long_name ? card->long_name : card->name);
	snprintf(card->snd_card->driver, sizeof(card->snd_card->driver),
		 "%s", card->driver_name ? card->driver_name : card->name);
	for (i = 0; i < ARRAY_SIZE(card->snd_card->driver); i++) {
		switch (card->snd_card->driver[i]) {
		case '_':
		case '-':
		case '\0':
			break;
		default:
			if (!isalnum(card->snd_card->driver[i]))
				card->snd_card->driver[i] = '_';
			break;
		}
	}

	...

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

	card->instantiated = 1;

如上代码所示,在该函数最后先填充 snd_card 的重要参数,如 shortname、longname、driver 等,最后调用标准 alsa core 函数 snd_card_register() 来注册声卡以及注册该声卡下的所有 snd_devices.

至此,整个 Machine 驱动的初始化已经完成,通过各个子结构 probe 调用等,实际上,也完成了部分 Platform 驱动和 Codec 驱动的初始化工作,整个过程可以用以下的序列图表示:
在这里插入图片描述

四、创建 Pcm Device 节点

snd_soc_dai_link mt2701_wm8960_dai_link 定义如下:

static struct snd_soc_dai_link mt2701_wm8960_dai_links[] = {
	/* FE */
	{
		.name = "wm8960-playback",
		.stream_name = "wm8960-playback",
		.cpu_dai_name = "PCMO0",
		.codec_name = "snd-soc-dummy",
		.codec_dai_name = "snd-soc-dummy-dai",
		.trigger = {SND_SOC_DPCM_TRIGGER_POST,
			    SND_SOC_DPCM_TRIGGER_POST},
		.dynamic = 1,
		.dpcm_playback = 1,
	},
	{
		.name = "wm8960-capture",
		.stream_name = "wm8960-capture",
		.cpu_dai_name = "PCM0",
		.codec_name = "snd-soc-dummy",
		.codec_dai_name = "snd-soc-dummy-dai",
		.trigger = {SND_SOC_DPCM_TRIGGER_POST,
			    SND_SOC_DPCM_TRIGGER_POST},
		.dynamic = 1,
		.dpcm_capture = 1,
	},
	/* BE */
	{
		.name = "wm8960-codec",
		.cpu_dai_name = "I2S0",
		.no_pcm = 1,
		.codec_dai_name = "wm8960-hifi",
		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS
			| SND_SOC_DAIFMT_GATED,
		.ops = &mt2701_wm8960_be_ops,
		.dpcm_playback = 1,
		.dpcm_capture = 1,
	},
};

根据上面 Machine 驱动中调用 soc_new_pcm() 创建 pcm device 节点后如下:
在这里插入图片描述
参考链接:
linux-alsa详解7 ASOC-machine

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux ALSA 之十:ALSA ASOC Machine Driver 的相关文章

  • 在 /dev/input/eventX 中写入事件需要哪些命令?

    我正在开发一个android需要将触摸事件发送到 dev input eventX 的应用程序 我知道C执行此类操作的代码结构如下 struct input event struct timeval time unsigned short
  • GCC 和 ld 找不到导出的符号...但它们在那里

    我有一个 C 库和一个 C 应用程序 尝试使用从该库导出的函数和类 该库构建良好 应用程序可以编译 但无法链接 我得到的错误遵循以下形式 app source file cpp text 0x2fdb 对 lib namespace Get
  • Godaddy 托管上的 CakePHP 控制台

    我一直在努力让我的 CakePHP 网站在 Godaddy 网格托管 帐户上运行 我的蛋糕应用程序设置是从帐户的子目录托管的 并且可以通过子域访问 我必须调整我的 htaccess 文件才能使其正常工作 现在我需要让 CakePHP 控制台
  • xsel -o 对于 OS X 等效项

    是否有一个等效的解决方案可以在 OS X 中抓取选定的文本 就像适用于 Linux 的 xsel o 一样 只需要当前的选择 这样我就可以在 shell 脚本中使用文本 干杯 埃里克 你也许可以安装xsel在 MacOS 上 更新 根据 A
  • 修改linux下的路径

    虽然我认为我已经接近 Linux 专业人士 但显然我仍然是一个初学者 当我登录服务器时 我需要使用最新版本的R 统计软件 R 安装在 2 个地方 当我运行以下命令时 which R I get usr bin R 进而 R version
  • 是否可以在Linux上将C转换为asm而不链接libc?

    测试平台为Linux 32位 但也欢迎 Windows 32 位上的某些解决方案 这是一个c代码片段 int a 0 printf d n a 如果我使用 gcc 生成汇编代码 gcc S test c 然后我会得到 movl 0 28 e
  • 创建 jar 文件 - 保留文件权限

    我想知道如何创建一个保留其内容的文件权限的 jar 文件 我将源代码和可执行文件打包在一个 jar 文件中 该文件将在使用前提取 人们应该能够通过运行批处理 shell 脚本文件立即运行示例和演示 然后他们应该能够修改源代码并重新编译所有内
  • Bash 解析和 shell 扩展

    我对 bash 解析输入和执行扩展的方式感到困惑 对于输入来说 hello world 作为 bash 中的参数传递给显示其输入内容的脚本 我不太确定 Bash 如何解析它 Example var hello world displaywh
  • 强制卸载 NFS 安装目录 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的答案
  • Linux 中的无缓冲 I/O

    我正在写入大量的数据 这些数据数周内都不会再次读取 由于我的程序运行 机器上的可用内存量 显示为 空闲 或 顶部 很快下降 我的内存量应用程序使用量不会增加 其他进程使用的内存量也不会增加 这让我相信内存正在被文件系统缓存消耗 因为我不打算
  • 从 csv 文件中删除特定列,保持输出上的相同结构[重复]

    这个问题在这里已经有答案了 我想删除第 3 列并在输出文件中保留相同的结构 输入文件 12 10 10 10 10 1 12 23 1 45 6 7 11 2 33 45 1 2 1 2 34 5 6 I tried awk F 3 fil
  • 仅打印“docker-container ls -la”输出中的“Names”列

    发出时docker container ls la命令 输出如下所示 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a67f0c2b1769 busybox tail f dev
  • Jenkins中找不到环境变量

    我想在詹金斯中设置很多变量 我试过把它们放进去 bashrc bash profile and profile of the jenkins用户 但 Jenkins 在构建发生时找不到它们 唯一有效的方法是将所有环境变量放入Jenkinsf
  • 为什么 Linux perf 使用事件 l1d.replacement 来处理 x86 上的“L1 dcache misses”?

    在英特尔 x86 上 Linux用途 https stackoverflow com a 52172985 149138事件l1d replacements来实施其L1 dcache load misses event 该事件定义如下 计数
  • 无法从 jenkins 作为后台进程运行 nohup 命令

    更新 根据下面的讨论 我编辑了我的答案以获得更准确的描述 我正在尝试从詹金斯运行 nohup 命令 完整的命令是 nohup java jar home jar server process 0 35 jar prod gt gt var
  • 在哪里可以找到并安装 pygame 的依赖项?

    我对 Linux 比较陌生 正在尝试安装 python 的 pygame 开发环境 当我运行 setup py 时 它说我需要安装以下依赖项 我找到并安装了其中之一 SDL 然而 其他人则更加难以捉摸 Hunting dependencie
  • 如何在数组中存储包含双引号的命令参数?

    我有一个 Bash 脚本 它生成 存储和修改数组中的值 这些值稍后用作命令的参数 对于 MCVE 我想到了任意命令bash c echo 0 0 echo 1 1 这解释了我的问题 我将用两个参数调用我的命令 option1 without
  • 如何有效截断文件头?

    大家都知道truncate file size 函数 通过截断文件尾部将文件大小更改为给定大小 但是如何做同样的事情 只截断文件的尾部和头部呢 通常 您必须重写整个文件 最简单的方法是跳过前几个字节 将其他所有内容复制到临时文件中 并在完成
  • Discord.net 无法在 Linux 上运行

    我正在尝试让在 Linux VPS 上运行的 Discord net 中编码的不和谐机器人 我通过单声道运行 但我不断收到此错误 Unhandled Exception System Exception Connection lost at
  • 如何在 Linux shell 中将十六进制转换为 ASCII 字符?

    假设我有一个字符串5a 这是 ASCII 字母的十六进制表示Z 我需要找到一个 Linux shell 命令 它将接受一个十六进制字符串并输出该十六进制字符串代表的 ASCII 字符 所以如果我这样做 echo 5a command im

随机推荐

  • Rsa读取pem公钥和私钥

    1 把pem文件用text打开 xff0c 把开头和结尾的begin和end内容删去 xff0c 只留下字符串内容 xff1b 2 用流读取文件中的字符串 xff1b 3 用以下代码生成公钥和私钥对象 xff1b public static
  • 当 rsync/scp 不可用时,从系统传输文件或者 sosreport。

    环境 红帽企业 Linux 5 x红帽企业 Linux 6 x红帽企业 Linux 7 x 问题 文件需要转移到另一个系统 xff0c 但是ssh和rsync未设置和 或允许 一个sosreport需要转移到另一个系统 红帽企业 Linux
  • 如何配置Linux操作系统内DNS配置不被DHCP自动更新

    问题原因 NetworkManager是一个软件工具 xff0c 旨在简化计算机网络的使用 NetworkManager可用于基于Linux内核和其他类Unix操作系统 目前主流Linux操作系统都使用NetworkManager进行网络自
  • 2021-09-27

    虚拟环境中用pip下载安装包却安装到base环境解决方案 原因解决方案 遇到的问题 xff1a windows环境下进入虚拟环境 xff0c 使用pip install指令安装包时发现没有安装到虚拟环境下 xff0c 而是安装到了base环
  • 网络服务(Neutron)安装配置 ,这一篇就够了!

    实验目标 OpenStack Networking xff08 neutron xff09 xff0c 允许创建 插入接口设备 xff0c 这些设备由其它的OpenStack服务管理 插件式的实现可以容纳不同的网络设备和软件 xff0c 为
  • ssh服务和日志的基本常识

    查看网桥 brctl show 创建链接 ln s mnt dir file mnt lianjie 1 查看进程 gnome system monltor ps 当前shell的进程 A 所有的进程 a shell中所有的进程 xff0c
  • ESP8266基础详细使用

    前言 刚买一块ESP8266 琢磨一天才弄明白怎么使用 xff0c 小白第一次弄这个确实不太友好 xff0c 这里记录一下 xff0c 怕以后自己用到又忘了 xff0c 在物联网这一方面ESP8266还是特别实用的 材料准备 淘宝 xff0
  • nvm安装流程

    nvm nvm是管理node版本的工具 一般我们会负责多个项目 xff0c 不同项目有不同版本的node环境 xff0c 此时就需要nvm对node版本进行切换处理 1 首先卸载node 2 nvm下载 安装包下载地址 xff1a http
  • 树莓派 raspbian (各版本)换国内源

    xff08 看到师兄的博客后感觉还行 xff0c 所以自己也来写下 xff0c 第一次写 xff0c 所以不会编排 xff0c 有什么错误希望被指出 xff0c 谢谢 xff09 相信来寻找换源的人都和一样知道为什么要换国内更新源了吧 xf
  • 树莓派4b的i2c配置及wiringPi通信

    一 配置i2c设备 1 xff09 在终端中操作 xff0c 输入指令 sudo raspi config 2 xff09 然后会出现设置界面 xff0c 然后跟着如下图片操作 第一项 xff1a Change User Password
  • 树莓派驱动之设备树覆盖

    一 前言 由于是初学者 xff0c 所以对于一些操作需要记录下方便自己查找 附上 xff1a 树莓派设备树官网 我只从官网上了解到一点点内容 xff0c 还有许多没看懂的和还没学的 一个常规的Arm Linux设备树 xff0c 主要是由源
  • ConcurrentHashMap 常用方法

    void clear 从该映射中移除所有映射关系 boolean containsKey Object key 测试指定对象是否为此表中的键 boolean containsValue Object value 如果此映射将一个或多个键映射
  • .vscode中常用的配置文件

    文件 一 安装常用插件二 c cpp properties json文件三 settings json文件 一 安装常用插件 根据自己需要安装相应的插件 xff1a span class token number 1 span span c
  • 操作系统相关知识

    目录 一 嵌入式操作系统二 实时操作系统 xff08 RTOS xff09 三 Freertos 操作系统四 Linux xff08 操作系统 xff09 五 Linux 和 FreeRTOS操作系统的区别 xff08 面试中被问到 xff
  • 结构体对齐(全)

    目录 一 结构体对齐规则二 结构体位域对齐规则 一 结构体对齐规则 1 第一个成员在与结构体偏移量为0的地址处 xff1b 2 其他成员变量要与自身类型的整数倍地址处对齐 xff1b 3 结构体总大小为要与 处理器字节数与成员类型所占字节数
  • C语言自我实现模块化打印log

    在一个嵌入式稍微大些的工程中实现模块化控制打印输出信息是很有必要的 xff0c 下面是模仿别人的实现的模块化打印 xff0c 需要时可以根据下面的实现代码去修改满足自己所需要的 xff01 xff01 xff01 span class to
  • 卸载ibus后无法进入桌面的解决方法

    过程 一 复现现象 xff1a 二 复现原因 xff1a 三 解决方法 xff1a 重新安装ubuntu桌面 一 复现现象 xff1a 开机进入 Ubuntu xff0c 输入密码成功后一直卡在这个页面 xff0c 无法进入 ubuntu
  • Linux内核之 printk 打印

    Linux内核之 printk 打印 前言一 printk 介绍1 printk 消息级别2 内核 printk 文件 二 调整打印级别1 在 menuconfig 中修改2 在系统中修改 xff08 常用 xff09 三 使用示例四 查看
  • Linux ALSA 之四:Tinyalsa->Alsa Driver Flow分析

    Tinyalsa gt Alsa Driver Flow 一 概述二 Tinyalsa2 1 tinypcminfo2 2 tinymix2 3 tinyplay2 4 tinycap 三 Tinyalsa gt alsa driver f
  • Linux ALSA 之十:ALSA ASOC Machine Driver

    ALSA ASOC Machine Driver 一 Machine 简介二 ASoC Machine Driver2 1 Machine Driver 的 Platform Driver amp Platform Device 驱动模型2