summaryrefslogtreecommitdiffstats
path: root/sound/usb
diff options
context:
space:
mode:
Diffstat (limited to 'sound/usb')
-rw-r--r--sound/usb/pcm.c89
1 files changed, 87 insertions, 2 deletions
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c
index cec6e91afea2..6a460225f2e3 100644
--- a/sound/usb/pcm.c
+++ b/sound/usb/pcm.c
@@ -659,6 +659,9 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
#define hwc_debug(fmt, args...) do { } while(0)
#endif
+#define MAX_BUFFER_BYTES (1024 * 1024)
+#define MAX_PERIOD_BYTES (512 * 1024)
+
static const struct snd_pcm_hardware snd_usb_hardware =
{
.info = SNDRV_PCM_INFO_MMAP |
@@ -669,9 +672,9 @@ static const struct snd_pcm_hardware snd_usb_hardware =
SNDRV_PCM_INFO_PAUSE,
.channels_min = 1,
.channels_max = 256,
- .buffer_bytes_max = 1024 * 1024,
+ .buffer_bytes_max = MAX_BUFFER_BYTES,
.period_bytes_min = 64,
- .period_bytes_max = 512 * 1024,
+ .period_bytes_max = MAX_PERIOD_BYTES,
.periods_min = 2,
.periods_max = 1024,
};
@@ -971,6 +974,78 @@ static int hw_rule_periods_implicit_fb(struct snd_pcm_hw_params *params,
ep->cur_buffer_periods);
}
+/* get the adjusted max buffer (or period) bytes that can fit with the
+ * paired format for implicit fb
+ */
+static unsigned int
+get_adjusted_max_bytes(struct snd_usb_substream *subs,
+ struct snd_usb_substream *pair,
+ struct snd_pcm_hw_params *params,
+ unsigned int max_bytes,
+ bool reverse_map)
+{
+ const struct audioformat *fp, *pp;
+ unsigned int rmax = 0, r;
+
+ list_for_each_entry(fp, &subs->fmt_list, list) {
+ if (!fp->implicit_fb)
+ continue;
+ if (!reverse_map &&
+ !hw_check_valid_format(subs, params, fp))
+ continue;
+ list_for_each_entry(pp, &pair->fmt_list, list) {
+ if (pp->iface != fp->sync_iface ||
+ pp->altsetting != fp->sync_altsetting ||
+ pp->ep_idx != fp->sync_ep_idx)
+ continue;
+ if (reverse_map &&
+ !hw_check_valid_format(pair, params, pp))
+ break;
+ if (!reverse_map && pp->channels > fp->channels)
+ r = max_bytes * fp->channels / pp->channels;
+ else if (reverse_map && pp->channels < fp->channels)
+ r = max_bytes * pp->channels / fp->channels;
+ else
+ r = max_bytes;
+ rmax = max(rmax, r);
+ break;
+ }
+ }
+ return rmax;
+}
+
+/* Reduce the period or buffer bytes depending on the paired substream;
+ * when a paired configuration for implicit fb has a higher number of channels,
+ * we need to reduce the max size accordingly, otherwise it may become unusable
+ */
+static int hw_rule_bytes_implicit_fb(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct snd_usb_substream *subs = rule->private;
+ struct snd_usb_substream *pair;
+ struct snd_interval *it;
+ unsigned int max_bytes;
+ unsigned int rmax;
+
+ pair = &subs->stream->substream[!subs->direction];
+ if (!pair->ep_num)
+ return 0;
+
+ if (rule->var == SNDRV_PCM_HW_PARAM_PERIOD_BYTES)
+ max_bytes = MAX_PERIOD_BYTES;
+ else
+ max_bytes = MAX_BUFFER_BYTES;
+
+ rmax = get_adjusted_max_bytes(subs, pair, params, max_bytes, false);
+ if (!rmax)
+ rmax = get_adjusted_max_bytes(pair, subs, params, max_bytes, true);
+ if (!rmax)
+ return 0;
+
+ it = hw_param_interval(params, rule->var);
+ return apply_hw_params_minmax(it, 0, rmax);
+}
+
/*
* set up the runtime hardware information.
*/
@@ -1085,6 +1160,16 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre
SNDRV_PCM_HW_PARAM_PERIODS, -1);
if (err < 0)
return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ hw_rule_bytes_implicit_fb, subs,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ hw_rule_bytes_implicit_fb, subs,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, -1);
+ if (err < 0)
+ return err;
list_for_each_entry(fp, &subs->fmt_list, list) {
if (fp->implicit_fb) {