【Audio】DMIC_clk如何设置及cdc_i2s_tx_clk何有关

1 前言

客户问codec的DAC/ADC的采样率是否是动态可调的,正好学习一下时钟及采样率的关系.

在这里插入图片描述

2 硬件连接图

项目为sda660+dmic,使用的是sda660内部的codec。
具体的电路图就不放了,参见高通的连接框图,如图连接到digital codec上。
在这里插入图片描述
dmic内部大致框架,由PDM modulator + ADC + Amp + MEMS transducer组成。

  • PDM modulator是PDM调制器,用来产生1bit的数据
  • ADC是模数转换,模拟信号转成数字信号
  • Amp是功率放大器
  • MEMS传感器输出模拟信号

dmic有两根通信线路,分别是dmic_clk和dmic_data,即PDM通信协议。

在这里插入图片描述
其中需要关注的几点:

  1. DMIC_CLK是如何定义的
  2. DMIC中的ADC采样速率如何,是否等于DMIC_CLK
  3. 内部codec中的I2S的clk如何定义的
  4. tinycap的sample_rate,channel,width这三个参数最终传给什么

3 通信协议

参见几个参考文档
audio相关的通信协议主要的四种: I2S, PDM, PCM,Slimbus

3.1 PDM

信号线:一根时钟线,一根数据线
如DMIC和internal codec的连接线。
DMIC_CLK 时钟线
DMIC_DATA 数据线

参考文章:PDM接口介绍

data输出只有1bit,输出0/1,具体如何处理的有空再细看。
在这里插入图片描述

3.2 I2S

参考文档:I2S的理解
至少三个信号
SCK,串行时钟(即BCLK位时钟),一位数据对应一个SCK脉冲
WS,声道选择,选择左右声道
SDATA,串行数据,1位
在这里插入图片描述

注意:
SCK = 采样率sample_rate * 位深 width* 声道数channel
这三个参数tinycap可以设置。
sda660中默认channel数为2

3.3 PCM

参见文章:PCM / I2S / AC97/PDM
ALSA 架构

3.4 slimbus

PDM连接PMIC
soundwire连接双wsa
slimbus连接WCD

在这里插入图片描述

4 编码方式

参考文章
https://blog.csdn.net/xjw1874/article/details/81536127
https://tieba.baidu.com/p/4400689097

4.1 PCM

在这里插入图片描述
图片来源:揭开DSD的神秘面纱

4.2 DSD

在这里插入图片描述
图片来源:揭开DSD的神秘面纱

5 DMIC_rate设置

80-P7747-5C

如图sda660的internal codec支持4个dmic,其中当MCLK为9.6Mhz时支持4种。
MCLK是晶振频率,询问硬件确认的。
DMIC的MCLK数值有两个地方可以设定,其中dts文件中定义的最终还是会调用到code的设置。
差异在于,内部codec为未在dts文件中找到直接设置的属性,目前只看到了外部codec(如wcd934x的)。

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

5.1 dts设置(外部codec)

外部codec
qcom,cdc-mclk-clk-rate
qcom,cdc-dmic-sample-rate
qcom,cdc-mad-dmic-rate

如图,其中设置外部codec的dts node定义,这里有属性直接设置。

在这里插入图片描述

5.2 codec驱动定义(内部codec)

XXX_enable_dmic()
@android\vendor\qcom\opensource\audio-kernel\asoc\codecs\xxx_cdc.c

msm_dig_cdc_codec_enable_dmic()
在这里插入图片描述
重点关注上电设置。
将0X04赋值给dmic_clk_reg,只设置前三位。前三位代表的含义如下:
Regarding the register MSM89XX_CDC_CORE_CLK_DMIC_Bx_CTL:
Bit 3:1 is defined as:
0x0: DIV2
0x1: DIV3
0x2: DIV4
0x3: DIV6
0x4: DIV16

0x0E == 1110,计算前3位

0x04 == 0100,所以是0x2,即DIV4

所以除数为4,即MCLK/4=9.6/4=2.4Mhz
所以内部codec连接的dmic_clk为2.4Mhz。
存疑:dmic_clk时钟是否为ADC的采样时钟,即是否(1/dmic_clk)s采一次数值

5.3 codec_dai

codec的dai接口定义,以sda660的internal codec为例。
在这里插入图片描述
在这里插入图片描述
注意code中定义的dai driver和实际的框图一一对应。查看codec即可知道该i2s的rate/channel/format有哪些可选。

注意:
BCLK=sample_rate * width * channel

6 tinycap命令的rate,width设置

code路径
external\tinyalsa\tinycap.c

6.1 初始化

在这里插入图片描述

6.2 tinycap命令

tinycap /data/test_dmic3.wav -c 2 -r 48000 -T 10

其中/data/test_dmic3.wav为存放路径,-c为channel数,-r为采样率,-T为采样时间
这里未设置width,就使用默认的数值,即为16.

代码中先定义了wav_head,后续接收到命令传下的参数会再更新录音设置参数。

6.3 获取命令参数更新数值

在这里插入图片描述

在这里插入图片描述
传下来的width选择了format,对应关系如上。

6.4 录音

先预留音频文件头信息,用来存放采样率等信息。
然后调用capture_sample进行录音。
录音结束后会打印当前录音的信息,多少frames。
在这里插入图片描述

unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device,
                            unsigned int channels, unsigned int rate,
                            enum pcm_format format, unsigned int period_size,
                            unsigned int period_count, unsigned int cap_time)
{
    struct pcm_config config;
    struct pcm *pcm;
    char *buffer;
    unsigned int size;
    unsigned int bytes_read = 0;
    unsigned int frames = 0;
    struct timespec end;
    struct timespec now;

    memset(&config, 0, sizeof(config));
    config.channels = channels;
    config.rate = rate;
    config.period_size = period_size;
    config.period_count = period_count;
    config.format = format;
    config.start_threshold = 0;
    config.stop_threshold = 0;
    config.silence_threshold = 0;

	//打开pcm设备
    pcm = pcm_open(card, device, PCM_IN, &config);
    if (!pcm || !pcm_is_ready(pcm)) {
        fprintf(stderr, "Unable to open PCM device (%s)\n",
                pcm_get_error(pcm));
        return 0;
    }

	//确定一帧的大小并分配
    size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
    buffer = malloc(size);
    if (!buffer) {
        fprintf(stderr, "Unable to allocate %u bytes\n", size);
        free(buffer);
        pcm_close(pcm);
        return 0;
    }

    printf("Capturing sample: %u ch, %u hz, %u bit\n", channels, rate,
           pcm_format_to_bits(format));

    clock_gettime(CLOCK_MONOTONIC, &now);
    end.tv_sec = now.tv_sec + cap_time;
    end.tv_nsec = now.tv_nsec;

	//while循环读取,最终执行是fwrite函数,将数据存到file
    while (capturing && !pcm_read(pcm, buffer, size)) {
        if (fwrite(buffer, 1, size, file) != size) {
            fprintf(stderr,"Error capturing sample\n");
            break;
        }
        bytes_read += size;
        if (cap_time) {
            clock_gettime(CLOCK_MONOTONIC, &now);
            if (now.tv_sec > end.tv_sec ||
                (now.tv_sec == end.tv_sec && now.tv_nsec >= end.tv_nsec))
                break;
        }
    }

    frames = pcm_bytes_to_frames(pcm, bytes_read);
    free(buffer);
    pcm_close(pcm);
    return frames;
}

所以最终采样率,位深,声道数会存放到config中,然后作为pcm_open的入参。

struct pcm *pcm_open(unsigned int card, unsigned int device,
                     unsigned int flags, struct pcm_config *config)
{
	//将config存到pcm->config中
    pcm->config = *config;

	//获取pcm设备名称
    snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
             flags & PCM_IN ? 'c' : 'p');

    pcm->flags = flags;
    //打开pcm设备,这个就调用到pcm操作了。
    pcm->fd = open(fn, O_RDWR|O_NONBLOCK);
    if (pcm->fd < 0) {
        oops(pcm, errno, "cannot open device '%s'", fn);
        return pcm;
    }

	//设置位深,采样率以及声道数到硬件参数
    param_set_int(&params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
                  pcm_format_to_bits(config->format));
    param_set_int(&params, SNDRV_PCM_HW_PARAM_FRAME_BITS,
                  pcm_format_to_bits(config->format) * config->channels);
    param_set_int(&params, SNDRV_PCM_HW_PARAM_CHANNELS,
                  config->channels);
    param_set_int(&params, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
    param_set_int(&params, SNDRV_PCM_HW_PARAM_RATE, config->rate);
}

dsp的驱动lsm中定义pcm硬件信息

//msm-lsm-client.c
static struct snd_pcm_hardware msm_pcm_hardware_capture = {
	.info =                 (SNDRV_PCM_INFO_MMAP |
				SNDRV_PCM_INFO_BLOCK_TRANSFER |
				SNDRV_PCM_INFO_INTERLEAVED |
				SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
	.formats =              (SNDRV_PCM_FMTBIT_S16_LE |
				SNDRV_PCM_FMTBIT_S24_LE),
	.rates =		(SNDRV_PCM_RATE_16000 |
				SNDRV_PCM_RATE_48000),
	.rate_min =             16000,
	.rate_max =             48000,
	.channels_min =         LSM_INPUT_NUM_CHANNELS_MIN,
	.channels_max =         LSM_INPUT_NUM_CHANNELS_MAX,
	.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,
};

具体的硬件链路相关推荐文章:Linux ALSA 音频系统:物理链路篇
Linux ALSA音频系统:platform,machine,codec

tinycap命令的参数最终会设置到i2s,至于i2s则关系到dai相关的驱动,后续有空再看。

7 小结

本文重点是了解dmic_clk和内部codec的i2s时钟。

  1. dmic_clk针对外部codec可以直接在dts文件中有属性可以查看及设置,内部codec则需要查看代码,找到DMIC_MCLK的时钟除率。
    在XXX_enable_dmic()函数中可以找到上电设置,配置的clk寄存器的值则对应DIV_X
    用晶振频率除以该X则得到DMIC_CLK。

  2. i2s的时钟计算公式:BCLK=sample_rate * width * channel
    其中三大参数是tinycap命令设置的,当然在dai相关代码中的也可以找到设置范围。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值