一、 ALSA 音频体系介绍

ALSA 是 Advanced Linux Sound Architecture 的缩写,目前已经成为了Linux 的主流音频体系结构。

在内核驱动层,ALSA 提供了 alsa-driver,
在应用层,ALSA 为我们提供了 alsa-lib ,应用程序只要调用 alsa-lib 提供的API ,即可完成对音频硬件的控制。

在这里插入图片描述

\src\kernel\msm-3.18\sound\core  
	--->	目录中主要包含ALSA驱动的中间层,它是整个ALSA驱动的核心部份。
	
\src\kernel\msm-3.18\sound\soc
	--->	目录中主要包含针对system-on-chip体系的中间层代码,/soc/codecs 针对soc体系的各种codec的代码,与平台无关。

1. ASoC介绍(Machine 、Platform 和 Codec)

在这里插入图片描述
ASoc 把音频系统分为三大部份: Machine 、Platform 和 Codec。

  1. Platform 平台
    一般是指某个SOC平台,比如 MT6752,MSM8937,SDM670 等等,与音频相关的通常包含该Soc 中的Clock、FAE、I2S、DMA等。

  2. Codec 编解码器
    Codec 里面包含了 I2C 接口,DAC,ADC,Mixer,PA(功放),通常包含多种输入(MIC,Line-in,I2S,PCM) 和多个输出(耳机,喇叭,听筒,Line-out),Codec 和 platform 一样,是可得用的

  3. Machine 绑定 Platform driver 和 Codec driver


2. AFE介绍(Audio Front-End 前端后端)

AFE(Audio Front-End) 的前端一般是指输入设备,后端一般是指输出设备。
如下图是某平台的 afe_interconn 内部框图:

红色的 I_0 到 I_16 是指输入端口,也就是Audio 前端。
绿色的 O_0 到 O_27 就是输出端口,一般指 Audio 后端。

这样前端后端就有 很多种组合,可以有很多的 Path,我们目录代码中用到的有 23 种path:
比如:
播放: 可能是 前端 I5/I5 连接到 后端 O3/O4
录音: 可能是 前端 I3/I4 连接到 后端 O9/O10

在这里插入图片描述


3. snd_soc_dai_link (Platform 和 Codec 联系在一起的结构体)

在snd_soc_dai_link 结构体中,主要描述 Platform 和 Codec 各自的信息,及该AFE 作的功能,是playback 还是 capture,或者都支持。

snd_soc_dai_link 结构体描述如下:

@ \kernel\msm-3.18\include\sound\soc.h

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;	// CPU 的名字
	struct device_node *cpu_of_node;	//CPU的DTS 节点
	/*
	 * 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;	// CPU dai 的名字
	/*
	 * You MUST specify the link's codec, either by device name, or by
	 * DT/OF node, but not both.
	 */
	const char *codec_name;	// codec 的名字
	struct device_node *codec_of_node;	// codec 的dts 节点
	/* You MUST specify the DAI name within the codec */
	const char *codec_dai_name;	// codec dai 的名字

	struct snd_soc_dai_link_component *codecs;	
	unsigned int num_codecs;	// 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;

	/* This DAI can support no host IO (no pcm data is copied to from host) */
	unsigned int no_host_mode:2;

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

	/* this value determines what all ops can be started asynchronously */
	enum snd_soc_async_ops async_ops;
};

参考代码如下:

@ \kernel\msm-3.18\sound\soc\msm\msm8952.c

/* Digital audio interface glue - connects codec <---> CPU */
static struct snd_soc_dai_link msm8952_dai[] = {
	/* FrontEnd DAI Links */
	{/* hw:x,0 */
		.name = "MSM8952 Media1",
		.stream_name = "MultiMedia1",
		.cpu_dai_name	= "MultiMedia1",
		.platform_name  = "msm-pcm-dsp.0",
		.dynamic = 1,
		.async_ops = ASYNC_DPCM_SND_SOC_PREPARE,
		.dpcm_playback = 1,
		.dpcm_capture = 1,
		.trigger = {SND_SOC_DPCM_TRIGGER_POST,
			SND_SOC_DPCM_TRIGGER_POST},
		.codec_dai_name = "snd-soc-dummy-dai",
		.codec_name = "snd-soc-dummy",
		.ignore_suspend = 1,
		/* this dainlink has playback support */
		.ignore_pmdown_time = 1,
		.be_id = MSM_FRONTEND_DAI_MULTIMEDIA1
	},
	{/* hw:x,2 */
		.name = "Circuit-Switch Voice",
		.stream_name = "CS-Voice",
		.cpu_dai_name   = "CS-VOICE",
		.platform_name  = "msm-pcm-voice",
		.dynamic = 1,
		.dpcm_playback = 1,
		.dpcm_capture = 1,
		.trigger = {SND_SOC_DPCM_TRIGGER_POST,
			SND_SOC_DPCM_TRIGGER_POST},
		.no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
		.ignore_suspend = 1,
		/* this dainlink has playback support */
		.ignore_pmdown_time = 1,
		.be_id = MSM_FRONTEND_DAI_CS_VOICE,
		.codec_dai_name = "snd-soc-dummy-dai",
		.codec_name = "snd-soc-dummy",
	},

4. Platform DAI 平台驱动 msm_soc_platform (以 platform_name = “msm-pcm-voice” 举例)

在 snd_soc_dai_link 中的每一个paltform 都有对应着各自的Driver 驱动,
我们以 .platform_name = “msm-pcm-voice”, 来举例:
有关 msm-pcm-voice 的驱动如下:

@ \kernel\msm-3.18\sound\soc\msm\qdsp6v2\msm-pcm-voice-v2.c

// platform name 是在 msm_pcm_driver 中的 .name 中定义的:
static struct platform_driver msm_pcm_driver = {
	.driver = {
		.name = "msm-pcm-voice",
		.owner = THIS_MODULE,
		.of_match_table = msm_voice_dt_match,
	},
	.probe = msm_pcm_probe,
	.remove = msm_pcm_remove,
};

// msm-pcm-voice 这个 paltfrom 对应的 dts 的节点名字为:  qcom,msm-pcm-voice
static const struct of_device_id msm_voice_dt_match[] = {
	{.compatible = "qcom,msm-pcm-voice"},
	{}
};

// 在 probe 代码中,调用 snd_soc_register_platform 来注册,msm_soc_platform ,
// 里面包含了 audio platform driver相关的 方法
static int msm_pcm_probe(struct platform_device *pdev)
{
	rc = snd_soc_register_platform(&pdev->dev,
				       &msm_soc_platform);
}


static struct snd_soc_platform_driver msm_soc_platform = {
	.ops		= &msm_pcm_ops,			// playback / capture 相关的操作函数
	.pcm_new	= msm_asoc_pcm_new,
	.probe		= msm_pcm_voice_probe,	//当paltform  和 codec 匹配成功后,会调用该函数
};

static struct snd_pcm_ops msm_pcm_ops = {
	.open			= msm_pcm_open,
	.hw_params		= msm_pcm_hw_params,
	.close			= msm_pcm_close,
	.prepare		= msm_pcm_prepare,
	.trigger		= msm_pcm_trigger,
	.ioctl			= msm_pcm_ioctl,
	.compat_ioctl		= msm_pcm_ioctl,
};

static int msm_pcm_voice_probe(struct snd_soc_platform *platform)
{
	snd_soc_add_platform_controls(platform, msm_voice_controls,
					ARRAY_SIZE(msm_voice_controls));

	return 0;
}

// 在platform 和 codec 匹配后,会过入 msm_pcm_voice_probe 函数
//  在该函数中主要是注册了 一系列 该platform 所支持的一些控件,用于配置 audio 的各属性,各控件对应着不同的操作函数。
// 这些控件 对应着 hal 层 xml 中的配置。
static struct snd_kcontrol_new msm_voice_controls[] = {
	// 配置 rx  静音
	SOC_SINGLE_MULTI_EXT("Voice Rx Device Mute", SND_SOC_NOPM, 0, VSID_MAX,0, 3, NULL, msm_voice_rx_device_mute_put),
	SOC_SINGLE_MULTI_EXT("Voice Tx Device Mute", SND_SOC_NOPM, 0, VSID_MAX,0, 3, NULL, msm_voice_tx_device_mute_put),
	SOC_SINGLE_MULTI_EXT("Voice Tx Mute", SND_SOC_NOPM, 0, VSID_MAX,0, 3, NULL, msm_voice_mute_put),
	// 配置 rx 增益
	SOC_SINGLE_MULTI_EXT("Voice Rx Gain", SND_SOC_NOPM, 0, VSID_MAX, 0, 3,NULL, msm_voice_gain_put),
	SOC_ENUM_EXT("TTY Mode", msm_tty_mode_enum[0], msm_voice_tty_mode_get,msm_voice_tty_mode_put),
	SOC_SINGLE_MULTI_EXT("Slowtalk Enable", SND_SOC_NOPM, 0, VSID_MAX, 0, 2,NULL, msm_voice_slowtalk_put),
	SOC_SINGLE_MULTI_EXT("Voice Topology Disable", SND_SOC_NOPM, 0,VSID_MAX, 0, 2, NULL,msm_voice_topology_disable_put),
	SOC_SINGLE_MULTI_EXT("HD Voice Enable", SND_SOC_NOPM, 0, VSID_MAX, 0, 2,NULL, msm_voice_hd_voice_put),
	{
		.access = SNDRV_CTL_ELEM_ACCESS_READ,
		.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
		.name	= "CVD Version",
		.info	= msm_voice_cvd_version_info,
		.get	= msm_voice_cvd_version_get,
	},
};

5. CPU DAI 驱动 snd_soc_dai_driver ( 以 cpu_dai_name = “CS-VOICE” 举例)

可以看到 在 snd_soc_dai_driver 驱动中,主要定义了,playback / capture 相关的参数,
一般所有的CPU DAI 在同一个文件中。

@ \src\kernel\msm-3.18\sound\soc\msm\msm-dai-fe.c

static struct snd_soc_dai_driver msm_fe_dais[] = {
	{
		.playback = {
			.stream_name = "MultiMedia1 Playback",
			.aif_name = "MM_DL1",
			.rates = (SNDRV_PCM_RATE_8000_384000|
					SNDRV_PCM_RATE_KNOT),
			.formats = (SNDRV_PCM_FMTBIT_S16_LE |
				    SNDRV_PCM_FMTBIT_S24_LE |
				    SNDRV_PCM_FMTBIT_S24_3LE |
				    SNDRV_PCM_FMTBIT_S32_LE),
			.channels_min = 1,
			.channels_max = 8,
			.rate_min =     8000,
			.rate_max =     384000,
		},
		.capture = {
			.stream_name = "MultiMedia1 Capture",
			.aif_name = "MM_UL1",
			.rates = (SNDRV_PCM_RATE_8000_192000|
					SNDRV_PCM_RATE_KNOT),
			.formats = (SNDRV_PCM_FMTBIT_S16_LE |
				    SNDRV_PCM_FMTBIT_S24_LE |
				    SNDRV_PCM_FMTBIT_S24_3LE |
				    SNDRV_PCM_FMTBIT_S32_LE),
			.channels_min = 1,
			.channels_max = 8,
			.rate_min =     8000,
			.rate_max =     48000,
		},
		.ops = &msm_fe_Multimedia_dai_ops,
		.name = "MultiMedia1",
		.probe = fe_dai_probe,
	},

	{
		.playback = {
			.stream_name = "CS-VOICE Playback",
			.aif_name = "CS-VOICE_DL1",
			.rates = SNDRV_PCM_RATE_8000_48000,
			.formats = SNDRV_PCM_FMTBIT_S16_LE,
			.channels_min = 1,
			.channels_max = 2,
			.rate_min =     8000,
			.rate_max =     48000,
		},
		.capture = {
			.stream_name = "CS-VOICE Capture",
			.aif_name = "CS-VOICE_UL1",
			.rates = SNDRV_PCM_RATE_8000_48000,
			.formats = SNDRV_PCM_FMTBIT_S16_LE,
			.channels_min = 1,
			.channels_max = 2,
			.rate_min =     8000,
			.rate_max =     48000,
		},
		.ops = &msm_fe_dai_ops,
		.name = "CS-VOICE",
		.probe = fe_dai_probe,
	},

6. Codec DAI 驱动 ( 以 .codec_name = “snd-soc-dummy”, 举例)

snd-soc-dummy 是指默认的 codec dai 驱动:
其定义在,其中定义了,该codec 中所支持 Playback 和 Capture 的所有类型

@ \src\kernel\msm-3.18\sound\soc\soc-utils.c

static struct platform_driver soc_dummy_driver = {
	.driver = {
		.name = "snd-soc-dummy",
		.owner = THIS_MODULE,
	},
	.probe = snd_soc_dummy_probe,
	.remove = snd_soc_dummy_remove,
};

// 在 snd_soc_dummy_probe 中 注册 dummy_dai为codec dai。
static int snd_soc_dummy_probe(struct platform_device *pdev)
{
	memset(&dummy_codec, 0,sizeof(struct snd_soc_codec_driver));
	ret = snd_soc_register_codec(&pdev->dev, &dummy_codec, &dummy_dai, 1);
	ret = snd_soc_register_platform(&pdev->dev, &dummy_platform);
	return ret;
}

// Codec DAI 定义如下
#define STUB_RATES	SNDRV_PCM_RATE_8000_192000
#define STUB_FORMATS	(SNDRV_PCM_FMTBIT_S8 | \
			SNDRV_PCM_FMTBIT_U8 | \
			SNDRV_PCM_FMTBIT_S16_LE | \
			SNDRV_PCM_FMTBIT_U16_LE | \
			SNDRV_PCM_FMTBIT_S24_LE | \
			SNDRV_PCM_FMTBIT_U24_LE | \
			SNDRV_PCM_FMTBIT_S32_LE | \
			SNDRV_PCM_FMTBIT_U32_LE | \
			SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE)
static struct snd_soc_dai_driver dummy_dai = {
	.name = "snd-soc-dummy-dai",
	.playback = {
		.stream_name	= "Playback",
		.channels_min	= 1,
		.channels_max	= 384,
		.rates		= STUB_RATES,
		.formats	= STUB_FORMATS,
	},
	.capture = {
		.stream_name	= "Capture",
		.channels_min	= 1,
		.channels_max	= 384,
		.rates = STUB_RATES,
		.formats = STUB_FORMATS,
	 },
};


7. Machine驱动 ( 核心代码 soc-core.c)

soc-core.c 是ASoC架构最核心的一个源文件,soc_probe 函数是整个ALSA Kernel 部份的起点。

@ \src\kernel\msm-3.18\sound\soc\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,
};

/* probes a new socdev 注册声卡 */
static int soc_probe(struct platform_device *pdev)
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);
	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 int __init snd_soc_init(void)
{
	snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL);

	if (!debugfs_create_file("codecs", 0444, snd_soc_debugfs_root, NULL,&codec_list_fops))
		pr_warn("ASoC: Failed to create CODEC list debugfs file\n");

	debugfs_create_file("dais", 0444, snd_soc_debugfs_root, NULL,&dai_list_fops);

	if (!debugfs_create_file("platforms", 0444, snd_soc_debugfs_root, NULL,&platform_list_fops))
		pr_warn("ASoC: Failed to create platform list debugfs file\n");

	snd_soc_util_init();

	return platform_driver_register(&soc_driver);
}
module_init(snd_soc_init);

8. snd_soc_register_card() 初始化 snd_soc_pcm_runtime 用于绑定 codec 和 platform

Machine 代码中,核心为 snd_soc_pcm_runtime 结构体。
在 snd_soc_register_card 中,主要工作就是,
遍历所有的dai_link , 在snd_soc_pcm_runtime 结构体数组 中为所有 dai_link 申请内存,
把 snd_soc_card 中的 dai_link 、codec_dais 、platform 结构体 配置复制到相应的 snd_soc_pcm_runtime 结构体中
这样,machine 就把 所需要的 codec 和 platform 绑定。

@ \src\kernel\msm-3.18\sound\soc\soc-core.c
/**
 * snd_soc_register_card - Register a card with the ASoC core
 * @card: Card to register
 */
int snd_soc_register_card(struct snd_soc_card *card)
{
	for (i = 0; i < card->num_links; i++) {
		struct snd_soc_dai_link *link = &card->dai_link[i];
		ret = snd_soc_init_multicodec(card, link);
	}

	dev_set_drvdata(card->dev, card);
	snd_soc_initialize_card_lists(card);
	soc_init_card_debugfs(card);

	// 为 snd_soc_pcm_runtime 结构体数组申请内存,每个dai_link 对应 snd_soc_pcm_runtime 数组的一个单元。
	// 然后把 snd_soc_card 中的 dai_link 配置复制到相应的 snd_soc_pcm_runtime 中。
	card->rtd = devm_kzalloc(card->dev,
				 sizeof(struct snd_soc_pcm_runtime) *
				 (card->num_links + card->num_aux_devs),
				 GFP_KERNEL);

	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];  //把 snd_soc_card 中的 dai_link 配置复制到相应的 snd_soc_pcm_runtime 中
		card->rtd[i].codec_dais = devm_kzalloc(card->dev,sizeof(struct snd_soc_dai *) *(card->rtd[i].dai_link->num_codecs),
									GFP_KERNEL);
	}

	for (i = 0; i < card->num_aux_devs; i++)
		card->rtd_aux[i].card = card;

	ret = snd_soc_instantiate_card(card);
	
}
EXPORT_SYMBOL_GPL(snd_soc_register_card);

9. snd_soc_instantiate_card() -> snd_card_new() 创建 control 设备节点

snd_card 可以说是 整个ALSA 音频驱动最顶层的一个结构, 整个声卡的软件逻辑结构开始于该结构,
几乎所有的声音相关的逻辑设备都是在snd_card 的管理下的,
声卡驱动的第一个动作通常就是创建一个snd_card 结构体。

index —> 一个整数值,该声卡的编号
id —> 字符串,声卡的标识符
card —> 返回所创建的snd_card 实例的指针

@ \src\kernel\msm-3.18\sound\soc\soc-core.c

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

	/* bind DAIs */
	for (i = 0; i < card->num_links; i++) {
		ret = soc_bind_dai_link(card, i);
	}

	/* bind aux_devs too */
	for (i = 0; i < card->num_aux_devs; i++) {
		ret = soc_bind_aux_dev(card, 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);
	}

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


	card->dapm.bias_level = SND_SOC_BIAS_OFF;
	card->dapm.dev = card->dev;
	card->dapm.card = card;
	list_add(&card->dapm.list, &card->dapm_list);

#ifdef CONFIG_DEBUG_FS
	snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);
#endif

#ifdef CONFIG_PM_SLEEP
	/* deferred resume work */
	INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
#endif

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

	/* initialise the sound card only once */
	if (card->probe) {
		ret = card->probe(card);   // 声卡probe
		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); //
		}
	}

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

	for (i = 0; i < card->num_aux_devs; i++) {
		ret = soc_probe_aux_dev(card, i);
	}

	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);  //添加 kcontrol

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

	for (i = 0; i < card->num_links; i++) {
		struct snd_soc_pcm_runtime *rtd = &card->rtd[i];
		dai_link = &card->dai_link[i];
		dai_fmt = dai_link->dai_fmt;

		if (dai_fmt) {
			struct snd_soc_dai **codec_dais = rtd->codec_dais;
			int j;

			for (j = 0; j < rtd->num_codecs; j++) {
				struct snd_soc_dai *codec_dai = codec_dais[j];
				ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt);
			}
		}

		/* If this is a regular CPU link there will be a platform */
		if (dai_fmt && (dai_link->platform_name || dai_link->platform_of_node)) {
			ret = snd_soc_dai_set_fmt(card->rtd[i].cpu_dai,dai_fmt);
		} else if (dai_fmt) {
			/* Flip the polarity for the "CPU" end */
			dai_fmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
			switch (dai_link->dai_fmt &
				SND_SOC_DAIFMT_MASTER_MASK) {
			case SND_SOC_DAIFMT_CBM_CFM:
				dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
				break;
			case SND_SOC_DAIFMT_CBM_CFS:
				dai_fmt |= SND_SOC_DAIFMT_CBS_CFM;
				break;
			case SND_SOC_DAIFMT_CBS_CFM:
				dai_fmt |= SND_SOC_DAIFMT_CBM_CFS;
				break;
			case SND_SOC_DAIFMT_CBS_CFS:
				dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
				break;
			}

			ret = snd_soc_dai_set_fmt(card->rtd[i].cpu_dai,
						  dai_fmt);
		}
	}

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

	if (card->late_probe) {
		ret = card->late_probe(card);
	}

	if (card->fully_routed)
		snd_soc_dapm_auto_nc_pins(card);

	snd_soc_dapm_new_widgets(card);

	ret = snd_card_register(card->snd_card);  //注册声卡

#ifdef CONFIG_SND_SOC_AC97_BUS
	/* register any AC97 codecs */
	for (i = 0; i < card->num_rtd; i++) {
		ret = soc_register_ac97_dai_link(&card->rtd[i]);
	}
#endif

	card->instantiated = 1;
	snd_soc_dapm_sync(&card->dapm);

	return 0;

在 snd_card_new() 中: 创建 声卡的 control 设备( controlC0 )

/**
 *  snd_card_new - create and initialize a soundcard structure
 *  @parent: the parent device object
 *  @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
 *  @xid: card identification (ASCII string)
 *  @module: top level module for locking
 *  @extra_size: allocate this extra size after the main soundcard structure
 *  @card_ret: the pointer to store the created card instance
 *
 *  Creates and initializes a soundcard structure.
 *
 *  The function allocates snd_card instance via kzalloc with the given
 *  space for the driver to use freely.  The allocated struct is stored
 *  in the given card_ret pointer.
 *
 *  Return: Zero if successful or a negative error code.
 */
int snd_card_new(struct device *parent, int idx, const char *xid,
		    struct module *module, int extra_size,
		    struct snd_card **card_ret)
{
	*card_ret = NULL;

	if (extra_size < 0)
		extra_size = 0;
	card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);

	if (extra_size > 0)
		card->private_data = (char *)card + sizeof(struct snd_card);
	if (xid)
		strlcpy(card->id, xid, sizeof(card->id));

	card->dev = parent;
	card->number = idx;
	card->module = module;
	
	device_initialize(&card->card_dev);
	card->card_dev.parent = parent;
	card->card_dev.class = sound_class;
	card->card_dev.release = release_card_device;
	card->card_dev.groups = card_dev_attr_groups;
	err = kobject_set_name(&card->card_dev.kobj, "card%d", idx);


	/* the control interface cannot be accessed from the user space until */
	/* snd_cards_bitmask and snd_cards are set with snd_card_register */
	err = snd_ctl_create(card);

	err = snd_info_card_create(card);

	*card_ret = card;
	return 0;

}
EXPORT_SYMBOL(snd_card_new);

在 snd_ctl_create(card) 中


/*
 * create control core:
 * called from init.c
 */
int snd_ctl_create(struct snd_card *card)
{
	static struct snd_device_ops ops = {
		.dev_free = snd_ctl_dev_free,
		.dev_register =	snd_ctl_dev_register,
		.dev_disconnect = snd_ctl_dev_disconnect,
	};

	return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
}

10. Codec probe

11. soc_new_pcm

@ \src\kernel\msm-3.18\sound\soc\soc-core.c
static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order)
{
	ret = soc_new_pcm(rtd, num);
}

@\src\kernel\msm-3.18\sound\soc\soc-pcm.c
/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
	/* 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);
	}

可以发现在代码中,
会根据声卡号和设备索引号,依次创建 pcmC%iD%ip 和 pcmC%iD%ic 两个设备节点的名字。
接着,调用 snd_register_device_for_dev 来创建设备节点,传入换参数就是 设备节点的名字。

@\kernel\msm-3.18\sound\core\pcm.c

static int snd_pcm_dev_register(struct snd_device *device)
{
	pcm = device->device_data;
	err = snd_pcm_add(pcm);
	
	for (cidx = 0; cidx < 2; cidx++) {
		int devtype = -1;
		switch (cidx) {
		case SNDRV_PCM_STREAM_PLAYBACK:
			sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
			devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
			break;
		case SNDRV_PCM_STREAM_CAPTURE:
			sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
			devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
			break;
		}
		/* device pointer to use, pcm->dev takes precedence if
		 * it is assigned, otherwise fall back to card's device
		 * if possible */
		dev = pcm->dev;

		/* register pcm */
		err = snd_register_device_for_dev(devtype, pcm->card,
						  pcm->device,
						  &snd_pcm_f_ops[cidx],
						  pcm, str, dev);

		dev = snd_get_device(devtype, pcm->card, pcm->device);
		if (dev) {
			err = sysfs_create_groups(&dev->kobj,
						  pcm_dev_attr_groups);
			put_device(dev);
		}

		for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
			snd_pcm_timer_init(substream);
	}

	list_for_each_entry(notify, &snd_pcm_notify_list, list)
		notify->n_register(pcm);

	return 0;
}

在这里插入图片描述
至此,pcm 相关的驱动设备节点,就创建好了。


12. 总结整个ALSA初始化的流程

如下图, 在 Platform /machine / codec 各自 module_init 进probe 后,
接下来,就由 Machine 来主导整个初始化过程来,分别调用 codec-> probe ,platform->probe,cpu_dai->probe,pcm_new 等。

在这里插入图片描述


现在,我们只是简单的写了下,整个调用过程,
有时间,我们再详细写下整个 ASOC 驱动初始化的过程。




—>
学自 《ALSADriver_PartI.mp4》

Date: 20190918-18:11


13. alsa 代码目录结构

 在Linux2.6代码树中,Alsa的代码文件结构如下:

    sound
		/core
			/oss
			/seq
		/ioctl32
		/include
		/drivers
		/i2c
		/synth
			/emux
		/pci
			/(cards)
		/isa
			/(cards)
		/ppc
		/sparc
		/usb
		/pcmcia /(cards)
		/oss
		/soc
			/codecs

core          		该目录包含了ALSA驱动的中间层,它是整个ALSA驱动的核心部分
core/oss        	包含模拟旧的OSS架构的PCM和Mixer模块
core/seq        	有关音序器相关的代码
include          	ALSA驱动的公共头文件目录,该目录的头文件需要导出给用户空间的应用程序使用,通常,驱动模块私有的头文件不应放置在这里
 drivers           	放置一些与CPU、BUS架构无关的公用代码
i2c            		ALSA自己的I2C控制代码
pci                	pci声卡的顶层目录,子目录包含各种pci声卡的代码
isa                	isa声卡的顶层目录,子目录包含各种isa声卡的代码
soc               	针对system-on-chip体系的中间层代码
soc/codecs    		针对soc体系的各种codec的代码,与平台无关

Date: 20190924 - 9:56

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐