linux 音频子系统代码分析

Posted tumaohe

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux 音频子系统代码分析相关的知识,希望对你有一定的参考价值。

目录:

1. 架构

整体框架

2. 文件组成

文件/文件夹作用
aoa苹果板载音频驱动
armarm音频设备支持
atmelatmel ABDAC(音频字节流数模转换器)和ac97C(ac97控制器)
corealsa驱动中间层,是alsa驱动的核心
core/oss模拟旧OSS架构的PCM和Mixer模块
core/seq有关音序器相关代码
drivers与cpu、bus无关的公用代码
firewireIEEE-1394/FireWire/iLink音频设备支持
hdaHD audio(高保真音频)支持
i2calsa的i2c控制代码
isa各种isa声卡的代码
mipsmips音频设备支持
oss对oss的兼容支持
parisc鸿蒙和PA-RISC架构的GSC音频设备支持
pcipci音频设备支持
pcmciapcmcia音频设备支持
ppcPowerPC音频设备支持
shSuperH架构音频设备支持
socsystem-on-chip体系的中间层代码
soc/codec针对asoc体系各种codec代码,与平台无关
sparcSPARC架构音频设备支持
spispi音频设备支持
synth一些工具
usbusb音频设备支持
ac97_bus.c实现ac97标准总线
last.c音频设备注册完成后打印”ALSA devices List”
sound_core.c注册音频核心层子系统
sound_firmware.c加载音频驱动固件

3. ALSA核心层

3.1 alsa驱动的设备文件结构:

目前ALSA内核提供给用户空间的接口有:

  • 信息接口(proc/asound)

  • 控制接口(dev/snd/controlCX)

  • 混音器接口(dev/snd/mixerCXDX)

  • PCM接口(dev/snd/pcmCXDX)

  • Raw迷笛接口(dev/snd/midiCXDX)

  • 音序器接口(dev/snd/seq)

  • 定时器接口(dev/snd/timer)

 tree /proc/asound/
/proc/asound/
|-- Codec -> card0
|-- card0
|   |-- id
|   |-- oss_mixer
|   |-- pcm0c
|   |   |-- info
|   |   |-- oss
|   |   |-- sub0
|   |       |-- hw_params
|   |       |-- info
|   |       |-- prealloc
|   |       |-- prealloc_max
|   |       |-- status
|   |       |-- sw_params
|   |-- pcm0p
|       |-- info
|       |-- oss
|       |-- sub0
|           |-- hw_params
|           |-- info
|           |-- prealloc
|           |-- prealloc_max
|           |-- status
|           |-- sw_params
|-- cards
|-- devices
|-- oss
|   |-- devices
|   |-- sndstat
|-- pcm
|-- timers
|-- version

7 directories, 25 files

# ls /dev/snd/ -l
total 0
crw-------    1 root     root      116,   0 Jan  1 00:00 controlC0     // 用于声卡的控制,例如通道选择,混音,麦克风的控制等
crw-------    1 root     root      116,  24 Jan  1 00:00 pcmC0D0c      // 用于录音的pcm设备
crw-------    1 root     root      116,  16 Jan  1 00:00 pcmC0D0p      //用于播放的pcm设备
crw-------    1 root     root      116,  33 Jan  1 00:00 timer         //定时器

注:
部分设备未开启: midiC0D0(用于播放midi音频), 2. seq (音序器),等

设备文件注册代码, 以pcm设备为例:

int snd_register_device(int type, struct snd_card *card, int dev,
			const struct file_operations *f_ops,
			void *private_data, struct device *device)

static int snd_pcm_dev_register(struct snd_device *device)

static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, bool internal,
		struct snd_pcm **rpcm)

int snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, struct snd_pcm **rpcm)
------------------------------------------------------------------------
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)

static int soc_link_init(struct snd_soc_card *card,
			 struct snd_soc_pcm_runtime *rtd)

static int soc_link_init(struct snd_soc_card *card,
			 struct snd_soc_pcm_runtime *rtd)
			 
static int snd_soc_instantiate_card(struct snd_soc_card *card)

static int snd_soc_bind_card(struct snd_soc_card *card)

int snd_soc_register_card(struct snd_soc_card *card)

3.2 数据结构和操作函数

3.2.1 声卡

直接接口: int snd_soc_register_card(struct snd_soc_card *card)(soc\\soc-core.c)

snd_soc_register_card -> snd_soc_bind_card -> snd_soc_instantiate_card -> snd_card_new

3.2.2 pcm设备

snd_pcm_new

snd_pcm_set_ops

3.2.2 控制接口

struct snd_kcontrol
struct snd_kcontrol_new

-> snd_pcm_new

3.3 alsa声卡设备

3.3.1 platform层

3.3.2 cpu-dai层

3.3.3 codec-dai层

4. ASoC框架

参考帖子 :ALSA-ASOC音频驱动框架简述

4.1 整体框架

4.2 主要接口

4.3 源码分析

machine/platform/codec代码分析从其他地方摘的图,链接:https://blog.csdn.net/baidu_36250852/article/details/120614976
注: 此图中应该未使用设备树,最新版本Linux应该会有细节区别

4.3.1 无外置codec芯片,直接输出模拟信号

4.3.1.1 配置

设备树:

&codec 
	allwinner,audio-routing =
		"Headphone", "HP",
		"Headphone", "HPCOM",
		"LINEIN", "Line In",
		"FMINL", "Left FM In",
		"FMINR", "Right FM In",
		"MIC", "Mic";
	status = "okay";
;

codec: codec@1c23c00 
	compatible = "allwinner,suniv-f1c100s-codec";
	reg = <0x01c23c00 0x400>;
	interrupts = <21>;
	clocks = <&ccu CLK_BUS_CODEC>,
		 <&ccu CLK_CODEC>;
	clock-names = "apb", "codec";
	resets = <&ccu RST_BUS_CODEC>;
	dmas = <&dma SUN4I_DMA_NORMAL 0x0c>, 
		 <&dma SUN4I_DMA_NORMAL 0x0c>;
	dma-names = "rx", "tx";
	status = "disabled";
;


4.3.1.2 源码摘录

sound\\soc\\sunxi\\sun4i-codec.c

	static struct snd_soc_card *suniv_codec_create_card(struct device *dev)

	struct snd_soc_card *card;
	int ret;

	card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
	if (!card)
		return ERR_PTR(-ENOMEM);

	card->dai_link = sun4i_codec_create_link(dev, &card->num_links);
	if (!card->dai_link)
		return ERR_PTR(-ENOMEM);

	card->dev		= dev;
	card->name		= "F1C100s Audio Codec";
	card->dapm_widgets	= suniv_codec_card_dapm_widgets;
	card->num_dapm_widgets	= ARRAY_SIZE(suniv_codec_card_dapm_widgets);
	card->dapm_routes	= suniv_codec_card_routes;
	card->num_dapm_routes	= ARRAY_SIZE(suniv_codec_card_routes);
	card->fully_routed	= true;

	ret = snd_soc_of_parse_audio_routing(card, "allwinner,audio-routing");
	if (ret)
		dev_warn(dev, "failed to parse audio-routing: %d\\n", ret);

	return card;
;
	static const struct sun4i_codec_quirks suniv_f1c100s_codec_quirks = 
	.regmap_config	= &suniv_codec_regmap_config,
	.codec		= &suniv_codec_codec,
	.create_card	= suniv_codec_create_card,
	.reg_adc_fifoc	= REG_FIELD(SUNIV_CODEC_ADC_FIFOC, 0, 31),
	.reg_dac_txdata	= SUN4I_CODEC_DAC_TXDATA,
	.reg_adc_rxdata	= SUNIV_CODEC_ADC_RXDATA,
	.has_reset	= true,
	.dma_max_burst	= SUNIV_DMA_MAX_BURST,
;
	static const struct of_device_id sun4i_codec_of_match[] = 
	
		.compatible = "allwinner,suniv-f1c100s-codec",
		.data = &suniv_f1c100s_codec_quirks,
	,
;
static int sun4i_codec_probe(struct platform_device *pdev)

	struct snd_soc_card *card;
	struct sun4i_codec *scodec;
	const struct sun4i_codec_quirks *quirks;
	struct resource *res;
	void __iomem *base;
	int ret;

	scodec = devm_kzalloc(&pdev->dev, sizeof(*scodec), GFP_KERNEL);
	if (!scodec)
		return -ENOMEM;

	scodec->dev = &pdev->dev;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(base)) 
		dev_err(&pdev->dev, "Failed to map the registers\\n");
		return PTR_ERR(base);
	

	quirks = of_device_get_match_data(&pdev->dev);
	if (quirks == NULL) 
		dev_err(&pdev->dev, "Failed to determine the quirks to use\\n");
		return -ENODEV;
	

	scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base,
					       quirks->regmap_config);
	if (IS_ERR(scodec->regmap)) 
		dev_err(&pdev->dev, "Failed to create our regmap\\n");
		return PTR_ERR(scodec->regmap);
	

	/* Get the clocks from the DT */
	scodec->clk_apb = devm_clk_get(&pdev->dev, "apb");
	if (IS_ERR(scodec->clk_apb)) 
		dev_err(&pdev->dev, "Failed to get the APB clock\\n");
		return PTR_ERR(scodec->clk_apb);
	

	scodec->clk_module = devm_clk_get(&pdev->dev, "codec");
	if (IS_ERR(scodec->clk_module)) 
		dev_err(&pdev->dev, "Failed to get the module clock\\n");
		return PTR_ERR(scodec->clk_module);
	

	if (quirks->has_reset) 
		scodec->rst = devm_reset_control_get_exclusive(&pdev->dev,
							       NULL);
		if (IS_ERR(scodec->rst)) 
			dev_err(&pdev->dev, "Failed to get reset control\\n");
			return PTR_ERR(scodec->rst);
		
	

	scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa",
						  GPIOD_OUT_LOW);
	if (IS_ERR(scodec->gpio_pa)) 
		ret = PTR_ERR(scodec->gpio_pa);
		if (ret != -EPROBE_DEFER)
			dev_err(&pdev->dev, "Failed to get pa gpio: %d\\n", ret);
		return ret;
	

	/* reg_field setup */
	scodec->reg_adc_fifoc = devm_regmap_field_alloc(&pdev->dev,
							scodec->regmap,
							quirks->reg_adc_fifoc);
	if (IS_ERR(scodec->reg_adc_fifoc)) 
		ret = PTR_ERR(scodec->reg_adc_fifoc);
		dev_err(&pdev->dev, "Failed to create regmap fields: %d\\n",
			ret);
		return ret;
	

	/* Enable the bus clock */
	if (clk_prepare_enable(scodec->clk_apb)) 
		dev_err(&pdev->dev, "Failed to enable the APB clock\\n");
		return -EINVAL;
	

	/* Deassert the reset control */
	if (scodec->rst) 
		ret = reset_control_deassert(scodec->rst);
		if (ret) 
			dev_err(&pdev->dev,
				"Failed to deassert the reset control\\n");
			goto err_clk_disable;
		
	

	/* DMA configuration for TX FIFO */
	scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata;
	scodec->playback_dma_data.maxburst = quirks->dma_max_burst;
	scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;

	/* DMA configuration for RX FIFO */
	scodec->capture_dma_data.addr = res->start + quirks->reg_adc_rxdata;
	scodec->capture_dma_data.maxburst = quirks->dma_max_burst;
	scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;

	ret = devm_snd_soc_register_component(&pdev->dev, quirks->codec,
				     &sun4i_codec_dai, 1);
	if (ret) 
		dev_err(&pdev->dev, "Failed to register our codec\\n");
		goto err_assert_reset;
	

	ret = devm_snd_soc_register_component(&pdev->dev,
					      &sun4i_codec_component,
					      &dummy_cpu_dai, 1);
	if (ret) 
		dev_err(&pdev->dev, "Failed to register our DAI\\n");
		goto err_assert_reset;
	

	ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
	if (ret) 
		dev_err(&pdev->dev, "Failed to register against DMAEngine\\n");
		goto err_assert_reset;
	

	card = quirks->create_card(&pdev->dev);
	if (IS_ERR(card)) 
		ret = PTR_ERR(card);
		dev_err(&pdev->dev, "Failed to create our card\\n");
		goto err_assert_reset;
	

	snd_soc_card_set_drvdata(card, scodec);

	ret = snd_soc_register_card(card);                        //注册声卡
	if (ret) 
		dev_err(&pdev->dev, "Failed to register our card\\n");
		goto err_assert_reset;
	

	return 0;

err_assert_reset:
	if (scodec->rst)
		reset_control_assert(scodec->rst);
err_clk_disable:
	clk_disable_unprepare(scodec->clk_apb);
	return ret;

static struct platform_driver sun4i_codec_driver = 
	.driver = 
		.name = "sun4i-codec",
		.of_match_table = sun4i_codec_of_match,
	,
	.probe = sun4i_codec_probe,
	.remove = sun4i_codec_remove,
;
module_platform_driver(sun4i_codec_driver);

5 测试

# tinymix contents
# tinymix set 2 1
# tinymix set 1 63
# tinymix set 13 1

# tinycap 1.wav
# tinyplay 1.wav

Number of controls: 25
ctl     type    num     name                                    value
0       INT     1       DAC Playback Volume                     63 (range 0->63)
1       INT     1       Headphone Playback Volume               63 (range 0->63)
2       BOOL    2       Headphone Playback Switch               On, On
3       INT     1       Line In Playback Volume                 0 (range 0->7)
4       INT     1       FM In Playback Volume                   0 (range 0->7)
5       INT     1       Mic In Playback Volume                  3 (range 0->7)
6       INT     1       Mic Boost Volume                        4 (range 0->7)
7       INT     1       ADC Capture Volume                      3 (range 0->7)
8       BOOL    1       ADC Mixer Right Out Capture Switch      Off
9       BOOL    1       ADC Mixer Left Out Capture Switch       Off
10      BOOL    1       ADC Mixer Line In Capture Switch        Off
11      BOOL    1       ADC Mixer Right FM In Capture Switch    Off
12      BOOL    1       ADC Mixer Left FM In Capture Switch     Off
13      BOOL    1       ADC Mixer Mic Capture Switch            On
14      BOOL    1       Left Mixer Right DAC Playback Switch    Off
15      BOOL    1       Left Mixer Left DAC Playback Switch     Off
16      BOOL    1       Left Mixer FM In Playback Switch        Off
17      BOOL    1       Left Mixer Line In Playback Switch      Off
18      BOOL    1       Left Mixer Mic In Playback Switch       Off
19      BOOL    1       Right Mixer Left DAC Playback Switch    Off
20      BOOL    1       Right Mixer Right DAC Playback Switch   Off
21      BOOL    1       Right Mixer FM In Playback Switch       Off
22      BOOL    1       Right Mixer Line In Playback Switch     Off
23      BOOL    1       Right Mixer Mic In Playback Switch      Off
24      ENUM    2       Headphone Source Playback Route         , DACMixer, , DACMixer
<

Linux音频子系统 -代码分析(以YMU836为例)

1. 前言

        数据流通过I2S的接口来传输,而I2c/Spi接口主要完成控制接口,例如控制声音的大小,功放的增益等操作。

        platform 部分对应的代码在 kernel_3.10/sound/soc/fsl/fsl_ssi.c;

        codec 部分对应的代码在 kernel_3.10/sound/soc/codecs/ymu836.c;

        machine 部分对应的代码在 kernel_3.10/sound/soc/fsl/imx-ymu836.c。

2. platform部分

        前面有介绍过,platform主要负责DMA的控制和I2S的控制,即PCM dma和cpu dai两个部分。

module_platform_driver(fsl_ssi_driver);
	fsl_ssi_probe
		snd_soc_register_component
			snd_soc_register_dai			
				list_add(&dai->list, &dai_list);
			imx_pcm_dma_init
				snd_dmaengine_pcm_register
					snd_soc_add_platform
						list_add(&platform->list, &platform_list);

        module_platform_driver是一个宏,最终调用的是platform_driver_register这个接口,注册一个平台驱动,所以直接从fsl_ssi_driver的probe回调函数开始分析。

static struct platform_driver fsl_ssi_driver = {
	.driver = {
		.name = "fsl-ssi-dai",
		.owner = THIS_MODULE,
		.of_match_table = fsl_ssi_ids,
		.pm = &fsl_ssi_pm,
	},
	.probe = fsl_ssi_probe,
	.remove = fsl_ssi_remove,
};

static int fsl_ssi_probe(struct platform_device *pdev)
{
	······
	pm_runtime_enable(&pdev->dev);

	/* Register with ASoC */
	dev_set_drvdata(&pdev->dev, ssi_private);
	ret = snd_soc_register_component(&pdev->dev, &fsl_ssi_component, &ssi_private->cpu_dai_drv, 1);  // cpu dai
	if (ret) {
		dev_err(&pdev->dev, "failed to register DAI: %d\\n", ret);
		goto error_dev;
	}

	if (ssi_private->ssi_on_imx) {
		ret = imx_pcm_dma_init(pdev, SND_DMAENGINE_PCM_FLAG_NO_RESIDUE, IMX_SSI_DMABUF_SIZE);	// pcm dma
	}
 
	return ret;
}

        cpu dai的注册过程如下:

// sound/soc/soc-core.c
int snd_soc_register_component(struct device *dev, const struct snd_soc_component_driver *cmpnt_drv,
			 struct snd_soc_dai_driver *dai_drv, int num_dai)
{
	if (1 == num_dai)
		ret = snd_soc_register_dai(dev, dai_drv); //注册 cpu dai
	else
		ret = snd_soc_register_dais(dev, dai_drv, num_dai);
	if (ret < 0) {
		dev_err(dev, "ASoC: Failed to regster DAIs: %d\\n", ret);
		goto error_component_name;
	}

	list_add(&cmpnt->list, &component_list);
	return ret;
}

// sound/soc/soc-core.c
static int snd_soc_register_dai(struct device *dev, struct snd_soc_dai_driver *dai_drv)
{
	struct snd_soc_codec *codec;
	struct snd_soc_dai *dai;
	dev_dbg(dev, "ASoC: dai register %s\\n", dev_name(dev));
	dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
	dai->dev = dev;
	dai->driver = dai_drv;
	dai->dapm.dev = dev;

	list_for_each_entry(codec, &codec_list, list) {
		if (codec->dev == dev) {
			dev_dbg(dev, "ASoC: Mapped DAI %s to CODEC %s\\n",
				dai->name, codec->name);
			dai->codec = codec;
			break;
		}
	}
	list_add(&dai->list, &dai_list);  //将cpu dai挂接到静态全局链表dai_list

	dev_dbg(dev, "ASoC: Registered DAI '%s'\\n", dai->name);
	return 0;
}

        pcm dma的注册过程如下:

// sound/soc/fsl/imx-pcm-dma.c
int imx_pcm_dma_init(struct platform_device *pdev, unsigned int flags, size_t size)
{
    ······
	return snd_dmaengine_pcm_register(&pdev->dev, config, flags);	//--->
}

// sound/soc/soc-generic-dmaengine-pcm.c
int snd_dmaengine_pcm_register(struct device *dev,
	const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{
	struct dmaengine_pcm *pcm;
	pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
	dmaengine_pcm_request_chan_of(pcm, dev);
	return snd_soc_add_platform(dev, &pcm->platform, &dmaengine_pcm_platform); // --->
}

// sound/soc/soc-core.c
int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,
		const struct snd_soc_platform_driver *platform_drv)
{
	/* create platform component name */
	platform->name = fmt_single_name(dev, &platform->id);
	platform->dev = dev;
	platform->driver = platform_drv;
	platform->dapm.dev = dev;
	platform->dapm.platform = platform;
	platform->dapm.stream_event = platform_drv->stream_event;
 
	list_add(&platform->list, &platform_list);  //将pcm dma挂接到静态全局链表platform_list
	
	dev_dbg(dev, "ASoC: Registered platform '%s'\\n", platform->name);
	return 0;
}

3. codec部分

        因为Codec驱动的代码要做到平台无关性,要使得Machine驱动能够使用该Codec,Codec驱动的首要任务就是确定snd_soc_codec(音频的控制)和snd_soc_dai(数据处理)的实例,并把它们注册到系统中。

module_spi_driver(ymu836_spi_driver)
	ymu836_spi_probe
		snd_soc_register_codec(&spi->dev,&soc_codec_dev_ymu836, &ymu836_dai, 1);		
			list_add(&codec->list, &codec_list)
			snd_soc_register_dais
				list_add(&dai->list, &dai_list);
			soc_codec_dev_ymu836
				ymu836_probe		//注册snd_soc_codec(音频的控制),在machine端会通过链表将snd_soc_codec结构体取出,并调用里面的初始化回调函数

        module_spi_driver宏和module_platform_driver宏类似,就不做赘述了

static struct spi_driver ymu836_spi_driver = {
	.driver = {
		.name = "ymu836",
		.owner = THIS_MODULE,
		.of_match_table = ymu836_of_match,
	},
	.probe =    ymu836_spi_probe,
	.remove =   ymu836_spi_remove,
	.shutdown = ymu836_spi_shutdown,
	.id_table = ymu836_spi_id,
};

static int ymu836_spi_probe(struct spi_device *spi)
{	
	int ret;
	ymu836 = devm_kzalloc(&spi->dev, sizeof(struct ymu836_priv), GFP_KERNEL);
	······
   spi->mode=0;
	ret = spi_setup(spi);
	spi_set_drvdata(spi, ymu836);
	spidev=spi;
	ret = snd_soc_register_codec(&spi->dev,&soc_codec_dev_ymu836, &ymu836_dai, 1);  //		

	return ret;
}

// sound/soc/soc-core.c
int snd_soc_register_codec(struct device *dev, const struct snd_soc_codec_driver *codec_drv,
			   struct snd_soc_dai_driver *dai_drv, int num_dai)
{
	size_t reg_size;
	struct snd_soc_codec *codec;
	int ret, i;

	dev_dbg(dev, "codec register %s\\n", dev_name(dev));
	codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);

	/* create CODEC component name */
	codec->name = fmt_single_name(dev, &codec->id);
	codec->write = codec_drv->write;
	codec->read = codec_drv->read;
	codec->volatile_register = codec_drv->volatile_register;
	codec->readable_register = codec_drv->readable_register;
	codec->writable_register = codec_drv->writable_register;
	codec->ignore_pmdown_time = codec_drv->ignore_pmdown_time;
	codec->dapm.bias_level = SND_SOC_BIAS_OFF;
	codec->dapm.dev = dev;
	codec->dapm.codec = codec;
	codec->dapm.seq_notifier = codec_drv->seq_notifier;
	codec->dapm.stream_event = codec_drv->stream_event;
	codec->dev = dev;
	codec->driver = codec_drv;  //传进来的soc_codec_dev_ymu836赋值给了codec
	codec->num_dai = num_dai;
	mutex_init(&codec->mutex);

	list_add(&codec->list, &codec_list);  //将ymu836 这款codec相关的信息挂接到静态全局链表codec_list

	/* register any DAIs */
	ret = snd_soc_register_dais(dev, dai_drv, num_dai);  // 注册codec dai

	dev_dbg(codec->dev, "ASoC: Registered codec '%s'\\n", codec->name);
	return 0;
}

// sound/soc/soc-core.c
static int snd_soc_register_dais(struct device *dev, struct snd_soc_dai_driver *dai_drv, size_t count)
{
	struct snd_soc_codec *codec;
	struct snd_soc_dai *dai;
	int i, ret = 0;

	dev_dbg(dev, "ASoC: dai register %s #%Zu\\n", dev_name(dev), count);
	list_for_each_entry(codec, &codec_list, list) {
		if (codec->dev == dev) {
			dev_dbg(dev, "ASoC: Mapped DAI %s to CODEC %s\\n", dai->name, codec->name);
				dai->codec = codec;
				break;
			}
		}
		list_add(&dai->list, &dai_list);  // 将codec dai挂接到静态全局链表dai_list
		dev_dbg(dai->dev, "ASoC: Registered DAI '%s'\\n", dai->name);
	}

	return 0;
}

4. machine部分

        通常有两种方法注册一个card:

        第一种是在machine驱动中注册一个名为 “soc-audio” 的 platform device,并将 struct snd_soc_card 结构变量作为这个device的私有数据,在 soc-core.c 调用 名为 “soc-audio” 的 platform drv的probe函数,probe中调用 snd_soc_register_card 注册这个card;

        第二种是直接在machine驱动中调用 snd_soc_register_card 函数注册card。

imx_ymu836_probe
	snd_soc_register_card
		snd_soc_instantiate_card		//关键接口
			soc_bind_dai_link				//绑定dais
				rtd->cpu_dai = cpu_dai;			//从链表dai_list中找到cpu_dai并存储在rtd中
				rtd->codec = codec;			//从链表codec_list中找到codec并存储在rtd中
				rtd->codec_dai = codec_dai;		//从链表dai_list中找到codec_dai并存储在rtd中
				rtd->platform = platform;		//从链表platform_list中找到platform并存储在rtd中
			codec->cache_init				//调用回调函数cache_init进行初始化			
			snd_card_create				
				snd_ctl_create			//.dev_register = snd_ctl_dev_register	===》疑问1			
					snd_ctl_dev_register
						snd_register_device    //生成dev/controlC%i设备文件,sound/core/control.c
							snd_register_device_for_dev
								preg->f_ops = f_ops;
								snd_minors[minor] = preg;	//将controlC设备对应的f_ops放到全局数组snd_minors[]中
								
			card->probe															//回调card的probe函数
			soc_probe_link_components					//针对rtd下的每个component,回调其初始化函数--->
				soc_probe_codec(card, cpu_dai->codec)			//调用platform侧codec层注册的driver中的probe函数	===>该codec在platform部分注册
				soc_probe_codec(card, codec_dai->codec)			//调用codec侧codec层注册的driver中的probe函数	===>该codec在codec部分注册
				soc_probe_platform(card, platform)			//调用platform侧dma层注册的driver中的probe函数	===>该codec在platform部分注册
								
			soc_probe_link_dais			
				soc_new_pcm
					snd_pcm_new
						_snd_pcm_new		//.dev_register = snd_pcm_dev_register  ===》疑问2
							snd_pcm_dev_register		//生成dev/pcmC0D0c或pcmC0D0p设备文件,sound/core/pcm.c	
								snd_register_device_for_dev
									preg->f_ops = f_ops;
									snd_minors[minor] = preg;	//将pcmC0D0c设备或pcmC0D0p设备对应的f_ops放到全局数组snd_minors[]中
					snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);	//底层驱动需要实现rtd->ops这个结构体
					snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);	//用户空间打开pcm设备文件以后,会间接调用rtd->ops里的底层驱动回调函数
					platform->driver->pcm_new(rtd)					//此回调函数中会分配环形缓冲区
					
			snd_soc_add_card_controls
			snd_soc_dapm_add_routes
			card->late_probe(card)
			snd_soc_dapm_new_widgets
								
			snd_card_register
				snd_device_register_all
					dev->ops->dev_register(dev) //调用疑问1和疑问2处赋值的dev_register函数指针

        其实呢,machine部分才是关键部分,platform和codec部分的很多内容都可以通过分析machine部分来得到结论,所以machine部分的解析也会杂乱一些,详解如下:

static struct platform_driver imx_ymu836_driver = {
	.driver = {
		.name = "imx-ymu836",
		.owner = THIS_MODULE,
		.pm = &snd_soc_pm_ops,
		.of_match_table = imx_ymu836_dt_ids,
	},
	.probe = imx_ymu836_probe,
	.remove = imx_ymu836_remove,
};
module_platform_driver(imx_ymu836_driver);

static int imx_ymu836_probe(struct platform_device *pdev)
{
	······
	ret = snd_soc_register_card(&data->card);	//---> 
	if (ret) {
		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\\n", ret);
		goto fail;
	}
	priv->snd_card = data->card.snd_card;

	return ret;
}

// sound/soc/soc-core.c
int snd_soc_register_card(struct snd_soc_card *card)
{
	int i, ret;
    ······
	ret = snd_soc_instantiate_card(card);	//--->
	return ret;
}

// sound/soc/soc-core.c
static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
	struct snd_soc_codec *codec;
	struct snd_soc_codec_conf *codec_conf;
	enum snd_soc_compress_type compress_type;
	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);	//--->4-1代码
		if (ret != 0)
			goto base_error;
	}

	/* initialize the register cache for each available codec */
	list_for_each_entry(codec, &codec_list, list) {
		if (codec->cache_init)			//调用回调函数cache_init进行初始化
			continue;
           ······
	}

	/* card bind complete so register a sound card */
	ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,	//创建声卡--->4-2代码
			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;
	}
	card->snd_card->dev = card->dev;
	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);


	/* initialise the sound card only once */
	if (card->probe) {				//回调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);  //针对rtd下的每个component,回调其初始化函数--->4-3代码
			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);	//针对rtd下的每个dai,回调其初始化函数--->4-4代码
			if (ret < 0) {
				dev_err(card->dev, "ASoC: failed to instantiate card %d\\n", ret);
				goto probe_dai_err;
			}
		}
	}

	snd_soc_dapm_link_dai_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->late_probe) 
		ret = card->late_probe(card);	
	snd_soc_dapm_new_widgets(&card->dapm);	

	if (card->fully_routed)
		list_for_each_entry(codec, &card->codec_dev_list, card_list)
			snd_soc_dapm_auto_nc_codec_pins(codec);

	ret = snd_card_register(card->snd_card);	//注册声卡,此后用户空间出现设备节点,用户程序可与底层交互了--->4-5代码
	if (ret < 0) {
		dev_err(card->dev, "ASoC: failed to register soundcard %d\\n", ret);
		goto probe_aux_dev_err;
	}
	return 0;
}

4-1代码详解

// sound/soc/soc-core.c
static int soc_bind_dai_link(struct snd_soc_card *card, int num)
{
	struct snd_soc_dai_link *dai_link = &card->dai_link[num];
	struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
	struct snd_soc_codec *codec;
	struct snd_soc_platform *platform;
	struct snd_soc_dai *codec_dai, *cpu_dai;
	const char *platform_name;

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

	/* Find CPU DAI from registered DAIs*/
	list_for_each_entry(cpu_dai, &dai_list, list) {
		if (dai_link->cpu_of_node &&
		    (cpu_dai->dev->of_node != dai_link->cpu_of_node))
			continue;
		if (dai_link->cpu_name &&
		    strcmp(dev_name(cpu_dai->dev), dai_link->cpu_name))
			continue;
		if (dai_link->cpu_dai_name &&
		    strcmp(cpu_dai->name, dai_link->cpu_dai_name))
			continue;

		rtd->cpu_dai = cpu_dai;		//找到cpu_dai并存储在rtd中
	}

	/* Find CODEC from registered CODECs */
	list_for_each_entry(codec, &codec_list, list) {
		if (dai_link->codec_of_node) {
			if (codec->dev->of_node != dai_link->codec_of_node)
				continue;
		} else {
			if (strcmp(codec->name, dai_link->codec_name))
				continue;
		}

		rtd->codec = codec;		//找到codec并存储在rtd中

		/*
		 * CODEC found, so find CODEC DAI from registered DAIs from
		 * this CODEC
		 */
		list_for_each_entry(codec_dai, &dai_list, list) {
			if (codec->dev == codec_dai->dev &&
				!strcmp(codec_dai->name,
					dai_link->codec_dai_name)) {

				rtd->codec_dai = codec_dai;		//找到codec_dai并存储在rtd中
			}
		}
	}

	/* if there's no platform we match on the empty platform */
	platform_name = dai_link->platform_name; //目前为止,并没有看到有任何地方给platform_name赋值,所以此时的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->name, platform_name))
				continue;
		}

		rtd->platform = platform;		//找到platform并存储在rtd中
	}
	card->num_rtd++;
	return 0;
}

4-2 代码详解

// sound/core/init.c
int snd_card_create(int idx, const char *xid, struct module *module, int extra_size, struct snd_card **card_ret)
{
	struct snd_card *card;
	int err, idx2;
    ······
	/* 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);
	if (err < 0) {
		snd_printk(KERN_ERR "unable to register control minors\\n");
		goto __error;
	}
	return 0;
}

// sound/core/control.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,   // ===》疑问1
		.dev_disconnect = snd_ctl_dev_disconnect,
	};

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

static int snd_ctl_dev_register(struct snd_device *device)
{
	struct snd_card *card = device->device_data;
	int err, cardnum;
	char name[16];

	cardnum = card->number;
	sprintf(name, "controlC%i", cardnum);
   //生成dev/controlC%i设备文件
	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1, &snd_ctl_f_ops, card, name)) < 0)
		return err;
	return 0;
}

static inline int snd_register_device(int type, struct snd_card *card, int dev,
				      const struct file_operations *f_ops,
				      void *private_data,
				      const char *name)
{
	return snd_register_device_for_dev(type, card, dev, f_ops,
					   private_data, name,
					   snd_card_get_device_link(card));
}

int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
				const struct file_operations *f_ops,
				void *private_data,
				const char *name, struct device *device)
{
	int minor;
	struct snd_minor *preg;

	preg = kmalloc(sizeof *preg, GFP_KERNEL);
	preg->type = type;
	preg->card = card ? card->number : -1;
	preg->device = dev;
	preg->f_ops = f_ops;       //重点操作之一
	preg->private_data = private_data;
	preg->card_ptr = card;

 //将controlC设备对应的f_ops放到全局数组snd_minors[]中,用户空间打开设备文件时会从snd_minors[]数组中取出对应的ops操作集
	snd_minors[minor] = preg;     
	preg->dev = device_create(sound_class, device, MKDEV(major, minor),
				  private_data, "%s", name);
	return 0;
}

4-3 代码详解

static int soc_probe_link_components(struct snd_soc_card *card, int num,
				     int order)
{
	struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
    // 4-1代码中对cpu_dai、codec_dai、platform进行了赋值
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;  
	struct snd_soc_dai *codec_dai = rtd->codec_dai;
	struct snd_soc_platform *platform = rtd->platform;
	int ret;

	/* probe the CPU-side component, if it is a CODEC */
	if (cpu_dai->codec &&
	    !cpu_dai->codec->probed &&
	    cpu_dai->codec->driver->probe_order == order) {
         //调用platform侧codec层注册的driver中的probe函数 ===>该codec在platform部分注册
		ret = soc_probe_codec(card, cpu_dai->codec);	
		if (ret < 0)
			return ret;
	}

	/* probe the CODEC-side component */
	if (!codec_dai->codec->probed &&
	    codec_dai->codec->driver->probe_order == order) {
         //调用codec侧codec层注册的driver中的probe函数 ===>该codec在codec部分注册
		ret = soc_probe_codec(card, codec_dai->codec);
		if (ret < 0)
			return ret;
	}

	/* probe the platform */
	if (!platform->probed &&
	    platform->driver->probe_order == order) {
         //调用platform侧dma层注册的driver中的probe函数 ===>该codec在platform部分注册
		ret = soc_probe_platform(card, platform);
		if (ret < 0)
			return ret;
	}

	return 0;
}

static int soc_probe_codec(struct snd_soc_card *card, struct snd_soc_codec *codec)
{
	int ret = 0;
	const struct snd_soc_codec_driver *driver = codec->driver;
	struct snd_soc_dai *dai;

	codec->card = card;
	codec->dapm.card = card;
	soc_set_name_prefix(card, codec);
	/* Create DAPM widgets for each DAI stream */
	list_for_each_entry(dai, &dai_list, list) {
		if (dai->dev != codec->dev)
			continue;
		snd_soc_dapm_new_dai_widgets(&codec->dapm, dai);
	}
	if (driver->probe) {	// 调用driver里对应的probe回调函数
		ret = driver->probe(codec);
		if (ret < 0) {
			dev_err(codec->dev, "ASoC: failed to probe CODEC %d\\n", ret);
			goto err_probe;
		}
	}
	/* mark codec as probed and add to card codec list */
	codec->probed = 1;
	list_add(&codec->card_list, &card->codec_dev_list);
	list_add(&codec->dapm.list, &card->dapm_list);
	return 0;
}

4-4代码详解

static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order)
{
	int ret;
	
	if (!dai_link->params) {
		/* create the pcm */
		ret = soc_new_pcm(rtd, num);	//针对每个rtd(也就是每个dai_link),创建一个PCM逻辑设备--->
		if (ret < 0) {
			dev_err(card->dev, "ASoC: can't create pcm %s :%d\\n",
		        dai_link->stream_name, ret);
			return ret;
    	}
	}	
	return 0;
}

// sound/soc/soc-pcm.c
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 = rtd->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;

		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, codec_dai->name, num);
            //创建PCM逻辑设备 --->
		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);

	if (playback)
           //底层驱动需要实现rtd->ops这个结构体
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);	

	if (capture)
            //用户空间打开pcm设备文件以后,会间接调用rtd->ops里的底层驱动回调函数
		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;
		}
	}
	return ret;
} 

int snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, struct snd_pcm **rpcm)
{
	return _snd_pcm_new(card, id, device, playback_count, capture_count, false, rpcm);
}

// sound/core/pcm.c
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, bool internal,
		struct snd_pcm **rpcm)
{
	struct snd_pcm *pcm;
	int err;
	static struct snd_device_ops ops = {
		.dev_free = snd_pcm_dev_free,
		.dev_register =	snd_pcm_dev_register,     // ===》疑问2
		.dev_disconnect = snd_pcm_dev_disconnect,
	};
   ······
	if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}
	if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}
	return 0;
}

static int snd_pcm_dev_register(struct snd_device *device)
{
	int cidx, err;
	struct snd_pcm_substream *substream;
	struct snd_pcm_notify *notify;
	char str[16];
	struct snd_pcm *pcm;
	struct device *dev;

	/* register pcm 生成dev/pcmC0D0c或pcmC0D0p设备文件*/
	err = snd_register_device_for_dev(devtype, pcm->card,
					  pcm->device,
					  &snd_pcm_f_ops[cidx],
					  pcm, str, dev);
	return 0;
}

int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
				const struct file_operations *f_ops,
				void *private_data,
				const char *name, struct device *device)
{
	int minor;
	struct snd_minor *preg;

	preg = kmalloc(sizeof *preg, GFP_KERNEL);
	preg->type = type;
	preg->card = card ? card->number : -1;
	preg->device = dev;
	preg->f_ops = f_ops;       //重点操作之一
	preg->private_data = private_data;
	preg->card_ptr = card;

 //将pcmC0D0c设备或pcmC0D0p设备对应的f_ops放到全局数组snd_minors[]中,用户空间打开设备文件时会从snd_minors[]数组中取出对应的ops操作集
	snd_minors[minor] = preg;     
	preg->dev = device_create(sound_class, device, MKDEV(major, minor),
				  private_data, "%s", name);
	return 0;
}

4-5代码详解

// sound/core/init.c
int snd_card_register(struct snd_card *card)
{
	int err;
	if (!card->card_dev) {
		card->card_dev = device_create(sound_class, card->dev,
					       MKDEV(0, 0), card,
					       "card%i", card->number);
	}

	if ((err = snd_device_register_all(card)) < 0)  // --->
		return err;
	snd_cards[card->number] = card;
	mutex_unlock(&snd_card_mutex);
	init_info_for_card(card);

	return 0;
}

// sound/core/device.c
int snd_device_register_all(struct snd_card *card)
{
	struct snd_device *dev;
	int err;
	
	list_for_each_entry(dev, &card->devices, list) {
		if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
                    //调用疑问1和疑问2处赋值的dev_register函数指针
			if ((err = dev->ops->dev_register(dev)) < 0)
				return err;
			dev->state = SNDRV_DEV_REGISTERED;
		}
	}
	return 0;
}

5. 与用户交互的接口

        在snd_open中, 会根据次设备号(每个逻辑设备都有自己的次设备号), 选择对应的逻辑设备的ops函数, 然后替换file->f_op, 此后用户空间的任何操作都是直接与逻辑设备的ops函数交互的。

        ALSA系统中有一个全局数组snd_minors[], 数组的下标就是次设备号, 每个元素对应一个逻辑设备, snd_minor[i]-> f_ops存储的就是逻辑设备自己的ops函数,用次设备号作为下标, 从snd_minors[]中找到对应的元素, 然后用元素的f_ops替换file->f_op即可。

//sound/core/sound.c
#define CONFIG_SND_MAJOR	116	/* standard configuration */
static int major = CONFIG_SND_MAJOR;

static int __init alsa_sound_init(void)
{
	snd_major = major;
	snd_ecards_limit = cards_limit;
	if (register_chrdev(major, "alsa", &snd_fops)) {  //注册字符设备,snd_fops是关键
		snd_printk(KERN_ERR "unable to register native major device number %d\\n", major);
		return -EIO;
	}
	snd_info_minor_register();
	return 0;
}
subsys_initcall(alsa_sound_init);

static const struct file_operations snd_fops =
{
	.owner =	THIS_MODULE,
	.open =		snd_open,
	.llseek =	noop_llseek,
};

static int snd_open(struct inode *inode, struct file *file)
{
	unsigned int minor = iminor(inode);
	struct snd_minor *mptr = NULL;
	const struct file_operations *old_fops;
	int err = 0;

	if (minor >= ARRAY_SIZE(snd_minors))
		return -ENODEV;
 
//根据次设备号取出设备对应的file_operations结构体
	mptr = snd_minors[minor];
	old_fops = file->f_op;
 
//将file->f_op 函数操作集替换成对应逻辑设备的file_operations结构体,譬如dev/controlC0设备
	file->f_op = fops_get(mptr->f_ops);
	if (file->f_op == NULL) {
		file->f_op = old_fops;
		err = -ENODEV;
	}
	
	if (file->f_op->open) {
		err = file->f_op->open(inode, file);
		if (err) {
			fops_put(file->f_op);
			file->f_op = fops_get(old_fops);
		}
	}
	fops_put(old_fops);
	return err;
}

以上是关于linux 音频子系统代码分析的主要内容,如果未能解决你的问题,请参考以下文章

Linux ALSA 音频系统:逻辑设备篇

Linux ALSA 音频系统:逻辑设备篇

Linux ALSA介绍

Linux音频驱动学习之:ASOC分析

C++如何播放wav声音文件.Linux系统

linux音频alsa-uda134x驱动分析之二(时钟)