调用write函数实现把数据写到设备里面去,这里会触发trigger函数也就是DMA的启动。
用户层的write到内核里面都是通过ioctl来做的,这里面会触发trigger函数的执行,等trigger执行完以后,
才会真正调用函数把用户层的东西copy到dma分配的空间,其中函数snd_pcm_lib_write1非常复杂,这里面有同步的操作,
也就是要等到有空余的空间的时候才允许写,否则就要等待,唤醒是通过函数snd_pcm_update_hw_ptr_post来做的,
这个函数会在DMA传输完一帧的中断到来的时候被调用,用来更新缓冲区指针。
对于回放的情形,PCM 数据流向大致是:
copy_from_user DMA I2S DAC
^ ^ ^ ^
+---------+ | +----------+ | +-----------+ | +-----+ | +------+
|userspace+-------->DMA Buffer+------->I2S TX FIFO+------->CODEC+------->SPK/HP|
+---------+ +----------+ +-----------+ +-----+ +------+
ALSA的Write流程
snd_pcm_playback_ioctl => snd_pcm_playback_ioctl1 => SNDRV_PCM_IOCTL_WRITEN_FRAMES =>
snd_pcm_lib_writev => snd_pcm_lib_write1 => |||| => snd_pcm_lib_write_transfer => copy_from_user [copy user speace data to dma]
snd_pcm_start => snd_pcm_action => snd_pcm_action_group => snd_pcm_do_start => substream->ops->trigger
// external/tinyalsa/pcm.c,用户层通过ioctl的方式来调用kernel
int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
{
struct snd_xferi x;
if (pcm->flags & PCM_IN)
return -EINVAL;
x.buf = (void*)data;
x.frames = count / (pcm->config.channels *
pcm_format_to_bits(pcm->config.format) / 8);
for (;;) {
if (!pcm->running) {
int prepare_error = pcm_prepare(pcm);
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))//写入初始化数据
return oops(pcm, errno, "cannot write initial data");
pcm->running = 1;//写入初始化之后设置标致位
return 0;
}
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {//写入普通pcm数据
pcm->prepared = 0;//如果写入失败,则重置
pcm->running = 0;//如果写入失败,则重置
if (errno == EPIPE) {
pcm->underruns++;
if (pcm->flags & PCM_NORESTART)
return -EPIPE;
continue;
}
return oops(pcm, errno, "cannot write stream data");
}
return 0;
}
}
// kernel-3.18/sound/core/pcm_native.c,kernel层的实现
// 在内核中发起系统调用,执行本应用户空间发起调用的fops函数集,完成参数设置任务
/*const struct file_operations snd_pcm_f_ops[2] = {
{
.owner = THIS_MODULE,
.write = snd_pcm_write,
.aio_write = snd_pcm_aio_write,
.open = snd_pcm_playback_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_playback_poll,
.unlocked_ioctl = snd_pcm_playback_ioctl,//pcm文件的ioctl函数
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
},
{
.owner = THIS_MODULE,
.read = snd_pcm_read,
.aio_read = snd_pcm_aio_read,
.open = snd_pcm_capture_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_capture_poll,
.unlocked_ioctl = snd_pcm_capture_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
}
};*/
static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct snd_pcm_file *pcm_file;
pcm_file = file->private_data;
if (((cmd >> 8) & 0xff) != 'A')
return -ENOTTY;
return snd_pcm_playback_ioctl1(file, pcm_file->substream, cmd,
(void __user *)arg);
}
static int snd_pcm_playback_ioctl1(struct file *file,
struct snd_pcm_substream *substream,
unsigned int cmd, void __user *arg)
{
if (snd_BUG_ON(!substream))
return -ENXIO;
if (snd_BUG_ON(substream->stream != SNDRV_PCM_STREAM_PLAYBACK))
return -EINVAL;
switch (cmd) {
case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
{
struct snd_xferi xferi;
struct snd_xferi __user *_xferi = arg;
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_sframes_t result;
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
return -EBADFD;
if (put_user(0, &_xferi->result))
return -EFAULT;
if (copy_from_user(&xferi, _xferi, sizeof(xferi)))
return -EFAULT;
result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
__put_user(result, &_xferi->result);
return result < 0 ? result : 0;
}
}
snd_pcm_sframes_t snd_pcm_lib_write(struct snd_pcm_substream *substream,
const void __user *buf,
snd_pcm_uframes_t size)
{
struct snd_pcm_runtime *runtime;
int nonblock;
int err;
err = pcm_sanity_check(substream);
runtime = substream->runtime;
nonblock = !!(substream->f_flags & O_NONBLOCK);
if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&
runtime->channels > 1)
return -EINVAL;
return snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock,
snd_pcm_lib_write_transfer);
}
static snd_pcm_sframes_t snd_pcm_lib_write1(struct snd_pcm_substream *substream,
unsigned long data,
snd_pcm_uframes_t size,
int nonblock,
transfer_f transfer)
{
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_uframes_t xfer = 0;
snd_pcm_uframes_t offset = 0;
snd_pcm_uframes_t avail;
int err = 0;
if (size == 0)
return 0;
snd_pcm_stream_lock_irq(substream);
switch (runtime->status->state) {
case SNDRV_PCM_STATE_PREPARED:
case SNDRV_PCM_STATE_RUNNING:
case SNDRV_PCM_STATE_PAUSED:
break;
case SNDRV_PCM_STATE_XRUN:
err = -EPIPE;
goto _end_unlock;
case SNDRV_PCM_STATE_SUSPENDED:
err = -ESTRPIPE;
goto _end_unlock;
default:
err = -EBADFD;
goto _end_unlock;
}
runtime->twake = runtime->control->avail_min ? : 1;
if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
snd_pcm_update_hw_ptr(substream);
avail = snd_pcm_playback_avail(runtime);
while (size > 0) {
snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
snd_pcm_uframes_t cont;
if (!avail) {
if (nonblock) {
err = -EAGAIN;
goto _end_unlock;
}
runtime->twake = min_t(snd_pcm_uframes_t, size,
runtime->control->avail_min ? : 1);
err = wait_for_avail(substream, &avail);
if (err < 0)
goto _end_unlock;
}
frames = size > avail ? avail : size;
cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
if (frames > cont)
frames = cont;
if (snd_BUG_ON(!frames)) {
runtime->twake = 0;
snd_pcm_stream_unlock_irq(substream);
return -EINVAL;
}
appl_ptr = runtime->control->appl_ptr;
appl_ofs = appl_ptr % runtime->buffer_size;
snd_pcm_stream_unlock_irq(substream);
err = transfer(substream, appl_ofs, data, offset, frames);//向DMA写入数据
snd_pcm_stream_lock_irq(substream);
if (err < 0)
goto _end_unlock;
switch (runtime->status->state) {
case SNDRV_PCM_STATE_XRUN:
err = -EPIPE;
goto _end_unlock;
case SNDRV_PCM_STATE_SUSPENDED:
err = -ESTRPIPE;
goto _end_unlock;
default:
break;
}
appl_ptr += frames;
if (appl_ptr >= runtime->boundary)
appl_ptr -= runtime->boundary;
runtime->control->appl_ptr = appl_ptr;
if (substream->ops->ack)
substream->ops->ack(substream);
offset += frames;
size -= frames;
xfer += frames;
avail -= frames;
if (runtime->status->state == SNDRV_PCM_STATE_PREPARED &&
snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) {
err = snd_pcm_start(substream);//开始dma传送数据的过程
if (err < 0)
goto _end_unlock;
}
}
_end_unlock:
runtime->twake = 0;
if (xfer > 0 && err >= 0)
snd_pcm_update_state(substream, runtime);
snd_pcm_stream_unlock_irq(substream);
return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
}
transfer的具体实现
static int snd_pcm_lib_write_transfer(struct snd_pcm_substream *substream,
unsigned int hwoff,
unsigned long data, unsigned int off,
snd_pcm_uframes_t frames)
{
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);
if (substream->ops->copy) {
if ((err = substream->ops->copy(substream, -1, hwoff, buf, frames)) < 0)
return err;
} else {
char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
if (copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)))
return -EFAULT;
}
return 0;
}
static int mtk_pcm_I2S0dl1_copy(struct snd_pcm_substream *substream,
int channel, snd_pcm_uframes_t pos,
void __user *dst, snd_pcm_uframes_t count)
{
AFE_BLOCK_T *Afe_Block = NULL;
int copy_size = 0, Afe_WriteIdx_tmp;
unsigned long flags;
/* struct snd_pcm_runtime *runtime = substream->runtime; */
char *data_w_ptr = (char *)dst;
/* get total bytes to copy */
count = audio_frame_to_bytes(substream , count);
/* check which memif nned to be write */
Afe_Block = &pI2S0dl1MemControl->rBlock;
/* handle for buffer management */
if (Afe_Block->u4BufferSize == 0) {
pr_err(" u4BufferSize=0 Error");
return 0;
}
AudDrv_checkDLISRStatus();
spin_lock_irqsave(&auddrv_I2S0dl1_lock, flags);
copy_size = Afe_Block->u4BufferSize -
Afe_Block->u4DataRemained; /* free space of the buffer */
spin_unlock_irqrestore(&auddrv_I2S0dl1_lock, flags);
if (count <= copy_size) {
if (copy_size < 0)
copy_size = 0;
else
copy_size = count;
}
copy_size = Align64ByteSize(copy_size);
if (copy_size != 0) {
spin_lock_irqsave(&auddrv_I2S0dl1_lock, flags);
Afe_WriteIdx_tmp = Afe_Block->u4WriteIdx;
spin_unlock_irqrestore(&auddrv_I2S0dl1_lock, flags);
if (Afe_WriteIdx_tmp + copy_size < Afe_Block->u4BufferSize) { /* copy once */
if (!access_ok(VERIFY_READ, data_w_ptr, copy_size)) {
} else {
if (copy_from_user((Afe_Block->pucVirtBufAddr + Afe_WriteIdx_tmp), data_w_ptr,
copy_size)) {
return -1;
}
}
spin_lock_irqsave(&auddrv_I2S0dl1_lock, flags);
Afe_Block->u4DataRemained += copy_size;
Afe_Block->u4WriteIdx = Afe_WriteIdx_tmp + copy_size;
Afe_Block->u4WriteIdx %= Afe_Block->u4BufferSize;
spin_unlock_irqrestore(&auddrv_I2S0dl1_lock, flags);
data_w_ptr += copy_size;
count -= copy_size;
} else { /* copy twice */
kal_uint32 size_1 = 0, size_2 = 0;
size_1 = Align64ByteSize((Afe_Block->u4BufferSize - Afe_WriteIdx_tmp));
size_2 = Align64ByteSize((copy_size - size_1));
if (!access_ok(VERIFY_READ, data_w_ptr, size_1)) {
} else {
if ((copy_from_user((Afe_Block->pucVirtBufAddr + Afe_WriteIdx_tmp), data_w_ptr ,
size_1))) {
PRINTK_AUDDRV(" Fail 1 copy from user");
return -1;
}
}
spin_lock_irqsave(&auddrv_I2S0dl1_lock, flags);
Afe_Block->u4DataRemained += size_1;
Afe_Block->u4WriteIdx = Afe_WriteIdx_tmp + size_1;
Afe_Block->u4WriteIdx %= Afe_Block->u4BufferSize;
Afe_WriteIdx_tmp = Afe_Block->u4WriteIdx;
spin_unlock_irqrestore(&auddrv_I2S0dl1_lock, flags);
if (!access_ok(VERIFY_READ, data_w_ptr + size_1, size_2)) {
} else {
if ((copy_from_user((Afe_Block->pucVirtBufAddr + Afe_WriteIdx_tmp),
(data_w_ptr + size_1), size_2))) {
return -1;
}
}
spin_lock_irqsave(&auddrv_I2S0dl1_lock, flags);
Afe_Block->u4DataRemained += size_2;
Afe_Block->u4WriteIdx = Afe_WriteIdx_tmp + size_2;
Afe_Block->u4WriteIdx %= Afe_Block->u4BufferSize;
spin_unlock_irqrestore(&auddrv_I2S0dl1_lock, flags);
count -= copy_size;
data_w_ptr += copy_size;
}
}
return 0;
}
// 启动传输,将数据
int snd_pcm_start(struct snd_pcm_substream *substream)
{
return snd_pcm_action(&snd_pcm_action_start, substream,
SNDRV_PCM_STATE_RUNNING);
}
static int snd_pcm_action(struct action_ops *ops,
struct snd_pcm_substream *substream,
int state)
{
int res;
if (substream->pcm->nonatomic)
return snd_pcm_action_mutex(ops, substream, state);
if (snd_pcm_stream_linked(substream)) {
res = snd_pcm_action_group(ops, substream, state, 1);
} else {
res = snd_pcm_action_single(ops, substream, state);
}
return res;
}
static int snd_pcm_action_single(struct action_ops *ops,
struct snd_pcm_substream *substream,
int state)
{
int res;
res = ops->pre_action(substream, state);
if (res < 0)
return res;
res = ops->do_action(substream, state);
if (res == 0)
ops->post_action(substream, state);
else if (ops->undo_action)
ops->undo_action(substream, state);
return res;
}
其中trigger的逻辑如下,简单的说就是启动DMA。
/*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);
}
//pcm subtream的操作函数
/*int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
......
if (rtd->dai_link->dynamic) {
rtd->ops.open = dpcm_fe_dai_open;
rtd->ops.hw_params = dpcm_fe_dai_hw_params;
rtd->ops.prepare = dpcm_fe_dai_prepare;
rtd->ops.trigger = dpcm_fe_dai_trigger;
rtd->ops.hw_free = dpcm_fe_dai_hw_free;
rtd->ops.close = dpcm_fe_dai_close;
rtd->ops.pointer = soc_pcm_pointer;
rtd->ops.ioctl = soc_pcm_ioctl;
} else {
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;
}
if (platform->driver->ops) {
rtd->ops.ack = platform->driver->ops->ack;
rtd->ops.copy = platform->driver->ops->copy;
rtd->ops.silence = platform->driver->ops->silence;
rtd->ops.page = platform->driver->ops->page;
rtd->ops.mmap = platform->driver->ops->mmap;
}
if (playback)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
......
}*/
static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dai *codec_dai;
int i, ret;
for (i = 0; i < rtd->num_codecs; i++) {
codec_dai = rtd->codec_dais[i];
if (codec_dai->driver->ops && codec_dai->driver->ops->trigger) {
ret = codec_dai->driver->ops->trigger(substream,
cmd, codec_dai);//调用codec_dai driver的trigger函数
}
if (platform->driver->ops && platform->driver->ops->trigger) {
ret = platform->driver->ops->trigger(substream, cmd);//调用platform driver的trigger函数
}
if (cpu_dai->driver->ops && cpu_dai->driver->ops->trigger) {
ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai);//调用cpu_dai driver的trigger函数
}
if (rtd->dai_link->ops && rtd->dai_link->ops->trigger) {
ret = rtd->dai_link->ops->trigger(substream, cmd);//调用dai_link driver的trigger函数
}
return 0;
}
处理platform、codec-dai、cpu-dai的trigger回调函数
//codec_dai driver的trigger函数,没有实际作用
/*static const struct snd_soc_dai_ops mt6323_aif1_dai_ops = {
.startup = mt63xx_codec_startup,
.prepare = mt63xx_codec_prepare,
.trigger = mt6323_codec_trigger,
};*/
static int mt6323_codec_trigger(struct snd_pcm_substream *substream, int command,
struct snd_soc_dai *Daiport)
{
switch (command) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
break;
}
return 0;
}
//platform driver的trigger函数
static int mtk_pcm_I2S0dl1_trigger(struct snd_pcm_substream *substream, int cmd)
{
/* pr_warn("mtk_pcm_I2S0dl1_trigger cmd = %d\n", cmd); */
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
return mtk_pcm_I2S0dl1_start(substream);// 启动 dma 传输
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
return mtk_pcm_I2S0dl1_stop(substream);//停止 dma 传输
}
return -EINVAL;
}
static int mtk_pcm_I2S0dl1_start(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
/* here start digital part */
SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I05,
Soc_Aud_InterConnectionOutput_O00);
SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I06,
Soc_Aud_InterConnectionOutput_O01);
SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I05,
Soc_Aud_InterConnectionOutput_O03);
SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I06,
Soc_Aud_InterConnectionOutput_O04);
/* here to set interrupt */
irq_add_user(substream,
Soc_Aud_IRQ_MCU_MODE_IRQ1_MCU_MODE,
substream->runtime->rate,
irq1_cnt ? irq1_cnt : substream->runtime->period_size);
irq_user_id = substream;
SetSampleRate(Soc_Aud_Digital_Block_MEM_DL1, runtime->rate);
SetChannels(Soc_Aud_Digital_Block_MEM_DL1, runtime->channels);
SetMemoryPathEnable(Soc_Aud_Digital_Block_MEM_DL1, true);
EnableAfe(true);
#ifdef _DEBUG_6328_CLK
/* Debug 6328 Digital to Analog path clock and data work or not. and read TEST_OUT(0x206) */
/* Ana_Set_Reg(AFE_MON_DEBUG0, 0x4600 , 0xcf00); // monitor 6328 digitala data sent to analog or not */
Ana_Set_Reg(AFE_MON_DEBUG0, 0x4200 ,
0xcf00); /* monitor 6328 digitala data sent to analog or not */
Ana_Set_Reg(TEST_CON0, 0x0e00 , 0xffff);
#endif
return 0;
}
//cpu_dai driver的trigger函数,没有实现
/*static struct snd_soc_dai_ops mtk_dai_stub_ops = {
.startup = multimedia_startup,
};*/
//dai_link driver的trigger函数,没有实现
/*static struct snd_soc_ops mt_machine_audio_ops = {
.startup = mtmachine_startup,
.prepare = mtmachine_prepare,
};*/