Audio System 三 之 Linux ALSA音频系统分析


https://blog.csdn.net/zyuanyun

Google Pixel、Pixel XL 内核代码(Kernel-3.18):Kernel source for Pixel and Pixel XL - GitHub

AOSP 源码(Android 7.1.2): Android 系统全套源代码分享 (更新到 8.1.0_r1)


User space audio code 代码路径

User space audio code 代码路径


/hardware/qcom/audio/hal/(Audio HAL 源码)

/external/tinyalsa/(tinymix, tinyplay, tinycap 源码)

/vendor/qcom/proprietary/mm-audio/(QTI OMX audio encoder and decoders 源码,未公开)

/frameworks/av/media/audioserver/(Audioserver 源码)

/frameworks/av/media/libstagefright/(Google Stagefright 多媒体框架源码)

/frameworks/av/services/audioflinger/(Audioflinger 相关源码)

/external/bluetooth/bluedroid/(A2DP audio HAL 相关源码)/

/hardware/libhardware/modules/usbaudio/(USB HAL 源码)/

/vendor/qcom/proprietary/wfd/mm/source/framework/src/(Wi-Fi Display (WFD)、 WFDMMSourceAudioSource.cpp,未公开)
		
/system/core/include/system/(audio.h)/

Kernel space audio code 代码路径

/kernel/sound/soc/msm/     – msm8996.c machine driver 源码

• /kernel/sound/soc/msm/qdsp6v2
		– platform drivers, front end (FE), and back-end (BE) DAI driver, 
		– Hexagon DSP drivers for AFE, ADM, and ASM, voice driver 相关源码

• kernel/sound/soc/soc-.c 
		– All the SoC-.c ALSA SoCs framework 源码

• kernel/drivers/slimbus/ 
		– SLIMbus driver 源码

• kernel/arch/arm/mach-msm/ 
		– 包含比如 acpuclock-8996.c, board-8996-gpiomux.c, board-8996.c, and clock-8996.c 
		– related to the GPIO, clock, and board-specific information on the MSM8996 相关源码

• /kernel/arch/arm/mach-msm/qdsp6v2/ 
		– Contains the drivers for DSP-based encoders and decoders, 
		– code for the aDSP loader, APR driver, Io memory driver, and other utility files

• /kernel/arch/arm/boot/dts 
		– Contains MSM8996-.dts and MSM8996-.Dtsi files that contain MSM8996-specific information; 
		– audio-related customization is available in files such as MSM8996.dtsi, msm8996-mtp.dtsi, and msm8996-cdp.dtsi

• /kernel/sound/soc/codecs/ 
		– Contains the source code for the codec driver for WCD9335; 
		– codec driver-related source files are wcd9335.c, wcd9xxx-mbhc.c, wcd9xxx-resmgr.c, 
		– wcd9xxx-common.c, and so on./kernel/drivers/mfd/ 
		– Contains the source code for the codec driver; wcd9xxx-core.c, 
		– wcd9xxx-slimslave.c, and wcd9xxx-irq.c are the codec driverrelated files

三、Linux ALSA 音频系统架构

硬件平台及软件版本:
☁ Kernel - 3.18
☁ SoC - Qualcomm snapdragon
☁ CODEC - WCD9335
☁ Machine - msm8996
☁ Userspace - tinyalsa

Linux ALSA 音频系统架构大致如下:
在这里插入图片描述

  • Native ALSA Application
    tinyplay/tinycap/tinymix,这些用户程序直接调用 alsa 用户库接口来实现放音、录音、控制

  • ALSA Library API
    alsa 用户库接口,常见有 tinyalsa、alsa-lib

  • ALSA CORE
    alsa 核心层,向上提供逻辑设备(PCM/CTL/MIDI/TIMER/…)系统调用,向下驱动硬件设备(Machine/I2S/DMA/CODEC)

  • ASoC CORE
    asoc 是建立在标准 alsa core 基础上,为了更好支持嵌入式系统和应用于移动设备的音频 codec 的一套软件体系

  • Hardware Driver
    音频硬件设备驱动,由三大部分组成,分别是 Machine、Platform、Codec。

3.1 Platform

指某款 SoC 平台的音频模块,如 exynos、omap、qcom 等等。
Platform 又可细分两部分:

  • cpu dai
    在嵌入式系统里面通常指 SoC 的 I2S、PCM 总线控制器,
    负责把音频数据从 I2S tx FIFO 搬运到 CODEC(这是回放的情形,录制则方向相反)。
    cpu_dai 通过 snd_soc_register_dai() 来注册。
    注:DAI 是 Digital Audio Interface 的简称,分为 cpu_dai 和 codec_dai,这两者通过 I2S/PCM 总线连接;AIF 是 Audio Interface 的简称,嵌入式系统中一般是 I2S 和 PCM 接口。

  • pcm dma
    负责把 dma buffer 中的音频数据搬运到 I2S tx FIFO。
    值得留意的是:
    某些情形下是不需要 dma 操作的,比如 Modem 和 CODEC 直连,
    因为 Modem 本身已经把数据送到 FIFO 了,这时只需启动 codec_dai 接收数据即可;
    该情形下,Machine 驱动 dai_link 中需要设定 .platform_name = “msm-pcm-xxx”。


3.2 Codec

对于回放来说,userspace 送过来的音频数据是经过采样量化的数字信号,
在 codec 经过 DAC 转换成模拟信号然后输出到外放或耳机,这样我们就可以听到声音了。

Codec 字面意思是编解码器,但芯片里面的功能部件很多,常见的有 AIF、DAC、ADC、Mixer、PGA、Line-in、Line-out,有些高端的 codec 芯片还有 EQ、DSP、SRC、DRC、AGC、Echo-Canceller、Noise-Suppression 等部件。


3.3 Machine

指某款机器,通过配置 dai_link
把 cpu_dai、codec_dai、modem_dai 各个音频接口给链结成一条条音频链路,然后注册 snd_soc_card。

和上面两个不一样,Platform 和 CODEC 驱动一般是可以重用的,而 Machine 有它特定的硬件特性,几乎是不可重用的。
所谓的硬件特性指:

  • SoC Platform 与 Codec 的差异;
  • DAIs 之间的链结方式;
  • 通过某个 GPIO 打开 Amplifier;
  • 通过某个 GPIO 检测耳机插拔;
  • 使用某个时钟如 MCLK/External-OSC 作为 I2S、CODEC 的时钟源等等。

从上面的描述来看,对于回放的情形,PCM 数据流向大致是:

        copy_from_user           DMA                 I2S           DAC
              ^                   ^                   ^             ^
+---------+   |    +----------+   |   +-----------+   |   +-----+   |   +------+
|userspace+-------->DMA Buffer+------->I2S TX FIFO+------->CODEC+------->SPK/HP|
+---------+        +----------+       +-----------+       +-----+       +------+

几个音频物理链路的概念:

3.3.1 dai_link

machine 驱动中定义的音频数据链路,它指定链路用到的 codec、codec_dai、cpu_dai、platform。

比如对于 WCD9335 平台的 media 链路:
.codec_dai_name = “snd-soc-dummy-dai”,
.codec_name = “snd-soc-dummy”,
.cpu_dai_name = “MultiMediaX”,
.platform_name = “msm-pcm-dsp.0”,
这四者就构成了一条音频数据链路用于多媒体声音的回放和录制。

一个系统可能有多个音频数据链路,比如 media 和 voice,因此可以定义多个 dai_link 。
代码如下:

[->/sound/soc/msm/msm8996.c]
/* Digital audio interface glue - connects codec <---> CPU */
static struct snd_soc_dai_link msm8996_common_dai_links[] = {
	/* FrontEnd DAI Links */
	{
		.name = "MSM8996 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
	},
	......
}

在这里插入图片描述
高通平台因DSP而存在特殊性,如上图,
Frontend 链接 “Platform”,经由 “Platform”->Backend链接到Codec。

Front-end DAI:

[->/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_192000|
					SNDRV_PCM_RATE_KNOT),
			.formats = (SNDRV_PCM_FMTBIT_S16_LE |
						SNDRV_PCM_FMTBIT_S24_LE |
						SNDRV_PCM_FMTBIT_S24_3LE),
			.channels_min = 1,
			.channels_max = 8,
			.rate_min =     8000,
			.rate_max =	192000,
		},
		.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),
			.channels_min = 1,
			.channels_max = 4,
			.rate_min =     8000,
			.rate_max =	48000,
		},
		.ops = &msm_fe_Multimedia_dai_ops,
		.name = "MultiMedia1",
		.probe = fe_dai_probe,
	},
	......
}

Back-end DAI:

[->sound/soc/msm/msm8996.c]
static struct snd_soc_dai_link msm8996_tasha_be_dai_links[] = {
	/* Backend DAI Links */
	{
		.name = LPASS_BE_SLIMBUS_0_RX,
		.stream_name = "Slimbus Playback",
		.cpu_dai_name = "msm-dai-q6-dev.16384",
		.platform_name = "msm-pcm-routing",
		.codec_name = "tasha_codec",
		.codec_dai_name = "tasha_mix_rx1",
		.no_pcm = 1,
		.dpcm_playback = 1,
		.be_id = MSM_BACKEND_DAI_SLIMBUS_0_RX,
		.init = &msm_audrx_init,
		.be_hw_params_fixup = msm_slim_0_rx_be_hw_params_fixup,
		/* this dainlink has playback support */
		.ignore_pmdown_time = 1,
		.ignore_suspend = 1,
		.ops = &msm8996_be_ops,
	},
	......
}
static struct snd_soc_dai_link msm8996_tasha_fe_dai_links[] = {
	{
		.name = LPASS_BE_SLIMBUS_4_TX,
		.stream_name = "Slimbus4 Capture",
		.cpu_dai_name = "msm-dai-q6-dev.16393",
		.platform_name = "msm-pcm-hostless",
		.codec_name = "tasha_codec",
		.codec_dai_name = "tasha_vifeedback",
		.be_id = MSM_BACKEND_DAI_SLIMBUS_4_TX,
		.be_hw_params_fixup = msm_slim_4_tx_be_hw_params_fixup,
		.ops = &msm8996_be_ops,
		.no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
		.ignore_suspend = 1,
	},
	......
}

3.3.2 hw constraints

指平台本身的硬件限制,如所能支持的通道数 / 采样率 / 数据格式、DMA 支持的数据周期大小(period size)、
周期次数(period count)等,通过 snd_pcm_hardware 结构体描述:

[->sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c]
static struct snd_pcm_hardware msm_pcm_hardware_capture = {
	.info =    (SNDRV_PCM_INFO_MMAP |
				SNDRV_PCM_INFO_BLOCK_TRANSFER |
				SNDRV_PCM_INFO_MMAP_VALID |
				SNDRV_PCM_INFO_INTERLEAVED |
				SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
	.formats = (SNDRV_PCM_FMTBIT_S16_LE |
				SNDRV_PCM_FMTBIT_S24_LE |
				SNDRV_PCM_FMTBIT_S24_3LE),
	.rates =                SNDRV_PCM_RATE_8000_48000,
	.rate_min =             8000,
	.rate_max =             48000,
	.channels_min =         1,
	.channels_max =         4,
	.buffer_bytes_max =     CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,
	.period_bytes_min =		CAPTURE_MIN_PERIOD_SIZE,
	.period_bytes_max =     CAPTURE_MAX_PERIOD_SIZE,
	.periods_min =          CAPTURE_MIN_NUM_PERIODS,
	.periods_max =          CAPTURE_MAX_NUM_PERIODS,
	.fifo_size =            0,
};

hw params:用户层设置的硬件参数,如 channels、sample rate、pcm format、period size、period count;这些参数受 hw constraints 约束。

sw params:用户层设置的软件参数,如 start threshold、stop threshold、silence threshold。



四、ASoC Core

ASoC:ALSA System on Chip,是建立在标准 ALSA 驱动上,为了更好支持嵌入系系统 和 应用于移动设备的音频 codec 的一套软件体系,它依赖于标准 ALSA 驱动框架。
内核文档 /Documentation/alsa/soc/overview.txt 中详细介绍了 ASoC 的设计初衷。

  • 独立的 codec 驱动: 标准的 ALSA 驱动框架里面 codec 驱动往往与 SoC / CPU 耦合过于紧密,不利于在多样化的平台/机器上移植复用
  • 方便 codec 与 SoC 通过 PCM/I2S 总线建立链接
  • 动态音频电源管理 DAPM,使得 codec 任何时候都工作在最低功耗状态,同时负责音频路由的创建
  • POPs 和 click 音抑制弱化处理,在 ASoC 中通过正确的音频部件上下电次序来实现
  • Machine 驱动的特定控制,比如耳机、麦克风的插拔检测,外放功放的开关

在概述中已经介绍了 ASoC 硬件设备驱动的三大构成:Codec、Platform 和 Machine,
下面列举各驱动的功能构成:

4.1ASoC Codec Driver

  • Codec DAI 和 PCM 的配置信息
  • Codec 的控制接口,如 I2C/SPI
  • Mixer 和其他音频控件
  • Codec 的音频接口函数,见 snd_soc_dai_ops 结构体定义
  • DAPM 描述信息
  • DAPM 事件处理句柄
  • DAC 数字静音控制

4.2 ASoC Platform Driver

包括 dma 和 cpu_dai 两部分:

  • dma 驱动实现音频 dma 操作,具体见 snd_pcm_ops 结构体定义
  • cpu_dai 驱动实现音频数字接口控制器的描述和配置

4.3 ASoC Machine Driver

作为链结 Platform 和 Codec 的载体,它必须配置 dai_link 为音频数据链路指定 Platform 和 Codec。
处理 机器特有的音频控件 和音频事件,例如,回放时打开外放功放。

硬件设备驱动相关结构体:

  • snd_soc_codec_driver
    音频解码芯片,描述函数,如控件 / 微件 /音频路由 的描述信息、时钟配置、IO 控制等。

  • snd_soc_dai_driver
    音频数据接口描述 及 操作函数,根据 codec 端和 soc 端,分为 codec_dai 和 cpu_dai。

  • snd_soc_platform_driver
    音频 dma 设备描述及操作函数

  • snd_soc_dai_link
    音频链路描述 及 板级操作函数



五、Codec Driver

基本是以内核文档 Documentation/sound/alsa/soc/codec.txt 中的内容为脉络来分析的。

Codec 的作用,之前已有描述,本章主要罗列下 Codec driver 中重要的数据结构及注册流程。

其中有着各种功能部件,包括但不限于 :

  • ADC 把麦克风拾取的模拟信号转换成数字信号
  • DAC 把音频接口过来的数字信号转换成模拟信号
  • MIXER 混音器,把多路输入信号混合成单路输出

5.1 Codec DAI and PCM configuration

codec_dai 和 pcm 配置信息通过结构体 snd_soc_dai_driver 描述,包括 dai 的能力描述和操作接口,snd_soc_dai_driver 最终会被注册到 soc-core 中。

[->include/sound/soc-dai.h]
/*
 * Digital Audio Interface Driver.
 *
 * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97
 * operations and capabilities. Codec and platform drivers will register this
 * structure for every DAI they have.
 * This structure covers the clocking, formating and ALSA operations for each
 * interface.
 */
struct snd_soc_dai_driver {
    /* DAI description */
    const char *name;
    unsigned int id;
    int ac97_control;

    /* DAI driver callbacks */
    int (*probe)(struct snd_soc_dai *dai);
    int (*remove)(struct snd_soc_dai *dai);
    int (*suspend)(struct snd_soc_dai *dai);
    int (*resume)(struct snd_soc_dai *dai);

    /* ops */
    const struct snd_soc_dai_ops *ops;

    /* DAI capabilities */
    struct snd_soc_pcm_stream capture;
    struct snd_soc_pcm_stream playback;
    unsigned int symmetric_rates:1;

    /* probe ordering - for components with runtime dependencies */
    int probe_order;
    int remove_order;
};

name:codec_dai 的名称标识,dai_link 通过配置 codec_dai_name 来找到对应的 codec_dai;
probe:codec_dai 的初始化函数,注册声卡时回调;
playback:回放能力描述,如回放设备所支持的声道数、采样率、音频格式;
capture:录制能力描述,如录制设备所支持声道数、采样率、音频格式;
ops:codec_dai 的操作函数集,这些函数集非常重要,用于 dai 的时钟配置、格式配置、硬件参数配置。


codec_dai:

[->sound/soc/codecs/wcd9335.c]
static struct snd_soc_dai_driver tasha_i2s_dai[] = {
	{
		.name = "tasha_i2s_rx1",
		.id = AIF1_PB,
		.playback = {
			.stream_name = "AIF1 Playback",
			.rates = WCD9335_RATES_MASK,
			.formats = TASHA_FORMATS_S16_S24_LE,
			.rate_max = 192000,
			.rate_min = 8000,
			.channels_min = 1,
			.channels_max = 2,
		},
		.ops = &tasha_dai_ops,
	},
	{
		.name = "tasha_i2s_tx1",
		.id = AIF1_CAP,
		.capture = {
			.stream_name = "AIF1 Capture",
			.rates = WCD9335_RATES_MASK,
			.formats = TASHA_FORMATS,
			.rate_max = 192000,
			.rate_min = 8000,
			.channels_min = 1,
			.channels_max = 4,
		},
		.ops = &tasha_dai_ops,
	},
	......
}

5.2 Codec control IO

移动设备的音频 Codec,其控制接口一般是 I2C 或 SPI,控制接口用于读写 codec 的寄存器。
在 snd_soc_codec_driver 结构体中,有如下字段描述 Codec 的控制接口:

[->include/sound/soc.h]
/* codec driver */
struct snd_soc_codec_driver {

	......
	/* codec IO */
	struct regmap *(*get_regmap)(struct device *);
	unsigned int (*read)(struct snd_soc_codec *, unsigned int);
	int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
	int (*display_register)(struct snd_soc_codec *, char *,
				size_t, unsigned int);
	int (*volatile_register)(struct snd_soc_codec *, unsigned int);
	int (*readable_register)(struct snd_soc_codec *, unsigned int);
	int (*writable_register)(struct snd_soc_codec *, unsigned int);
	unsigned int reg_cache_size;
	short reg_cache_step;
	short reg_word_size;
	const void *reg_cache_default;

	......
};
  • read:读寄存器;
  • write:写寄存器;
  • volatile_register:
    判断指定的寄存器是否是 volatile 属性;假如是,则读取寄存器时不是读 cache,而直接访问硬件;
  • readable_register:判断指定的寄存器是否可读;
  • reg_cache_default:寄存器的缺省值;
  • reg_cache_size:缺省的寄存器值数组大小;
  • reg_word_size:寄存器宽度。

在 Linux-3.4.5 中,很多 codec 的控制接口都改用 regmap 了。
soc-core 中判断是否用的是 regmap,如果是,则调用 regmap 接口。


5.3 Mixers and audio controls

音频控件多用于部件开关和音量的设定,音频控件可通过 soc.h 中的宏来定义,
例如单一型控件:

[->include/sound/soc.h]
#define SOC_SINGLE(xname, reg, shift, max, invert) \
{   .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
    .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
    .put = snd_soc_put_volsw, \
    .private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) }

这种控件只有一个设置量,一般用于部件开关。
宏定义的参数说明:

  • xname:控件的名称标识;
  • reg:控件对应的寄存器地址;
  • shift:控件控制位在寄存器中的偏移;
  • max:控件设置值范围;
  • invert:设定值是否取反。
    其他类型控件类似,不一一介绍了。

上述只是宏定义,音频控件真正的结构是 snd_kcontrol_new:

[->/include/sound/control.h]
struct snd_kcontrol_new {
    snd_ctl_elem_iface_t iface; /* interface identifier */
    unsigned int device;        /* device/client number */
    unsigned int subdevice;     /* subdevice (substream) number */
    const unsigned char *name;  /* ASCII name of item */
    unsigned int index;     /* index of item */
    unsigned int access;        /* access rights */
    unsigned int count;     /* count of same elements */
    snd_kcontrol_info_t *info;
    snd_kcontrol_get_t *get;
    snd_kcontrol_put_t *put;
    union {
        snd_kcontrol_tlv_rw_t *c;
        const unsigned int *p;
    } tlv;
    unsigned long private_value;
};

Codec 初始化时,通过 snd_soc_add_codec_controls() 把所有定义好的音频控件注册到 alsa-core ,
上层可以通过 tinymix、alsa_amixer 等工具查看修改这些控件的设定。



六、Codec audio operations

Codec 音频操作接口通过结构体 snd_soc_dai_ops 描述:

[->include/sound/soc-dai.h]
struct snd_soc_dai_ops {
	/*
	 * DAI clocking configuration, all optional.
	 * Called by soc_card drivers, normally in their hw_params.
	 */
	int (*set_sysclk)(struct snd_soc_dai *dai,
				int clk_id, unsigned int freq, int dir);
	int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
				unsigned int freq_in, unsigned int freq_out);
	int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
	int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);

	/*
	 * DAI format configuration
	 * Called by soc_card drivers, normally in their hw_params.
	 */
	int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
	int (*xlate_tdm_slot_mask)(unsigned int slots,
				unsigned int *tx_mask, unsigned int *rx_mask);
	int (*set_tdm_slot)(struct snd_soc_dai *dai,
				unsigned int tx_mask, unsigned int rx_mask,
				int slots, int slot_width);
	int (*set_channel_map)(struct snd_soc_dai *dai,
				unsigned int tx_num, unsigned int *tx_slot,
				unsigned int rx_num, unsigned int *rx_slot);
	int (*set_tristate)(struct snd_soc_dai *dai, int tristate);
	int (*get_channel_map)(struct snd_soc_dai *dai,
				unsigned int *tx_num, unsigned int *tx_slot,
				unsigned int *rx_num, unsigned int *rx_slot);
	......
};

注释比较详细的了,Codec 音频操作接口分为 5 大部分:
时钟配置、格式配置、数字静音、PCM 音频接口、FIFO 延迟。

着重说下时钟配置及格式配置接口:

  • set_sysclk
    codec_dai 系统时钟设置,当上层打开 pcm 设备时,
    需要回调该接口设置 Codec 的系统时钟,Codec 才能正常工作;

  • set_pll
    Codec FLL 设置,Codec 一般接了一个 MCLK 输入时钟,
    回调该接口基于 MCLK 来产生 Codec FLL 时钟,
    接着 codec_dai 的 sysclk、bclk、lrclk 均可从 FLL 分频出来(假设 Codec 作为 master);

  • set_fmt:codec_dai 格式设置,具体见 soc-dai.h;

  • SND_SOC_DAIFMT_I2S:音频数据是 I2S 格式,常用于多媒体音频;

  • SND_SOC_DAIFMT_DSP_A:音频数据是 PCM 格式,常用于通话语音;

  • SND_SOC_DAIFMT_CBM_CFM:Codec 作为 master,BCLK 和 LRCLK 由 Codec 提供;

  • SND_SOC_DAIFMT_CBS_CFS:Codec 作为 slave,BCLK 和 LRCLK 由 SoC/CPU 提供;

  • hw_params
    codec_dai 硬件参数设置,根据上层设定的声道数、采样率、数据格式,来配置 codec_dai 相关寄存器。


WCD9335的snd_soc_dai_ops :

[->/sound/soc/codecs/wcd9335.c]
static struct snd_soc_dai_ops tasha_dai_ops = {
	.startup = tasha_startup,
	.shutdown = tasha_shutdown,
	.hw_params = tasha_hw_params,
	.prepare = tasha_prepare,
	.set_sysclk = tasha_set_dai_sysclk,
	.set_fmt = tasha_set_dai_fmt,
	.set_channel_map = tasha_set_channel_map,
	.get_channel_map = tasha_get_channel_map,
};

6.1 Codec register

当 platform_driver:

[->/sound/soc/codecs/wcd9335.c]
static struct platform_driver tasha_codec_driver = {
	.probe = tasha_probe,
	.remove = tasha_remove,
	.driver = {
		.name = "tasha_codec",
		.owner = THIS_MODULE,
#ifdef CONFIG_PM
		.pm = &tasha_pm_ops,
#endif
	},
};

与.name = “tasha_codec” 的 platform_device
(该 platform_device 在 drivers/mfd/wcd9xxx-core.c 中注册wcd9xxx_device_init->wcd9xxx_check_codec_type->tasha_devs)匹配后,

[->drivers/mfd/wcd9xxx-core.c]
static struct mfd_cell tasha_devs[] = {
	{
		.name = "tasha_codec",
	},
};

立即回调 tasha_probe() 注册 Codec:

[->/sound/soc/codecs/wcd9335.c]
static int tasha_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct tasha_priv *tasha;
	struct clk *wcd_ext_clk, *wcd_native_clk;
	struct wcd9xxx_resmgr_v2 *resmgr;
	struct wcd9xxx_power_region *cdc_pwr;
	......
	tasha = devm_kzalloc(&pdev->dev, sizeof(struct tasha_priv), GFP_KERNEL);
	......
    tasha->resmgr = resmgr;
	tasha->swr_plat_data.handle = (void *) tasha;
	tasha->swr_plat_data.read = tasha_swrm_read;
	tasha->swr_plat_data.write = tasha_swrm_write;
	tasha->swr_plat_data.bulk_write = tasha_swrm_bulk_write;
	tasha->swr_plat_data.clk = tasha_swrm_clock;
	tasha->swr_plat_data.handle_irq = tasha_swrm_handle_irq;

	/* Register for Clock */
	wcd_ext_clk = clk_get(tasha->wcd9xxx->dev, "wcd_clk");

	tasha->wcd_ext_clk = wcd_ext_clk;
	tasha->sido_voltage = SIDO_VOLTAGE_NOMINAL_MV;
	set_bit(AUDIO_NOMINAL, &tasha->status_mask);
	tasha->sido_ccl_cnt = 0;
	......
	if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_SLIMBUS)
		ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_tasha,
					     tasha_dai, ARRAY_SIZE(tasha_dai));
	else if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C)
		ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_tasha,
					     tasha_i2s_dai,
					     ARRAY_SIZE(tasha_i2s_dai));
	else
		ret = -EINVAL;
	......
}

snd_soc_register_codec:将 codec_driver 和 codec_dai_driver 注册到 soc-core。

[->]
/**
 * snd_soc_register_codec - Register a codec with the ASoC core
 *
 * @codec: codec to register
 */
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)

创建一个 snd_soc_codec 实例,包含 codec_drv(snd_soc_dai_driver)相关信息,
封装给 soc-core 使用,相关代码段如下

[sound/soc/soc-core.c: snd_soc_register_codec]
    struct snd_soc_codec *codec;

    dev_dbg(dev, "codec register %s\n", dev_name(dev));

    codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
    if (codec == NULL)
        return -ENOMEM;

    /* create CODEC component name */
    codec->name = fmt_single_name(dev, &codec->id);
    if (codec->name == NULL) {
        kfree(codec);
        return -ENOMEM;
    }

    // 初始化 Codec 的寄存器缓存配置及读写接口
    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;
    codec->num_dai = num_dai;
    mutex_init(&codec->mutex);

把以上 codec 实例插入到 codec_list链表中(声卡注册时会遍历该链表,找到 dai_link 声明的 codec 并绑定):

[sound/soc/soc-core.c: snd_soc_register_codec]
list_add(&codec->list, &codec_list);

把 codec_drv 中的 snd_soc_dai_driver(tasha_dai 或者tasha_i2s_dai )注册到 soc-core:

[sound/soc/soc-core.c: snd_soc_register_codec]
snd_soc_register_dais(&codec->component, dai_drv, num_dai, false);

snd_soc_register_dais() 会把 dai 插入到 dai_list 链表中
(声卡注册时会遍历该链表,找到 dai_link 声明的 codec_dai 并绑定):

[sound/soc/soc-core.c: snd_soc_register_codec]
list_add(&dai->list, &dai_list);

最后顺便提下 codec 和 codec_dai 的区别:
codec 指音频芯片共有的部分,包括 codec 初始化函数、控制接口、寄存器缓存、控件、dapm 部件、音频路由、偏置电压设置函数等描述信息;
而 codec_dai 指 codec 上的音频接口驱动描述,包括时钟配置、格式配置、能力描述等等,各个接口的描述信息不一定都是一致的,所以每个音频接口都有着各自的驱动描述。



七、Platform Driver

概述中提到音频 Platform 驱动主要用于音频数据传输,这里又细分为两步

启动 dma 设备,把音频数据从 dma buffer 搬运到 cpu_dai FIFO,这部分驱动用 snd_soc_platform_driver 描述,后面分析用 pcm_dma 指代它。

启动数字音频接口控制器(I2S/PCM/AC97),把音频数据从 cpu_dai FIFO 传送到 codec_dai(高通平台会将数据传送到ADSP)这部分驱动用 snd_soc_dai_driver 描述,后面分析用 cpu_dai 指代它。

MSM8996 包含三个 Hexagon DSP :application, modem, and sensor。
Application DSP:不仅可以处理语音和音频,还可以处理计算机 视觉、视频、图像和Camera。
Sensor DSP:也叫做SLPI,所有的sensor都链接到SLPI上面,它管理所有的Sensor及相关算法。

对于 cpu_dai 驱动,从上面的类图我们可知,主要工作有:

  • 实现 dai 操作函数,见 snd_soc_dai_ops 定义,用于配置和操作音频数字接口控制器,
    如时钟配置 set_sysclk()、格式配置 set_fmt()、硬件参数配置 hw_params()、启动/停止数据传输 trigger() 等;

  • 实现 probe 函数(初始化)、remove 函数(卸载)、suspend/resume 函数(电源管理);

  • 初始化 snd_soc_dai_driver 实例,包括回放和录制的能力描述、dai 操作函数集、probe/remove 回调、电源管理相关的 suspend/resume 回调;

  • 通过 snd_soc_register_dai() 把初始化完成的 snd_soc_dai_driver 注册到 soc-core
    首先创建一个 snd_soc_dai 实例,然后把该 snd_soc_dai 实例插入到 dai_list 链表
    (声卡注册时会遍历该链表,找到 dai_link 声明的 cpu_dai 并绑定)。

[sound/soc/soc-core.c]
static int snd_soc_register_dais(struct snd_soc_component *component,
	struct snd_soc_dai_driver *dai_drv, size_t count, bool legacy_dai_naming)
{
	struct device *dev = component->dev;
	struct snd_soc_dai *dai;
	dev_dbg(dev, "ASoC: dai register %s #%Zu\n", dev_name(dev), count);

	component->dai_drv = dai_drv;
	component->num_dai = count;

	for (i = 0; i < count; i++) {
		dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
		......
		if (count == 1 && legacy_dai_naming) {
			dai->name = fmt_single_name(dev, &dai->id);
		} else {
			dai->name = fmt_multiple_name(dev, &dai_drv[i]);
			if (dai_drv[i].id)
				dai->id = dai_drv[i].id;
			else
				dai->id = i;
		}
		......
		dai->component = component;
		dai->dev = dev;
		dai->driver = &dai_drv[i];
		if (!dai->driver->ops)
			dai->driver->ops = &null_dai_ops;

		list_add(&dai->list, &component->dai_list);
	}
	return 0;
}

dai 操作函数的实现是 cpu_dai 驱动的主体,需要配置好相关寄存器让 I2S/PCM 总线控制器正常运转,snd_soc_dai_ops 字段的详细说明见 Codec audio operations 章节。

cpu_dai 驱动应该算是这个系列中最简单的一环,因此不多花费笔墨在这里了。倒是某些平台上,dma 设备信息(总线地址、通道号、传输单元大小)是在这里初始化的,这点要留意,这些 dma 设备信息在 pcm_dma 驱动中用到。


7.1 pcm operations

操作函数的实现是本模块的主体,见 snd_pcm_ops 结构体描述:

[->include/sound/pcm.h]
struct snd_pcm_ops {
	int (*open)(struct snd_pcm_substream *substream);
	int (*close)(struct snd_pcm_substream *substream);
	int (*ioctl)(struct snd_pcm_substream * substream, unsigned int cmd, void *arg);
	int (*compat_ioctl)(struct snd_pcm_substream *substream, unsigned int cmd, void *arg);
	int (*hw_params)(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params);
	int (*hw_free)(struct snd_pcm_substream *substream);
	int (*prepare)(struct snd_pcm_substream *substream);
	int (*trigger)(struct snd_pcm_substream *substream, int cmd);
	snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
	int (*delay_blk)(struct snd_pcm_substream *substream);
	int (*wall_clock)(struct snd_pcm_substream *substream, struct timespec *audio_ts);
	int (*copy)(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t pos, void __user *buf, snd_pcm_uframes_t count);
	int (*silence)(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
	struct page *(*page)(struct snd_pcm_substream *substream, unsigned long offset);
	int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
	int (*ack)(struct snd_pcm_substream *substream);
	int (*restart)(struct snd_pcm_substream *substream);
};

7.2 platform_driver 注册

当 platform_driver

[->sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c]
static struct platform_driver msm_pcm_driver = {
	.driver = {
		.name = "msm-pcm-dsp",
		.owner = THIS_MODULE,
		.of_match_table = msm_pcm_dt_match,
	},
	.probe = msm_pcm_probe,
	.remove = msm_pcm_remove,
};

与 .name = “msm-pcm-dsp” 的 platform_device 注册 匹配后,系统会回调 msm_pcm_probe() 注册 platform:

[->sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c]
static int msm_pcm_probe(struct platform_device *pdev)
{
	struct msm_plat_data *pdata;
	const char *latency_level;

	rc = of_property_read_u32(pdev->dev.of_node, "qcom,msm-pcm-dsp-id", &id);
	......
	pdata = kzalloc(sizeof(struct msm_plat_data), GFP_KERNEL);
	......

	if (of_property_read_bool(pdev->dev.of_node, "qcom,msm-pcm-low-latency")) {
		pdata->perf_mode = LOW_LATENCY_PCM_MODE;
		rc = of_property_read_string(pdev->dev.of_node, "qcom,latency-level", &latency_level);
		if (!rc) {
			if (!strcmp(latency_level, "ultra"))
				pdata->perf_mode = ULTRA_LOW_LATENCY_PCM_MODE;
			else if (!strcmp(latency_level, "ull-pp"))
				pdata->perf_mode = ULL_POST_PROCESSING_PCM_MODE;
		}
	}
	else
		pdata->perf_mode = LEGACY_PCM_MODE;
		
	dev_set_drvdata(&pdev->dev, pdata);
	return snd_soc_register_platform(&pdev->dev,  &msm_soc_platform);
}

snd_soc_register_platform:将 platform_drv 注册到 soc-core。

创建一个 snd_soc_platform 实例,包含 platform_drv(snd_soc_platform_driver)的相关信息,封装给 soc-core 使用;
把以上创建的 platform 实例插入到 platform_list 链表上
(声卡注册时会遍历该链表,找到 dai_link 声明的 platform 并绑定)。

代码实现:

int snd_soc_register_platform(struct device *dev, const struct snd_soc_platform_driver *platform_drv)
{
	struct snd_soc_platform *platform;
	
	platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);
	ret = snd_soc_add_platform(dev, platform, platform_drv);
	return ret;
}

至此,完成了 Platform 驱动的实现。
回放情形下,pcm_dma 设备负责把 dma buffer 中的数据搬运到 I2S tx FIFO,
I2S 总线控制器负责把 I2S tx FIFO 中的数据传送DSP,DSP经处理后传送到到 Codec。


八、Machine Driver

在前在的 Codec 和 Platform 介绍了 Codec、Platform 驱动,
但仅有 Codec、Platform 驱动是不能工作的,
需要一个角色把 codec、codec_dai、cpu_dai、platform 给链结起来才能构成一个完整的音频链路,
这个角色就由 machine_drv 承担了。

snd_soc_dai_link 结构体:

[->/include/sound/soc.h]
struct snd_soc_dai_link {
	const char *name;			/* Codec name */
	const char *stream_name;		/* Stream name */
	const char *cpu_name;
	struct device_node *cpu_of_node;
	const char *cpu_dai_name;
	const char *codec_name;
	struct device_node *codec_of_node;
	const char *codec_dai_name;
	struct snd_soc_dai_link_component *codecs;
	unsigned int num_codecs;
	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 */
	unsigned int ignore_suspend:1;
	unsigned int symmetric_rates:1;
	unsigned int symmetric_channels:1;
	unsigned int symmetric_samplebits:1;
	unsigned int no_pcm:1;
	unsigned int dynamic:1;
	unsigned int no_host_mode:2;
	unsigned int dpcm_capture:1;
	unsigned int dpcm_playback:1;
	unsigned int ignore_pmdown_time:1;
	int (*init)(struct snd_soc_pcm_runtime *rtd);
	int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params);
	const struct snd_soc_ops *ops;
	const struct snd_soc_compr_ops *compr_ops;
	bool playback_only;
	bool capture_only;
	enum snd_soc_async_ops async_ops;
}

重点介绍如下几个字段:

  • codec_name
    音频链路需要绑定的 codec 名称,声卡注册时会遍历 codec_list,找到同名的 codec 并绑定;

  • platform_name
    音频链路需要绑定的 platform 名称,声卡注册时会遍历 platform_list,找到同名的 platform 并绑定;

  • cpu_dai_name
    音频链路需要绑定的 cpu_dai 名称,声卡注册时会遍历 dai_list,找到同名的 dai 并绑定;

  • codec_dai_name
    音频链路需要绑定的 codec_dai 名称,声卡注册时会遍历 dai_list,找到同名的 dai 并绑定;

ops:重点留意 hw_params() 回调,一般来说这个回调是要实现的,用于配置 codec、codec_dai、cpu_dai 的数据格式和系统时钟。在 Codec audio operations 小节中有描述。

/sound/soc/msm/msm8996.c 中的 dai_link 定义,两个音频链路分别用于 Media 和 Voice:

[->/sound/soc/msm/msm8996.c]
/* Digital audio interface glue - connects codec <---> CPU */
static struct snd_soc_dai_link msm8996_common_dai_links[] = {
	/* FrontEnd DAI Links */
	{
		.name = "MSM8996 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
	},
	......
	{
		.name = "VoiceMMode1",
		.stream_name = "VoiceMMode1",
		.cpu_dai_name = "VoiceMMode1",
		.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,
		.ignore_pmdown_time = 1,
		.codec_dai_name = "snd-soc-dummy-dai",
		.codec_name = "snd-soc-dummy",
		.be_id = MSM_FRONTEND_DAI_VOICEMMODE1,
	},
	
}

除了 dai_link,机器中一些特定的音频控件和音频事件也可以在 machine_drv 定义,如耳机插拔检测、外部功放打开关闭等。

我们再分析 machine_drv 初始化过程:

[->/sound/soc/msm/msm8996.c]
static struct platform_driver msm8996_asoc_machine_driver = {
	.driver = {
		.name = DRV_NAME,
		.owner = THIS_MODULE,
		.pm = &snd_soc_pm_ops,
		.of_match_table = msm8996_asoc_machine_of_match,
	},
	.probe = msm8996_asoc_machine_probe,
	.remove = msm8996_asoc_machine_remove,
};

[->/sound/soc/msm/msm8996.c]
static int msm8996_asoc_machine_probe(struct platform_device *pdev)
{
	struct snd_soc_card *card;
	struct msm8996_asoc_mach_data *pdata;
	const char *mbhc_audio_jack_type = NULL;
	char *mclk_freq_prop_name;
	const struct of_device_id *match;
    ......
	pdata = devm_kzalloc(&pdev->dev, sizeof(struct msm8996_asoc_mach_data), GFP_KERNEL);


	card = populate_snd_card_dailinks(&pdev->dev);
	......
	match = of_match_node(msm8996_asoc_machine_of_match, pdev->dev.of_node);
	ret = msm8996_populate_dai_link_component_of_node(card);
	......
	
	ret = snd_soc_register_card(card);
}

设置dailinks后,继而调用 snd_soc_register_card() 注册声卡。
由于该过程很冗长,这里不一一贴代码分析了,但整个流程是比较简单的,
流程图如下:
在这里插入图片描述

  • 取出 platform_device 的私有数据,该私有数据就是 snd_soc_card ;

  • snd_soc_register_card() 为每个 dai_link 分配一个 snd_soc_pcm_runtime 实例,别忘了之前提过 snd_soc_pcm_runtime 是 ASoC 的桥梁,保存着 codec、codec_dai、cpu_dai、platform 等硬件设备实例。

  • 随后的工作都在 snd_soc_instantiate_card() 进行:

  • 遍历 dai_list、codec_list、platform_list 链表,为每个音频链路找到对应的 cpu_dai、codec_dai、codec、platform;找到的 cpu_dai、codec_dai、codec、platform 保存到 snd_soc_pcm_runtime ,完成音频链路的设备绑定;

  • 调用 snd_card_create() 创建声卡;

  • soc_probe_dai_link() 依次回调 cpu_dai、codec、platform、codec_dai 的 probe() 函数,完成各音频设备的初始化,随后调用

  • soc_new_pcm() 创建 pcm 逻辑设备(因为涉及到本系列的重点内容,后面具体分析这个函数);

  • 最后调用 snd_card_register() 注册声卡。


    [->sound/soc/soc-core.c]
    在这里插入图片描述

soc_new_pcm 源码分析:

[->/sound/soc/soc-pcm.c]
/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
	struct snd_soc_platform *platform = rtd->platform;
	struct snd_soc_dai *codec_dai;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	struct snd_pcm *pcm;
	char new_name[64];
	int ret = 0, playback = 0, capture = 0;
	int i;

	if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {
		playback = rtd->dai_link->dpcm_playback;
		capture = rtd->dai_link->dpcm_capture;
	} else {
		for (i = 0; i < rtd->num_codecs; i++) {
			codec_dai = rtd->codec_dais[i];
			if (codec_dai->driver->playback.channels_min)
				playback = 1;
			if (codec_dai->driver->capture.channels_min)
				capture = 1;
		}

		capture = capture && cpu_dai->driver->capture.channels_min;
		playback = playback && cpu_dai->driver->playback.channels_min;
	}

	if (rtd->dai_link->playback_only) {
		playback = 1;
		capture = 0;
	}

	if (rtd->dai_link->capture_only) {
		playback = 0;
		capture = 1;
	}

	/* create the PCM */
	if (rtd->dai_link->no_pcm) {
		snprintf(new_name, sizeof(new_name), "(%s)",
			rtd->dai_link->stream_name);

		ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,
				playback, capture, &pcm);
	} else {
		if (rtd->dai_link->dynamic)
			snprintf(new_name, sizeof(new_name), "%s (*)",
				rtd->dai_link->stream_name);
		else
			snprintf(new_name, sizeof(new_name), "%s %s-%d",
				rtd->dai_link->stream_name,
				(rtd->num_codecs > 1) ?
				"multicodec" : rtd->codec_dai->name, num);

		ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,
			capture, &pcm);
	}
	if (ret < 0) {
		dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\n",
			rtd->dai_link->name);
		return ret;
	}
	dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name);

	/* DAPM dai link stream work */
	INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);

	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;
		if (platform->driver->pcm_new)
			rtd->platform->driver->pcm_new(rtd);
		goto out;
	}

	/* setup any hostless PCMs - i.e. no host IO is performed */
	if (rtd->dai_link->no_host_mode) {
		if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
			pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->hw_no_buffer = 1;
			snd_soc_set_runtime_hwparams(
				pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
				&no_host_hardware);
		}
		if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
			pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->hw_no_buffer = 1;
			snd_soc_set_runtime_hwparams(
				pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
				&no_host_hardware);
		}
	}

	/* 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.delay_blk	= soc_pcm_delay_blk;
		rtd->ops.ioctl		= soc_pcm_ioctl;
		rtd->ops.compat_ioctl   = soc_pcm_compat_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.delay_blk	= soc_pcm_delay_blk;
		rtd->ops.ioctl		= soc_pcm_ioctl;
		rtd->ops.compat_ioctl   = soc_pcm_compat_ioctl;
	}

	if (platform->driver->ops) {
		rtd->ops.ack		= platform->driver->ops->ack;
		rtd->ops.copy		= platform->driver->ops->copy;
		rtd->ops.silence	= platform->driver->ops->silence;
		rtd->ops.page		= platform->driver->ops->page;
		rtd->ops.mmap		= platform->driver->ops->mmap;
		rtd->ops.restart	= platform->driver->ops->restart;
	}

	if (playback)
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);

	if (capture)
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);

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

可见 soc_new_pcm() 最主要的工作是创建 pcm 逻辑设备,创建回放子流和录制子流实例,并初始化回放子流和录制子流的 pcm 操作函数(数据搬运时,需要调用这些函数来驱动 codec、codec_dai、cpu_dai、dma 设备工作)。



发布了329 篇原创文章 · 获赞 66 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/Ciellee/article/details/101691234