目录
4 tdm_rx_cfg/tdm_tx_cfg(音频设备参数)
5 tdm_rx_slot_offset/ tdm_tx_slot_offset(数据在slot数据映射关系)
我们知道I2S接口一根data线最多可以传输两个通道的数据,对于一些需要多通道传输的场景则需要使用TDM接口。采用TDM数字音频可以实现多通道音频传输,当前广泛运用于车载音频领域。
注意:TDM GPIO配置和解析流程跟MI2S一致,如下链接已经详细讲解。[linux audio driver] 高通平台MI2S总线调试_千千动听的博客-CSDN博客
本节不再赘述。
1 TDM特性
1 模式配置:
支持主模式和从模式配置。这意味着它可以作为主设备控制数据传输,也可以作为从设备。
2 支持的数据格式:
16位
24位左对齐(32位帧中左对齐的24位数据,最高位是MSB,最低位用零填充)
3 支持的采样率(主模式和从模式):
8 kHz
16 kHz
32 kHz
48 kHz
4 在从模式下支持的最大位时钟频率为24.576 MHz,主模式最大支持12.288MHz。
5 支持组设备,可以在同一数据线上发送多个数据流。
2 TDM BE DAI 设备与驱动
platform device 设备树,arch/arm64/boot/dts/qcom/msm-audio-lpass.dtsi
&soc {
...
tdm_tert_rx: qcom,msm-dai-tdm-tert-rx {
compatible = "qcom,msm-dai-tdm";
qcom,msm-cpudai-tdm-group-id = <37152>; //TDM group ID
qcom,msm-cpudai-tdm-group-num-ports = <5>; //TDM AFE port的数量
qcom,msm-cpudai-tdm-group-port-id = <36896 36898 36900 36902 36904>; //TDM AFE port ID
qcom,msm-cpudai-tdm-clk-rate = <1536000>; //Bit clk初始值,运行中代码会根据采样率 slots数量 slot宽度重新计算
qcom,msm-cpudai-tdm-clk-internal = <1>; //Bit clk时钟源 内部时钟
qcom,msm-cpudai-tdm-sync-mode = <1>; //Long sync mode
qcom,msm-cpudai-tdm-sync-src = <1>; //同步时钟源
qcom,msm-cpudai-tdm-data-out = <0>; //disable data
qcom,msm-cpudai-tdm-invert-sync = <1>; //同步时钟是否要翻转
qcom,msm-cpudai-tdm-data-delay = <1>; //有效数据 延时Bit clk个数
pinctrl-names = "default", "sleep";
pinctrl-0 = <&ter_mi2s_tdm_auxpcm_active &ter_mi2s_tdm_auxpcm_din_active
&ter_mi2s_tdm_auxpcm_dout_active &i2s_mclk1_active>;
pinctrl-1 = <&ter_mi2s_tdm_auxpcm_sleep &ter_mi2s_tdm_auxpcm_din_sleep
&ter_mi2s_tdm_auxpcm_dout_sleep &i2s_mclk1_sleep>;
dai_tert_tdm_rx_0: qcom,msm-dai-q6-tdm-tert-rx-0 {
compatible = "qcom,msm-dai-q6-tdm";
qcom,msm-cpudai-tdm-dev-id = <36896>;
qcom,msm-cpudai-tdm-data-align = <0>;
};
...
dai_tert_tdm_rx_4: qcom,msm-dai-q6-tdm-tert-rx-4 {
compatible = "qcom,msm-dai-q6-tdm";
qcom,msm-cpudai-tdm-dev-id = <36904>;
qcom,msm-cpudai-tdm-data-align = <0>;
};
};
tdm_tert_tx: qcom,msm-dai-tdm-tert-tx {
compatible = "qcom,msm-dai-tdm";
qcom,msm-cpudai-tdm-group-id = <37153>;
qcom,msm-cpudai-tdm-group-num-ports = <5>;
qcom,msm-cpudai-tdm-group-port-id = <36897 36899 36901 36903 36911>;
qcom,msm-cpudai-tdm-clk-rate = <1536000>;
qcom,msm-cpudai-tdm-clk-internal = <1>;
qcom,msm-cpudai-tdm-sync-mode = <1>;
qcom,msm-cpudai-tdm-sync-src = <1>;
qcom,msm-cpudai-tdm-data-out = <0>;
qcom,msm-cpudai-tdm-invert-sync = <1>;
qcom,msm-cpudai-tdm-data-delay = <1>;
dai_tert_tdm_tx_0: qcom,msm-dai-q6-tdm-tert-tx-0 {
compatible = "qcom,msm-dai-q6-tdm";
qcom,msm-cpudai-tdm-dev-id = <36897 >;
qcom,msm-cpudai-tdm-data-align = <0>;
};
...
dai_tert_tdm_tx_7: qcom,msm-dai-q6-tdm-tert-tx-7 {
compatible = "qcom,msm-dai-q6-tdm";
qcom,msm-cpudai-tdm-dev-id = <36911>;
qcom,msm-cpudai-tdm-data-align = <0>;
};
};
...
}
Documentation/devicetree/bindings/sound/qcom-audio-dev.txt 对参数也有详细的说明:
- qcom,msm-cpudai-tdm-clk-rate: Clock rate for tdm - 12288000.
When clock rate is set to zero,
then external clock is assumed.
- qcom,msm-cpudai-tdm-clk-internal: Clock Source.
0 - EBIT clock from clk tree
1 - IBIT clock from clk tree
- qcom,msm-cpudai-tdm-sync-mode: Synchronization setting.
0 - Short sync bit mode
1 - Long sync mode
2 - Short sync slot mode
- qcom,msm-cpudai-tdm-sync-src: Synchronization source.
0 - External source
1 - Internal source
- qcom,msm-cpudai-tdm-data-out: Data out signal to drive with other masters.
0 - Disable
1 - Enable
- qcom,msm-cpudai-tdm-invert-sync: Invert the sync.
0 - Normal
1 - Invert
- qcom,msm-cpudai-tdm-data-delay: Number of bit clock to delay data
with respect to sync edge.
0 - 0 bit clock cycle
1 - 1 bit clock cycle
2 - 2 bit clock cycle
platform driver
vendor/qcom/opensource/audio-kernel/asoc/msm-dai-q6-v2.c
TDM group 参数解析和存储:
static int msm_dai_tdm_q6_remove(struct platform_device *pdev)
{
return 0;
}
static const struct of_device_id msm_dai_tdm_dt_match[] = {
{ .compatible = "qcom,msm-dai-tdm", },
{}
};
MODULE_DEVICE_TABLE(of, msm_dai_tdm_dt_match);
static struct platform_driver msm_dai_tdm_q6 = {
.probe = msm_dai_tdm_q6_probe,
.remove = msm_dai_tdm_q6_remove,
.driver = {
.name = "msm-dai-tdm",
.owner = THIS_MODULE,
.of_match_table = msm_dai_tdm_dt_match,
.suppress_bind_attrs = true,
},
};
TDM Device参数解析,ASOC BE DAI注册
static int msm_dai_q6_tdm_dev_remove(struct platform_device *pdev)
{
struct msm_dai_q6_tdm_dai_data *dai_data =
dev_get_drvdata(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
kfree(dai_data);
return 0;
}
static const struct of_device_id msm_dai_q6_tdm_dev_dt_match[] = {
{ .compatible = "qcom,msm-dai-q6-tdm", },
{}
};
MODULE_DEVICE_TABLE(of, msm_dai_q6_tdm_dev_dt_match);
static struct platform_driver msm_dai_q6_tdm_driver = {
.probe = msm_dai_q6_tdm_dev_probe,
.remove = msm_dai_q6_tdm_dev_remove,
.driver = {
.name = "msm-dai-q6-tdm",
.owner = THIS_MODULE,
.of_match_table = msm_dai_q6_tdm_dev_dt_match,
.suppress_bind_attrs = true,
},
};
static int msm_dai_q6_tdm_dev_probe(struct platform_device *pdev)
{
... //TDM 设备数参数解析与存储
//调用ASOC snd_soc_register_component函数,将TDM Digital Audio Interface Driver注册到Asoc框架中,供ASOC框架使用。
rc = snd_soc_register_component(&pdev->dev,
&msm_q6_tdm_dai_component,
&msm_dai_q6_tdm_dai[port_idx], 1);
...
}
3 tdm_slot (slot数量和宽度)
如上图:
- 一个Frame的数据可以由n个slots组成,高通可以支持2,4,8,16,32slots;每个slot的数据宽度可以配置,8-32bit。
- 帧同步信号有短帧同步和长帧同步两种模式,高通平台可以选择配置。
- BCLK(bit clock) = sampling rate * slots(n)* bit per slot (width),限制高通平台在从模式下,BCLK最大支持24.576 MHz
- 每一帧数据延时一个BCLK,兼容I2S模式,可配置是否需要延时。
如上图是采样率为48KHZ情况下,slot数量和slot宽度的支持情况表格。
vendor/qcom/opensource/audio-kernel/asoc/sm6150.c
//1 tdm slot配置的数据结构
/* TDM default slot config */
struct tdm_slot_cfg {
u32 width;
u32 num;
};
//2 tdm slot配置结构体数组
static struct tdm_slot_cfg tdm_slot[TDM_INTERFACE_MAX] = {
/* PRI TDM */
{16, 16},
/* SEC TDM */
{32, 8},
/* TERT TDM */
{32, 8},
/* QUAT TDM */
{32, 8},
/* QUIN TDM */
{32, 8}
};
//3 提供control节点修改上述结构数组:slots num 和每个slot数据宽度
static const char *const tdm_slot_num_text[] = {"One", "Two", "Four",
"Eight", "Sixteen", "ThirtyTwo"};
static const char *const tdm_slot_width_text[] = {"16", "24", "32"};
...
static SOC_ENUM_SINGLE_EXT_DECL(tdm_slot_num, tdm_slot_num_text);
static SOC_ENUM_SINGLE_EXT_DECL(tdm_slot_width, tdm_slot_width_text);
...
static const struct snd_kcontrol_new msm_snd_controls[] = {
...
SOC_ENUM_EXT("TERT_TDM SlotNumber", tdm_slot_num,
tdm_slot_num_get, tdm_slot_num_put),
SOC_ENUM_EXT("TERT_TDM SlotWidth", tdm_slot_width,
tdm_slot_width_get, tdm_slot_width_put),
...
}
//4 tdm_slot slot配置被调用流程
见后续
4 tdm_rx_cfg/tdm_tx_cfg(音频设备参数)
需要注意的是,一个TDM slot并不代表一个音频流数据,它是TDM物理信号层面的定义,是传输音频的载体。
使用TDM Group标识不同的TDM设备,Group Port表示同一个TDM设备里不同的端口。在ASOC中,Group Port对应不同的BE DAI,在ADSP中对应的是AFE port。高通平台上,一个TDM Group最多可以支持8个AFE port,不同的后端可以对应到不同的前端,这也是TDM可以同时传输多个音频流的核心设计,每个音频流也有自己的数据定义,例如采样率、数据格式和通道数。
vendor/qcom/opensource/audio-kernel/asoc/sm6150.c
// 1 音频设备(AFE port)参数
struct dev_config {
u32 sample_rate;
u32 bit_format;
u32 channels;
};
//2 tdm tx/rx 默认配置参数
/* TDM default config */
static struct dev_config tdm_rx_cfg[TDM_INTERFACE_MAX][TDM_PORT_MAX] = {
...
{ /* TERT TDM */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 6}, /* RX_0 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_1 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_2 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_3 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_4 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_5 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_6 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_7 */
},
...
};
/* TDM default config */
static struct dev_config tdm_tx_cfg[TDM_INTERFACE_MAX][TDM_PORT_MAX] = {
...
{ /* TERT TDM */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 4}, /* TX_0 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 2}, /* TX_1 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 2}, /* TX_2 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* TX_3 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* TX_4 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* TX_5 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* TX_6 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* TX_7 */
},
...
};
//3 同样的,提供kcontrol节点修改参数:采样率、数据格式和通道数
static const struct snd_kcontrol_new msm_snd_controls[] = {
...
SOC_ENUM_EXT("TERT_TDM_RX_0 SampleRate", tdm_rx_sample_rate,
tdm_rx_sample_rate_get,
tdm_rx_sample_rate_put),
SOC_ENUM_EXT("TERT_TDM_RX_1 SampleRate", tdm_rx_sample_rate,
tdm_rx_sample_rate_get,
tdm_rx_sample_rate_put),
...
SOC_ENUM_EXT("TERT_TDM_RX_0 Format", tdm_rx_format,
tdm_rx_format_get,
tdm_rx_format_put),
SOC_ENUM_EXT("TERT_TDM_RX_1 Format", tdm_rx_format,
tdm_rx_format_get,
tdm_rx_format_put),
...
SOC_ENUM_EXT("TERT_TDM_RX_0 Channels", tdm_rx_chs,
tdm_rx_ch_get,
tdm_rx_ch_put),
SOC_ENUM_EXT("TERT_TDM_RX_1 Channels", tdm_rx_chs,
tdm_rx_ch_get,
tdm_rx_ch_put),
...
}
//4 tdm_rx_cfg 配置被调用流程
见后续
5 tdm_rx_slot_offset/ tdm_tx_slot_offset(数据在slot数据映射关系)
从上面两个小节我们知道,slot数量和宽度,AFE port(Or BE DAI)数据参数是两个独立的配置项。虽然音频流在AP、ADSP AFE均是独立的,但最终还是需要组合在一起,通过一根DATA线进行传输。因此,还需要定义各个音频流在TDM slot上的映射关系(偏移地址)。
//1 TDM default slot offset config
/* TDM default slot offset config */
#define TDM_SLOT_OFFSET_MAX 32
static unsigned int tdm_rx_slot_offset
[TDM_INTERFACE_MAX][TDM_PORT_MAX][TDM_SLOT_OFFSET_MAX] = {
...
{/* TERT TDM */
{0, 4, 8, 12, 16, 20, 0xFFFF}, /* 单位是字节。6 channels,每个channel 4字节,0xFFFF表示结束*/
{24, 0xFFFF}, /* 1 channels, 从第24字节开始,同时根据tdm_rx_cfg的配置确定宽度*/
{28, 0xFFFF}, /* 1 channels, 从第28字节开始,同时根据tdm_rx_cfg的配置确定宽度*/
{0xFFFF}, /* not used */
{0xFFFF}, /* not used */
{0xFFFF}, /* not used */
{0xFFFF}, /* not used */
{0xFFFF}, /* not used */
},
...
};
static unsigned int tdm_tx_slot_offset
[TDM_INTERFACE_MAX][TDM_PORT_MAX][TDM_SLOT_OFFSET_MAX] = {
...
{/* TERT TDM */
{0, 4, 8, 12, 0xFFFF},
{16, 20, 0xFFFF},
{24, 28, 0xFFFF},
{0xFFFF}, /* not used */
{0xFFFF}, /* not used */
{0xFFFF}, /* not used */
{0xFFFF}, /* not used */
{28, 0xFFFF},
},
...
};
//2 同样的,提供kcontrol节点修改参数:
static int tdm_rx_slot_mapping_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
unsigned int *slot_offset;
int i;
struct tdm_port port;
int ret = tdm_get_port_idx(kcontrol, &port);
if (ret) {
pr_err("%s: unsupported control: %s\n",
__func__, kcontrol->id.name);
} else {
slot_offset = tdm_rx_slot_offset[port.mode][port.channel];
pr_debug("%s: mode = %d, channel = %d\n",
__func__, port.mode, port.channel);
for (i = 0; i < TDM_SLOT_OFFSET_MAX; i++) {
ucontrol->value.integer.value[i] = slot_offset[i];
pr_debug("%s: offset %d, value %d\n",
__func__, i, slot_offset[i]);
}
}
return ret;
}
static int tdm_rx_slot_mapping_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
unsigned int *slot_offset;
...
return ret;
}
static int tdm_tx_slot_mapping_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
...
return ret;
}
static int tdm_tx_slot_mapping_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
...
return ret;
}
static const struct snd_kcontrol_new msm_snd_controls[] = {
...
SOC_SINGLE_MULTI_EXT("TERT_TDM_RX_0 SlotMapping",
SND_SOC_NOPM, 0, 0xFFFF, 0, TDM_SLOT_OFFSET_MAX,
tdm_rx_slot_mapping_get, tdm_rx_slot_mapping_put),
SOC_SINGLE_MULTI_EXT("TERT_TDM_RX_1 SlotMapping",
SND_SOC_NOPM, 0, 0xFFFF, 0, TDM_SLOT_OFFSET_MAX,
tdm_rx_slot_mapping_get, tdm_rx_slot_mapping_put),
...
SOC_SINGLE_MULTI_EXT("TERT_TDM_TX_0 SlotMapping",
SND_SOC_NOPM, 0, 0xFFFF, 0, TDM_SLOT_OFFSET_MAX,
tdm_tx_slot_mapping_get, tdm_tx_slot_mapping_put),
SOC_SINGLE_MULTI_EXT("TERT_TDM_TX_1 SlotMapping",
SND_SOC_NOPM, 0, 0xFFFF, 0, TDM_SLOT_OFFSET_MAX,
tdm_tx_slot_mapping_get, tdm_tx_slot_mapping_put),
...
}
//4 tdm_rx_cfg、tdm_slot和tdm_tx_slot_offset被调用流程。
static struct snd_soc_ops sm6150_tdm_be_ops = {
.hw_params = sm6150_tdm_snd_hw_params,
.startup = sm6150_tdm_snd_startup,
.shutdown = sm6150_tdm_snd_shutdown
};
static struct snd_soc_dai_link msm_common_be_dai_links[] = {
/* Backend AFE DAI Links */
...
{
.name = LPASS_BE_TERT_TDM_RX_0,
.stream_name = "Tertiary TDM0 Playback",
.cpu_dai_name = "msm-dai-q6-tdm.36896",
.platform_name = "msm-pcm-routing",
.codec_name = "msm-stub-codec.1",
.codec_dai_name = "msm-stub-rx",
.no_pcm = 1,
.dpcm_playback = 1,
.id = MSM_BACKEND_DAI_TERT_TDM_RX_0,
.be_hw_params_fixup = msm_tdm_be_hw_params_fixup,
.ops = &sm6150_tdm_be_ops,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
},
...
};
//打开PCM流时,当前TDM BE被连接上后,params指的后端runtime的硬件参数,调用流程如下:
//根据tdm_rx_cfg和tdm_tx_cfg的数据设置当前BE DPCM 后端runtime 参数——channels bit_format rate
static int msm_tdm_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
{
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_interval *rate = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval *channels = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
switch (cpu_dai->id) {
...
case AFE_PORT_ID_TERTIARY_TDM_RX:
channels->min = channels->max =
tdm_rx_cfg[TDM_TERT][TDM_0].channels;
param_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT,
tdm_rx_cfg[TDM_TERT][TDM_0].bit_format);
rate->min = rate->max =
tdm_rx_cfg[TDM_TERT][TDM_0].sample_rate;
break;
...
case AFE_PORT_ID_TERTIARY_TDM_TX:
channels->min = channels->max =
tdm_tx_cfg[TDM_TERT][TDM_0].channels;
param_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT,
tdm_tx_cfg[TDM_TERT][TDM_0].bit_format);
rate->min = rate->max =
tdm_tx_cfg[TDM_TERT][TDM_0].sample_rate;
break;
...
default:
pr_err("%s: dai id 0x%x not supported\n",
__func__, cpu_dai->id);
return -EINVAL;
}
pr_debug("%s: dai id = 0x%x channels = %d rate = %d format = 0x%x\n",
__func__, cpu_dai->id, channels->max, rate->max,
params_format(params));
return 0;
}
static int sm6150_tdm_snd_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int ret = 0;
int channels, slot_width, slots, rate, format;
unsigned int slot_mask;
unsigned int *slot_offset;
int offset_channels = 0;
int i;
int clk_freq;
pr_debug("%s: dai id = 0x%x\n", __func__, cpu_dai->id);
// 当前BE DPCM channels
channels = params_channels(params);
...
// 当前BE DPCM data
format
format = params_format(params);
...
//获取当前BE TDM所在group的slots num和slot width
//同时从tdm_rx_slot_offset获取当前BE port在TDM的数据偏移信息,
switch (cpu_dai->id) {
...
case AFE_PORT_ID_TERTIARY_TDM_RX:
slots = tdm_slot[TDM_TERT].num;
slot_width = tdm_slot[TDM_TERT].width;
slot_offset = tdm_rx_slot_offset[TDM_TERT][TDM_0];
break;
case AFE_PORT_ID_TERTIARY_TDM_RX_1:
slots = tdm_slot[TDM_TERT].num;
slot_width = tdm_slot[TDM_TERT].width;
slot_offset = tdm_rx_slot_offset[TDM_TERT][TDM_1];
break;
...
default:
pr_err("%s: dai id 0x%x not supported\n",
__func__, cpu_dai->id);
return -EINVAL;
}
//省略slot channel计算和校验过程
...
slot_mask = tdm_param_set_slot_mask(slots);
if (!slot_mask) {
pr_err("%s: invalid slot_mask 0x%x\n",
__func__, slot_mask);
return -EINVAL;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {}
snd_soc_dai_set_tdm_slot(cpu_dai, 0, slot_mask,slots, slot_width);
+ msm_dai_q6_tdm_set_tdm_slot() // msm-dai-q6-v2.c
+ 设置对应BE DAI的tdm_group(nslots_per_frame slot_width slot_mask)
snd_soc_dai_set_channel_map(cpu_dai,0, NULL, channels, slot_offset);
+ msm_dai_q6_tdm_set_channel_map() // msm-dai-q6-v2.c
+ 设置对应BE DAI的slot_mapping(offset num_channel)
else
snd_soc_dai_set_tdm_slot(cpu_dai, slot_mask, 0,
slots, slot_width); //同上
snd_soc_dai_set_channel_map(cpu_dai,channels, slot_offset, 0, NULL); //同上
//当前BE DPCM rate
rate = params_rate(params);
//计算当前Bit Clock的频率大小
clk_freq = rate * slot_width * slots;
ret = snd_soc_dai_set_sysclk(cpu_dai, 0, clk_freq, SND_SOC_CLOCK_OUT);
+msm_dai_q6_tdm_set_sysclk (struct snd_soc_dai *dai,int clk_id, unsigned int freq, int dir) // msm-dai-q6-v2.c
+struct msm_dai_q6_tdm_dai_data *dai_data =
dev_get_drvdata(dai->dev);
+dai_data->clk_set.clk_freq_in_hz = freq;
}
msm-dai-q6-v2.c
//!!! 当msm_dai_q6_tdm_prepare被调用时,上述设置的各种参数最终都会调用q6afe.c中函数,通过APR传递给ADSP!!!
6 实战演练(RX)
高通TDM提供了多样化的参数可以修改,实际配置时候需要根据外挂codec的时序图进行对应。
如下我们定义了三种帧同步模式:long sync mode,long sync mode(带电平反转)和shot sync mode。
数据位默认延时一个bit clk。一共8个slot,每个slot的32位,共5个Audio port,通道数和slot偏移如图和表格所示。
Audio Port |
Channels |
Slots分布 |
tdm_rx_cfg |
tdm_rx_slot_offset |
TERTIARY_TDM_RX0 |
2 |
slot1 slot2 |
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S32_LE, 2} |
0 4 0xffff |
TERTIARY_TDM_RX1 |
1 |
slot3 |
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S32_LE, 1} |
8 0xffff |
TERTIARY_TDM_RX2 |
2 |
slot4 slot5 |
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S32_LE, 2} |
12 16 0xffff |
TERTIARY_TDM_RX3 |
1 |
slot6 |
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S32_LE, 1} |
20 0xffff |
TERTIARY_TDM_RX4 |
2 |
sot7 slot8 |
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S32_LE, 2} |
24 28 0xffff |
vendor/qcom/opensource/audio-kernel/asoc/sm6150.c
@@ -181,11 +225,11 @@ static struct dev_config tdm_rx_cfg[TDM_INTERFACE_MAX][TDM_PORT_MAX] = {
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_7 */
},
{ /* TERT TDM */
- {SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 6}, /* RX_0 */
- {SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_1 */
- {SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_2 */
- {SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_3 */
- {SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_4 */
+ {SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S32_LE, 2}, /* RX_0 */
+ {SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S32_LE, 1}, /* RX_1 */
+ {SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S32_LE, 2}, /* RX_2 */
+ {SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S32_LE, 1}, /* RX_3 */
+ {SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S32_LE, 2}, /* RX_4 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_5 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_6 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* RX_7 */
@@ -391,11 +435,11 @@ static unsigned int tdm_rx_slot_offset
{28, 0xFFFF},
},
{/* TERT TDM */
- {0, 4, 8, 12, 16, 20, 0xFFFF},
- {24, 0xFFFF},
- {28, 0xFFFF},
- {0xFFFF}, /* not used */
- {0xFFFF}, /* not used */
+ {0, 4, 0xFFFF},
+ {8, 0xFFFF},
+ {12,16 ,0xFFFF},
+ {20,0xFFFF},
+ {24,28,0xFFFF},
{0xFFFF}, /* not used */
{0xFFFF}, /* not used */
{0xFFFF}, /* not used */
@@ -3745,6 +3789,36 @@ err:
return ret;
}
模式 |
qcom,msm-cpudai-tdm-clk-internal |
qcom,msm-cpudai-tdm-sync-mode |
qcom,msm-cpudai-tdm-sync-src |
qcom,msm-cpudai-tdm-data-out |
qcom,msm-cpudai-tdm-invert-sync |
qcom,msm-cpudai-tdm-data-delay |
long sync mode |
1 |
1 |
1 |
0 |
0 |
1 |
long sync mode(Invert) |
1 |
1 |
1 |
0 |
1 |
1 |
short sync mode |
1 |
0 |
1 |
0 |
0 |
1 |
6.1 long sync mode(Invert)
Tips:在ASOC DPCM概念中,音频流参数分为前端和后端,它们可以不一致,如前端播放2 ch, 44100 hz, 16 bit的音频文件,后端配置为2 ch, 48000 hz, 32 bit,同样可以正常工作。ADSP会进行重采样和格式转换,后面机会展开讲讲。
一个前端PCM0 对应两个后端TERT_TDM_RX_0,TERT_TDM_RX_4
tinymix "TERT_TDM_RX_0 Audio Mixer MultiMedia1" 1
tinymix "TERT_TDM_RX_4 Audio Mixer MultiMedia1" 1
tinyplay /sdcard/test1.wav
从波形图可以看出TERT_TDM_RX_0和TERT_TDM_RX_4 数据是一样的
long sync mode(Invert)模式在很多厂商也定义为I2S兼容模式,帧同步信号是从低电平开始(I2S WS低表示左声道,WS高为右声道)。
两路独立音频流:前端PCM0 对应后端TERT_TDM_RX_0,前端PCM9 对应后端TERT_TDM_RX_4
tinymix "TERT_TDM_RX_0 Audio Mixer MultiMedia1" 1
tinymix "TERT_TDM_RX_4 Audio Mixer MultiMedia5" 1
tinyplay /sdcard/test1.wav -D 0 -d 0
另外打开一个shell窗口
tinyplay /sdcard/test2.wav -D 0 -d 9
一个前端PCM0 对应TERT_TDM_RX_0到TERT_TDM_RX_4 五个后端
tinymix "TERT_TDM_RX_0 Audio Mixer MultiMedia1" 1
tinymix "TERT_TDM_RX_1 Audio Mixer MultiMedia1" 1
tinymix "TERT_TDM_RX_2 Audio Mixer MultiMedia1" 1
tinymix "TERT_TDM_RX_3 Audio Mixer MultiMedia1" 1
tinymix "TERT_TDM_RX_4 Audio Mixer MultiMedia1" 1
tinyplay /sdcard/test1.wav
6.2 short sync mode
一个前端PCM0 对应两个后端TERT_TDM_RX_0,TERT_TDM_RX_4
tinymix "TERT_TDM_RX_0 Audio Mixer MultiMedia1" 1
tinymix "TERT_TDM_RX_4 Audio Mixer MultiMedia1" 1
tinyplay /sdcard/test1.wav
与long sync mode(Invert)的相比,short sync mode差异在于帧同步信号是一个脉冲,脉冲的宽度为一个Bit Clock。
7 实战演练(TX)
在前面RX的基础上,我们增加TX的定义,定义两个TERTIARY_TDM_TX0和TERTIARY_TDM_TX1。
Audio Port |
Channels |
Slots分布 |
tdm_rx_cfg |
tdm_rx_slot_offset |
TERTIARY_TDM_TX0 |
2 |
slot1 slot2 |
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S32_LE, 2} |
0 4 0xffff |
TERTIARY_TDM_TX1 |
2 |
sot7 slot8 |
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S32_LE, 2} |
24 28 0xffff |
vendor/qcom/opensource/audio-kernel/asoc/sm6150.c
@@ -235,8 +279,8 @@ static struct dev_config tdm_tx_cfg[TDM_INTERFACE_MAX][TDM_PORT_MAX] = {
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* TX_7 */
},
{ /* TERT TDM */
- {SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 4}, /* TX_0 */
- {SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 2}, /* TX_1 */
+ {SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S32_LE, 2}, /* TX_0 */
+ {SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S32_LE, 2}, /* TX_1 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 2}, /* TX_2 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* TX_3 */
{SAMPLING_RATE_48KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, /* TX_4 */
@@ -445,8 +489,7 @@ static unsigned int tdm_tx_slot_offset
{0xFFFF}, /* not used */
},
{/* TERT TDM */
- {0, 4, 8, 12, 0xFFFF},
- {16, 20, 0xFFFF},
+ {0, 4, 0xFFFF},
{24, 28, 0xFFFF},
{0xFFFF}, /* not used */
{0xFFFF}, /* not used */
@@ -3745,6 +3788,36 @@ err:
测试时候将GPIO21和GPIO22短接,自发自收,
shell窗口1:
tinymix "TERT_TDM_RX_0 Audio Mixer MultiMedia1" 1
tinymix "TERT_TDM_RX_4 Audio Mixer MultiMedia1" 1
tinyplay /sdcard/test1.wav
shell窗口2:
tinymix "MultiMedia1 Mixer TERT_TDM_TX_0" 1
tinycap /sdcard/record1.pcm -D 0 -d 0
shell窗口3:
tinymix "MultiMedia5 Mixer TERT_TDM_TX_1" 1
tinycap /sdcard/record2.pcm -D 0 -d 9
record1.pcm 和 record2.pcm 可以播放出正常的声音,验证有效。