ALSA声卡驱动:
1.Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介
2.Linux ALSA声卡驱动之二:Platform
3. Linux ALSA声卡驱动之三:Platform之Cpu_dai
4. Linux ALSA声卡驱动之四:Codec 以及Codec_dai
5.Linux ALSA声卡驱动之五:Machine 以及ALSA声卡的注册
6.Linux ALSA声卡驱动之六:PCM的注册流程
7.Linux ALSA声卡驱动之七:录音(Capture) 调用流程
一、录音(Capture)调用时间时序图
从上面的时序图能看出,tingyalsa的调用是通过open的方式来调用底层驱动函数。上面图片不够清晰可以看这个pdf文档(//download.youkuaiyun.com/download/Bill_xiao/12260596).
-
1.1 tinyalsa调用
tinyalsa 录音:tinycap /sdcard/record.wav -D 0 -d 13 -c 2 -r 32000 -b 16 -T 5 这些参数是什么意思。
tinycap :调用的c程序,一个可执行程序,在system/bin下面。
/sdcard/record.wav:录音数据保存的文件路径。
-D 0:声卡号 0的驱动,一般我们手机只有一个声卡。
-d 13:设备号13,也就pcm号13,就是驱动配置录音设备(codec)
-c 2 :c 是channels(声道)的首字母 2代码两个声道,左右声道。
-r 32000 : r是rate(采样率)的首字母 32000是采用频率,以hz为单位
-b 16 :b是bate(量化度),16位宽大小,常用的有8 16 24 32 ,量化度越高,音频信号越可能接近原生信号。
-T 5:T time 录音的时间大小,单位是s。
-
1.2 tinyalsa main函数
int main(int argc, char **argv)
{
FILE *file;
struct wav_header header;
unsigned int card = 0;
unsigned int device = 0;
unsigned int channels = 2;
unsigned int rate = 44100;
unsigned int bits = 16;
unsigned int frames;
unsigned int period_size = 1024;
unsigned int period_count = 4;
unsigned int cap_time = 0;
enum pcm_format format;
for(int i=0;i<argc;i++){
printf("tinycap argv[%d] ::%s \n",i,argv[i]);
}
if (argc < 2) {
fprintf(stderr, "Usage: %s file.wav [-D card] [-d device]"
" [-c channels] [-r rate] [-b bits] [-p period_size]"
" [-n n_periods] [-T capture time]\n", argv[0]);
return 1;
}
file = fopen(argv[1], "wb");
if (!file) {
fprintf(stderr, "Unable to create file '%s'\n", argv[1]);
return 1;
}
/* parse command line arguments */
argv += 2;
while (*argv) {
if (strcmp(*argv, "-d") == 0) {
argv++;
if (*argv)
device = atoi(*argv);
} else if (strcmp(*argv, "-c") == 0) {
argv++;
if (*argv)
channels = atoi(*argv);
} else if (strcmp(*argv, "-r") == 0) {
argv++;
if (*argv)
rate = atoi(*argv);
} else if (strcmp(*argv, "-b") == 0) {
argv++;
if (*argv)
bits = atoi(*argv);
} else if (strcmp(*argv, "-D") == 0) {
argv++;
if (*argv)
card = atoi(*argv);
} else if (strcmp(*argv, "-p") == 0) {
argv++;
if (*argv)
period_size = atoi(*argv);
} else if (strcmp(*argv, "-n") == 0) {
argv++;
if (*argv)
period_count = atoi(*argv);
} else if (strcmp(*argv, "-T") == 0) {
argv++;
if (*argv)
cap_time = atoi(*argv);
}
if (*argv)
argv++;
}
header.riff_id = ID_RIFF;
header.riff_sz = 0;
header.riff_fmt = ID_WAVE;
header.fmt_id = ID_FMT;
header.fmt_sz = 16;
header.audio_format = FORMAT_PCM;
header.num_channels = channels;
header.sample_rate = rate;
switch (bits) {
case 32:
format = PCM_FORMAT_S32_LE;
break;
case 24:
format = PCM_FORMAT_S24_LE;
break;
case 16:
format = PCM_FORMAT_S16_LE;
break;
default:
fprintf(stderr, "%d bits is not supported.\n", bits);
return 1;
}
header.bits_per_sample = pcm_format_to_bits(format);//bit 32 16 24 采样精度
header.byte_rate = (header.bits_per_sample / 8) * channels * rate; //采样大小 byte
header.block_align = channels * (header.bits_per_sample / 8);//每帧的大小,单位byte
header.data_id = ID_DATA;
/* leave enough room for header */
fseek(file, sizeof(struct wav_header), SEEK_SET);
/* install signal handler and begin capturing */
signal(SIGINT, sigint_handler);
signal(SIGHUP, sigint_handler);
signal(SIGTERM, sigint_handler);
frames = capture_sample(file, card, device, header.num_channels,
header.sample_rate, format,
period_size, period_count, cap_time);
printf("tinycap frames= %u byte\n", frames);
/* write header now all information is known */
header.data_sz = frames * header.block_align;//帧的大小,相当于period_size
header.riff_sz = header.data_sz + sizeof(header) - 8; //pcm或者wav文件的大小
printf("tinycap header.data_sz= %u byte\n", header.data_sz);//XIAO
fseek(file, 0, SEEK_SET);
fwrite(&header, sizeof(struct wav_header), 1, file);
fseek(file, 0, SEEK_END);//XIAO
printf("tinycap file size= %ld byte\n", ftell(file));//XIAO
fclose(file);
return 0;
}
上述代码 file = fopen(argv[1], "wb"),虽然不知道argv[1]是什么,但是fopen函数是知道,打开一个文件。argv[1]是我们执行
tinycap /sdcard/record.wav -D 0 -d 13 -c 2 -r 32000 -b 16 -T 5 第二个参数: /sdcard/record.wav
while (*argv) {
if (strcmp(*argv, "-d") == 0) {
argv++;
if (*argv)
device = atoi(*argv);
} else if (strcmp(*argv, "-c") == 0) {
argv++;
if (*argv)
channels = atoi(*argv);
} else if (strcmp(*argv, "-r") == 0) {
上面代码的-d -c -r 不就是我们输入的参数么?不错,其实就是对我们输入的参数进行校正和赋值,如果我们输入的参数跟程序里面的字符串匹配就对初始化值重新赋值,否则就不做处理。上述代码中有很多关于音频相关的名词如果不懂的可以看上章(Linux ALSA声卡驱动之六:PCM的注册流程).
-
1.3 capture_sample,
上述的代码只是初始化赋值而已,对于音频的数据的获取以及底层驱动的交互一点都没有。不要急在main函数里面有个capture_sample,这个里面应该是有关音频的实际操作。点击进去果然,找到open_pcm,那open_pcm有做了什么事呢?
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;
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_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;
}
printf("tinycap capture_sample buffer: %u \n", pcm_get_buffer_size(pcm));
size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
printf("tinycap capture_sample period_size= %u byte ,period_count= %u byte ,size= %u byte\n", config.period_size,config.period_count,size);//xiao
buffer = malloc(size);
if (!buffer) {
fprintf(stderr, "Unable to allocate %d bytes\n", size);
free(buffer);
pcm_close(pcm);
return 0;
}
// printf("tinycap 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 (capturing && !pcm_read(pcm, buffer, size)) {
printf("tinycap capture_sample pcm_read size: %u \n", 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;
}
}
free(buffer);
pcm_close(pcm);
return pcm_bytes_to_frames(pcm, bytes_read);
}
-
1.4 open_pcm 做的工作:
1.open(fn, O_RDWR|O_NONBLOCK)打开底层驱动函数,到了现在我们已经与底层驱动联系上了。其实这个open首先打开的是sound.c snd_open 函数,从snd_minors取出我们指定的PCM设备。
2. ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms) 通过ioctl把一些硬件参数设置到底层。
struct pcm *pcm_open(unsigned int card, unsigned int device,
unsigned int flags, struct pcm_config *config)
{
struct pcm *pcm;
struct snd_pcm_info info;
struct snd_pcm_hw_params params;
struct snd_pcm_sw_params sparams;
char fn[256];
int rc;
pcm = calloc(1, sizeof(struct pcm));
if (!pcm || !config)
return &bad_pcm; /* TODO: could support default config here */
pcm->config = *config;
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
flags & PCM_IN ? 'c' : 'p');
pcm->flags = flags;
pcm->fd = open(fn, O_RDWR|O_NONBLOCK);
if (pcm->fd < 0) {
oops(pcm, errno, "cannot open device '%s'", fn);
return pcm;
}
if (fcntl(pcm->fd, F_SETFL, fcntl(pcm->fd, F_GETFL) &
~O_NONBLOCK) < 0) {
oops(pcm, errno, "failed to reset blocking mode '%s'", fn);
goto fail_close;
}
//由底层file->private_data->substream 来赋值
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {
printf("tinycap pcm_open ioctl SNDRV_PCM_IOCTL_INFO \n");
oops(pcm, errno, "cannot get info");
goto fail_close;
}
printf("tinycap pcm_open ioctl info.subdevice=%u ,info.device=%u,info.name=%s \n",info.subdevice,info.device,info.name);
pcm->subdevice = info.subdevice;
//下面所有的params操作都是对snd_mask masks 和snd_interval intervals这两个结构体数组的填充赋值。
param_init(¶ms);
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT,
pcm_format_to_alsa(config->format));
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT,
SNDRV_PCM_SUBFORMAT_STD);
param_set_min(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
pcm_format_to_bits(config->format));
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_FRAME_BITS,
pcm_format_to_bits(config->format) * config->channels);
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS,
config->channels);
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_RATE, config->rate);
if (flags & PCM_NOIRQ) {
if (!(flags & PCM_MMAP)) {
oops(pcm, EINVAL, "noirq only currently supported with mmap().");
goto fail_close;
}
params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP;
pcm->noirq_frames_per_msec = config->rate / 1000;
}
if (flags & PCM_MMAP)
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS,
SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
else
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS,
SNDRV_PCM_ACCESS_RW_INTERLEAVED);
//硬件信息的匹配,而且还初始化了DMA内存
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) {
oops(pcm, errno, "cannot set hw params");
goto fail_close;
}
/* get our refined hw_params */
config->period_size = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
config->period_count = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS);
printf("tinycap pcm_open config->period_size=%u config->period_count=%u \n",config->period_size,config->period_count);
pcm->buffer_size = config->period_count * config->period_size;
-
1.5 pcm_read 做了如下工作
1.pcm_start 录音准备,搭起录音通道,为准备录音做准备。
2.ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x),读取录音数据。并写到sdcard/record.wav
int pcm_read(struct pcm *pcm, void *data, unsigned int count)
{
struct snd_xferi x;
if (!(pcm->flags & PCM_IN))
return -EINVAL;
x.buf = data;//读的数据都存储在这里,而且data的大小是count
x.frames = count / (pcm->config.channels *
pcm_format_to_bits(pcm->config.format) / 8);
printf("tinycap pcm_read x.frames=%lu \n",x.frames);
for (;;) {
printf("tinycap pcm_read pcm->running=%d \n",pcm->running);
if (!pcm->running) {
if (pcm_start(pcm) < 0) {
fprintf(stderr, "start error");
return -errno;
}
}
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {
printf("tinycap pcm_read ioctl SNDRV_PCM_IOCTL_READI_FRAMES\n");
pcm->prepared = 0;
pcm->running = 0;
if (errno == EPIPE) {
/* we failed to make our window -- try to restart */
pcm->underruns++;
continue;
}
return oops(pcm, errno, "cannot read stream data");
}
return 0;
}
}
二、PCM设备底层的调用流程
2.1 open
前面已经讲过上层open之后会调用 snd_open,最终会调用snd_pcm_f_ops里面的对应的open函数,ioctl也是一样的执行流程。
函数调用流程:snd_pcm_capture_open->snd_pcm_open->snd_pcm_open_file ->snd_pcm_open_substream,最后会如下面的代码调用substream->ops->open(substream)
substream->ops->open是什么呢?如下定义。这些定义在soc_new_pcm
rtd->ops.open = soc_pcm_open;
rtd->ops.hw_params = soc_pcm_hw_params;
rtd->ops.prepare = soc_pcm_prepare;
rtd->ops.trigger = soc_pcm_trigger;
rtd->ops.hw_free = soc_pcm_hw_free;
rtd->ops.close = soc_pcm_close;
rtd->ops.pointer = soc_pcm_pointer;
rtd->ops.ioctl = soc_pcm_ioctl;
int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,
struct file *file,
struct snd_pcm_substream **rsubstream)
{
struct snd_pcm_substream *substream;
int err;
err = snd_pcm_attach_substream(pcm, stream, file, &substream);
if (err < 0)
return err;
if (substream->ref_count > 1) {
*rsubstream = substream;
return 0;
}
err = snd_pcm_hw_constraints_init(substream);
if (err < 0) {
pcm_dbg(pcm, "snd_pcm_hw_constraints_init failed\n");
goto error;
}
if ((err = substream->ops->open(substream)) < 0)
goto error;
substream->hw_opened = 1;
err = snd_pcm_hw_constraints_complete(substream);
if (err < 0) {
pcm_dbg(pcm, "snd_pcm_hw_constraints_complete failed\n");
goto error;
}
*rsubstream = substream;
return 0;
error:
snd_pcm_release_substream(substream);
return err;
}
}
static int soc_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dai *codec_dai;
const char *codec_dai_name = "multicodec";
int i, ret = 0;
DEBUG_I("soc_pcm_open substream->name=%s \n", substream->name);//xiao
pinctrl_pm_select_default_state(cpu_dai->dev);
for (i = 0; i < rtd->num_codecs; i++)
pinctrl_pm_select_default_state(rtd->codec_dais[i]->dev);
pm_runtime_get_sync(cpu_dai->dev);
for (i = 0; i < rtd->num_codecs; i++)
pm_runtime_get_sync(rtd->codec_dais[i]->dev);
pm_runtime_get_sync(platform->dev);
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
/* startup the audio subsystem */
if (cpu_dai->driver->ops && cpu_dai->driver->ops->startup) {
ret = cpu_dai->driver->ops->startup(substream, cpu_dai);
if (ret < 0) {
dev_err(cpu_dai->dev, "ASoC: can't open interface"
" %s: %d\n", cpu_dai->name, ret);
goto out;
}
}
if (platform->driver->ops && platform->driver->ops->open) {
ret = platform->driver->ops->open(substream);
DEBUG_I("soc_pcm_open platform->driver->ops->open substream->name=%s \n", substream->name);//xiao
if (ret < 0) {
dev_err(platform->dev, "ASoC: can't open platform"
" %s: %d\n", platform->component.name, ret);
goto platform_err;
}
}
for (i = 0; i < rtd->num_codecs; i++) {
codec_dai = rtd->codec_dais[i];
if (codec_dai->driver->ops && codec_dai->driver->ops->startup) {
ret = codec_dai->driver->ops->startup(substream,
codec_dai);
DEBUG_I("soc_pcm_open codec_dai->driver->ops->startup substream->name=%s \n", substream->name);//xiao
if (ret < 0) {
dev_err(codec_dai->dev,
"ASoC: can't open codec %s: %d\n",
codec_dai->name, ret);
goto codec_dai_err;
}
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
codec_dai->tx_mask = 0;
else
codec_dai->rx_mask = 0;
}
if (rtd->dai_link->ops && rtd->dai_link->ops->startup) {
ret = rtd->dai_link->ops->startup(substream);
if (ret < 0) {
pr_err("ASoC: %s startup failed: %d\n",
rtd->dai_link->name, ret);
goto machine_err;
}
}
经过上面的代码调用会到之前注册的platform codec cpu_dai codec_dai的驱动程序代码。
我们以mtk-soc-tdm-capture.c程序驱动来说明
static struct snd_pcm_ops mtk_afe_capture_ops = {
.open = mtk_capture_pcm_open,
.close = mtk_capture_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = mtk_capture_pcm_hw_params,
.hw_free = mtk_capture_pcm_hw_free,
.prepare = mtk_capture_pcm_prepare,
.trigger = mtk_capture_pcm_trigger,
.pointer = mtk_capture_pcm_pointer,
.copy = mtk_capture_pcm_copy,
.silence = mtk_capture_pcm_silence,
.page = mtk_capture_pcm_page,
};
static int mtk_capture_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
int ret = 0;
DEBUG_I("mtk-soc-tdm-capture mtk_capture_pcm_open substream->name=%s \n", substream->name);//xiao
AudDrv_Clk_On();
pr_debug("%s\n", __func__);
TDM_VUL_Control_context = Get_Mem_ControlT(Soc_Aud_Digital_Block_MEM_VUL);
runtime->hw = mtk_capture_hardware;
memcpy((void *)(&(runtime->hw)), (void *)&mtk_capture_hardware,
sizeof(struct snd_pcm_hardware));
pr_debug("runtime->hw->rates = 0x%x\n ", runtime->hw.rates);
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_sample_rates);
ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
pr_err("snd_pcm_hw_constraint_integer failed\n");
pr_debug("mtk_capture_pcm_open runtime rate = %d channels = %d\n", runtime->rate,
runtime->channels);
runtime->hw.info |= SNDRV_PCM_INFO_INTERLEAVED;
runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED;
runtime->hw.info |= SNDRV_PCM_INFO_MMAP_VALID;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
pr_debug("SNDRV_PCM_STREAM_CAPTURE mtkalsa_capture_constraints\n");
if (ret < 0) {
pr_err("mtk_capture_pcm_close\n");
mtk_capture_pcm_close(substream);
return ret;
}
pr_debug("mtk_capture_pcm_open return\n");
return 0;
}
其实上述代码没做什么就是对pcm硬件参数的初始化,打开audio 时钟。至此open的流程讲完了。
2.2 ioctl SNDRV_PCM_IOCTL_HW_PARAMS
函数调用流程:snd_pcm_capture_ioctl->snd_pcm_capture_ioctl1->snd_pcm_common_ioctl1->snd_pcm_hw_params_user->snd_pcm_hw_params->substream->ops->hw_params->mtk_capture_pcm_hw_params
经过这一系列的函数调用最终就做两件事:1.确定pcm硬件参数。2.开辟DMA内存,DMA内存是MTK事先已经划分好一块区域,而我们只是向系统申请DMA内存的大小。
DMA MTK有做类型区分,主要是不同类型的DMA IO地址不同
DMA类型定义在mtk-soc-digital-type.h
Soc_Aud_Digital_Block_MEM_DL1 = 0,
Soc_Aud_Digital_Block_MEM_DL1_DATA2,
Soc_Aud_Digital_Block_MEM_DL2,
Soc_Aud_Digital_Block_MEM_VUL,
Soc_Aud_Digital_Block_MEM_VUL2,
Soc_Aud_Digital_Block_MEM_DAI,
Soc_Aud_Digital_Block_MEM_DL3,
Soc_Aud_Digital_Block_MEM_AWB,
Soc_Aud_Digital_Block_MEM_MOD_DAI,
对应定义的内存地址定义在mtk-soc-afe-control.h
#define AUDIO_TOP_CON0 (AFE_BASE + 0x0000)
#define AUDIO_TOP_CON1 (AFE_BASE + 0x0004)
#define AUDIO_TOP_CON3 (AFE_BASE + 0x000c)
#define AFE_DAC_CON0 (AFE_BASE + 0x0010)
#define AFE_DAC_CON1 (AFE_BASE + 0x0014)
#define AFE_I2S_CON (AFE_BASE + 0x0018)
#define AFE_DAIBT_CON0 (AFE_BASE + 0x001c)
#define AFE_CONN0 (AFE_BASE + 0x0020)
#define AFE_CONN1 (AFE_BASE + 0x0024)
#define AFE_CONN2 (AFE_BASE + 0x0028)
#define AFE_CONN3 (AFE_BASE + 0x002c)
#define AFE_CONN4 (AFE_BASE + 0x0030)
#define AFE_I2S_CON1 (AFE_BASE + 0x0034)
#define AFE_I2S_CON2 (AFE_BASE + 0x0038)
static int mtk_capture_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_dma_buffer *dma_buf = &substream->dma_buffer;
int ret = 0;
dma_buf->dev.type = SNDRV_DMA_TYPE_DEV;
dma_buf->dev.dev = substream->pcm->card->dev;
dma_buf->private_data = NULL;
runtime->dma_bytes = params_buffer_bytes(hw_params);
if (AllocateAudioSram(&substream->runtime->dma_addr,
&substream->runtime->dma_area,
substream->runtime->dma_bytes, substream,
params_format(hw_params), false) == 0) {
DEBUG_I("mtk_capture_pcm_hw_params AllocateAudioSram success\n");
pr_aud("AllocateAudioSram success\n");
SetHighAddr(Soc_Aud_Digital_Block_MEM_VUL, false, substream->runtime->dma_addr);
} else if (Capture_dma_buf->area) {
pr_aud("Capture_dma_buf = %p Capture_dma_buf->area = %p apture_dma_buf->addr = 0x%lx\n",
Capture_dma_buf, Capture_dma_buf->area, (long) Capture_dma_buf->addr);
DEBUG_I("Capture_dma_buf = %p Capture_dma_buf->area = %p apture_dma_buf->addr = 0x%lx\n",
Capture_dma_buf, Capture_dma_buf->area, (long) Capture_dma_buf->addr);
runtime->dma_area = Capture_dma_buf->area;
runtime->dma_addr = Capture_dma_buf->addr;
SetHighAddr(Soc_Aud_Digital_Block_MEM_VUL, true, runtime->dma_addr);
mCaptureUseSram = true;
AudDrv_Emi_Clk_On();
} else {
pr_warn("mtk_capture_pcm_hw_params snd_pcm_lib_malloc_pages\n");
ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
}
set_mem_block(substream, hw_params, TDM_VUL_Control_context, Soc_Aud_Digital_Block_MEM_VUL);
pr_aud("mtk_capture_pcm_hw_params dma_bytes = %zu dma_area = %p dma_addr = 0x%lx\n",
substream->runtime->dma_bytes, substream->runtime->dma_area, (long)substream->runtime->dma_addr);
DEBUG_I("mtk_capture_pcm_hw_params dma_bytes = %zu dma_area = %p dma_addr = 0x%lx\n",
substream->runtime->dma_bytes, substream->runtime->dma_area, (long)substream->runtime->dma_addr);
return ret;
}
2.3 ioctl SNDRV_PCM_IOCTL_START
函数调用流程:snd_pcm_capture_ioctl->snd_pcm_capture_ioctl1->snd_pcm_common_ioctl1->snd_pcm_action_lock_irq->snd_pcm_action_start->snd_pcm_do_start->substream->ops->trigge->mtk_capture_pcm_trigger->StartAudioCaptureHardware
static struct action_ops snd_pcm_action_start = {
.pre_action = snd_pcm_pre_start,
.do_action = snd_pcm_do_start,
.undo_action = snd_pcm_undo_start,
.post_action = snd_pcm_post_start
};
static int snd_pcm_do_start(struct snd_pcm_substream *substream, int state)
{
if (substream->runtime->trigger_master != substream)
return 0;
return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
}
static void StartAudioCaptureHardware(struct snd_pcm_substream *substream)
{
struct audio_digital_i2s m2ndI2SInAttribute;
struct snd_pcm_runtime *runtime = substream->runtime;//xuce
pr_debug("StartAudioCaptureHardware\n");
DEBUG_I(" mtk-soc-tdm-capture StartAudioCaptureHardwaresubstream->name = %s\n", substream->name);
memset_io((void *)&m2ndI2SInAttribute, 0, sizeof(m2ndI2SInAttribute));
m2ndI2SInAttribute.mLR_SWAP = Soc_Aud_LR_SWAP_NO_SWAP;
m2ndI2SInAttribute.mI2S_IN_PAD_SEL = true; /* I2S_IN_FROM_IO_MUX */
m2ndI2SInAttribute.mI2S_SLAVE = Soc_Aud_I2S_SRC_MASTER_MODE;//Soc_Aud_I2S_SRC_SLAVE_MODE
m2ndI2SInAttribute.mI2S_SAMPLERATE = substream->runtime->rate;
m2ndI2SInAttribute.mINV_LRCK = Soc_Aud_INV_LRCK_NO_INVERSE;
m2ndI2SInAttribute.mI2S_FMT = Soc_Aud_I2S_FORMAT_I2S;
if (substream->runtime->format == SNDRV_PCM_FORMAT_S32_LE ||
substream->runtime->format == SNDRV_PCM_FORMAT_U32_LE)
m2ndI2SInAttribute.mI2S_WLEN = Soc_Aud_I2S_WLEN_WLEN_32BITS;
else
m2ndI2SInAttribute.mI2S_WLEN = Soc_Aud_I2S_WLEN_WLEN_16BITS;
Set2ndI2SIn(&m2ndI2SInAttribute);
if (substream->runtime->format == SNDRV_PCM_FORMAT_S32_LE ||
substream->runtime->format == SNDRV_PCM_FORMAT_U32_LE) {
SetMemIfFetchFormatPerSample(Soc_Aud_Digital_Block_MEM_VUL, AFE_WLEN_32_BIT_ALIGN_8BIT_0_24BIT_DATA);
SetConnectionFormat(OUTPUT_DATA_FORMAT_24BIT, Soc_Aud_AFE_IO_Block_MEM_VUL);
} else {
SetMemIfFetchFormatPerSample(Soc_Aud_Digital_Block_MEM_VUL, AFE_WLEN_16_BIT);
SetConnectionFormat(OUTPUT_DATA_FORMAT_16BIT, Soc_Aud_AFE_IO_Block_MEM_VUL);
}
if (GetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_IN_2) == false) {
SetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_IN_2, true);
Set2ndI2SInEnable(true);
} else
SetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_IN_2, true);
/* here to set interrupt */
irq_add_user(substream,
irq_request_number(Soc_Aud_Digital_Block_MEM_VUL),
substream->runtime->rate,
substream->runtime->period_size);
SetSampleRate(Soc_Aud_Digital_Block_MEM_VUL, substream->runtime->rate);//采样率
SetMemoryPathEnable(Soc_Aud_Digital_Block_MEM_VUL, true);
//配置输入输出的i2s地址 mConnectionLink d mtk-soc-afe-connection.c SetConnectionState(ConnectionState, Soc_Aud_InterConnectionInput_I05,
//Soc_Aud_InterConnectionOutput_O00);
SetIntfConnection(Soc_Aud_InterCon_Connection,
Soc_Aud_AFE_IO_Block_I2S0, Soc_Aud_AFE_IO_Block_MEM_VUL);
if (!mtk_soc_always_hd) {
pr_warn("%s mtk_soc_always_hd == %d\n", __func__,
mtk_soc_always_hd);
EnableALLbySampleRate(runtime->rate);
EnableAPLLTunerbySampleRate(runtime->rate);
}
SetCLkMclk(Soc_Aud_I2S0, substream->runtime->rate);//设置采样频率
EnableI2SCLKDiv(Soc_Aud_I2S0_MCKDIV, true);//给设备供电
EnableAfe(true);//启动AFE
}
StartAudioCaptureHardware会根据创建的值在下面的数组选择匹配关系,找到了就执行类似SetDl1ToI2s0的函数
static const struct connection_link_t mConnectionLink[] = {
{Soc_Aud_AFE_IO_Block_MEM_DL1, Soc_Aud_AFE_IO_Block_I2S3, SetDl1ToI2s0},
{Soc_Aud_AFE_IO_Block_MEM_DL1, Soc_Aud_AFE_IO_Block_I2S1_DAC, SetDl1ToI2s1Dac},
{Soc_Aud_AFE_IO_Block_ADDA_UL2, Soc_Aud_AFE_IO_Block_MEM_VUL_DATA2, SetAdc2ToVulData2},
{Soc_Aud_AFE_IO_Block_ADDA_UL, Soc_Aud_AFE_IO_Block_MEM_VUL, SetAdcToVul},
{Soc_Aud_AFE_IO_Block_ADDA_UL, Soc_Aud_AFE_IO_Block_MEM_VUL_DATA2, SetAdcToVulData2},
{Soc_Aud_AFE_IO_Block_MEM_DL1, Soc_Aud_AFE_IO_Block_I2S1_DAC_2, SetDl1ToI2s1Dac2},
{Soc_Aud_AFE_IO_Block_MEM_DL1, Soc_Aud_AFE_IO_Block_MEM_AWB, SetDl1ToAwb},
{Soc_Aud_AFE_IO_Block_MEM_DL2, Soc_Aud_AFE_IO_Block_MEM_AWB, SetDl2ToAwb},
{Soc_Aud_AFE_IO_Block_MEM_DL2, Soc_Aud_AFE_IO_Block_MODEM_PCM_1_O, SetDl2ToModem1Out},
{Soc_Aud_AFE_IO_Block_MEM_DL2, Soc_Aud_AFE_IO_Block_MODEM_PCM_2_O, SetDl2ToModem2Out},
{Soc_Aud_AFE_IO_Block_MEM_DL1, Soc_Aud_AFE_IO_Block_DAI_BT_OUT, SetDl1ToDaiBtOut},
{Soc_Aud_AFE_IO_Block_MODEM_PCM_1_I_CH1, Soc_Aud_AFE_IO_Block_I2S3, SetModem1InCh1ToI2s3},
{Soc_Aud_AFE_IO_Block_MODEM_PCM_2_I_CH1, Soc_Aud_AFE_IO_Block_I2S3, SetModem2InCh1ToI2s3},
{Soc_Aud_AFE_IO_Block_I2S0_CH2, Soc_Aud_AFE_IO_Block_MODEM_PCM_1_O_CH4, SetI2s0Ch2ToModem1OutCh4},
{Soc_Aud_AFE_IO_Block_I2S0_CH2, Soc_Aud_AFE_IO_Block_MODEM_PCM_2_O_CH4, SetI2s0Ch2ToModem2OutCh4},
{Soc_Aud_AFE_IO_Block_MEM_DL2, Soc_Aud_AFE_IO_Block_I2S1_DAC, SetDl2ToI2s1Dac},
bool SetDl1ToI2s0(unsigned int ConnectionState)
{
SetConnectionState(ConnectionState, Soc_Aud_InterConnectionInput_I05,
Soc_Aud_InterConnectionOutput_O00);
SetConnectionState(ConnectionState, Soc_Aud_InterConnectionInput_I06,
Soc_Aud_InterConnectionOutput_O01);
return true;
}
Soc_Aud_InterConnectionInput_I05 Soc_Aud_InterConnectionOutput_O00 Soc_Aud_InterConnectionOutput_I06 Soc_Aud_InterConnectionOutput_O01 这几个值是不是跟下图afe图相似。看到这里应该不难理解了,如果我们需要把platform(afe)里面的接口联通就需要向SetConnectionState填写对应的通道值,假如我想O3 O4 连接I5 I6 需要这样写
SetConnectionState(ConnectionState, Soc_Aud_InterConnectionInput_I05,
Soc_Aud_InterConnectionOutput_O03);
SetConnectionState(ConnectionState, Soc_Aud_InterConnectionInput_I06,
Soc_Aud_InterConnectionOutput_O04);
那SetConnectionState具体是做什么呢?其实就是调用regmap_update_bits函数对寄存器地址操作,这样达到通道的联通还断开。对上传来说就是录音有没有数据,播放音乐有没有声音
2.3 ioctl SNDRV_PCM_IOCTL_READI_FRAMES
函数调用流程:snd_pcm_capture_ioctl->snd_pcm_capture_ioctl1->snd_pcm_lib_read->snd_pcm_lib_read_transfer->substream->ops->copy->mtk_capture_pcm_copy->mtk_memblk_copy
依据传递的标识符不同,mtk_mem_dlblk_copy还是mtk_mem_ulblk_copy 。这两个函数的功能无非就是:把pcm数据从DMA拷贝到文件中或者把pcm数据拷贝到DMA内存中,供硬件使用。
int mtk_memblk_copy(struct snd_pcm_substream *substream,
int channel, snd_pcm_uframes_t pos, void __user *dst, snd_pcm_uframes_t count,
struct afe_mem_control_t *pMemControl, enum soc_aud_digital_block mem_blk)
{
if (pMemControl == NULL)
return 0;
switch (mem_blk) {
case Soc_Aud_Digital_Block_MEM_DL1:
case Soc_Aud_Digital_Block_MEM_DL2:
case Soc_Aud_Digital_Block_MEM_DL3:
mtk_mem_dlblk_copy(substream, channel, pos, dst, count, pMemControl, mem_blk);
break;
case Soc_Aud_Digital_Block_MEM_VUL:
case Soc_Aud_Digital_Block_MEM_DAI:
case Soc_Aud_Digital_Block_MEM_AWB:
case Soc_Aud_Digital_Block_MEM_MOD_DAI:
case Soc_Aud_Digital_Block_MEM_VUL_DATA2:
case Soc_Aud_Digital_Block_MEM_VUL2:
mtk_mem_ulblk_copy(substream, channel, pos, dst, count, pMemControl, mem_blk);
break;
default:
pr_err("%s not support", __func__);
}
return 0;
}
2.4 PCM数据 DMA 硬件 它们的数据传递关系