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 ALSA 系统架构
主要针对初始化,设备打开

架构:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值