// SPDX-License-Identifier: GPL-2.0-or-later
/*
* US-X2Y AUDIO
* Copyright (c) 2002-2004 by Karsten Wiese
*
* based on
*
* (Tentative) USB Audio Driver for ALSA
*
* Main and PCM part
*
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
*
* Many codes borrowed from audio.c by
* Alan Cox (alan@lxorguk.ukuu.org.uk)
* Thomas Sailer (sailer@ife.ee.ethz.ch)
*/
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/info.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "usx2y.h"
#include "usbusx2y.h"
/* Default value used for nr of packs per urb.
* 1 to 4 have been tested ok on uhci.
* To use 3 on ohci, you'd need a patch:
* look for "0000425-linux-2.6.9-rc4-mm1_ohci-hcd.patch.gz" on
* "https://bugtrack.alsa-project.org/alsa-bug/bug_view_page.php?bug_id=0000425"
*
* 1, 2 and 4 work out of the box on ohci, if I recall correctly.
* Bigger is safer operation, smaller gives lower latencies.
*/
#define USX2Y_NRPACKS 4
/* If your system works ok with this module's parameter
* nrpacks set to 1, you might as well comment
* this define out, and thereby produce smaller, faster code.
* You'd also set USX2Y_NRPACKS to 1 then.
*/
#define USX2Y_NRPACKS_VARIABLE 1
#ifdef USX2Y_NRPACKS_VARIABLE
static int nrpacks = USX2Y_NRPACKS; /* number of packets per urb */
#define nr_of_packs() nrpacks
module_param(nrpacks, int, 0444);
MODULE_PARM_DESC(nrpacks, "Number of packets per URB.");
#else
#define nr_of_packs() USX2Y_NRPACKS
#endif
static int usx2y_urb_capt_retire(struct snd_usx2y_substream *subs)
{
struct urb *urb = subs->completed_urb;
struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
unsigned char *cp;
int i, len, lens = 0, hwptr_done = subs->hwptr_done;
int cnt, blen;
struct usx2ydev *usx2y = subs->usx2y;
for (i = 0; i < nr_of_packs(); i++) {
cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset;
if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */
snd_printk(KERN_ERR
"active frame status %i. Most probably some hardware problem.\n",
urb->iso_frame_desc[i].status);
return urb->iso_frame_desc[i].status;
}
len = urb->iso_frame_desc[i].actual_length / usx2y->stride;
if (!len) {
snd_printd("0 == len ERROR!\n");
continue;
}
/* copy a data chunk */
if ((hwptr_done + len) > runtime->buffer_size) {
cnt = runtime->buffer_size - hwptr_done;
blen = cnt * usx2y->stride;
memcpy(runtime->dma_area + hwptr_done * usx2y->stride, cp, blen);
memcpy(runtime->dma_area, cp + blen, len * usx2y->stride - blen);
} else {
memcpy(runtime->dma_area + hwptr_done * usx2y->stride, cp,
len * usx2y->stride);
}
lens += len;
hwptr_done += len;
if (hwptr_done >= runtime->buffer_size)
hwptr_done -= runtime->buffer_size;
}
subs->hwptr_done = hwptr_done;
subs->transfer_done += lens;
/* update the pointer, call callback if necessary */
if (subs->transfer_done >= runtime->period_size) {
subs->transfer_done -= runtime->period_size;
snd_pcm_period_elapsed(subs->pcm_substream);
}
return 0;
}
/*
* prepare urb for playback data pipe
*
* we copy the data directly from the pcm buffer.
* the current position to be copied is held in hwptr field.
* since a urb can handle only a single linear buffer, if the total
* transferred area overflows the buffer boundary, we cannot send
* it directly from the buffer. thus the data is once copied to
* a temporary buffer and urb points to that.
*/
static int usx2y_urb_play_prepare(struct snd_usx2y_substream *subs,
struct urb *cap_urb,
struct urb *urb)
{
struct usx2ydev *usx2y = subs->usx2y;
struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
int count, counts, pack, len;
count = 0;
for (pack = 0; pack < nr_of_packs(); pack++) {
/* calculate the size of a packet */
counts = cap_urb->iso_frame_desc[pack].actual_length / usx2y->stride;
count += counts;
if (counts < 43 || counts > 50) {
snd_printk(KERN_ERR "should not be here with counts=%i\n", counts);
return -EPIPE;
}
/* set up descriptor */
urb->iso_frame_desc[pack].offset = pack ?
urb->iso_frame_desc[pack - 1].offset +
urb->iso_frame_desc[pack - 1].length :
0;
urb->iso_frame_desc[pack].length = cap_urb->iso_frame_desc[pack].actual_length;
}
if (atomic_read(&subs->state) >= STATE_PRERUNNING) {
if (subs->hwptr + count > runtime->buffer_size) {
/* err, the transferred area goes over buffer boundary.
* copy the data to the temp buffer.
*/
len = runtime->buffer_size - subs->hwptr;
urb->transfer_buffer = subs->tmpbuf;
memcpy(subs->tmpbuf, runtime->dma_area +
subs->hwptr * usx2y->stride, len * usx2y->stride);
memcpy(subs->tmpbuf + len * usx2y->stride,
runtime->dma_area, (count - len) * usx2y->stride);
subs->hwptr += count;
subs->hwptr -= runtime->buffer_size;
} else {
/* set the buffer pointer */
urb->transfer_buffer = runtime->dma_area +