/* * Driver for Digigram VX soundcards * * PCM part * * Copyright (c) 2002,2003 by Takashi Iwai <tiwai@suse.de> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * STRATEGY * for playback, we send series of "chunks", which size is equal with the * IBL size, typically 126 samples. at each end of chunk, the end-of-buffer * interrupt is notified, and the interrupt handler will feed the next chunk. * * the current position is calculated from the sample count RMH. * pipe->transferred is the counter of data which has been already transferred. * if this counter reaches to the period size, snd_pcm_period_elapsed() will * be issued. * * for capture, the situation is much easier. * to get a low latency response, we'll check the capture streams at each * interrupt (capture stream has no EOB notification). if the pending * data is accumulated to the period size, snd_pcm_period_elapsed() is * called and the pointer is updated. * * the current point of read buffer is kept in pipe->hw_ptr. note that * this is in bytes. * * * TODO * - linked trigger for full-duplex mode. * - scheduled action on the stream. */ #include <linux/slab.h> #include <linux/delay.h> #include <sound/core.h> #include <sound/asoundef.h> #include <sound/pcm.h> #include <sound/vx_core.h> #include "vx_cmd.h" /* * read three pending pcm bytes via inb() */ static void vx_pcm_read_per_bytes(struct vx_core *chip, struct snd_pcm_runtime *runtime, struct vx_pipe *pipe) { int offset = pipe->hw_ptr; unsigned char *buf = (unsigned char *)(runtime->dma_area + offset); *buf++ = vx_inb(chip, RXH); if (++offset >= pipe->buffer_bytes) { offset = 0; buf = (unsigned char *)runtime->dma_area; } *buf++ = vx_inb(chip, RXM); if (++offset >= pipe->buffer_bytes) { offset = 0; buf = (unsigned char *)runtime->dma_area; } *buf++ = vx_inb(chip, RXL); if (++offset >= pipe->buffer_bytes) { offset = 0; buf = (unsigned char *)runtime->dma_area; } pipe->hw_ptr = offset; } /* * vx_set_pcx_time - convert from the PC time to the RMH status time. * @pc_time: the pointer for the PC-time to set * @dsp_time: the pointer for RMH status time array */ static void vx_set_pcx_time(struct vx_core *chip, pcx_time_t *pc_time, unsigned int *dsp_time) { dsp_time[0] = (unsigned int)((*pc_time) >> 24) & PCX_TIME_HI_MASK; dsp_time[1] = (unsigned int)(*pc_time) & MASK_DSP_WORD; } /* * vx_set_differed_time - set the differed time if specified * @rmh: the rmh record to modify * @pipe: the pipe to be checked * * if the pipe is programmed with the differed time, set the DSP time * on the rmh and changes its command length. * * returns the increase of the command length. */ static int vx_set_differed_time(struct vx_core *chip, struct vx_rmh *rmh, struct vx_pipe *pipe) { /* Update The length added to the RMH command by the timestamp */ if (! (pipe->differed_type & DC_DIFFERED_DELAY)) return 0; /* Set the T bit */ rmh->Cmd[0] |= DSP_DIFFERED_COMMAND_MASK; /* Time stamp is the 1st following parameter */ vx_set_pcx_time(chip, &pipe->pcx_time, &rmh->Cmd[1]); /* Add the flags to a notified differed command */ if (pipe->differed_type & DC_NOTIFY_DELAY) rmh->Cmd[1] |= NOTIFY_MASK_TIME_HIGH ; /* Add the flags to a multiple differed command */ if (pipe->differed_type & DC_MULTIPLE_DELAY) rmh->Cmd[1] |= MULTIPLE_MASK_TIME_HIGH; /* Add the flags to a stream-time differed command */ if (pipe->differed_type & DC_STREAM_TIME_DELAY) rmh->Cmd[1] |= STREAM_MASK_TIME_HIGH; rmh->LgCmd += 2; return 2; } /* * vx_set_stream_format - send the stream format command * @pipe: the affected pipe * @data: format bitmask */ static int vx_set_stream_format(struct vx_core *chip, struct vx_pipe *pipe, unsigned int data) { struct vx_rmh rmh; vx_init_rmh(&rmh, pipe->is_capture ? CMD_FORMAT_STREAM_IN : CMD_FORMAT_STREAM_OUT); rmh.Cmd[0] |= pipe->number << FIELD_SIZE; /* Command might be longer since we may have to add a timestamp */ vx_set_differed_time(chip, &rmh, pipe); rmh.Cmd[rmh.LgCmd] = (data & 0xFFFFFF00) >> 8; rmh.Cmd[rmh.LgCmd + 1] = (data & 0xFF) << 16 /*| (datal & 0xFFFF00) >> 8*/; rmh.LgCmd += 2; return vx_send_msg(chip, &rmh); } /* * vx_set_format - set the format of a pipe * @pipe: the affected pipe * @runtime: pcm runtime instance to be referred * * returns 0 if successful, or a negative error code. */ static int vx_set_format(struct vx_core *chip, struct vx_pipe *pipe, struct snd_pcm_runtime *runtime) { unsigned int header = HEADER_FMT_BASE; if (runtime->channels == 1) header |= HEADER_FMT_MONO; if (snd_pcm_format_little_endian(runtime->format)) header |= HEADER_FMT_INTEL; if (runtime->rate < 32000 && runtime->rate > 11025) header |= HEADER_FMT_UPTO32; else if (runtime->rate <= 11025) header |= HEADER_FMT_UPTO11; switch (snd_pcm_format_physical_width(runtime->format)) { // case 8: break; case 16: header |= HEADER_FMT_16BITS; break; case 24: header |= HEADER_FMT_24BITS; break; default : snd_BUG(); return -EINVAL; }; return vx_set_stream_format(chip, pipe, header); } /* * set / query the IBL size */ static int vx_set_ibl(struct vx_core *chip, struct vx_ibl_info *info) { int err; struct vx_rmh rmh; vx_init_rmh(&rmh, CMD_IBL); rmh.Cmd[0] |= info->size & 0x03ffff; err = vx_send_msg(chip, &rmh); if (err < 0) return err; info->size = rmh.Stat[0]; info->max_size = rmh.Stat[1]; info->min_size = rmh.Stat[2]; info->granularity = rmh.Stat[3]; snd_printdd(KERN_DEBUG "vx_set_ibl: size = %d, max = %d, min = %d, gran = %d\n", info->size, info->max_size, info->min_size, info->granularity); return 0; } /* * vx_get_pipe_state - get the state of a pipe * @pipe: the pipe to be checked * @state: the pointer for the returned state * * checks the state of a given pipe, and stores the state (1 = running, * 0 = paused) on the given pointer. * * called from trigger callback only */ static int vx_get_pipe_state(struct vx_core *chip, struct vx_pipe *pipe, int *state) { int err; struct vx_rmh rmh; vx_init_rmh(&rmh, CMD_PIPE_STATE); vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); err = vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ if (! err) *state = (rmh.Stat[0] & (1 << pipe->number)) ? 1 : 0; return err; } /* * vx_query_hbuffer_size - query available h-buffer size in bytes * @pipe: the pipe to be checked * * return the available size on h-buffer in bytes, * or a negative error code. * * NOTE: calling this function always switches to the stream mode. * you'll need to disconnect the host to get back to the * normal mode. */ static int vx_query_hbuffer_size(struct vx_core *chip, struct vx_pipe *pipe) { int result; struct vx_rmh rmh; vx_init_rmh(&rmh, CMD_SIZE_HBUFFER); vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); if (pipe->is_capture) rmh.Cmd[0] |= 0x00000001; result = vx_send_msg(chip, &rmh); if (! result) result = rmh.Stat[0] & 0xffff; return result; } /* * vx_pipe_can_start - query whether a pipe is ready for start * @pipe: the pipe to be checked * * return 1 if ready, 0 if not ready, and negative value on error. * * called from trigger callback only */ static int vx_pipe_can_start(struct vx_core *chip, struct vx_pipe *pipe) { int err; struct vx_rmh rmh; vx_init_rmh(&rmh, CMD_CAN_START_PIPE); vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); rmh.Cmd[0] |= 1; err = vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ if (! err) { if (rmh.Stat[0]) err = 1; } return err; } /* * vx_conf_pipe - tell the pipe to stand by and wait for IRQA. * @pipe: the pipe to be configured */ static int vx_conf_pipe(struct vx_core *chip, struct vx_pipe *pipe) { struct vx_rmh rmh; vx_init_rmh(&rmh, CMD_CONF_PIPE); if (pipe->is_capture) rmh.Cmd[0] |= COMMAND_RECORD_MASK; rmh.Cmd[1] = 1 << pipe->number; return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ } /* * vx_send_irqa - trigger IRQA */ static int vx_send_irqa(struct vx_core *chip) { struct vx_rmh rmh; vx_init_rmh(&rmh, CMD_SEND_IRQA); return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ } #define MAX_WAIT_FOR_DSP 250 /* * vx boards do not support inter-card sync, besides * only 126 samples require to be prepared before a pipe can start */ #define CAN_START_DELAY 2 /* wait 2ms only before asking if the pipe is ready*/ #define WAIT_STATE_DELAY 2 /* wait 2ms after irqA was requested and check if the pipe state toggled*/ /* * vx_toggle_pipe - start / pause a pipe * @pipe: the pipe to be triggered * @state: start = 1, pause = 0 * * called from trigger callback only * */ static int vx_toggle_pipe(struct vx_core *chip, struct vx_pipe *pipe, int state) { int err, i, cur_state; /* Check the pipe is not already in the requested state */ if (vx_get_pipe_state(chip, pipe, &cur_state) < 0) return -EBADFD; if (state == cur_state) return 0; /* If a start is requested, ask the DSP to get prepared * and wait for a positive acknowledge (when there are * enough sound buffer for this pipe) */ if (state) { for (i = 0 ; i < MAX_WAIT_FOR_DSP; i++) { err = vx_pipe_can_start(chip, pipe); if (err > 0) break; /* Wait for a few, before asking again * to avoid flooding the DSP with our requests */ mdelay(1); } } if ((err = vx_conf_pipe(chip, pipe)) < 0) return err; if ((err = vx_send_irqa(chip)) < 0) return err; /* If it completes successfully, wait for the pipes * reaching the expected state before returning * Check one pipe only (since they are synchronous) */ for (i = 0; i < MAX_WAIT_FOR_DSP; i++) { err = vx_get_pipe_state(chip, pipe, &cur_state); if (err < 0 || cur_state == state) break; err = -EIO; mdelay(1); } return err < 0 ? -EIO : 0; } /* * vx_stop_pipe - stop a pipe * @pipe: the pipe to be stopped * * called from trigger callback only */ static int vx_stop_pipe(struct vx_core *chip, struct vx_pipe *pipe) { struct vx_rmh rmh; vx_init_rmh(&rmh, CMD_STOP_PIPE); vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ } /* * vx_alloc_pipe - allocate a pipe and initialize the pipe instance * @capture: 0 = playback, 1 = capture operation * @audioid: the audio id to be assigned * @num_audio: number of audio channels * @pipep: the returned pipe instance * * return 0 on success, or a negative error code. */ static int vx_alloc_pipe(struct vx_core *chip, int capture, int audioid, int num_audio, struct vx_pipe **pipep) { int err; struct vx_pipe *pipe; struct vx_rmh rmh; int data_mode; *pipep = NULL; vx_init_rmh(&rmh, CMD_RES_PIPE); vx_set_pipe_cmd_params(&rmh, capture, audioid, num_audio); #if 0 // NYI if (underrun_skip_sound) rmh.Cmd[0] |= BIT_SKIP_SOUND; #endif // NYI data_mode = (chip->uer_bits & IEC958_AES0_NONAUDIO) != 0; if (! capture && data_mode) rmh.Cmd[0] |= BIT_DATA_MODE; err = vx_send_msg(chip, &rmh); if (err < 0) return err; /* initialize the pipe record */ pipe = kzalloc(sizeof(*pipe), GFP_KERNEL); if (! pipe) { /* release the pipe */ vx_init_rmh(&rmh, CMD_FREE_PIPE); vx_set_pipe_cmd_params(&rmh, capture, audioid, 0); vx_send_msg(chip, &rmh); return -ENOMEM; } /* the pipe index should be identical with the audio index */ pipe->number = audioid; pipe->is_capture = capture; pipe->channels = num_audio; pipe->differed_type = 0; pipe->pcx_time = 0; pipe->data_mode = data_mode; *pipep = pipe; return 0; } /* * vx_free_pipe - release a pipe * @pipe: pipe to be released */ static int vx_free_pipe(struct vx_core *chip, struct vx_pipe *pipe) { struct vx_rmh rmh; vx_init_rmh(&rmh, CMD_FREE_PIPE); vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); vx_send_msg(chip, &rmh); kfree(pipe); return 0; } /* * vx_start_stream - start the stream * * called from trigger callback only */ static int vx_start_stream(struct vx_core *chip, struct vx_pipe *pipe) { struct vx_rmh rmh; vx_init_rmh(&rmh, CMD_START_ONE_STREAM); vx_set_stream_cmd_params(&rmh, pipe->is_capture, pipe->number); vx_set_differed_time(chip, &rmh, pipe); return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ } /* * vx_stop_stream - stop the stream * * called from trigger callback only */ static int vx_stop_stream(struct vx_core *chip, struct vx_pipe *pipe) { struct vx_rmh rmh; vx_init_rmh(&rmh, CMD_STOP_STREAM); vx_set_stream_cmd_params(&rmh, pipe->is_capture, pipe->number); return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ } /* * playback hw information */ static struct snd_pcm_hardware vx_pcm_playback_hw = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID /*|*/ /*SNDRV_PCM_INFO_RESUME*/), .formats = (/*SNDRV_PCM_FMTBIT_U8 |*/ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE), .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, .rate_min = 5000, .rate_max = 48000, .channels_min = 1, .channels_max = 2, .buffer_bytes_max = (128*1024), .period_bytes_min = 126, .period_bytes_max = (128*1024), .periods_min = 2, .periods_max = VX_MAX_PERIODS, .fifo_size = 126, }; static void vx_pcm_delayed_start(unsigned long arg); /* * vx_pcm_playback_open - open callback for playback */ static int vx_pcm_playback_open(struct snd_pcm_substream *subs) { struct snd_pcm_runtime *runtime = subs->runtime; struct vx_core *chip = snd_pcm_substream_chip(subs); struct vx_pipe *pipe = NULL; unsigned int audio; int err; if (chip->chip_status & VX_STAT_IS_STALE) return -EBUSY; audio = subs->pcm->device * 2; if (snd_BUG_ON(audio >= chip->audio_outs)) return -EINVAL; /* playback pipe may have been already allocated for monitoring */ pipe = chip->playback_pipes[audio]; if (! pipe) { /* not allocated yet */ err = vx_alloc_pipe(chip, 0, audio, 2, &pipe); /* stereo playback */ if (err < 0) return err; chip->playback_pipes[audio] = pipe; } /* open for playback */ pipe->references++; pipe->substream = subs; tasklet_init(&pipe->start_tq, vx_pcm_delayed_start, (unsigned long)subs); chip->playback_pipes[audio] = pipe; runtime->hw = vx_pcm_playback_hw; runtime->hw.period_bytes_min = chip->ibl.size; runtime->private_data = pipe; /* align to 4 bytes (otherwise will be problematic when 24bit is used) */ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4); snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4); return 0; } /* * vx_pcm_playback_close - close callback for playback */ static int vx_pcm_playback_close(struct snd_pcm_substream *subs) { struct vx_core *chip = snd_pcm_substream_chip(subs); struct vx_pipe *pipe; if (! subs->runtime->private_data) return -EINVAL; pipe = subs->runtime->private_data; if (--pipe->references == 0) { chip->playback_pipes[pipe->number] = NULL; vx_free_pipe(chip, pipe); } return 0; } /* * vx_notify_end_of_buffer - send "end-of-buffer" notifier at the given pipe * @pipe: the pipe to notify * * NB: call with a certain lock. */ static int vx_notify_end_of_buffer(struct vx_core *chip, struct vx_pipe *pipe) { int err; struct vx_rmh rmh; /* use a temporary rmh here */ /* Toggle Dsp Host Interface into Message mode */ vx_send_rih_nolock(chip, IRQ_PAUSE_START_CONNECT); vx_init_rmh(&rmh, CMD_NOTIFY_END_OF_BUFFER); vx_set_stream_cmd_params(&rmh, 0, pipe->number); err = vx_send_msg_nolock(chip, &rmh); if (err < 0) return err; /* Toggle Dsp Host Interface back to sound transfer mode */ vx_send_rih_nolock(chip, IRQ_PAUSE_START_CONNECT); return 0; } /* * vx_pcm_playback_transfer_chunk - transfer a single chunk * @subs: substream * @pipe: the pipe to transfer * @size: chunk size in bytes * * transfer a single buffer chunk. EOB notificaton is added after that. * called from the interrupt handler, too. * * return 0 if ok. */ static int vx_pcm_playback_transfer_chunk(struct vx_core *chip, struct snd_pcm_runtime *runtime, struct vx_pipe *pipe, int size) { int space, err = 0; space = vx_query_hbuffer_size(chip, pipe); if (space < 0) { /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); snd_printd("error hbuffer\n"); return space; } if (space < size) { vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); snd_printd("no enough hbuffer space %d\n", space); return -EIO; /* XRUN */ } /* we don't need irqsave here, because this function * is called from either trigger callback or irq handler */ spin_lock(&chip->lock); vx_pseudo_dma_write(chip, runtime, pipe, size); err = vx_notify_end_of_buffer(chip, pipe); /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ vx_send_rih_nolock(chip, IRQ_CONNECT_STREAM_NEXT); spin_unlock(&chip->lock); return err; } /* * update the position of the given pipe. * pipe->position is updated and wrapped within the buffer size. * pipe->transferred is updated, too, but the size is not wrapped, * so that the caller can check the total transferred size later * (to call snd_pcm_period_elapsed). */ static int vx_update_pipe_position(struct vx_core *chip, struct snd_pcm_runtime *runtime, struct vx_pipe *pipe) { struct vx_rmh rmh; int err, update; u64 count; vx_init_rmh(&rmh, CMD_STREAM_SAMPLE_COUNT); vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); err = vx_send_msg(chip, &rmh); if (err < 0) return err; count = ((u64)(rmh.Stat[0] & 0xfffff) << 24) | (u64)rmh.Stat[1]; update = (int)(count - pipe->cur_count); pipe->cur_count = count; pipe->position += update; if (pipe->position >= (int)runtime->buffer_size) pipe->position %= runtime->buffer_size; pipe->transferred += update; return 0; } /* * transfer the pending playback buffer data to DSP * called from interrupt handler */ static void vx_pcm_playback_transfer(struct vx_core *chip, struct snd_pcm_substream *subs, struct vx_pipe *pipe, int nchunks) { int i, err; struct snd_pcm_runtime *runtime = subs->runtime; if (! pipe->prepared || (chip->chip_status & VX_STAT_IS_STALE)) return; for (i = 0; i < nchunks; i++) { if ((err = vx_pcm_playback_transfer_chunk(chip, runtime, pipe, chip->ibl.size)) < 0) return; } } /* * update the playback position and call snd_pcm_period_elapsed() if necessary * called from interrupt handler */ static void vx_pcm_playback_update(struct vx_core *chip, struct snd_pcm_substream *subs, struct vx_pipe *pipe) { int err; struct snd_pcm_runtime *runtime = subs->runtime; if (pipe->running && ! (chip->chip_status & VX_STAT_IS_STALE)) { if ((err = vx_update_pipe_position(chip, runtime, pipe)) < 0) return; if (pipe->transferred >= (int)runtime->period_size) { pipe->transferred %= runtime->period_size; snd_pcm_period_elapsed(subs); } } } /* * start the stream and pipe. * this function is called from tasklet, which is invoked by the trigger * START callback. */ static void vx_pcm_delayed_start(unsigned long arg) { struct snd_pcm_substream *subs = (struct snd_pcm_substream *)arg; struct vx_core *chip = subs->pcm->private_data; struct vx_pipe *pipe = subs->runtime->private_data; int err; /* printk( KERN_DEBUG "DDDD tasklet delayed start jiffies = %ld\n", jiffies);*/ if ((err = vx_start_stream(chip, pipe)) < 0) { snd_printk(KERN_ERR "vx: cannot start stream\n"); return; } if ((err = vx_toggle_pipe(chip, pipe, 1)) < 0) { snd_printk(KERN_ERR "vx: cannot start pipe\n"); return; } /* printk( KERN_DEBUG "dddd tasklet delayed start jiffies = %ld \n", jiffies);*/ } /* * vx_pcm_playback_trigger - trigger callback for playback */ static int vx_pcm_trigger(struct snd_pcm_substream *subs, int cmd) { struct vx_core *chip = snd_pcm_substream_chip(subs); struct vx_pipe *pipe = subs->runtime->private_data; int err; if (chip->chip_status & VX_STAT_IS_STALE) return -EBUSY; switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: if (! pipe->is_capture) vx_pcm_playback_transfer(chip, subs, pipe, 2); /* FIXME: * we trigger the pipe using tasklet, so that the interrupts are * issued surely after the trigger is completed. */ tasklet_schedule(&pipe->start_tq); chip->pcm_running++; pipe->running = 1; break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: vx_toggle_pipe(chip, pipe, 0); vx_stop_pipe(chip, pipe); vx_stop_stream(chip, pipe); chip->pcm_running--; pipe->running = 0; break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: if ((err = vx_toggle_pipe(chip, pipe, 0)) < 0) return err; break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if ((err = vx_toggle_pipe(chip, pipe, 1)) < 0) return err; break; default: return -EINVAL; } return 0; } /* * vx_pcm_playback_pointer - pointer callback for playback */ static snd_pcm_uframes_t vx_pcm_playback_pointer(struct snd_pcm_substream *subs) { struct snd_pcm_runtime *runtime = subs->runtime; struct vx_pipe *pipe = runtime->private_data; return pipe->position; } /* * vx_pcm_hw_params - hw_params callback for playback and capture */ static int vx_pcm_hw_params(struct snd_pcm_substream *subs, struct snd_pcm_hw_params *hw_params) { return snd_pcm_lib_alloc_vmalloc_32_buffer (subs, params_buffer_bytes(hw_params)); } /* * vx_pcm_hw_free - hw_free callback for playback and capture */ static int vx_pcm_hw_free(struct snd_pcm_substream *subs) { return snd_pcm_lib_free_vmalloc_buffer(subs); } /* * vx_pcm_prepare - prepare callback for playback and capture */ static int vx_pcm_prepare(struct snd_pcm_substream *subs) { struct vx_core *chip = snd_pcm_substream_chip(subs); struct snd_pcm_runtime *runtime = subs->runtime; struct vx_pipe *pipe = runtime->private_data; int err, data_mode; // int max_size, nchunks; if (chip->chip_status & VX_STAT_IS_STALE) return -EBUSY; data_mode = (chip->uer_bits & IEC958_AES0_NONAUDIO) != 0; if (data_mode != pipe->data_mode && ! pipe->is_capture) { /* IEC958 status (raw-mode) was changed */ /* we reopen the pipe */ struct vx_rmh rmh; snd_printdd(KERN_DEBUG "reopen the pipe with data_mode = %d\n", data_mode); vx_init_rmh(&rmh, CMD_FREE_PIPE); vx_set_pipe_cmd_params(&rmh, 0, pipe->number, 0); if ((err = vx_send_msg(chip, &rmh)) < 0) return err; vx_init_rmh(&rmh, CMD_RES_PIPE); vx_set_pipe_cmd_params(&rmh, 0, pipe->number, pipe->channels); if (data_mode) rmh.Cmd[0] |= BIT_DATA_MODE; if ((err = vx_send_msg(chip, &rmh)) < 0) return err; pipe->data_mode = data_mode; } if (chip->pcm_running && chip->freq != runtime->rate) { snd_printk(KERN_ERR "vx: cannot set different clock %d " "from the current %d\n", runtime->rate, chip->freq); return -EINVAL; } vx_set_clock(chip, runtime->rate); if ((err = vx_set_format(chip, pipe, runtime)) < 0) return err; if (vx_is_pcmcia(chip)) { pipe->align = 2; /* 16bit word */ } else { pipe->align = 4; /* 32bit word */ } pipe->buffer_bytes = frames_to_bytes(runtime, runtime->buffer_size); pipe->period_bytes = frames_to_bytes(runtime, runtime->period_size); pipe->hw_ptr = 0; /* set the timestamp */ vx_update_pipe_position(chip, runtime, pipe); /* clear again */ pipe->transferred = 0; pipe->position = 0; pipe->prepared = 1; return 0; } /* * operators for PCM playback */ static struct snd_pcm_ops vx_pcm_playback_ops = { .open = vx_pcm_playback_open, .close = vx_pcm_playback_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = vx_pcm_hw_params, .hw_free = vx_pcm_hw_free, .prepare = vx_pcm_prepare, .trigger = vx_pcm_trigger, .pointer = vx_pcm_playback_pointer, .page = snd_pcm_lib_get_vmalloc_page, .mmap = snd_pcm_lib_mmap_vmalloc, }; /* * playback hw information */ static struct snd_pcm_hardware vx_pcm_capture_hw = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID /*|*/ /*SNDRV_PCM_INFO_RESUME*/), .formats = (/*SNDRV_PCM_FMTBIT_U8 |*/ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE), .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, .rate_min = 5000, .rate_max = 48000, .channels_min = 1, .channels_max = 2, .buffer_bytes_max = (128*1024), .period_bytes_min = 126, .period_bytes_max = (128*1024), .periods_min = 2, .periods_max = VX_MAX_PERIODS, .fifo_size = 126, }; /* * vx_pcm_capture_open - open callback for capture */ static int vx_pcm_capture_open(struct snd_pcm_substream *subs) { struct snd_pcm_runtime *runtime = subs->runtime; struct vx_core *chip = snd_pcm_substream_chip(subs); struct vx_pipe *pipe; struct vx_pipe *pipe_out_monitoring = NULL; unsigned int audio; int err; if (chip->chip_status & VX_STAT_IS_STALE) return -EBUSY; audio = subs->pcm->device * 2; if (snd_BUG_ON(audio >= chip->audio_ins)) return -EINVAL; err = vx_alloc_pipe(chip, 1, audio, 2, &pipe); if (err < 0) return err; pipe->substream = subs; tasklet_init(&pipe->start_tq, vx_pcm_delayed_start, (unsigned long)subs); chip->capture_pipes[audio] = pipe; /* check if monitoring is needed */ if (chip->audio_monitor_active[audio]) { pipe_out_monitoring = chip->playback_pipes[audio]; if (! pipe_out_monitoring) { /* allocate a pipe */ err = vx_alloc_pipe(chip, 0, audio, 2, &pipe_out_monitoring); if (err < 0) return err; chip->playback_pipes[audio] = pipe_out_monitoring; } pipe_out_monitoring->references++; /* if an output pipe is available, it's audios still may need to be unmuted. hence we'll have to call a mixer entry point. */ vx_set_monitor_level(chip, audio, chip->audio_monitor[audio], chip->audio_monitor_active[audio]); /* assuming stereo */ vx_set_monitor_level(chip, audio+1, chip->audio_monitor[audio+1], chip->audio_monitor_active[audio+1]); } pipe->monitoring_pipe = pipe_out_monitoring; /* default value NULL */ runtime->hw = vx_pcm_capture_hw; runtime->hw.period_bytes_min = chip->ibl.size; runtime->private_data = pipe; /* align to 4 bytes (otherwise will be problematic when 24bit is used) */ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4); snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4); return 0; } /* * vx_pcm_capture_close - close callback for capture */ static int vx_pcm_capture_close(struct snd_pcm_substream *subs) { struct vx_core *chip = snd_pcm_substream_chip(subs); struct vx_pipe *pipe; struct vx_pipe *pipe_out_monitoring; if (! subs->runtime->private_data) return -EINVAL; pipe = subs->runtime->private_data; chip->capture_pipes[pipe->number] = NULL; pipe_out_monitoring = pipe->monitoring_pipe; /* if an output pipe is attached to this input, check if it needs to be released. */ if (pipe_out_monitoring) { if (--pipe_out_monitoring->references == 0) { vx_free_pipe(chip, pipe_out_monitoring); chip->playback_pipes[pipe->number] = NULL; pipe->monitoring_pipe = NULL; } } vx_free_pipe(chip, pipe); return 0; } #define DMA_READ_ALIGN 6 /* hardware alignment for read */ /* * vx_pcm_capture_update - update the capture buffer */ static void vx_pcm_capture_update(struct vx_core *chip, struct snd_pcm_substream *subs, struct vx_pipe *pipe) { int size, space, count; struct snd_pcm_runtime *runtime = subs->runtime; if (! pipe->prepared || (chip->chip_status & VX_STAT_IS_STALE)) return; size = runtime->buffer_size - snd_pcm_capture_avail(runtime); if (! size) return; size = frames_to_bytes(runtime, size); space = vx_query_hbuffer_size(chip, pipe); if (space < 0) goto _error; if (size > space) size = space; size = (size / 3) * 3; /* align to 3 bytes */ if (size < DMA_READ_ALIGN) goto _error; /* keep the last 6 bytes, they will be read after disconnection */ count = size - DMA_READ_ALIGN; /* read bytes until the current pointer reaches to the aligned position * for word-transfer */ while (count > 0) { if ((pipe->hw_ptr % pipe->align) == 0) break; if (vx_wait_for_rx_full(chip) < 0) goto _error; vx_pcm_read_per_bytes(chip, runtime, pipe); count -= 3; } if (count > 0) { /* ok, let's accelerate! */ int align = pipe->align * 3; space = (count / align) * align; vx_pseudo_dma_read(chip, runtime, pipe, space); count -= space; } /* read the rest of bytes */ while (count > 0) { if (vx_wait_for_rx_full(chip) < 0) goto _error; vx_pcm_read_per_bytes(chip, runtime, pipe); count -= 3; } /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ vx_send_rih_nolock(chip, IRQ_CONNECT_STREAM_NEXT); /* read the last pending 6 bytes */ count = DMA_READ_ALIGN; while (count > 0) { vx_pcm_read_per_bytes(chip, runtime, pipe); count -= 3; } /* update the position */ pipe->transferred += size; if (pipe->transferred >= pipe->period_bytes) { pipe->transferred %= pipe->period_bytes; snd_pcm_period_elapsed(subs); } return; _error: /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ vx_send_rih_nolock(chip, IRQ_CONNECT_STREAM_NEXT); return; } /* * vx_pcm_capture_pointer - pointer callback for capture */ static snd_pcm_uframes_t vx_pcm_capture_pointer(struct snd_pcm_substream *subs) { struct snd_pcm_runtime *runtime = subs->runtime; struct vx_pipe *pipe = runtime->private_data; return bytes_to_frames(runtime, pipe->hw_ptr); } /* * operators for PCM capture */ static struct snd_pcm_ops vx_pcm_capture_ops = { .open = vx_pcm_capture_open, .close = vx_pcm_capture_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = vx_pcm_hw_params, .hw_free = vx_pcm_hw_free, .prepare = vx_pcm_prepare, .trigger = vx_pcm_trigger, .pointer = vx_pcm_capture_pointer, .page = snd_pcm_lib_get_vmalloc_page, .mmap = snd_pcm_lib_mmap_vmalloc, }; /* * interrupt handler for pcm streams */ void vx_pcm_update_intr(struct vx_core *chip, unsigned int events) { unsigned int i; struct vx_pipe *pipe; #define EVENT_MASK (END_OF_BUFFER_EVENTS_PENDING|ASYNC_EVENTS_PENDING) if (events & EVENT_MASK) { vx_init_rmh(&chip->irq_rmh, CMD_ASYNC); if (events & ASYNC_EVENTS_PENDING) chip->irq_rmh.Cmd[0] |= 0x00000001; /* SEL_ASYNC_EVENTS */ if (events & END_OF_BUFFER_EVENTS_PENDING) chip->irq_rmh.Cmd[0] |= 0x00000002; /* SEL_END_OF_BUF_EVENTS */ if (vx_send_msg(chip, &chip->irq_rmh) < 0) { snd_printdd(KERN_ERR "msg send error!!\n"); return; } i = 1; while (i < chip->irq_rmh.LgStat) { int p, buf, capture, eob; p = chip->irq_rmh.Stat[i] & MASK_FIRST_FIELD; capture = (chip->irq_rmh.Stat[i] & 0x400000) ? 1 : 0; eob = (chip->irq_rmh.Stat[i] & 0x800000) ? 1 : 0; i++; if (events & ASYNC_EVENTS_PENDING) i++; buf = 1; /* force to transfer */ if (events & END_OF_BUFFER_EVENTS_PENDING) { if (eob) buf = chip->irq_rmh.Stat[i]; i++; } if (capture) continue; if (snd_BUG_ON(p < 0 || p >= chip->audio_outs)) continue; pipe = chip->playback_pipes[p]; if (pipe && pipe->substream) { vx_pcm_playback_update(chip, pipe->substream, pipe); vx_pcm_playback_transfer(chip, pipe->substream, pipe, buf); } } } /* update the capture pcm pointers as frequently as possible */ for (i = 0; i < chip->audio_ins; i++) { pipe = chip->capture_pipes[i]; if (pipe && pipe->substream) vx_pcm_capture_update(chip, pipe->substream, pipe); } } /* * vx_init_audio_io - check the available audio i/o and allocate pipe arrays */ static int vx_init_audio_io(struct vx_core *chip) { struct vx_rmh rmh; int preferred; vx_init_rmh(&rmh, CMD_SUPPORTED); if (vx_send_msg(chip, &rmh) < 0) { snd_printk(KERN_ERR "vx: cannot get the supported audio data\n"); return -ENXIO; } chip->audio_outs = rmh.Stat[0] & MASK_FIRST_FIELD; chip->audio_ins = (rmh.Stat[0] >> (FIELD_SIZE*2)) & MASK_FIRST_FIELD; chip->audio_info = rmh.Stat[1]; /* allocate pipes */ chip->playback_pipes = kcalloc(chip->audio_outs, sizeof(struct vx_pipe *), GFP_KERNEL); if (!chip->playback_pipes) return -ENOMEM; chip->capture_pipes = kcalloc(chip->audio_ins, sizeof(struct vx_pipe *), GFP_KERNEL); if (!chip->capture_pipes) { kfree(chip->playback_pipes); return -ENOMEM; } preferred = chip->ibl.size; chip->ibl.size = 0; vx_set_ibl(chip, &chip->ibl); /* query the info */ if (preferred > 0) { chip->ibl.size = ((preferred + chip->ibl.granularity - 1) / chip->ibl.granularity) * chip->ibl.granularity; if (chip->ibl.size > chip->ibl.max_size) chip->ibl.size = chip->ibl.max_size; } else chip->ibl.size = chip->ibl.min_size; /* set to the minimum */ vx_set_ibl(chip, &chip->ibl); return 0; } /* * free callback for pcm */ static void snd_vx_pcm_free(struct snd_pcm *pcm) { struct vx_core *chip = pcm->private_data; chip->pcm[pcm->device] = NULL; kfree(chip->playback_pipes); chip->playback_pipes = NULL; kfree(chip->capture_pipes); chip->capture_pipes = NULL; } /* * snd_vx_pcm_new - create and initialize a pcm */ int snd_vx_pcm_new(struct vx_core *chip) { struct snd_pcm *pcm; unsigned int i; int err; if ((err = vx_init_audio_io(chip)) < 0) return err; for (i = 0; i < chip->hw->num_codecs; i++) { unsigned int outs, ins; outs = chip->audio_outs > i * 2 ? 1 : 0; ins = chip->audio_ins > i * 2 ? 1 : 0; if (! outs && ! ins) break; err = snd_pcm_new(chip->card, "VX PCM", i, outs, ins, &pcm); if (err < 0) return err; if (outs) snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &vx_pcm_playback_ops); if (ins) snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &vx_pcm_capture_ops); pcm->private_data = chip; pcm->private_free = snd_vx_pcm_free; pcm->info_flags = 0; strcpy(pcm->name, chip->card->shortname); chip->pcm[i] = pcm; } return 0; }