ASoC 音频接口配置过程

ASoC音频接口配置详解

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. ASoC 音频接口配置

音频接口(如 SAI,I2S 等)的配置,包括:

1. 主从模式
2. 时钟相位
3. 数据格式

ASoC 架构通过一个粘合驱动DAICODEC 拼接到一起,组成一个声卡驱动。粘合层驱动又有公版驱动SoC 厂家定制驱动两种形式,下面分别就这两种形式展开讨论。

2.1 公版粘合驱动下的音频接口配置

本小节以 RK3308 + Linux 5.10 内核代码为例进行分析。先看一下 DTS 配置:

simplecard: simplecard {
	status = "okay";
	compatible = "simple-audio-card";
	simple-audio-card,format = "i2s";
	simple-audio-card,mclk-fs = <512>;
	simple-audio-card,name = "rockchip,card";
	simple-audio-card,bitclock-master = <&master>; /* SoC 的 DAI 做从,由 CODEC 提供 BCLK */
	simple-audio-card,frame-master = <&master>; /* SoC 的 DAI 做从,由 CODEC 提供 LRCK */
	simple-audio-card,cpu {
		sound-dai = <&i2s_8ch_0>;
	};
	master: simple-audio-card,codec {
		sound-dai = <&dummy_codec>;
	};
};

2.1.1 音频接口配置数据解析

asoc_simple_probe()
	/* 1. 解析音频接口配置数据 */
	simple_parse_of()
		simple_dai_link_of()
			asoc_simple_parse_daifmt()
				snd_soc_of_parse_daifmt()

unsigned int snd_soc_of_parse_daifmt(struct device_node *np,
				     const char *prefix,
				     struct device_node **bitclkmaster,
				     struct device_node **framemaster)
{
	...
	unsigned int format = 0;
	...
	struct {
		char *name;
		unsigned int val;
	} of_fmt_table[] = {
		{ "i2s",	SND_SOC_DAIFMT_I2S },
		{ "right_j",	SND_SOC_DAIFMT_RIGHT_J },
		{ "left_j",	SND_SOC_DAIFMT_LEFT_J },
		{ "dsp_a",	SND_SOC_DAIFMT_DSP_A },
		{ "dsp_b",	SND_SOC_DAIFMT_DSP_B },
		{ "ac97",	SND_SOC_DAIFMT_AC97 },
		{ "pdm",	SND_SOC_DAIFMT_PDM},
		{ "msb",	SND_SOC_DAIFMT_MSB },
		{ "lsb",	SND_SOC_DAIFMT_LSB },
	};

	...
	ret = of_property_read_string(np, "dai-format", &str);
	...

	snprintf(prop, sizeof(prop), "%sbitclock-inversion", prefix);
	bit = !!of_get_property(np, prop, NULL);

	snprintf(prop, sizeof(prop), "%sframe-inversion", prefix);
	frame = !!of_get_property(np, prop, NULL);

	switch ((bit << 4) + frame) {
	case 0x11:
		format |= SND_SOC_DAIFMT_IB_IF;
		break;
	case 0x10:
		format |= SND_SOC_DAIFMT_IB_NF;
		break;
	case 0x01:
		format |= SND_SOC_DAIFMT_NB_IF;
		break;
	default:
		/* SND_SOC_DAIFMT_NB_NF is default */
		break;
	}

	...

	snprintf(prop, sizeof(prop), "%sbitclock-master", prefix); // "simple-audio-card,bitclock-master"
	bit = !!of_get_property(np, prop, NULL);
	if (bit && bitclkmaster)
		*bitclkmaster = of_parse_phandle(np, prop, 0);

	snprintf(prop, sizeof(prop), "%sframe-master", prefix); // "simple-audio-card,frame-master"
	frame = !!of_get_property(np, prop, NULL);
	if (frame && framemaster)
		*framemaster = of_parse_phandle(np, prop, 0); // @framemaster => &master

	switch ((bit << 4) + frame) {
	case 0x11:
		format |= SND_SOC_DAIFMT_CBM_CFM;
		break;
	case 0x10:
		format |= SND_SOC_DAIFMT_CBM_CFS;
		break;
	case 0x01:
		format |= SND_SOC_DAIFMT_CBS_CFM;
		break;
	default:
		format |= SND_SOC_DAIFMT_CBS_CFS;
		break;
	}

	return format; /* 返回解析的 DAI 格式配置 */
}

int asoc_simple_parse_daifmt(struct device *dev,
			     struct device_node *node,
			     struct device_node *codec,
			     char *prefix,
			     unsigned int *retfmt)
{
	struct device_node *bitclkmaster = NULL;
	struct device_node *framemaster = NULL;
	unsigned int daifmt;

	daifmt = snd_soc_of_parse_daifmt(node, prefix,
					 &bitclkmaster, &framemaster);
	daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK;

	if (!bitclkmaster && !framemaster) {
		...
		daifmt = snd_soc_of_parse_daifmt(codec, NULL, NULL, NULL) |
			(daifmt & ~SND_SOC_DAIFMT_CLOCK_MASK);
	} else {
		if (codec == bitclkmaster) // 本小节 SoC DAI 做从的情形
			daifmt |= (codec == framemaster) ?
				SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS;
		else
			daifmt |= (codec == framemaster) ?
				SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS;
	}

	...

	*retfmt = daifmt;

	return 0;
}

2.1.2 配置音频接口

asoc_simple_probe()
	/* 1. 解析音频接口配置数据 */
	simple_parse_of()
		simple_dai_link_of()
			asoc_simple_parse_daifmt()
				snd_soc_of_parse_daifmt()
	...
	/* 2. 配置音频接口数据 */
	devm_snd_soc_register_card()
		snd_soc_register_card()
			snd_soc_bind_card()
				soc_init_pcm_runtime()
					snd_soc_link_init()
						rtd->dai_link->init() = asoc_simple_dai_init()
							asoc_simple_init_dai()
								snd_soc_dai_set_tdm_slot()
									/* 设置 CPU 一侧 DAI 为 TDM 模式 */
									dai->driver->ops->set_tdm_slot() = rockchip_dai_tdm_slot() 
					snd_soc_runtime_set_dai_fmt()
						snd_soc_dai_set_fmt()
							dai->driver->ops->set_fmt() = rockchip_i2s_tdm_set_fmt()

static int rockchip_i2s_tdm_set_fmt(struct snd_soc_dai *cpu_dai,
				    unsigned int fmt)
{
	...
	
	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
	case SND_SOC_DAIFMT_CBS_CFS:
		val = I2S_CKR_MSS_MASTER;
		i2s_tdm->is_master_mode = true;
		break;
	case SND_SOC_DAIFMT_CBM_CFM: // 本小节 SoC DAI 做从的情形
		val = I2S_CKR_MSS_SLAVE;
		i2s_tdm->is_master_mode = false;
		/*
		 * TRCM require TX/RX enabled at the same time, or need the one
		 * which provide clk enabled at first for master mode.
		 *
		 * It is quite a different for slave mode which does not have
		 * these restrictions, because the BCLK / LRCK are provided by
		 * external master devices.
		 *
		 * So, we just set the right clk path value on TRCM register on
		 * stage probe and then drop the trcm value to make TX / RX work
		 * independently.
		 */
		i2s_tdm->clk_trcm = 0;
		break;
	default:
		ret = -EINVAL;
		goto err_pm_put;
	}

	regmap_update_bits(i2s_tdm->regmap, I2S_CKR, mask, val);

	...
	
	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
	case SND_SOC_DAIFMT_NB_NF:
		val = I2S_CKR_CKP_NORMAL |
		      I2S_CKR_TLP_NORMAL |
		      I2S_CKR_RLP_NORMAL;
		break;
	case SND_SOC_DAIFMT_NB_IF:
		val = I2S_CKR_CKP_NORMAL |
		      I2S_CKR_TLP_INVERTED |
		      I2S_CKR_RLP_INVERTED;
		break;
	case SND_SOC_DAIFMT_IB_NF:
		val = I2S_CKR_CKP_INVERTED |
		      I2S_CKR_TLP_NORMAL |
		      I2S_CKR_RLP_NORMAL;
		break;
	case SND_SOC_DAIFMT_IB_IF:
		val = I2S_CKR_CKP_INVERTED |
		      I2S_CKR_TLP_INVERTED |
		      I2S_CKR_RLP_INVERTED;
		break;
	default:
		ret = -EINVAL;
		goto err_pm_put;
	}

	regmap_update_bits(i2s_tdm->regmap, I2S_CKR, mask, val);

	...

	/* 设置波形格式: Right J, Left J, I2S, DSP A, DSP B */
	mask = I2S_TXCR_IBM_MASK | I2S_TXCR_TFS_MASK | I2S_TXCR_PBM_MASK;
	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
	case SND_SOC_DAIFMT_RIGHT_J:
		val = I2S_TXCR_IBM_RSJM;
		break;
	case SND_SOC_DAIFMT_LEFT_J:
		val = I2S_TXCR_IBM_LSJM;
		break;
	case SND_SOC_DAIFMT_I2S:
		val = I2S_TXCR_IBM_NORMAL;
		break;
	case SND_SOC_DAIFMT_DSP_A: /* PCM delay 1 mode */
		val = I2S_TXCR_TFS_PCM | I2S_TXCR_PBM_MODE(1);
		break;
	case SND_SOC_DAIFMT_DSP_B: /* PCM no delay mode */
		val = I2S_TXCR_TFS_PCM;
		break;
	default:
		ret = -EINVAL;
		goto err_pm_put;
	}

	regmap_update_bits(i2s_tdm->regmap, I2S_TXCR, mask, val);

	/* 等等其它 DAI 的格式参数设置 */
}

2.2 SoC 厂家定制粘合驱动下的音频接口配置

本小节以 RK3576 为例,分析该 SoC 内置 SAI 接口参数的配置过程。分析的内核版本为 Linux 6.1.75

2.2.1 音频接口配置数据解析

rk_multicodecs_probe()
	/* 1. 解析音频接口配置数据 */
	rk_multicodecs_parse_daifmt()

static int rk_multicodecs_parse_daifmt(struct device_node *node,
				       struct device_node *codec,
				       struct multicodecs_data *mc_data,
				       const char *prefix)
{
	struct snd_soc_dai_link *dai_link = &mc_data->dai_link[0];
	struct device_node *bitclkmaster = NULL;
	struct device_node *framemaster = NULL;
	unsigned int daifmt;
	
	daifmt = snd_soc_daifmt_parse_format(node, prefix); /* 解析时钟相位(是否反相)和格式等数据 */

	/* 解析主从模式配置:BCLK 和 LRCK 是来自于 音频接口 还是 CODEC */
	snd_soc_daifmt_parse_clock_provider_as_phandle(node, prefix, &bitclkmaster, &framemaster);
	if (!bitclkmaster && !framemaster) {
		/*
		 * No dai-link level and master setting was not found from
		 * sound node level, revert back to legacy DT parsing and
		 * take the settings from codec node.
		 */
		pr_debug("%s: Revert to legacy daifmt parsing\n", __func__);

		daifmt |= snd_soc_daifmt_parse_clock_provider_as_flag(codec, NULL);
	} else {
		daifmt |= snd_soc_daifmt_clock_provider_from_bitmap(
				((codec == bitclkmaster) << 4) | (codec == framemaster));
	}

	/*
	 * If there is NULL format means that the format isn't specified, we
	 * need to set i2s format by default.
	 */
	if (!(daifmt & SND_SOC_DAIFMT_FORMAT_MASK))
		daifmt |= SND_SOC_DAIFMT_I2S;

	dai_link->dai_fmt = daifmt;

	...
}

snd_soc_daifmt_parse_format() 函数解析了时钟相位(是否反相)和格式等数据,而 snd_soc_daifmt_parse_clock_provider_as_phandle() 解析了主从模式配置,即 BCLKLRCK 是来自于 音频接口还是 CODEC

2.2.2 配置音频接口

前面已经解析了音频接口的配置数据,接下来是按解析的数据对音频接口进行配置:

rk_multicodecs_probe()
	/* 1. 解析音频接口配置数据 */
	rk_multicodecs_parse_daifmt()
	...
	/* 2. 配置音频接口数据 */
	devm_snd_soc_register_card()
		snd_soc_register_card()
			snd_soc_bind_card()
				soc_init_pcm_runtime()
					snd_soc_runtime_get_dai_fmt()
					snd_soc_runtime_set_dai_fmt()
						snd_soc_dai_set_fmt()
							rockchip_sai_set_fmt()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值