文章目录
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. ASoC 音频接口配置
音频接口(如 SAI,I2S 等)的配置,包括:
1. 主从模式
2. 时钟相位
3. 数据格式
ASoC 架构通过一个粘合驱动将 DAI 和 CODEC 拼接到一起,组成一个声卡驱动。粘合层驱动又有公版驱动和 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() 解析了主从模式配置,即 BCLK 和 LRCK 是来自于 音频接口还是 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()
ASoC音频接口配置详解
1126

被折叠的 条评论
为什么被折叠?



