diff options
-rw-r--r-- | sound/usb/card.h | 9 | ||||
-rw-r--r-- | sound/usb/endpoint.c | 72 | ||||
-rw-r--r-- | sound/usb/pcm.c | 148 |
3 files changed, 175 insertions, 54 deletions
diff --git a/sound/usb/card.h b/sound/usb/card.h index 898a283576df..1f61be98a31d 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -114,6 +114,14 @@ struct snd_usb_endpoint { in a stream */ bool is_implicit_feedback; /* This endpoint is used as implicit feedback */ + /* for hw constraints */ + unsigned int cur_rate; + snd_pcm_format_t cur_format; + unsigned int cur_channels; + unsigned int cur_period_frames; + unsigned int cur_period_bytes; + unsigned int cur_buffer_periods; + spinlock_t lock; struct list_head list; }; @@ -144,6 +152,7 @@ struct snd_usb_substream { unsigned int stream_offset_adj; /* Bytes to drop from beginning of stream (for non-compliant devices) */ unsigned int running: 1; /* running status */ + unsigned int fixed_hw:1; /* fixed hw constraints due to sync EP */ unsigned int hwptr_done; /* processed byte position in the buffer */ unsigned int transfer_done; /* processed frames since last period update */ diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 8205a64a734e..94490d706013 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -459,6 +459,9 @@ snd_usb_get_endpoint(struct snd_usb_audio *chip, return NULL; } +#define ep_type_name(type) \ + (type == SND_USB_ENDPOINT_TYPE_DATA ? "data" : "sync") + /** * snd_usb_add_endpoint: Add an endpoint to an USB audio chip * @@ -500,9 +503,9 @@ struct snd_usb_endpoint *snd_usb_add_endpoint(struct snd_usb_audio *chip, } usb_audio_dbg(chip, "Creating new %s %s endpoint #%x\n", - is_playback ? "playback" : "capture", - type == SND_USB_ENDPOINT_TYPE_DATA ? "data" : "sync", - ep_num); + is_playback ? "playback" : "capture", + ep_type_name(type), + ep_num); ep = kzalloc(sizeof(*ep), GFP_KERNEL); if (!ep) @@ -644,13 +647,14 @@ static void release_urbs(struct snd_usb_endpoint *ep, int force) * Check data endpoint for format differences */ static bool check_ep_params(struct snd_usb_endpoint *ep, - snd_pcm_format_t pcm_format, - unsigned int channels, - unsigned int period_bytes, - unsigned int frames_per_period, - unsigned int periods_per_buffer, - struct audioformat *fmt, - struct snd_usb_endpoint *sync_ep) + snd_pcm_format_t pcm_format, + unsigned int channels, + unsigned int period_bytes, + unsigned int frames_per_period, + unsigned int periods_per_buffer, + unsigned int rate, + struct audioformat *fmt, + struct snd_usb_endpoint *sync_ep) { unsigned int maxsize, minsize, packs_per_ms, max_packs_per_urb; unsigned int max_packs_per_period, urbs_per_period, urb_packs; @@ -660,6 +664,14 @@ static bool check_ep_params(struct snd_usb_endpoint *ep, usb_pipeout(ep->pipe)); bool ret = 1; + /* matching with the saved parameters? */ + if (ep->cur_rate == rate && + ep->cur_format == pcm_format && + ep->cur_channels == channels && + ep->cur_period_frames == frames_per_period && + ep->cur_buffer_periods == periods_per_buffer) + return true; + if (pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE && fmt->dsd_dop) { /* * When operating in DSD DOP mode, the size of a sample frame @@ -917,7 +929,8 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep, * as their corresponding capture endpoint. */ if (usb_pipein(ep->pipe) || - snd_usb_endpoint_implicit_feedback_sink(ep)) { + ep->is_implicit_feedback || + snd_usb_endpoint_implicit_feedback_sink(ep)) { urb_packs = packs_per_ms; /* @@ -1076,12 +1089,17 @@ int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep, { int err; + usb_audio_dbg(ep->chip, + "Setting params for ep %x (type %s, count %d), rate=%d, format=%s, channels=%d, period_bytes=%d, periods=%d\n", + ep->ep_num, ep_type_name(ep->type), ep->use_count, + rate, snd_pcm_format_name(pcm_format), channels, + period_bytes, buffer_periods); + if (ep->use_count != 0) { bool check = ep->is_implicit_feedback && - check_ep_params(ep, pcm_format, - channels, period_bytes, - period_frames, buffer_periods, - fmt, sync_ep); + check_ep_params(ep, pcm_format, channels, period_bytes, + period_frames, buffer_periods, rate, + fmt, sync_ep); if (!check) { usb_audio_warn(ep->chip, @@ -1134,11 +1152,22 @@ int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep, err = -EINVAL; } - usb_audio_dbg(ep->chip, - "Setting params for ep #%x (type %d, %d urbs), ret=%d\n", - ep->ep_num, ep->type, ep->nurbs, err); + usb_audio_dbg(ep->chip, "Set up %d URBS, ret=%d\n", ep->nurbs, err); - return err; + if (err < 0) + return err; + + /* record the current set up in the endpoint (for implicit fb) */ + spin_lock_irq(&ep->lock); + ep->cur_rate = rate; + ep->cur_channels = channels; + ep->cur_format = pcm_format; + ep->cur_period_frames = period_frames; + ep->cur_period_bytes = period_bytes; + ep->cur_buffer_periods = buffer_periods; + spin_unlock_irq(&ep->lock); + + return 0; } /** @@ -1273,6 +1302,11 @@ void snd_usb_endpoint_deactivate(struct snd_usb_endpoint *ep) deactivate_urbs(ep, true); wait_clear_urbs(ep); + + /* clear the saved hw params */ + spin_lock_irq(&ep->lock); + ep->cur_rate = 0; + spin_unlock_irq(&ep->lock); } /** diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 78933b6571d0..6d1f5277cd90 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -81,30 +81,33 @@ static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream /* * find a matching audio format */ -static struct audioformat *find_format(struct snd_usb_substream *subs) +static struct audioformat *find_format(struct list_head *fmt_list_head, + snd_pcm_format_t format, + unsigned int rate, + unsigned int channels, + struct snd_usb_substream *subs) { struct audioformat *fp; struct audioformat *found = NULL; int cur_attr = 0, attr; - list_for_each_entry(fp, &subs->fmt_list, list) { - if (!(fp->formats & pcm_format_to_bits(subs->pcm_format))) + list_for_each_entry(fp, fmt_list_head, list) { + if (!(fp->formats & pcm_format_to_bits(format))) continue; - if (fp->channels != subs->channels) + if (fp->channels != channels) continue; - if (subs->cur_rate < fp->rate_min || - subs->cur_rate > fp->rate_max) + if (rate < fp->rate_min || rate > fp->rate_max) continue; - if (! (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)) { + if (!(fp->rates & SNDRV_PCM_RATE_CONTINUOUS)) { unsigned int i; for (i = 0; i < fp->nr_rates; i++) - if (fp->rate_table[i] == subs->cur_rate) + if (fp->rate_table[i] == rate) break; if (i >= fp->nr_rates) continue; } attr = fp->ep_attr & USB_ENDPOINT_SYNCTYPE; - if (! found) { + if (!found) { found = fp; cur_attr = attr; continue; @@ -114,7 +117,7 @@ static struct audioformat *find_format(struct snd_usb_substream *subs) * this is a workaround for the case like * M-audio audiophile USB. */ - if (attr != cur_attr) { + if (subs && attr != cur_attr) { if ((attr == USB_ENDPOINT_SYNC_ASYNC && subs->direction == SNDRV_PCM_STREAM_PLAYBACK) || (attr == USB_ENDPOINT_SYNC_ADAPTIVE && @@ -138,6 +141,12 @@ static struct audioformat *find_format(struct snd_usb_substream *subs) return found; } +static struct audioformat *find_substream_format(struct snd_usb_substream *subs) +{ + return find_format(&subs->fmt_list, subs->pcm_format, subs->cur_rate, + subs->channels, subs); +} + static int init_pitch_v1(struct snd_usb_audio *chip, int iface, struct usb_host_interface *alts, struct audioformat *fmt) @@ -744,7 +753,6 @@ static int match_endpoint_audioformats(struct snd_usb_substream *subs, */ static int configure_sync_endpoint(struct snd_usb_substream *subs) { - int ret; struct audioformat *fp; struct audioformat *sync_fp = NULL; int cur_score = 0; @@ -752,16 +760,16 @@ static int configure_sync_endpoint(struct snd_usb_substream *subs) struct snd_usb_substream *sync_subs = &subs->stream->substream[subs->direction ^ 1]; - if (subs->sync_endpoint->type != SND_USB_ENDPOINT_TYPE_DATA || - !subs->stream) - return snd_usb_endpoint_set_params(subs->sync_endpoint, - subs->pcm_format, - subs->channels, - subs->period_bytes, - 0, 0, - subs->cur_rate, - subs->cur_audiofmt, - NULL); + if (subs->fixed_hw || + !subs->sync_endpoint->is_implicit_feedback) { + sync_fp = subs->cur_audiofmt; + goto configure; + } + + sync_fp = find_format(&sync_subs->fmt_list, subs->pcm_format, + subs->cur_rate, subs->channels, NULL); + if (sync_fp) + goto configure; /* Try to find the best matching audioformat. */ list_for_each_entry(fp, &sync_subs->fmt_list, list) { @@ -794,16 +802,16 @@ static int configure_sync_endpoint(struct snd_usb_substream *subs) __func__, subs->period_bytes, sync_period_bytes); } - ret = snd_usb_endpoint_set_params(subs->sync_endpoint, - subs->pcm_format, - sync_fp->channels, - sync_period_bytes, - 0, 0, - subs->cur_rate, - sync_fp, - NULL); - - return ret; + configure: + return snd_usb_endpoint_set_params(subs->sync_endpoint, + subs->pcm_format, + sync_fp->channels, + sync_period_bytes, + subs->period_frames, + subs->buffer_periods, + subs->cur_rate, + sync_fp, + NULL); } /* @@ -912,7 +920,7 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream, subs->channels = params_channels(hw_params); subs->cur_rate = params_rate(hw_params); - fmt = find_format(subs); + fmt = find_substream_format(subs); if (!fmt) { dev_dbg(&subs->dev->dev, "cannot set format: format = %#x, rate = %d, channels = %d\n", @@ -956,12 +964,13 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream, static int snd_usb_hw_free(struct snd_pcm_substream *substream) { struct snd_usb_substream *subs = substream->runtime->private_data; + struct snd_usb_audio *chip = subs->stream->chip; snd_media_stop_pipeline(subs); subs->cur_audiofmt = NULL; subs->cur_rate = 0; subs->period_bytes = 0; - if (!snd_usb_lock_shutdown(subs->stream->chip)) { + if (!snd_usb_lock_shutdown(chip)) { stop_endpoints(subs); sync_pending_stops(subs); snd_usb_endpoint_deactivate(subs->sync_endpoint); @@ -971,7 +980,7 @@ static int snd_usb_hw_free(struct snd_pcm_substream *substream) subs->data_endpoint = NULL; } subs->sync_endpoint = NULL; - snd_usb_unlock_shutdown(subs->stream->chip); + snd_usb_unlock_shutdown(chip); } return 0; @@ -1288,6 +1297,64 @@ static int hw_rule_period_time(struct snd_pcm_hw_params *params, return changed; } +/* apply PCM hw constraints from the concurrent sync EP */ +static int apply_hw_constraint_from_sync(struct snd_pcm_runtime *runtime, + struct snd_usb_substream *subs) +{ + struct snd_usb_audio *chip = subs->stream->chip; + struct snd_usb_endpoint *ep; + struct audioformat *fp; + int err; + + subs->fixed_hw = 0; + list_for_each_entry(fp, &subs->fmt_list, list) { + ep = snd_usb_get_endpoint(chip, fp->endpoint, fp->iface, + fp->altsetting); + if (ep && ep->cur_rate) + goto found; + if (!fp->implicit_fb) + continue; + /* for the implicit fb, check the sync ep as well */ + ep = snd_usb_get_endpoint(chip, fp->sync_ep, fp->sync_iface, + fp->sync_altsetting); + if (ep && ep->cur_rate) + goto found; + } + return 0; + + found: + if (!find_format(&subs->fmt_list, ep->cur_format, ep->cur_rate, + ep->cur_channels, NULL)) { + usb_audio_dbg(chip, "EP 0x%x being used, but not applicable\n", + ep->ep_num); + return 0; + } + + usb_audio_dbg(chip, "EP 0x%x being used, using fixed params:\n", + ep->ep_num); + usb_audio_dbg(chip, "rate=%d, format=%s, channels=%d, period_size=%d, periods=%d\n", + ep->cur_rate, snd_pcm_format_name(ep->cur_format), + ep->cur_channels, ep->cur_period_frames, + ep->cur_buffer_periods); + + runtime->hw.formats = pcm_format_to_bits(ep->cur_format); + runtime->hw.rate_min = runtime->hw.rate_max = ep->cur_rate; + runtime->hw.channels_min = runtime->hw.channels_max = + ep->cur_channels; + runtime->hw.rates = SNDRV_PCM_RATE_KNOT; + runtime->hw.periods_min = runtime->hw.periods_max = + ep->cur_buffer_periods; + subs->fixed_hw = 1; + + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + ep->cur_period_frames, + ep->cur_period_frames); + if (err < 0) + return err; + + return 1; /* notify the finding */ +} /* * set up the runtime hardware information. @@ -1295,11 +1362,20 @@ static int hw_rule_period_time(struct snd_pcm_hw_params *params, static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substream *subs) { + struct snd_usb_audio *chip = subs->stream->chip; struct audioformat *fp; unsigned int pt, ptmin; - int param_period_time_if_needed; + int param_period_time_if_needed = -1; int err; + mutex_lock(&chip->mutex); + err = apply_hw_constraint_from_sync(runtime, subs); + mutex_unlock(&chip->mutex); + if (err < 0) + return err; + if (err > 0) /* found the matching? */ + goto add_extra_rules; + runtime->hw.formats = subs->formats; runtime->hw.rate_min = 0x7fffffff; @@ -1350,6 +1426,8 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre -1); if (err < 0) return err; + +add_extra_rules: err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, hw_rule_channels, subs, SNDRV_PCM_HW_PARAM_FORMAT, |