diff options
author | Clemens Ladisch | 2010-10-26 17:14:41 +0200 |
---|---|---|
committer | Takashi Iwai | 2010-10-27 09:17:41 +0200 |
commit | 89e1e66d6be8a520cdcd26043cda2cc870a34015 (patch) | |
tree | c9056471d3edf7eea0c665f0aaea10fbdda4e228 /sound/usb/urb.c | |
parent | 0d040df9984c8fcb6a777a8f6d5dc513eaefd2de (diff) |
ALSA: usb-audio: automatically detect feedback format
There are two USB Audio Class specifications (v1 and v2), but neither of
them clearly defines the feedback format for high-speed UAC v1 devices.
Add to this whatever the Creative and M-Audio firmware writers have been
smoking, and it becomes impossible to predict the exact feedback format
used by a particular device.
Therefore, automatically detect the feedback format by looking at the
magnitude of the first received feedback value.
Also, this allows us to get rid of some special cases for E-Mu devices.
Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/usb/urb.c')
-rw-r--r-- | sound/usb/urb.c | 170 |
1 files changed, 58 insertions, 112 deletions
diff --git a/sound/usb/urb.c b/sound/usb/urb.c index 8deeaad10f10..e184349aee83 100644 --- a/sound/usb/urb.c +++ b/sound/usb/urb.c @@ -225,6 +225,7 @@ int snd_usb_init_substream_urbs(struct snd_usb_substream *subs, else subs->freqn = get_usb_high_speed_rate(rate); subs->freqm = subs->freqn; + subs->freqshift = INT_MIN; /* calculate max. frequency */ if (subs->maxpacksize) { /* whatever fits into a max. size packet */ @@ -513,11 +514,10 @@ static int retire_paused_capture_urb(struct snd_usb_substream *subs, /* - * prepare urb for full speed playback sync pipe + * prepare urb for playback sync pipe * * set up the offset and length to receive the current frequency. */ - static int prepare_playback_sync_urb(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *urb) @@ -525,103 +525,78 @@ static int prepare_playback_sync_urb(struct snd_usb_substream *subs, struct snd_urb_ctx *ctx = urb->context; urb->dev = ctx->subs->dev; /* we need to set this at each time */ - urb->iso_frame_desc[0].length = 3; + urb->iso_frame_desc[0].length = min(4u, ctx->subs->syncmaxsize); urb->iso_frame_desc[0].offset = 0; return 0; } /* - * prepare urb for high speed playback sync pipe + * process after playback sync complete * - * set up the offset and length to receive the current frequency. - */ - -static int prepare_playback_sync_urb_hs(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - struct snd_urb_ctx *ctx = urb->context; - - urb->dev = ctx->subs->dev; /* we need to set this at each time */ - urb->iso_frame_desc[0].length = 4; - urb->iso_frame_desc[0].offset = 0; - 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(). + * Full speed devices report feedback values in 10.14 format as samples per + * frame, high speed devices in 16.16 format as samples per microframe. + * Because the Audio Class 1 spec was written before USB 2.0, many high speed + * devices use a wrong interpretation, some others use an entirely different + * format. Therefore, we cannot predict what format any particular device uses + * and must detect it automatically. */ static int retire_playback_sync_urb(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *urb) { unsigned int f; + int shift; unsigned long flags; - if (urb->iso_frame_desc[0].status == 0 && - urb->iso_frame_desc[0].actual_length == 3) { - f = combine_triple((u8*)urb->transfer_buffer) << 2; - if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) { - spin_lock_irqsave(&subs->lock, flags); - subs->freqm = f; - spin_unlock_irqrestore(&subs->lock, flags); - } - } - - return 0; -} + if (urb->iso_frame_desc[0].status != 0 || + urb->iso_frame_desc[0].actual_length < 3) + 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(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned int f; - unsigned long flags; + f = le32_to_cpup(urb->transfer_buffer); + if (urb->iso_frame_desc[0].actual_length == 3) + f &= 0x00ffffff; + else + f &= 0x0fffffff; + if (f == 0) + return 0; - if (urb->iso_frame_desc[0].status == 0 && - urb->iso_frame_desc[0].actual_length == 4) { - f = combine_quad((u8*)urb->transfer_buffer) & 0x0fffffff; - if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) { - spin_lock_irqsave(&subs->lock, flags); - subs->freqm = f; - spin_unlock_irqrestore(&subs->lock, flags); + if (unlikely(subs->freqshift == INT_MIN)) { + /* + * The first time we see a feedback value, determine its format + * by shifting it left or right until it matches the nominal + * frequency value. This assumes that the feedback does not + * differ from the nominal value more than +50% or -25%. + */ + shift = 0; + while (f < subs->freqn - subs->freqn / 4) { + f <<= 1; + shift++; + } + while (f > subs->freqn + subs->freqn / 2) { + f >>= 1; + shift--; } + subs->freqshift = shift; } + else if (subs->freqshift >= 0) + f <<= subs->freqshift; + else + f >>= -subs->freqshift; - return 0; -} - -/* - * process after E-Mu 0202/0404/Tracker Pre high speed playback sync complete - * - * These devices return the number of samples per packet instead of the number - * of samples per microframe. - */ -static int retire_playback_sync_urb_hs_emu(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned int f; - unsigned long flags; - - if (urb->iso_frame_desc[0].status == 0 && - urb->iso_frame_desc[0].actual_length == 4) { - f = combine_quad((u8*)urb->transfer_buffer) & 0x0fffffff; - f >>= subs->datainterval; - if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) { - spin_lock_irqsave(&subs->lock, flags); - subs->freqm = f; - spin_unlock_irqrestore(&subs->lock, flags); - } + if (likely(f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax)) { + /* + * If the frequency looks valid, set it. + * This value is referred to in prepare_playback_urb(). + */ + spin_lock_irqsave(&subs->lock, flags); + subs->freqm = f; + spin_unlock_irqrestore(&subs->lock, flags); + } else { + /* + * Out of range; maybe the shift value is wrong. + * Reset it so that we autodetect again the next time. + */ + subs->freqshift = INT_MIN; } return 0; @@ -878,21 +853,6 @@ static struct snd_urb_ops audio_urb_ops[2] = { }, }; -static struct snd_urb_ops audio_urb_ops_high_speed[2] = { - { - .prepare = prepare_nodata_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, - }, -}; - /* * initialize the substream instance. */ @@ -909,23 +869,9 @@ void snd_usb_init_substream(struct snd_usb_stream *as, subs->direction = stream; subs->dev = as->chip->dev; subs->txfr_quirk = as->chip->txfr_quirk; - if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL) { - subs->ops = audio_urb_ops[stream]; - } else { - subs->ops = audio_urb_ops_high_speed[stream]; - switch (as->chip->usb_id) { - case USB_ID(0x041e, 0x3f02): /* E-Mu 0202 USB */ - case USB_ID(0x041e, 0x3f04): /* E-Mu 0404 USB */ - case USB_ID(0x041e, 0x3f0a): /* E-Mu Tracker Pre */ - subs->ops.retire_sync = retire_playback_sync_urb_hs_emu; - break; - case USB_ID(0x0763, 0x2080): /* M-Audio Fast Track Ultra 8 */ - case USB_ID(0x0763, 0x2081): /* M-Audio Fast Track Ultra 8R */ - subs->ops.prepare_sync = prepare_playback_sync_urb; - subs->ops.retire_sync = retire_playback_sync_urb; - break; - } - } + subs->ops = audio_urb_ops[stream]; + if (snd_usb_get_speed(subs->dev) >= USB_SPEED_HIGH) + subs->ops.prepare_sync = prepare_capture_sync_urb_hs; snd_usb_set_pcm_ops(as->pcm, stream); |