// SPDX-License-Identifier: GPL-2.0-or-later /* * Driver for Digigram VX soundcards * * PCM part * * Copyright (c) 2002,2003 by Takashi Iwai <tiwai@suse.de> * * 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.
*/
/* * 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
*/ staticvoid vx_set_pcx_time(struct vx_core *chip, pcx_time_t *pc_time, unsignedint *dsp_time)
{
dsp_time[0] = (unsignedint)((*pc_time) >> 24) & PCX_TIME_HI_MASK;
dsp_time[1] = (unsignedint)(*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.
*/ staticint 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
*/ staticint vx_set_stream_format(struct vx_core *chip, struct vx_pipe *pipe, unsignedint data)
{ struct vx_rmh 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.
*/ staticint vx_set_format(struct vx_core *chip, struct vx_pipe *pipe, struct snd_pcm_runtime *runtime)
{ unsignedint 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; elseif (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;
}
/* * 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
*/ staticint vx_get_pipe_state(struct vx_core *chip, struct vx_pipe *pipe, int *state)
{ int err; struct vx_rmh rmh;
/* * 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.
*/ staticint 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
*/ staticint vx_pipe_can_start(struct vx_core *chip, struct vx_pipe *pipe)
{ int err; struct vx_rmh rmh;
err = vx_send_msg(chip, &rmh); 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
*/ staticint vx_conf_pipe(struct vx_core *chip, struct vx_pipe *pipe)
{ struct vx_rmh rmh;
#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 *
*/ staticint 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);
}
}
err = vx_conf_pipe(chip, pipe); if (err < 0) return err;
err = vx_send_irqa(chip); if (err < 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
*/ staticint 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(chip, &rmh);
}
/* * 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.
*/ staticint 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;
/* 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
*/ staticint vx_free_pipe(struct vx_core *chip, struct vx_pipe *pipe)
{ struct vx_rmh rmh;
/* 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);
/* * 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.
*/ staticint 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.
*/ staticint 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);
dev_dbg(chip->card->dev, "error hbuffer\n"); return space;
} if (space < size) {
vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT);
dev_dbg(chip->card->dev, "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
*/
mutex_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);
mutex_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).
*/ staticint 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;
/* * transfer the pending playback buffer data to DSP * called from interrupt handler
*/ staticvoid 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++) {
err = vx_pcm_playback_transfer_chunk(chip, runtime, pipe,
chip->ibl.size); if (err < 0) return;
}
}
/* * update the playback position and call snd_pcm_period_elapsed() if necessary * called from interrupt handler
*/ staticvoid 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)) {
err = vx_update_pipe_position(chip, runtime, pipe); if (err < 0) return; if (pipe->transferred >= (int)runtime->period_size) {
pipe->transferred %= runtime->period_size;
snd_pcm_period_elapsed(subs);
}
}
}
/* * vx_pcm_playback_trigger - trigger callback for playback
*/ staticint 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);
err = vx_start_stream(chip, pipe); if (err < 0) {
pr_debug("vx: cannot start stream\n"); return err;
}
err = vx_toggle_pipe(chip, pipe, 1); if (err < 0) {
pr_debug("vx: cannot start pipe\n");
vx_stop_stream(chip, pipe); return err;
}
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:
err = vx_toggle_pipe(chip, pipe, 0); if (err < 0) return err; break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
err = vx_toggle_pipe(chip, pipe, 1); if (err < 0) return err; break; default: return -EINVAL;
} return 0;
}
/* * vx_pcm_prepare - prepare callback for playback and capture
*/ staticint 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;
dev_dbg(chip->card->dev, "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);
err = vx_send_msg(chip, &rmh); if (err < 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;
err = vx_send_msg(chip, &rmh); if (err < 0) return err;
pipe->data_mode = data_mode;
}
if (chip->pcm_running && chip->freq != runtime->rate) {
dev_err(chip->card->dev, "vx: cannot set different clock %d from the current %d\n",
runtime->rate, chip->freq); return -EINVAL;
}
vx_set_clock(chip, runtime->rate);
/* 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 */
/* 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);
/* 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 */
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
*/ staticint 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) {
dev_err(chip->card->dev, "vx: cannot get the supported audio data\n"); return -ENXIO;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.