[linux audio driver] 高通平台TDM总线调试

   

目录

1 TDM特性

2 TDM BE DAI 设备与驱动

3 tdm_slot (slot数量和宽度)

4 tdm_rx_cfg/tdm_tx_cfg(音频设备参数)

5 tdm_rx_slot_offset/ tdm_tx_slot_offset(数据在slot数据映射关系)

6 实战演练(RX)

6.1 long sync mode(Invert)

6.2 short sync mode

7 实战演练(TX)


        我们知道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 可以播放出正常的声音,验证有效。

猜你喜欢

转载自blog.csdn.net/Q_Lee/article/details/131445870