diff options
| author | Takashi Iwai <tiwai@suse.de> | 2026-04-13 07:18:36 +0200 |
|---|---|---|
| committer | Takashi Iwai <tiwai@suse.de> | 2026-04-13 07:18:36 +0200 |
| commit | f365e47bfbe388b2dde411f8a016065274eee02f (patch) | |
| tree | 9b41ea2f46dc9d5646d807e02297518826b6fd77 /sound/usb | |
| parent | 52521e8398839105ef8eb22b3f0993f9b0d11a57 (diff) | |
| parent | 713e0f011178a2896e46db3244093454708066e2 (diff) | |
Merge branch 'for-next' into for-linus
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/usb')
| -rw-r--r-- | sound/usb/6fire/chip.c | 17 | ||||
| -rw-r--r-- | sound/usb/card.c | 44 | ||||
| -rw-r--r-- | sound/usb/format.c | 4 | ||||
| -rw-r--r-- | sound/usb/midi.c | 12 | ||||
| -rw-r--r-- | sound/usb/midi2.c | 6 | ||||
| -rw-r--r-- | sound/usb/mixer.c | 245 | ||||
| -rw-r--r-- | sound/usb/mixer.h | 4 | ||||
| -rw-r--r-- | sound/usb/mixer_quirks.c | 59 | ||||
| -rw-r--r-- | sound/usb/mixer_s1810c.c | 2 | ||||
| -rw-r--r-- | sound/usb/mixer_scarlett2.c | 2 | ||||
| -rw-r--r-- | sound/usb/qcom/qc_audio_offload.c | 2 | ||||
| -rw-r--r-- | sound/usb/quirks-table.h | 113 | ||||
| -rw-r--r-- | sound/usb/quirks.c | 82 | ||||
| -rw-r--r-- | sound/usb/stream.c | 8 | ||||
| -rw-r--r-- | sound/usb/usbaudio.h | 12 | ||||
| -rw-r--r-- | sound/usb/usx2y/us144mkii.c | 6 |
16 files changed, 519 insertions, 99 deletions
diff --git a/sound/usb/6fire/chip.c b/sound/usb/6fire/chip.c index 5ff78814e687..874f6cd503ca 100644 --- a/sound/usb/6fire/chip.c +++ b/sound/usb/6fire/chip.c @@ -53,11 +53,6 @@ static void usb6fire_chip_abort(struct sfire_chip *chip) usb6fire_comm_abort(chip); if (chip->control) usb6fire_control_abort(chip); - if (chip->card) { - snd_card_disconnect(chip->card); - snd_card_free_when_closed(chip->card); - chip->card = NULL; - } } } @@ -168,6 +163,7 @@ destroy_chip: static void usb6fire_chip_disconnect(struct usb_interface *intf) { struct sfire_chip *chip; + struct snd_card *card; chip = usb_get_intfdata(intf); if (chip) { /* if !chip, fw upload has been performed */ @@ -178,8 +174,19 @@ static void usb6fire_chip_disconnect(struct usb_interface *intf) chips[chip->regidx] = NULL; } + /* + * Save card pointer before teardown. + * snd_card_free_when_closed() may free card (and + * the embedded chip) immediately, so it must be + * called last and chip must not be accessed after. + */ + card = chip->card; chip->shutdown = true; + if (card) + snd_card_disconnect(card); usb6fire_chip_abort(chip); + if (card) + snd_card_free_when_closed(card); } } } diff --git a/sound/usb/card.c b/sound/usb/card.c index 270dad84d825..f42d72cd0378 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -631,9 +631,9 @@ static void usb_audio_make_shortname(struct usb_device *dev, } /* retrieve the device string as shortname */ - if (!dev->descriptor.iProduct || - usb_string(dev, dev->descriptor.iProduct, - card->shortname, sizeof(card->shortname)) <= 0) { + if (dev->product && *dev->product) { + strscpy(card->shortname, dev->product); + } else { /* no name available from anywhere, so use ID */ scnprintf(card->shortname, sizeof(card->shortname), "USB Device %#04x:%#04x", @@ -668,15 +668,11 @@ static void usb_audio_make_longname(struct usb_device *dev, else if (quirk && quirk->vendor_name) s = quirk->vendor_name; *card->longname = 0; - if (s && *s) { - strscpy(card->longname, s, sizeof(card->longname)); - } else { - /* retrieve the vendor and device strings as longname */ - if (dev->descriptor.iManufacturer) - usb_string(dev, dev->descriptor.iManufacturer, - card->longname, sizeof(card->longname)); - /* we don't really care if there isn't any vendor string */ - } + if (s && *s) + strscpy(card->longname, s); + else if (dev->manufacturer && *dev->manufacturer) + strscpy(card->longname, dev->manufacturer); + if (*card->longname) { strim(card->longname); if (*card->longname) @@ -870,19 +866,25 @@ static void find_last_interface(struct snd_usb_audio *chip) /* look for the corresponding quirk */ static const struct snd_usb_audio_quirk * -get_alias_quirk(struct usb_device *dev, unsigned int id) +get_alias_quirk(struct usb_interface *intf, unsigned int id) { const struct usb_device_id *p; + struct usb_device_id match_id; for (p = usb_audio_ids; p->match_flags; p++) { - /* FIXME: this checks only vendor:product pair in the list */ - if ((p->match_flags & USB_DEVICE_ID_MATCH_DEVICE) == - USB_DEVICE_ID_MATCH_DEVICE && - p->idVendor == USB_ID_VENDOR(id) && - p->idProduct == USB_ID_PRODUCT(id)) - return (const struct snd_usb_audio_quirk *)p->driver_info; - } + if ((p->match_flags & USB_DEVICE_ID_MATCH_DEVICE) != + USB_DEVICE_ID_MATCH_DEVICE) + continue; + if (p->idVendor != USB_ID_VENDOR(id) || + p->idProduct != USB_ID_PRODUCT(id)) + continue; + match_id = *p; + match_id.match_flags &= ~USB_DEVICE_ID_MATCH_DEVICE; + if (!match_id.match_flags || usb_match_one_id(intf, &match_id)) + return (const struct snd_usb_audio_quirk *) + p->driver_info; + } return NULL; } @@ -931,7 +933,7 @@ static int usb_audio_probe(struct usb_interface *intf, id = USB_ID(le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct)); if (get_alias_id(dev, &id)) - quirk = get_alias_quirk(dev, id); + quirk = get_alias_quirk(intf, id); if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum) return -ENXIO; if (quirk && quirk->ifnum == QUIRK_NODEV_INTERFACE) diff --git a/sound/usb/format.c b/sound/usb/format.c index 1207c507882a..030b4307927a 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -455,6 +455,10 @@ static int parse_uac2_sample_rate_range(struct snd_usb_audio *chip, if (chip->usb_id == USB_ID(0x194f, 0x010d) && !s1810c_valid_sample_rate(fp, rate)) goto skip_rate; + /* Filter out invalid rates on Presonus Studio 1824 */ + if (chip->usb_id == USB_ID(0x194f, 0x0107) && + !s1810c_valid_sample_rate(fp, rate)) + goto skip_rate; /* Filter out invalid rates on Focusrite devices */ if (USB_ID_VENDOR(chip->usb_id) == 0x1235 && diff --git a/sound/usb/midi.c b/sound/usb/midi.c index a8bddc90c0ed..0a5b8941ebda 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -699,15 +699,18 @@ static void snd_usbmidi_transmit_byte(struct usbmidi_out_port *port, static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint *ep, struct urb *urb) { - int p; + int port0 = ep->current_port; + int i; + + for (i = 0; i < 0x10; ++i) { + int portnum = (port0 + i) & 15; + struct usbmidi_out_port *port = &ep->ports[portnum]; - /* FIXME: lower-numbered ports can starve higher-numbered ports */ - for (p = 0; p < 0x10; ++p) { - struct usbmidi_out_port *port = &ep->ports[p]; if (!port->active) continue; while (urb->transfer_buffer_length + 3 < ep->max_transfer) { uint8_t b; + if (snd_rawmidi_transmit(port->substream, &b, 1) != 1) { port->active = 0; break; @@ -715,6 +718,7 @@ static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint *ep, snd_usbmidi_transmit_byte(port, b, urb); } } + ep->current_port = (port0 + 1) & 15; } static const struct usb_protocol_ops snd_usbmidi_standard_ops = { diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index ef602e81576d..3546ba926cb3 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -1057,10 +1057,8 @@ static void set_fallback_rawmidi_names(struct snd_usb_midi2_interface *umidi) strscpy(ump->core.name, ump->info.name, sizeof(ump->core.name)); /* use serial number string as unique UMP product id */ - if (!*ump->info.product_id && dev->descriptor.iSerialNumber) - usb_string(dev, dev->descriptor.iSerialNumber, - ump->info.product_id, - sizeof(ump->info.product_id)); + if (!*ump->info.product_id && dev->serial && *dev->serial) + strscpy(ump->info.product_id, dev->serial); } } diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index ac8c71ba9483..d4ef45bf53d7 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1204,6 +1204,13 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval, cval->min = -11264; /* Mute under it */ } break; + case USB_ID(0x31b2, 0x0111): /* MOONDROP JU Jiu */ + if (!strcmp(kctl->id.name, "PCM Playback Volume")) { + usb_audio_info(chip, + "set volume quirk for MOONDROP JU Jiu\n"); + cval->min = -10880; /* Mute under it */ + } + break; } } @@ -1226,12 +1233,79 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) } /* + * Additional checks for sticky mixers + * + * Some devices' volume control mixers are sticky, which accept SET_CUR but + * do absolutely nothing. + * + * Prevent sticky mixers from being registered, otherwise they confuses + * userspace and results in ineffective volume control. + */ +static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + int channel, int saved) +{ + int sticky_test_values[] = { cval->min, cval->max }; + int test, check, i; + + for (i = 0; i < ARRAY_SIZE(sticky_test_values); i++) { + test = sticky_test_values[i]; + if (test == saved) + continue; + + /* Assume non-sticky on failure. */ + if (snd_usb_set_cur_mix_value(cval, channel, 0, test) || + get_cur_mix_raw(cval, channel, &check) || + check != saved) /* SET_CUR effective, non-sticky. */ + return 0; + } + + usb_audio_err(cval->head.mixer->chip, + "%d:%d: sticky mixer values (%d/%d/%d => %d), disabling\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->min, cval->max, cval->res, saved); + + return -ENODEV; +} + +/* + * Additional checks for the proper resolution + * + * Some devices report smaller resolutions than actually reacting. + * They don't return errors but simply clip to the lower aligned value. + */ +static void check_volume_control_res(struct usb_mixer_elem_info *cval, + int channel, int saved) +{ + int last_valid_res = cval->res; + int test, check; + + for (;;) { + test = saved; + if (test < cval->max) + test += cval->res; + else + test -= cval->res; + + if (test < cval->min || test > cval->max || + snd_usb_set_cur_mix_value(cval, channel, 0, test) || + get_cur_mix_raw(cval, channel, &check)) { + cval->res = last_valid_res; + break; + } + if (test == check) + break; + + cval->res *= 2; + } +} + +/* * retrieve the minimum and maximum values for the specified control */ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, int default_min, struct snd_kcontrol *kctl) { - int i, idx; + int i, idx, ret; /* for failsafe */ cval->min = default_min; @@ -1257,7 +1331,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, "%d:%d: cannot get min/max values for control %d (id %d)\n", cval->head.id, mixer_ctrl_intf(cval->head.mixer), cval->control, cval->head.id); - return -EINVAL; + return -EAGAIN; } if (get_ctl_value(cval, UAC_GET_RES, (cval->control << 8) | minchn, @@ -1280,37 +1354,25 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, if (cval->res == 0) cval->res = 1; - /* Additional checks for the proper resolution - * - * Some devices report smaller resolutions than actually - * reacting. They don't return errors but simply clip - * to the lower aligned value. - */ - if (cval->min + cval->res < cval->max) { - int last_valid_res = cval->res; - int saved, test, check; + if (cval->min < cval->max) { + int saved; + if (get_cur_mix_raw(cval, minchn, &saved) < 0) - goto no_res_check; - for (;;) { - test = saved; - if (test < cval->max) - test += cval->res; - else - test -= cval->res; - if (test < cval->min || test > cval->max || - snd_usb_set_cur_mix_value(cval, minchn, 0, test) || - get_cur_mix_raw(cval, minchn, &check)) { - cval->res = last_valid_res; - break; - } - if (test == check) - break; - cval->res *= 2; + goto no_checks; + + ret = check_sticky_volume_control(cval, minchn, saved); + if (ret < 0) { + snd_usb_set_cur_mix_value(cval, minchn, 0, saved); + return ret; } + + if (cval->min + cval->res < cval->max) + check_volume_control_res(cval, minchn, saved); + snd_usb_set_cur_mix_value(cval, minchn, 0, saved); } -no_res_check: +no_checks: cval->initialized = 1; } @@ -1381,6 +1443,7 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); + int ret; if (cval->val_type == USB_MIXER_BOOLEAN || cval->val_type == USB_MIXER_INV_BOOLEAN) @@ -1391,8 +1454,9 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, if (cval->val_type != USB_MIXER_BOOLEAN && cval->val_type != USB_MIXER_INV_BOOLEAN) { if (!cval->initialized) { - get_min_max_with_quirks(cval, 0, kcontrol); - if (cval->initialized && cval->dBmin >= cval->dBmax) { + ret = get_min_max_with_quirks(cval, 0, kcontrol); + if ((ret >= 0 || ret == -EAGAIN) && + cval->initialized && cval->dBmin >= cval->dBmax) { kcontrol->vd[0].access &= ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); @@ -1660,9 +1724,72 @@ static const struct usb_feature_control_info *get_feature_control_info(int contr return NULL; } +static bool check_insane_volume_range(struct usb_mixer_interface *mixer, + struct snd_kcontrol *kctl, + struct usb_mixer_elem_info *cval) +{ + int range, steps, threshold; + + /* + * If a device quirk has overrode our TLV callback, no warning should + * be generated since our checks are only meaningful for dB volume. + */ + if (!(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) || + kctl->tlv.c != snd_usb_mixer_vol_tlv) + return false; + + /* + * Meaningless volume control capability (<1dB). This should cover + * devices mapping their volume to val = 0/100/1, which are very likely + * to be quirky. + */ + range = cval->max - cval->min; + if (range < 256) { + usb_audio_warn(mixer->chip, + "Warning! Unlikely small volume range (=%u), linear volume or custom curve?", + range); + return true; + } + + steps = range / cval->res; + + /* + * There are definitely devices with ~20,000 ranges (e.g., HyperX Cloud + * III with val = -18944/0/1), so we use some heuristics here: + * + * min < 0 < max: Attenuator + amplifier? Likely to be sane + * + * min < 0 = max: DSP? Voltage attenuator with FW conversion to dB? + * Likely to be sane + * + * min < max < 0: Measured values? Neutral + * + * min = 0 < max: Oversimplified FW conversion? Linear volume? Likely to + * be quirky (e.g., MV-SILICON) + * + * 0 < min < max: Amplifier with fixed gains? Likely to be quirky + * (e.g., Logitech webcam) + */ + if (cval->min < 0 && 0 <= cval->max) + threshold = 24576; /* 65535 * (3 / 8) */ + else if (cval->min < cval->max && cval->max < 0) + threshold = 1024; + else + threshold = 384; + + if (steps > threshold) { + usb_audio_warn(mixer->chip, + "Warning! Unlikely big volume step count (=%u), linear volume or wrong cval->res?", + steps); + return true; + } + + return false; +} + static void __build_feature_ctl(struct usb_mixer_interface *mixer, const struct usbmix_name_map *imap, - unsigned int ctl_mask, int control, + u64 ctl_mask, int control, struct usb_audio_term *iterm, struct usb_audio_term *oterm, int unitid, int nameid, int readonly_mask) @@ -1673,7 +1800,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, struct snd_kcontrol *kctl; struct usb_mixer_elem_info *cval; const struct usbmix_name_map *map; - unsigned int range; + int ret; if (control == UAC_FU_GRAPHIC_EQUALIZER) { /* FIXME: not supported yet */ @@ -1707,7 +1834,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, cval->master_readonly = readonly_mask; } else { int i, c = 0; - for (i = 0; i < 16; i++) + for (i = 0; i < MAX_CHANNELS; i++) if (ctl_mask & BIT(i)) c++; cval->channels = c; @@ -1787,10 +1914,10 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, } /* get min/max values */ - get_min_max_with_quirks(cval, 0, kctl); + ret = get_min_max_with_quirks(cval, 0, kctl); /* skip a bogus volume range */ - if (cval->max <= cval->min) { + if ((ret < 0 && ret != -EAGAIN) || cval->max <= cval->min) { usb_audio_dbg(mixer->chip, "[%d] FU [%s] skipped due to invalid volume\n", cval->head.id, kctl->id.name); @@ -1811,29 +1938,21 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, snd_usb_mixer_fu_apply_quirk(mixer, cval, unitid, kctl); - range = (cval->max - cval->min) / cval->res; - /* - * There are definitely devices with a range of ~20,000, so let's be - * conservative and allow for a bit more. - */ - if (range > 65535) { - usb_audio_warn(mixer->chip, - "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", - range); - usb_audio_warn(mixer->chip, - "[%d] FU [%s] ch = %d, val = %d/%d/%d", + if (check_insane_volume_range(mixer, kctl, cval)) { + usb_audio_warn(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", cval->head.id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res); + } else { + usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", + cval->head.id, kctl->id.name, cval->channels, + cval->min, cval->max, cval->res); } - usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", - cval->head.id, kctl->id.name, cval->channels, - cval->min, cval->max, cval->res); snd_usb_mixer_add_control(&cval->head, kctl); } static void build_feature_ctl(struct mixer_build *state, void *raw_desc, - unsigned int ctl_mask, int control, + u64 ctl_mask, int control, struct usb_audio_term *iterm, int unitid, int readonly_mask) { @@ -1845,7 +1964,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, } static void build_feature_ctl_badd(struct usb_mixer_interface *mixer, - unsigned int ctl_mask, int control, int unitid, + u64 ctl_mask, int control, int unitid, const struct usbmix_name_map *badd_map) { __build_feature_ctl(mixer, badd_map, ctl_mask, control, @@ -2021,7 +2140,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, bmaControls = ftr->bmaControls; } - if (channels > 32) { + if (channels > MAX_CHANNELS) { usb_audio_info(state->chip, "usbmixer: too many channels (%d) in unit %d\n", channels, unitid); @@ -2059,7 +2178,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, if (state->mixer->protocol == UAC_VERSION_1) { /* check all control types */ for (i = 0; i < 10; i++) { - unsigned int ch_bits = 0; + u64 ch_bits = 0; int control = audio_feature_info[i].control; for (j = 0; j < channels; j++) { @@ -2085,7 +2204,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, } } else { /* UAC_VERSION_2/3 */ for (i = 0; i < ARRAY_SIZE(audio_feature_info); i++) { - unsigned int ch_bits = 0; + u64 ch_bits = 0; unsigned int ch_read_only = 0; int control = audio_feature_info[i].control; @@ -2172,6 +2291,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, unsigned int i, len; struct snd_kcontrol *kctl; const struct usbmix_name_map *map; + int ret; map = find_map(state->map, unitid, 0); if (check_ignored_ctl(map)) @@ -2194,7 +2314,11 @@ static void build_mixer_unit_ctl(struct mixer_build *state, } /* get min/max values */ - get_min_max(cval, 0); + ret = get_min_max(cval, 0); + if (ret < 0 && ret != -EAGAIN) { + usb_mixer_elem_info_free(cval); + return; + } kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); if (!kctl) { @@ -2566,7 +2690,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, break; } - get_min_max(cval, valinfo->min_value); + err = get_min_max(cval, valinfo->min_value); break; } case USB_XU_CLOCK_RATE: @@ -2578,11 +2702,16 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, cval->max = 5; cval->res = 1; cval->initialized = 1; + err = 0; break; default: - get_min_max(cval, valinfo->min_value); + err = get_min_max(cval, valinfo->min_value); break; } + if (err < 0 && err != -EAGAIN) { + usb_mixer_elem_info_free(cval); + return err; + } err = get_cur_ctl_value(cval, cval->control << 8, &val); if (err < 0) { @@ -3398,7 +3527,7 @@ static void snd_usb_mixer_dump_cval(struct snd_info_buffer *buffer, [USB_MIXER_U32] = "U32", [USB_MIXER_BESPOKEN] = "BESPOKEN", }; - snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%x, " + snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%llx, " "channels=%i, type=\"%s\"\n", cval->head.id, cval->control, cval->cmask, cval->channels, val_types[cval->val_type]); diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 167fbfcf01ac..afbb3dd9f177 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -44,7 +44,7 @@ struct usb_mixer_interface { void (*private_suspend)(struct usb_mixer_interface *mixer); }; -#define MAX_CHANNELS 16 /* max logical channels */ +#define MAX_CHANNELS 64 /* max logical channels */ enum { USB_MIXER_BOOLEAN, @@ -81,7 +81,7 @@ struct usb_mixer_elem_list { struct usb_mixer_elem_info { struct usb_mixer_elem_list head; unsigned int control; /* CS or ICN (high byte) */ - unsigned int cmask; /* channel mask bitmap: 0 = master */ + u64 cmask; /* channel mask bitmap: 0 = master */ unsigned int idx_off; /* Control index offset */ unsigned int ch_readonly; unsigned int master_readonly; diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 11e205da7964..a01510a855c2 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -4477,6 +4477,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x194f, 0x010d): /* Presonus Studio 1824c */ err = snd_sc1810_init_mixer(mixer); break; + case USB_ID(0x194f, 0x0107): /* Presonus Studio 1824 */ + err = snd_sc1810_init_mixer(mixer); + break; case USB_ID(0x2a39, 0x3fb0): /* RME Babyface Pro FS */ err = snd_bbfpro_controls_create(mixer); break; @@ -4588,6 +4591,24 @@ static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, } } +static void snd_usb_mv_silicon_quirks(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, + struct snd_kcontrol *kctl) +{ + if (cval->min == 0 && cval->max == 4096 && cval->res == 1) { + /* The final effects will be printed later. */ + usb_audio_info(mixer->chip, "applying MV-SILICON quirks (0/4096/1 variant)\n"); + + /* Respect MIN_MUTE set by module parameters. */ + if (!(mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE)) + mixer->chip->quirk_flags |= QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL; + if (!(mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE)) + mixer->chip->quirk_flags |= QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL; + } else { + usb_audio_dbg(mixer->chip, "not applying MV-SILICON quirks on unknown variant"); + } +} + /* * Some Plantronics headsets have control names that don't meet ALSA naming * standards. This function fixes nonstandard source names. By the time @@ -4634,6 +4655,25 @@ triggered: usb_audio_dbg(chip, "something wrong in kctl name %s\n", id->name); } +static void snd_usb_mixer_fu_quirk_linear_scale(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, + struct snd_kcontrol *kctl) +{ + static const DECLARE_TLV_DB_LINEAR(scale, TLV_DB_GAIN_MUTE, 0); + + if (cval->min_mute) { + /* + * We are clearing SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, + * resulting in min_mute being a no-op. + */ + usb_audio_warn(mixer->chip, "LINEAR_VOL overrides MIN_MUTE\n"); + } + + kctl->tlv.p = scale; + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; +} + void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, int unitid, struct snd_kcontrol *kctl) @@ -4645,6 +4685,10 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, break; } + if (cval->control == UAC_FU_VOLUME && + !strncmp(mixer->chip->card->longname, "MV-SILICON", 10)) + snd_usb_mv_silicon_quirks(mixer, cval, kctl); + /* lowest playback value is muted on some devices */ if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE) if (strstr(kctl->id.name, "Playback")) { @@ -4660,6 +4704,21 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, "applying capture min mute quirk\n"); cval->min_mute = 1; } + + if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL) + if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Playback")) { + usb_audio_info(mixer->chip, + "applying playback linear volume quirk\n"); + snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl); + } + + if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL) + if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Capture")) { + usb_audio_info(mixer->chip, + "applying capture linear volume quirk\n"); + snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl); + } + /* ALSA-ify some Plantronics headset control names */ if (USB_ID_VENDOR(mixer->chip->usb_id) == 0x047f && (cval->control == UAC_FU_MUTE || cval->control == UAC_FU_VOLUME)) diff --git a/sound/usb/mixer_s1810c.c b/sound/usb/mixer_s1810c.c index 7eac7d1bce64..2e5a8d37ec57 100644 --- a/sound/usb/mixer_s1810c.c +++ b/sound/usb/mixer_s1810c.c @@ -362,6 +362,7 @@ static int snd_s1810c_init_mixer_maps(struct snd_usb_audio *chip) snd_s1810c_send_ctl_packet(dev, a, 3, 0, 1, MIXER_LEVEL_0DB); break; + case USB_ID(0x194f, 0x0107): /* 1824 */ case USB_ID(0x194f, 0x010d): /* 1824c */ /* Set all output faders to unity gain */ a = SC1810C_SEL_OUTPUT; @@ -685,6 +686,7 @@ int snd_sc1810_init_mixer(struct usb_mixer_interface *mixer) return ret; break; + case USB_ID(0x194f, 0x0107): /* Presonus Studio 1824 */ case USB_ID(0x194f, 0x010d): /* Presonus Studio 1824c */ ret = snd_s1810c_switch_init(mixer, &snd_s1824c_mono_sw); if (ret < 0) diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index fd1fb668929a..8eaa96222759 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -2262,7 +2262,7 @@ static const struct scarlett2_device_entry scarlett2_devices[] = { { USB_ID(0x1235, 0x820c), &clarett_8pre_info, "Clarett+" }, /* End of list */ - { 0, NULL }, + { 0, NULL, NULL }, }; /* get the starting port index number for a given port type/direction */ diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index 2ac813d57f4f..5f993b88448c 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -948,7 +948,7 @@ static int enable_audio_stream(struct snd_usb_substream *subs, _snd_pcm_hw_params_any(¶ms); m = hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT); - snd_mask_leave(m, pcm_format); + snd_mask_leave(m, (__force unsigned int)pcm_format); i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS); snd_interval_setinteger(i); diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index eafc0d73cca1..803e03d4d77b 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2652,6 +2652,54 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, +{ + /* + * The AudioBox USB advertises S24_3LE as the only supported format + * for both playback and capture. It does not support S16_LE despite + * being a USB full-speed device. + */ + USB_DEVICE(0x194f, 0x0301), + QUIRK_DRIVER_INFO { + .vendor_name = "PreSonus", + .product_name = "AudioBox USB", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { + QUIRK_DATA_AUDIOFORMAT(2) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 2, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + } + }, + { + QUIRK_DATA_AUDIOFORMAT(3) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 2, + .iface = 3, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x82, + .ep_attr = USB_ENDPOINT_XFER_ISOC, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + } + }, + QUIRK_COMPOSITE_END + } + } +}, #endif /* disabled */ { @@ -3900,5 +3948,70 @@ YAMAHA_DEVICE(0x7010, "UB99"), QUIRK_RME_DIGIFACE(0x3f8c), QUIRK_RME_DIGIFACE(0x3fa0), +#define QUIRK_AF16RIG(channel_count_, alt_setting_, \ + low_rate_, high_rate_, pack_size_, \ + clock_, interface_, endpoint_) \ + { \ + QUIRK_DATA_AUDIOFORMAT(interface_) { \ + .formats = SNDRV_PCM_FMTBIT_S32_LE, \ + .channels = channel_count_, \ + .fmt_type = UAC_FORMAT_TYPE_I_PCM, \ + .fmt_bits = 24, \ + .fmt_sz = 4, \ + .iface = interface_, \ + .altsetting = alt_setting_, \ + .altset_idx = alt_setting_, \ + .endpoint = endpoint_, \ + .ep_attr = USB_ENDPOINT_XFER_ISOC | \ + USB_ENDPOINT_SYNC_ASYNC, \ + .datainterval = 1, \ + .protocol = UAC_VERSION_2, \ + .maxpacksize = pack_size_, \ + .rates = SNDRV_PCM_RATE_##low_rate_ | \ + SNDRV_PCM_RATE_##high_rate_, \ + .rate_min = low_rate_, \ + .rate_max = high_rate_, \ + .nr_rates = 2, \ + .rate_table = (unsigned int[]) { \ + low_rate_, high_rate_ }, \ + .clock = clock_, \ + } \ + } + +#define QUIRK_AF16RIG_CLOCK(clock) \ + QUIRK_AF16RIG(34, 1, 44100, 48000, 0x3b8, clock, 1, 0x01), \ + QUIRK_AF16RIG(34, 1, 44100, 48000, 0x3b8, clock, 2, 0x81), \ + QUIRK_AF16RIG(18, 2, 88200, 96000, 0x3a8, clock, 1, 0x01), \ + QUIRK_AF16RIG(18, 2, 88200, 96000, 0x3a8, clock, 2, 0x81), \ + QUIRK_AF16RIG(10, 3, 176400, 192000, 0x3e8, clock, 1, 0x01), \ + QUIRK_AF16RIG(10, 3, 176400, 192000, 0x3e8, clock, 2, 0x81) + +/* Arturia AudioFuse 16Rig Audio */ +/* AF16Rig MIDI has USB PID 0xaf21 and appears to work OK without quirks */ +{ + USB_DEVICE(0x1c75, 0xaf20), + QUIRK_DRIVER_INFO { + .vendor_name = "Arturia", + .product_name = "AF16Rig", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(0) }, + QUIRK_AF16RIG_CLOCK(41), /* Internal clock */ +#if 0 +/* These are disabled because I don't have the required hardware to test + * them. I suspect that the ADAT clock might not follow 176400 or 192000 + * because the AF16Rig won't accept ADAT audio data at those rates. + */ + QUIRK_AF16RIG_CLOCK(43), /* ADAT clock */ + QUIRK_AF16RIG_CLOCK(44), /* BNC word clock */ +#endif + { QUIRK_DATA_IGNORE(3) }, /* Firmware update */ + QUIRK_COMPOSITE_END + } + } +}, + +#undef QUIRK_AF16RIG_CLOCK +#undef QUIRK_AF16RIG + #undef USB_DEVICE_VENDOR_SPEC #undef USB_AUDIO_DEVICE diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 4cfa24c06fcd..519d9d1a2a41 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2,8 +2,11 @@ /* */ +#include <linux/cleanup.h> +#include <linux/err.h> #include <linux/init.h> #include <linux/slab.h> +#include <linux/string.h> #include <linux/usb.h> #include <linux/usb/audio.h> #include <linux/usb/midi.h> @@ -2135,16 +2138,69 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, /* * driver behavior quirk flags */ +struct usb_string_match { + const char *manufacturer; + const char *product; +}; + struct usb_audio_quirk_flags_table { u32 id; u32 flags; + const struct usb_string_match *usb_string_match; }; #define DEVICE_FLG(vid, pid, _flags) \ { .id = USB_ID(vid, pid), .flags = (_flags) } #define VENDOR_FLG(vid, _flags) DEVICE_FLG(vid, 0, _flags) +/* + * Use as a last resort if using DEVICE_FLG() is prone to VID/PID conflicts. + * + * Usage: + * // match vid, pid, "manufacturer", and "product" + * DEVICE_STRING_FLG(vid, pid, "manufacturer", "product", flags) + * + * // match vid, pid, "manufacturer", and any product string + * DEVICE_STRING_FLG(vid, pid, "manufacturer", NULL, flags) + * + * // match vid, pid, "manufacturer", and device must have no product string + * DEVICE_STRING_FLG(vid, pid, "manufacturer", "", flags) + * + * // match vid, pid, any manufacturer string, and "product" + * DEVICE_STRING_FLG(vid, pid, NULL, "product", flags) + * + * // match vid, pid, no manufacturer string, and "product" + * DEVICE_STRING_FLG(vid, pid, "", "product", flags) + * + * // match vid, pid, no manufacturer string, and no product string + * DEVICE_STRING_FLG(vid, pid, "", "", flags) + */ +#define DEVICE_STRING_FLG(vid, pid, _manufacturer, _product, _flags) \ +{ \ + .id = USB_ID(vid, pid), \ + .usb_string_match = &(const struct usb_string_match) { \ + .manufacturer = _manufacturer, \ + .product = _product, \ + }, \ + .flags = (_flags), \ +} + +/* + * Use as a last resort if using VENDOR_FLG() is prone to VID conflicts. + * + * Usage: + * // match vid, and "manufacturer" + * VENDOR_STRING_FLG(vid, "manufacturer", flags) + * + * // match vid, and device must have no manufacturer string + * VENDOR_STRING_FLG(vid, "", flags) + */ +#define VENDOR_STRING_FLG(vid, _manufacturer, _flags) \ + DEVICE_STRING_FLG(vid, 0, _manufacturer, NULL, _flags) + static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + /* Device and string descriptor matches */ + /* Device matches */ DEVICE_FLG(0x001f, 0x0b21, /* AB13X USB Audio */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), @@ -2281,6 +2337,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x0d8c, 0x0014, /* C-Media */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x0e0b, 0xfa01, /* Feaulle Rainbow */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x0ecb, 0x205c, /* JBL Quantum610 Wireless */ QUIRK_FLAG_FIXED_RATE), DEVICE_FLG(0x0ecb, 0x2069, /* JBL Quantum810 Wireless */ @@ -2291,8 +2349,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ QUIRK_FLAG_GET_SAMPLE_RATE), - DEVICE_FLG(0x12d1, 0x3a07, /* Huawei Technologies Co., Ltd. */ - QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE | + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */ @@ -2421,6 +2480,13 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_ALIGN_TRANSFER), DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x84ef, 0x0082, /* Hotone Audio Pulze Mini */ + QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL | QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL), + + /* Vendor and string descriptor matches */ + VENDOR_STRING_FLG(0x1235, /* Conflict with Focusrite Novation */ + "MV-SILICON", + 0), /* Stop matching */ /* Vendor matches */ VENDOR_FLG(0x045e, /* MS Lifecam */ @@ -2522,6 +2588,8 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { QUIRK_STRING_ENTRY(MIXER_PLAYBACK_MIN_MUTE), QUIRK_STRING_ENTRY(MIXER_CAPTURE_MIN_MUTE), QUIRK_STRING_ENTRY(SKIP_IFACE_SETUP), + QUIRK_STRING_ENTRY(MIXER_PLAYBACK_LINEAR_VOL), + QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), NULL }; @@ -2578,6 +2646,16 @@ void snd_usb_init_quirk_flags_table(struct snd_usb_audio *chip) if (chip->usb_id == p->id || (!USB_ID_PRODUCT(p->id) && USB_ID_VENDOR(chip->usb_id) == USB_ID_VENDOR(p->id))) { + /* Handle DEVICE_STRING_FLG/VENDOR_STRING_FLG. */ + if (p->usb_string_match && p->usb_string_match->manufacturer && + strcmp(p->usb_string_match->manufacturer, + chip->dev->manufacturer ? chip->dev->manufacturer : "")) + continue; + if (p->usb_string_match && p->usb_string_match->product && + strcmp(p->usb_string_match->product, + chip->dev->product ? chip->dev->product : "")) + continue; + snd_usb_apply_flag_dbg("builtin table", chip, p->flags); chip->quirk_flags |= p->flags; return; diff --git a/sound/usb/stream.c b/sound/usb/stream.c index d38c39e28f38..2532bf97e05e 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -366,6 +366,8 @@ snd_pcm_chmap_elem *convert_chmap_v3(struct uac3_cluster_header_descriptor /* * TODO: this conversion is not complete, update it * after adding UAC3 values to asound.h + * NOTE: not all UAC3 channel relationship have a + * direct ALSA chmap equivalent. */ switch (is->bChRelationship) { case UAC3_CH_MONO: @@ -390,6 +392,12 @@ snd_pcm_chmap_elem *convert_chmap_v3(struct uac3_cluster_header_descriptor case UAC3_CH_FRONT_RIGHT_OF_CENTER: map = SNDRV_CHMAP_FRC; break; + case UAC3_CH_FRONT_WIDE_LEFT: + map = SNDRV_CHMAP_FLW; + break; + case UAC3_CH_FRONT_WIDE_RIGHT: + map = SNDRV_CHMAP_FRW; + break; case UAC3_CH_SIDE_LEFT: map = SNDRV_CHMAP_SL; break; diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 085530cf62d9..58fd07f8c3c9 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -228,6 +228,14 @@ extern bool snd_usb_skip_validation; * Skip the probe-time interface setup (usb_set_interface, * init_pitch, init_sample_rate); redundant with * snd_usb_endpoint_prepare() at stream-open time + * QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL + * Set linear volume mapping for devices where the playback volume control + * value is mapped to voltage (instead of dB) level linearly. In short: + * x(raw) = (raw - raw_min) / (raw_max - raw_min); V(x) = k * x; + * dB(x) = 20 * log10(x). Overrides QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE + * QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL + * Similar to QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL, but for capture streams. + * Overrides QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE */ enum { @@ -258,6 +266,8 @@ enum { QUIRK_TYPE_MIXER_PLAYBACK_MIN_MUTE = 24, QUIRK_TYPE_MIXER_CAPTURE_MIN_MUTE = 25, QUIRK_TYPE_SKIP_IFACE_SETUP = 26, + QUIRK_TYPE_MIXER_PLAYBACK_LINEAR_VOL = 27, + QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, /* Please also edit snd_usb_audio_quirk_flag_names */ }; @@ -290,5 +300,7 @@ enum { #define QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE QUIRK_FLAG(MIXER_PLAYBACK_MIN_MUTE) #define QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE QUIRK_FLAG(MIXER_CAPTURE_MIN_MUTE) #define QUIRK_FLAG_SKIP_IFACE_SETUP QUIRK_FLAG(SKIP_IFACE_SETUP) +#define QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL QUIRK_FLAG(MIXER_PLAYBACK_LINEAR_VOL) +#define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) #endif /* __USBAUDIO_H */ diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c index 0cf4fa74e210..94553b61013c 100644 --- a/sound/usb/usx2y/us144mkii.c +++ b/sound/usb/usx2y/us144mkii.c @@ -420,7 +420,11 @@ static int tascam_probe(struct usb_interface *intf, /* The device has two interfaces; we drive both from this driver. */ if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { - tascam = usb_get_intfdata(usb_ifnum_to_if(dev, 0)); + struct usb_interface *intf_zero = usb_ifnum_to_if(dev, 0); + + if (!intf_zero) + return -ENODEV; + tascam = usb_get_intfdata(intf_zero); if (tascam) { usb_set_intfdata(intf, tascam); tascam->iface1 = intf; |
