diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /sound/usb |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'sound/usb')
-rw-r--r-- | sound/usb/Kconfig | 32 | ||||
-rw-r--r-- | sound/usb/Makefile | 12 | ||||
-rw-r--r-- | sound/usb/usbaudio.c | 3337 | ||||
-rw-r--r-- | sound/usb/usbaudio.h | 251 | ||||
-rw-r--r-- | sound/usb/usbmidi.c | 1564 | ||||
-rw-r--r-- | sound/usb/usbmixer.c | 1545 | ||||
-rw-r--r-- | sound/usb/usbmixer_maps.c | 135 | ||||
-rw-r--r-- | sound/usb/usbquirks.h | 1202 | ||||
-rw-r--r-- | sound/usb/usx2y/Makefile | 3 | ||||
-rw-r--r-- | sound/usb/usx2y/usX2Yhwdep.c | 280 | ||||
-rw-r--r-- | sound/usb/usx2y/usX2Yhwdep.h | 6 | ||||
-rw-r--r-- | sound/usb/usx2y/usbus428ctldefs.h | 108 | ||||
-rw-r--r-- | sound/usb/usx2y/usbusx2y.c | 461 | ||||
-rw-r--r-- | sound/usb/usx2y/usbusx2y.h | 84 | ||||
-rw-r--r-- | sound/usb/usx2y/usbusx2yaudio.c | 1026 | ||||
-rw-r--r-- | sound/usb/usx2y/usx2y.h | 51 | ||||
-rw-r--r-- | sound/usb/usx2y/usx2yhwdeppcm.c | 807 | ||||
-rw-r--r-- | sound/usb/usx2y/usx2yhwdeppcm.h | 21 |
18 files changed, 10925 insertions, 0 deletions
diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig new file mode 100644 index 00000000000..9329e992c84 --- /dev/null +++ b/sound/usb/Kconfig @@ -0,0 +1,32 @@ +# ALSA USB drivers + +menu "USB devices" + depends on SND!=n && USB!=n + +config SND_USB_AUDIO + tristate "USB Audio/MIDI driver" + depends on SND && USB + select SND_RAWMIDI + select SND_PCM + help + Say Y here to include support for USB audio and USB MIDI + devices. + + To compile this driver as a module, choose M here: the module + will be called snd-usb-audio. + +config SND_USB_USX2Y + tristate "Tascam US-122, US-224 and US-428 USB driver" + depends on SND && USB && (X86 || PPC || ALPHA) + select SND_HWDEP + select SND_RAWMIDI + select SND_PCM + help + Say Y here to include support for Tascam USB Audio/MIDI + interfaces or controllers US-122, US-224 and US-428. + + To compile this driver as a module, choose M here: the module + will be called snd-usb-usx2y. + +endmenu + diff --git a/sound/usb/Makefile b/sound/usb/Makefile new file mode 100644 index 00000000000..2c1dc11a72e --- /dev/null +++ b/sound/usb/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for ALSA +# + +snd-usb-audio-objs := usbaudio.o usbmixer.o +snd-usb-lib-objs := usbmidi.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usb-lib.o +obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-lib.o + +obj-$(CONFIG_SND) += usx2y/ diff --git a/sound/usb/usbaudio.c b/sound/usb/usbaudio.c new file mode 100644 index 00000000000..84b0bbddbd2 --- /dev/null +++ b/sound/usb/usbaudio.c @@ -0,0 +1,3337 @@ +/* + * (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) + * + * + * 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 + * + * + * NOTES: + * + * - async unlink should be used for avoiding the sleep inside lock. + * 2.4.22 usb-uhci seems buggy for async unlinking and results in + * oops. in such a cse, pass async_unlink=0 option. + * - the linked URBs would be preferred but not used so far because of + * the instability of unlinking. + * - type II is not supported properly. there is no device which supports + * this type *correctly*. SB extigy looks as if it supports, but it's + * indeed an AC3 stream packed in SPDIF frames (i.e. no real AC3 stream). + */ + + +#include <sound/driver.h> +#include <linux/bitops.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/string.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 <sound/initval.h> + +#include "usbaudio.h" + + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("USB Audio"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Generic,USB Audio}}"); + + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ +static int vid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; /* Vendor ID for this card */ +static int pid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; /* Product ID for this card */ +static int nrpacks = 4; /* max. number of packets per urb */ +static int async_unlink = 1; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the USB audio adapter."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the USB audio adapter."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable USB audio adapter."); +module_param_array(vid, int, NULL, 0444); +MODULE_PARM_DESC(vid, "Vendor ID for the USB audio device."); +module_param_array(pid, int, NULL, 0444); +MODULE_PARM_DESC(pid, "Product ID for the USB audio device."); +module_param(nrpacks, int, 0444); +MODULE_PARM_DESC(nrpacks, "Max. number of packets per URB."); +module_param(async_unlink, bool, 0444); +MODULE_PARM_DESC(async_unlink, "Use async unlink mode."); + + +/* + * debug the h/w constraints + */ +/* #define HW_CONST_DEBUG */ + + +/* + * + */ + +#define MAX_PACKS 10 +#define MAX_PACKS_HS (MAX_PACKS * 8) /* in high speed mode */ +#define MAX_URBS 5 /* max. 20ms long packets */ +#define SYNC_URBS 2 /* always two urbs for sync */ +#define MIN_PACKS_URB 1 /* minimum 1 packet per urb */ + +typedef struct snd_usb_substream snd_usb_substream_t; +typedef struct snd_usb_stream snd_usb_stream_t; +typedef struct snd_urb_ctx snd_urb_ctx_t; + +struct audioformat { + struct list_head list; + snd_pcm_format_t format; /* format type */ + unsigned int channels; /* # channels */ + unsigned int fmt_type; /* USB audio format type (1-3) */ + unsigned int frame_size; /* samples per frame for non-audio */ + int iface; /* interface number */ + unsigned char altsetting; /* corresponding alternate setting */ + unsigned char altset_idx; /* array index of altenate setting */ + unsigned char attributes; /* corresponding attributes of cs endpoint */ + unsigned char endpoint; /* endpoint */ + unsigned char ep_attr; /* endpoint attributes */ + unsigned int maxpacksize; /* max. packet size */ + unsigned int rates; /* rate bitmasks */ + unsigned int rate_min, rate_max; /* min/max rates */ + unsigned int nr_rates; /* number of rate table entries */ + unsigned int *rate_table; /* rate table */ +}; + +struct snd_urb_ctx { + struct urb *urb; + snd_usb_substream_t *subs; + int index; /* index for urb array */ + int packets; /* number of packets per urb */ + int transfer; /* transferred size */ + char *buf; /* buffer for capture */ +}; + +struct snd_urb_ops { + int (*prepare)(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime, struct urb *u); + int (*retire)(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime, struct urb *u); + int (*prepare_sync)(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime, struct urb *u); + int (*retire_sync)(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime, struct urb *u); +}; + +struct snd_usb_substream { + snd_usb_stream_t *stream; + struct usb_device *dev; + snd_pcm_substream_t *pcm_substream; + int direction; /* playback or capture */ + int interface; /* current interface */ + int endpoint; /* assigned endpoint */ + struct audioformat *cur_audiofmt; /* current audioformat pointer (for hw_params callback) */ + unsigned int cur_rate; /* current rate (for hw_params callback) */ + unsigned int period_bytes; /* current period bytes (for hw_params callback) */ + unsigned int format; /* USB data format */ + unsigned int datapipe; /* the data i/o pipe */ + unsigned int syncpipe; /* 1 - async out or adaptive in */ + unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */ + unsigned int freqn; /* nominal sampling rate in fs/fps in Q16.16 format */ + unsigned int freqm; /* momentary sampling rate in fs/fps in Q16.16 format */ + unsigned int freqmax; /* maximum sampling rate, used for buffer management */ + unsigned int phase; /* phase accumulator */ + unsigned int maxpacksize; /* max packet size in bytes */ + unsigned int maxframesize; /* max packet size in frames */ + unsigned int curpacksize; /* current packet size in bytes (for capture) */ + unsigned int curframesize; /* current packet size in frames (for capture) */ + unsigned int fill_max: 1; /* fill max packet size always */ + unsigned int fmt_type; /* USB audio format type (1-3) */ + + unsigned int running: 1; /* running status */ + + unsigned int hwptr; /* free frame position in the buffer (only for playback) */ + unsigned int hwptr_done; /* processed frame position in the buffer */ + unsigned int transfer_sched; /* scheduled frames since last period (for playback) */ + unsigned int transfer_done; /* processed frames since last period update */ + unsigned long active_mask; /* bitmask of active urbs */ + unsigned long unlink_mask; /* bitmask of unlinked urbs */ + + unsigned int nurbs; /* # urbs */ + snd_urb_ctx_t dataurb[MAX_URBS]; /* data urb table */ + snd_urb_ctx_t syncurb[SYNC_URBS]; /* sync urb table */ + char syncbuf[SYNC_URBS * MAX_PACKS * 4]; /* sync buffer; it's so small - let's get static */ + char *tmpbuf; /* temporary buffer for playback */ + + u64 formats; /* format bitmasks (all or'ed) */ + unsigned int num_formats; /* number of supported audio formats (list) */ + struct list_head fmt_list; /* format list */ + spinlock_t lock; + + struct snd_urb_ops ops; /* callbacks (must be filled at init) */ +}; + + +struct snd_usb_stream { + snd_usb_audio_t *chip; + snd_pcm_t *pcm; + int pcm_index; + unsigned int fmt_type; /* USB audio format type (1-3) */ + snd_usb_substream_t substream[2]; + struct list_head list; +}; + + +/* + * we keep the snd_usb_audio_t instances by ourselves for merging + * the all interfaces on the same card as one sound device. + */ + +static DECLARE_MUTEX(register_mutex); +static snd_usb_audio_t *usb_chip[SNDRV_CARDS]; + + +/* + * convert a sampling rate into our full speed format (fs/1000 in Q16.16) + * this will overflow at approx 524 kHz + */ +inline static unsigned get_usb_full_speed_rate(unsigned int rate) +{ + return ((rate << 13) + 62) / 125; +} + +/* + * convert a sampling rate into USB high speed format (fs/8000 in Q16.16) + * this will overflow at approx 4 MHz + */ +inline static unsigned get_usb_high_speed_rate(unsigned int rate) +{ + return ((rate << 10) + 62) / 125; +} + +/* convert our full speed USB rate into sampling rate in Hz */ +inline static unsigned get_full_speed_hz(unsigned int usb_rate) +{ + return (usb_rate * 125 + (1 << 12)) >> 13; +} + +/* convert our high speed USB rate into sampling rate in Hz */ +inline static unsigned get_high_speed_hz(unsigned int usb_rate) +{ + return (usb_rate * 125 + (1 << 9)) >> 10; +} + + +/* + * prepare urb for full speed capture sync pipe + * + * fill the length and offset of each urb descriptor. + * the fixed 10.14 frequency is passed through the pipe. + */ +static int prepare_capture_sync_urb(snd_usb_substream_t *subs, + snd_pcm_runtime_t *runtime, + struct urb *urb) +{ + unsigned char *cp = urb->transfer_buffer; + snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context; + int i, offs; + + urb->number_of_packets = ctx->packets; + urb->dev = ctx->subs->dev; /* we need to set this at each time */ + for (i = offs = 0; i < urb->number_of_packets; i++, offs += 4, cp += 4) { + urb->iso_frame_desc[i].length = 3; + urb->iso_frame_desc[i].offset = offs; + cp[0] = subs->freqn >> 2; + cp[1] = subs->freqn >> 10; + cp[2] = subs->freqn >> 18; + } + return 0; +} + +/* + * prepare urb for high speed capture sync pipe + * + * fill the length and offset of each urb descriptor. + * the fixed 12.13 frequency is passed as 16.16 through the pipe. + */ +static int prepare_capture_sync_urb_hs(snd_usb_substream_t *subs, + snd_pcm_runtime_t *runtime, + struct urb *urb) +{ + unsigned char *cp = urb->transfer_buffer; + snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context; + int i, offs; + + urb->number_of_packets = ctx->packets; + urb->dev = ctx->subs->dev; /* we need to set this at each time */ + for (i = offs = 0; i < urb->number_of_packets; i++, offs += 4, cp += 4) { + urb->iso_frame_desc[i].length = 4; + urb->iso_frame_desc[i].offset = offs; + cp[0] = subs->freqn; + cp[1] = subs->freqn >> 8; + cp[2] = subs->freqn >> 16; + cp[3] = subs->freqn >> 24; + } + return 0; +} + +/* + * process after capture sync complete + * - nothing to do + */ +static int retire_capture_sync_urb(snd_usb_substream_t *subs, + snd_pcm_runtime_t *runtime, + struct urb *urb) +{ + return 0; +} + +/* + * prepare urb for capture data pipe + * + * fill the offset and length of each descriptor. + * + * we use a temporary buffer to write the captured data. + * since the length of written data is determined by host, we cannot + * write onto the pcm buffer directly... the data is thus copied + * later at complete callback to the global buffer. + */ +static int prepare_capture_urb(snd_usb_substream_t *subs, + snd_pcm_runtime_t *runtime, + struct urb *urb) +{ + int i, offs; + unsigned long flags; + snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context; + + offs = 0; + urb->dev = ctx->subs->dev; /* we need to set this at each time */ + urb->number_of_packets = 0; + spin_lock_irqsave(&subs->lock, flags); + for (i = 0; i < ctx->packets; i++) { + urb->iso_frame_desc[i].offset = offs; + urb->iso_frame_desc[i].length = subs->curpacksize; + offs += subs->curpacksize; + urb->number_of_packets++; + subs->transfer_sched += subs->curframesize; + if (subs->transfer_sched >= runtime->period_size) { + subs->transfer_sched -= runtime->period_size; + break; + } + } + spin_unlock_irqrestore(&subs->lock, flags); + urb->transfer_buffer = ctx->buf; + urb->transfer_buffer_length = offs; +#if 0 // for check + if (! urb->bandwidth) { + int bustime; + bustime = usb_check_bandwidth(urb->dev, urb); + if (bustime < 0) + return bustime; + printk("urb %d: bandwidth = %d (packets = %d)\n", ctx->index, bustime, urb->number_of_packets); + usb_claim_bandwidth(urb->dev, urb, bustime, 1); + } +#endif // for check + return 0; +} + +/* + * process after capture complete + * + * copy the data from each desctiptor to the pcm buffer, and + * update the current position. + */ +static int retire_capture_urb(snd_usb_substream_t *subs, + snd_pcm_runtime_t *runtime, + struct urb *urb) +{ + unsigned long flags; + unsigned char *cp; + int i; + unsigned int stride, len, oldptr; + + stride = runtime->frame_bits >> 3; + + for (i = 0; i < urb->number_of_packets; i++) { + cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset; + if (urb->iso_frame_desc[i].status) { + snd_printd(KERN_ERR "frame %d active: %d\n", i, urb->iso_frame_desc[i].status); + // continue; + } + len = urb->iso_frame_desc[i].actual_length / stride; + if (! len) + continue; + /* update the current pointer */ + spin_lock_irqsave(&subs->lock, flags); + oldptr = subs->hwptr_done; + subs->hwptr_done += len; + if (subs->hwptr_done >= runtime->buffer_size) + subs->hwptr_done -= runtime->buffer_size; + subs->transfer_done += len; + spin_unlock_irqrestore(&subs->lock, flags); + /* copy a data chunk */ + if (oldptr + len > runtime->buffer_size) { + unsigned int cnt = runtime->buffer_size - oldptr; + unsigned int blen = cnt * stride; + memcpy(runtime->dma_area + oldptr * stride, cp, blen); + memcpy(runtime->dma_area, cp + blen, len * stride - blen); + } else { + memcpy(runtime->dma_area + oldptr * stride, cp, len * stride); + } + /* update the pointer, call callback if necessary */ + spin_lock_irqsave(&subs->lock, flags); + if (subs->transfer_done >= runtime->period_size) { + subs->transfer_done -= runtime->period_size; + spin_unlock_irqrestore(&subs->lock, flags); + snd_pcm_period_elapsed(subs->pcm_substream); + } else + spin_unlock_irqrestore(&subs->lock, flags); + } + return 0; +} + + +/* + * prepare urb for full speed playback sync pipe + * + * set up the offset and length to receive the current frequency. + */ + +static int prepare_playback_sync_urb(snd_usb_substream_t *subs, + snd_pcm_runtime_t *runtime, + struct urb *urb) +{ + int i, offs; + snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context; + + urb->number_of_packets = ctx->packets; + urb->dev = ctx->subs->dev; /* we need to set this at each time */ + for (i = offs = 0; i < urb->number_of_packets; i++, offs += 4) { + urb->iso_frame_desc[i].length = 3; + urb->iso_frame_desc[i].offset = offs; + } + return 0; +} + +/* + * prepare urb for high speed playback sync pipe + * + * set up the offset and length to receive the current frequency. + */ + +static int prepare_playback_sync_urb_hs(snd_usb_substream_t *subs, + snd_pcm_runtime_t *runtime, + struct urb *urb) +{ + int i, offs; + snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context; + + urb->number_of_packets = ctx->packets; + urb->dev = ctx->subs->dev; /* we need to set this at each time */ + for (i = offs = 0; i < urb->number_of_packets; i++, offs += 4) { + urb->iso_frame_desc[i].length = 4; + urb->iso_frame_desc[i].offset = offs; + } + return 0; +} + +/* + * process after full speed playback sync complete + * + * retrieve the current 10.14 frequency from pipe, and set it. + * the value is referred in prepare_playback_urb(). + */ +static int retire_playback_sync_urb(snd_usb_substream_t *subs, + snd_pcm_runtime_t *runtime, + struct urb *urb) +{ + int i; + unsigned int f, found; + unsigned char *cp = urb->transfer_buffer; + unsigned long flags; + + found = 0; + for (i = 0; i < urb->number_of_packets; i++, cp += 4) { + if (urb->iso_frame_desc[i].status || + urb->iso_frame_desc[i].actual_length < 3) + continue; + f = combine_triple(cp) << 2; +#if 0 + if (f < subs->freqn - (subs->freqn>>3) || f > subs->freqmax) { + snd_printd(KERN_WARNING "requested frequency %d (%u,%03uHz) out of range (current nominal %d (%u,%03uHz))\n", + f, f >> 14, (f & ((1 << 14) - 1) * 1000) / ((1 << 14) - 1), + subs->freqn, subs->freqn >> 14, (subs->freqn & ((1 << 14) - 1) * 1000) / ((1 << 14) - 1)); + continue; + } +#endif + found = f; + } + if (found) { + spin_lock_irqsave(&subs->lock, flags); + subs->freqm = found; + spin_unlock_irqrestore(&subs->lock, flags); + } + + return 0; +} + +/* + * process after high speed playback sync complete + * + * retrieve the current 12.13 frequency from pipe, and set it. + * the value is referred in prepare_playback_urb(). + */ +static int retire_playback_sync_urb_hs(snd_usb_substream_t *subs, + snd_pcm_runtime_t *runtime, + struct urb *urb) +{ + int i; + unsigned int found; + unsigned char *cp = urb->transfer_buffer; + unsigned long flags; + + found = 0; + for (i = 0; i < urb->number_of_packets; i++, cp += 4) { + if (urb->iso_frame_desc[i].status || + urb->iso_frame_desc[i].actual_length < 4) + continue; + found = combine_quad(cp) & 0x0fffffff; + } + if (found) { + spin_lock_irqsave(&subs->lock, flags); + subs->freqm = found; + spin_unlock_irqrestore(&subs->lock, flags); + } + + 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 prepare_playback_urb(snd_usb_substream_t *subs, + snd_pcm_runtime_t *runtime, + struct urb *urb) +{ + int i, stride, offs; + unsigned int counts; + unsigned long flags; + snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context; + + stride = runtime->frame_bits >> 3; + + offs = 0; + urb->dev = ctx->subs->dev; /* we need to set this at each time */ + urb->number_of_packets = 0; + spin_lock_irqsave(&subs->lock, flags); + for (i = 0; i < ctx->packets; i++) { + /* calculate the size of a packet */ + if (subs->fill_max) + counts = subs->maxframesize; /* fixed */ + else { + subs->phase = (subs->phase & 0xffff) + subs->freqm; + counts = subs->phase >> 16; + if (counts > subs->maxframesize) + counts = subs->maxframesize; + } + /* set up descriptor */ + urb->iso_frame_desc[i].offset = offs * stride; + urb->iso_frame_desc[i].length = counts * stride; + offs += counts; + urb->number_of_packets++; + subs->transfer_sched += counts; + if (subs->transfer_sched >= runtime->period_size) { + subs->transfer_sched -= runtime->period_size; + if (subs->fmt_type == USB_FORMAT_TYPE_II) { + if (subs->transfer_sched > 0) { + /* FIXME: fill-max mode is not supported yet */ + offs -= subs->transfer_sched; + counts -= subs->transfer_sched; + urb->iso_frame_desc[i].length = counts * stride; + subs->transfer_sched = 0; + } + i++; + if (i < ctx->packets) { + /* add a transfer delimiter */ + urb->iso_frame_desc[i].offset = offs * stride; + urb->iso_frame_desc[i].length = 0; + urb->number_of_packets++; + } + } + break; + } + } + if (subs->hwptr + offs > runtime->buffer_size) { + /* err, the transferred area goes over buffer boundary. + * copy the data to the temp buffer. + */ + int len; + len = runtime->buffer_size - subs->hwptr; + urb->transfer_buffer = subs->tmpbuf; + memcpy(subs->tmpbuf, runtime->dma_area + subs->hwptr * stride, len * stride); + memcpy(subs->tmpbuf + len * stride, runtime->dma_area, (offs - len) * stride); + subs->hwptr += offs; + subs->hwptr -= runtime->buffer_size; + } else { + /* set the buffer pointer */ + urb->transfer_buffer = runtime->dma_area + subs->hwptr * stride; + subs->hwptr += offs; + } + spin_unlock_irqrestore(&subs->lock, flags); + urb->transfer_buffer_length = offs * stride; + ctx->transfer = offs; + + return 0; +} + +/* + * process after playback data complete + * + * update the current position and call callback if a period is processed. + */ +static int retire_playback_urb(snd_usb_substream_t *subs, + snd_pcm_runtime_t *runtime, + struct urb *urb) +{ + unsigned long flags; + snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context; + + spin_lock_irqsave(&subs->lock, flags); + subs->transfer_done += ctx->transfer; + subs->hwptr_done += ctx->transfer; + ctx->transfer = 0; + if (subs->hwptr_done >= runtime->buffer_size) + subs->hwptr_done -= runtime->buffer_size; + if (subs->transfer_done >= runtime->period_size) { + subs->transfer_done -= runtime->period_size; + spin_unlock_irqrestore(&subs->lock, flags); + snd_pcm_period_elapsed(subs->pcm_substream); + } else + spin_unlock_irqrestore(&subs->lock, flags); + return 0; +} + + +/* + */ +static struct snd_urb_ops audio_urb_ops[2] = { + { + .prepare = prepare_playback_urb, + .retire = retire_playback_urb, + .prepare_sync = prepare_playback_sync_urb, + .retire_sync = retire_playback_sync_urb, + }, + { + .prepare = prepare_capture_urb, + .retire = retire_capture_urb, + .prepare_sync = prepare_capture_sync_urb, + .retire_sync = retire_capture_sync_urb, + }, +}; + +static struct snd_urb_ops audio_urb_ops_high_speed[2] = { + { + .prepare = prepare_playback_urb, + .retire = retire_playback_urb, + .prepare_sync = prepare_playback_sync_urb_hs, + .retire_sync = retire_playback_sync_urb_hs, + }, + { + .prepare = prepare_capture_urb, + .retire = retire_capture_urb, + .prepare_sync = prepare_capture_sync_urb_hs, + .retire_sync = retire_capture_sync_urb, + }, +}; + +/* + * complete callback from data urb + */ +static void snd_complete_urb(struct urb *urb, struct pt_regs *regs) +{ + snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context; + snd_usb_substream_t *subs = ctx->subs; + snd_pcm_substream_t *substream = ctx->subs->pcm_substream; + int err = 0; + + if ((subs->running && subs->ops.retire(subs, substream->runtime, urb)) || + ! subs->running || /* can be stopped during retire callback */ + (err = subs->ops.prepare(subs, substream->runtime, urb)) < 0 || + (err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { + clear_bit(ctx->index, &subs->active_mask); + if (err < 0) { + snd_printd(KERN_ERR "cannot submit urb (err = %d)\n", err); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + } + } +} + + +/* + * complete callback from sync urb + */ +static void snd_complete_sync_urb(struct urb *urb, struct pt_regs *regs) +{ + snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context; + snd_usb_substream_t *subs = ctx->subs; + snd_pcm_substream_t *substream = ctx->subs->pcm_substream; + int err = 0; + + if ((subs->running && subs->ops.retire_sync(subs, substream->runtime, urb)) || + ! subs->running || /* can be stopped during retire callback */ + (err = subs->ops.prepare_sync(subs, substream->runtime, urb)) < 0 || + (err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { + clear_bit(ctx->index + 16, &subs->active_mask); + if (err < 0) { + snd_printd(KERN_ERR "cannot submit sync urb (err = %d)\n", err); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + } + } +} + + +/* + * unlink active urbs. + */ +static int deactivate_urbs(snd_usb_substream_t *subs, int force, int can_sleep) +{ + unsigned int i; + int async; + + subs->running = 0; + + if (!force && subs->stream->chip->shutdown) /* to be sure... */ + return -EBADFD; + + async = !can_sleep && async_unlink; + + if (! async && in_interrupt()) + return 0; + + for (i = 0; i < subs->nurbs; i++) { + if (test_bit(i, &subs->active_mask)) { + if (! test_and_set_bit(i, &subs->unlink_mask)) { + struct urb *u = subs->dataurb[i].urb; + if (async) { + u->transfer_flags |= URB_ASYNC_UNLINK; + usb_unlink_urb(u); + } else + usb_kill_urb(u); + } + } + } + if (subs->syncpipe) { + for (i = 0; i < SYNC_URBS; i++) { + if (test_bit(i+16, &subs->active_mask)) { + if (! test_and_set_bit(i+16, &subs->unlink_mask)) { + struct urb *u = subs->syncurb[i].urb; + if (async) { + u->transfer_flags |= URB_ASYNC_UNLINK; + usb_unlink_urb(u); + } else + usb_kill_urb(u); + } + } + } + } + return 0; +} + + +/* + * set up and start data/sync urbs + */ +static int start_urbs(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime) +{ + unsigned int i; + int err; + + if (subs->stream->chip->shutdown) + return -EBADFD; + + for (i = 0; i < subs->nurbs; i++) { + snd_assert(subs->dataurb[i].urb, return -EINVAL); + if (subs->ops.prepare(subs, runtime, subs->dataurb[i].urb) < 0) { + snd_printk(KERN_ERR "cannot prepare datapipe for urb %d\n", i); + goto __error; + } + } + if (subs->syncpipe) { + for (i = 0; i < SYNC_URBS; i++) { + snd_assert(subs->syncurb[i].urb, return -EINVAL); + if (subs->ops.prepare_sync(subs, runtime, subs->syncurb[i].urb) < 0) { + snd_printk(KERN_ERR "cannot prepare syncpipe for urb %d\n", i); + goto __error; + } + } + } + + subs->active_mask = 0; + subs->unlink_mask = 0; + subs->running = 1; + for (i = 0; i < subs->nurbs; i++) { + if ((err = usb_submit_urb(subs->dataurb[i].urb, GFP_ATOMIC)) < 0) { + snd_printk(KERN_ERR "cannot submit datapipe for urb %d, err = %d\n", i, err); + goto __error; + } + set_bit(i, &subs->active_mask); + } + if (subs->syncpipe) { + for (i = 0; i < SYNC_URBS; i++) { + if ((err = usb_submit_urb(subs->syncurb[i].urb, GFP_ATOMIC)) < 0) { + snd_printk(KERN_ERR "cannot submit syncpipe for urb %d, err = %d\n", i, err); + goto __error; + } + set_bit(i + 16, &subs->active_mask); + } + } + return 0; + + __error: + // snd_pcm_stop(subs->pcm_substream, SNDRV_PCM_STATE_XRUN); + deactivate_urbs(subs, 0, 0); + return -EPIPE; +} + + +/* + * wait until all urbs are processed. + */ +static int wait_clear_urbs(snd_usb_substream_t *subs) +{ + int timeout = HZ; + unsigned int i; + int alive; + + do { + alive = 0; + for (i = 0; i < subs->nurbs; i++) { + if (test_bit(i, &subs->active_mask)) + alive++; + } + if (subs->syncpipe) { + for (i = 0; i < SYNC_URBS; i++) { + if (test_bit(i + 16, &subs->active_mask)) + alive++; + } + } + if (! alive) + break; + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + } while (--timeout > 0); + if (alive) + snd_printk(KERN_ERR "timeout: still %d active urbs..\n", alive); + return 0; +} + + +/* + * return the current pcm pointer. just return the hwptr_done value. + */ +static snd_pcm_uframes_t snd_usb_pcm_pointer(snd_pcm_substream_t *substream) +{ + snd_usb_substream_t *subs = (snd_usb_substream_t *)substream->runtime->private_data; + return subs->hwptr_done; +} + + +/* + * start/stop substream + */ +static int snd_usb_pcm_trigger(snd_pcm_substream_t *substream, int cmd) +{ + snd_usb_substream_t *subs = (snd_usb_substream_t *)substream->runtime->private_data; + int err; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + err = start_urbs(subs, substream->runtime); + break; + case SNDRV_PCM_TRIGGER_STOP: + err = deactivate_urbs(subs, 0, 0); + break; + default: + err = -EINVAL; + break; + } + return err < 0 ? err : 0; +} + + +/* + * release a urb data + */ +static void release_urb_ctx(snd_urb_ctx_t *u) +{ + if (u->urb) { + usb_free_urb(u->urb); + u->urb = NULL; + } + if (u->buf) { + kfree(u->buf); + u->buf = NULL; + } +} + +/* + * release a substream + */< |