diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /sound/usb/implicit.c | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/usb/implicit.c')
-rw-r--r-- | sound/usb/implicit.c | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/sound/usb/implicit.c b/sound/usb/implicit.c new file mode 100644 index 0000000000..4727043fd7 --- /dev/null +++ b/sound/usb/implicit.c @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Special handling for implicit feedback mode +// + +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/usb/audio.h> +#include <linux/usb/audio-v2.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include "usbaudio.h" +#include "card.h" +#include "helper.h" +#include "pcm.h" +#include "implicit.h" + +enum { + IMPLICIT_FB_NONE, + IMPLICIT_FB_GENERIC, + IMPLICIT_FB_FIXED, + IMPLICIT_FB_BOTH, /* generic playback + capture (for BOSS) */ +}; + +struct snd_usb_implicit_fb_match { + unsigned int id; + unsigned int iface_class; + unsigned int ep_num; + unsigned int iface; + int type; +}; + +#define IMPLICIT_FB_GENERIC_DEV(vend, prod) \ + { .id = USB_ID(vend, prod), .type = IMPLICIT_FB_GENERIC } +#define IMPLICIT_FB_FIXED_DEV(vend, prod, ep, ifnum) \ + { .id = USB_ID(vend, prod), .type = IMPLICIT_FB_FIXED, .ep_num = (ep),\ + .iface = (ifnum) } +#define IMPLICIT_FB_BOTH_DEV(vend, prod, ep, ifnum) \ + { .id = USB_ID(vend, prod), .type = IMPLICIT_FB_BOTH, .ep_num = (ep),\ + .iface = (ifnum) } +#define IMPLICIT_FB_SKIP_DEV(vend, prod) \ + { .id = USB_ID(vend, prod), .type = IMPLICIT_FB_NONE } + +/* Implicit feedback quirk table for playback */ +static const struct snd_usb_implicit_fb_match playback_implicit_fb_quirks[] = { + /* Fixed EP */ + /* FIXME: check the availability of generic matching */ + IMPLICIT_FB_FIXED_DEV(0x0763, 0x2030, 0x81, 3), /* M-Audio Fast Track C400 */ + IMPLICIT_FB_FIXED_DEV(0x0763, 0x2031, 0x81, 3), /* M-Audio Fast Track C600 */ + IMPLICIT_FB_FIXED_DEV(0x0763, 0x2080, 0x81, 2), /* M-Audio FastTrack Ultra */ + IMPLICIT_FB_FIXED_DEV(0x0763, 0x2081, 0x81, 2), /* M-Audio FastTrack Ultra */ + IMPLICIT_FB_FIXED_DEV(0x2466, 0x8010, 0x81, 2), /* Fractal Audio Axe-Fx III */ + IMPLICIT_FB_FIXED_DEV(0x31e9, 0x0001, 0x81, 2), /* Solid State Logic SSL2 */ + IMPLICIT_FB_FIXED_DEV(0x31e9, 0x0002, 0x81, 2), /* Solid State Logic SSL2+ */ + IMPLICIT_FB_FIXED_DEV(0x0499, 0x172f, 0x81, 2), /* Steinberg UR22C */ + IMPLICIT_FB_FIXED_DEV(0x0d9a, 0x00df, 0x81, 2), /* RTX6001 */ + IMPLICIT_FB_FIXED_DEV(0x22f0, 0x0006, 0x81, 3), /* Allen&Heath Qu-16 */ + IMPLICIT_FB_FIXED_DEV(0x1686, 0xf029, 0x82, 2), /* Zoom UAC-2 */ + IMPLICIT_FB_FIXED_DEV(0x2466, 0x8003, 0x86, 2), /* Fractal Audio Axe-Fx II */ + IMPLICIT_FB_FIXED_DEV(0x0499, 0x172a, 0x86, 2), /* Yamaha MODX */ + + /* Special matching */ + { .id = USB_ID(0x07fd, 0x0004), .iface_class = USB_CLASS_AUDIO, + .type = IMPLICIT_FB_NONE }, /* MicroBook IIc */ + /* ep = 0x84, ifnum = 0 */ + { .id = USB_ID(0x07fd, 0x0004), .iface_class = USB_CLASS_VENDOR_SPEC, + .type = IMPLICIT_FB_FIXED, + .ep_num = 0x84, .iface = 0 }, /* MOTU MicroBook II */ + + {} /* terminator */ +}; + +/* Implicit feedback quirk table for capture: only FIXED type */ +static const struct snd_usb_implicit_fb_match capture_implicit_fb_quirks[] = { + {} /* terminator */ +}; + +/* set up sync EP information on the audioformat */ +static int add_implicit_fb_sync_ep(struct snd_usb_audio *chip, + struct audioformat *fmt, + int ep, int ep_idx, int ifnum, + const struct usb_host_interface *alts) +{ + struct usb_interface *iface; + + if (!alts) { + iface = usb_ifnum_to_if(chip->dev, ifnum); + if (!iface || iface->num_altsetting < 2) + return 0; + alts = &iface->altsetting[1]; + } + + fmt->sync_ep = ep; + fmt->sync_iface = ifnum; + fmt->sync_altsetting = alts->desc.bAlternateSetting; + fmt->sync_ep_idx = ep_idx; + fmt->implicit_fb = 1; + usb_audio_dbg(chip, + "%d:%d: added %s implicit_fb sync_ep %x, iface %d:%d\n", + fmt->iface, fmt->altsetting, + (ep & USB_DIR_IN) ? "playback" : "capture", + fmt->sync_ep, fmt->sync_iface, fmt->sync_altsetting); + return 1; +} + +/* Check whether the given UAC2 iface:altset points to an implicit fb source */ +static int add_generic_uac2_implicit_fb(struct snd_usb_audio *chip, + struct audioformat *fmt, + unsigned int ifnum, + unsigned int altsetting) +{ + struct usb_host_interface *alts; + struct usb_endpoint_descriptor *epd; + + alts = snd_usb_get_host_interface(chip, ifnum, altsetting); + if (!alts) + return 0; + if (alts->desc.bInterfaceClass != USB_CLASS_AUDIO || + alts->desc.bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING || + alts->desc.bInterfaceProtocol != UAC_VERSION_2 || + alts->desc.bNumEndpoints < 1) + return 0; + epd = get_endpoint(alts, 0); + if (!usb_endpoint_is_isoc_in(epd) || + (epd->bmAttributes & USB_ENDPOINT_USAGE_MASK) != + USB_ENDPOINT_USAGE_IMPLICIT_FB) + return 0; + return add_implicit_fb_sync_ep(chip, fmt, epd->bEndpointAddress, 0, + ifnum, alts); +} + +static bool roland_sanity_check_iface(struct usb_host_interface *alts) +{ + if (alts->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC || + (alts->desc.bInterfaceSubClass != 2 && + alts->desc.bInterfaceProtocol != 2) || + alts->desc.bNumEndpoints < 1) + return false; + return true; +} + +/* Like the UAC2 case above, but specific to Roland with vendor class and hack */ +static int add_roland_implicit_fb(struct snd_usb_audio *chip, + struct audioformat *fmt, + struct usb_host_interface *alts) +{ + struct usb_endpoint_descriptor *epd; + + if (!roland_sanity_check_iface(alts)) + return 0; + /* only when both streams are with ASYNC type */ + epd = get_endpoint(alts, 0); + if (!usb_endpoint_is_isoc_out(epd) || + (epd->bmAttributes & USB_ENDPOINT_SYNCTYPE) != USB_ENDPOINT_SYNC_ASYNC) + return 0; + + /* check capture EP */ + alts = snd_usb_get_host_interface(chip, + alts->desc.bInterfaceNumber + 1, + alts->desc.bAlternateSetting); + if (!alts || !roland_sanity_check_iface(alts)) + return 0; + epd = get_endpoint(alts, 0); + if (!usb_endpoint_is_isoc_in(epd) || + (epd->bmAttributes & USB_ENDPOINT_SYNCTYPE) != USB_ENDPOINT_SYNC_ASYNC) + return 0; + chip->quirk_flags |= QUIRK_FLAG_PLAYBACK_FIRST; + return add_implicit_fb_sync_ep(chip, fmt, epd->bEndpointAddress, 0, + alts->desc.bInterfaceNumber, alts); +} + +/* capture quirk for Roland device; always full-duplex */ +static int add_roland_capture_quirk(struct snd_usb_audio *chip, + struct audioformat *fmt, + struct usb_host_interface *alts) +{ + struct usb_endpoint_descriptor *epd; + + if (!roland_sanity_check_iface(alts)) + return 0; + epd = get_endpoint(alts, 0); + if (!usb_endpoint_is_isoc_in(epd) || + (epd->bmAttributes & USB_ENDPOINT_SYNCTYPE) != USB_ENDPOINT_SYNC_ASYNC) + return 0; + + alts = snd_usb_get_host_interface(chip, + alts->desc.bInterfaceNumber - 1, + alts->desc.bAlternateSetting); + if (!alts || !roland_sanity_check_iface(alts)) + return 0; + epd = get_endpoint(alts, 0); + if (!usb_endpoint_is_isoc_out(epd)) + return 0; + return add_implicit_fb_sync_ep(chip, fmt, epd->bEndpointAddress, 0, + alts->desc.bInterfaceNumber, alts); +} + +/* Playback and capture EPs on Pioneer devices share the same iface/altset + * for the implicit feedback operation + */ +static bool is_pioneer_implicit_fb(struct snd_usb_audio *chip, + struct usb_host_interface *alts) + +{ + struct usb_endpoint_descriptor *epd; + + if (USB_ID_VENDOR(chip->usb_id) != 0x2b73 && + USB_ID_VENDOR(chip->usb_id) != 0x08e4) + return false; + if (alts->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC) + return false; + if (alts->desc.bNumEndpoints != 2) + return false; + + epd = get_endpoint(alts, 0); + if (!usb_endpoint_is_isoc_out(epd) || + (epd->bmAttributes & USB_ENDPOINT_SYNCTYPE) != USB_ENDPOINT_SYNC_ASYNC) + return false; + + epd = get_endpoint(alts, 1); + if (!usb_endpoint_is_isoc_in(epd) || + (epd->bmAttributes & USB_ENDPOINT_SYNCTYPE) != USB_ENDPOINT_SYNC_ASYNC || + ((epd->bmAttributes & USB_ENDPOINT_USAGE_MASK) != + USB_ENDPOINT_USAGE_DATA && + (epd->bmAttributes & USB_ENDPOINT_USAGE_MASK) != + USB_ENDPOINT_USAGE_IMPLICIT_FB)) + return false; + + return true; +} + +static int __add_generic_implicit_fb(struct snd_usb_audio *chip, + struct audioformat *fmt, + int iface, int altset) +{ + struct usb_host_interface *alts; + struct usb_endpoint_descriptor *epd; + + alts = snd_usb_get_host_interface(chip, iface, altset); + if (!alts) + return 0; + + if ((alts->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC && + alts->desc.bInterfaceClass != USB_CLASS_AUDIO) || + alts->desc.bNumEndpoints < 1) + return 0; + epd = get_endpoint(alts, 0); + if (!usb_endpoint_is_isoc_in(epd) || + (epd->bmAttributes & USB_ENDPOINT_SYNCTYPE) != USB_ENDPOINT_SYNC_ASYNC) + return 0; + return add_implicit_fb_sync_ep(chip, fmt, epd->bEndpointAddress, 0, + iface, alts); +} + +/* More generic quirk: look for the sync EP next to the data EP */ +static int add_generic_implicit_fb(struct snd_usb_audio *chip, + struct audioformat *fmt, + struct usb_host_interface *alts) +{ + if ((fmt->ep_attr & USB_ENDPOINT_SYNCTYPE) != USB_ENDPOINT_SYNC_ASYNC) + return 0; + + if (__add_generic_implicit_fb(chip, fmt, + alts->desc.bInterfaceNumber + 1, + alts->desc.bAlternateSetting)) + return 1; + return __add_generic_implicit_fb(chip, fmt, + alts->desc.bInterfaceNumber - 1, + alts->desc.bAlternateSetting); +} + +static const struct snd_usb_implicit_fb_match * +find_implicit_fb_entry(struct snd_usb_audio *chip, + const struct snd_usb_implicit_fb_match *match, + const struct usb_host_interface *alts) +{ + for (; match->id; match++) + if (match->id == chip->usb_id && + (!match->iface_class || + (alts->desc.bInterfaceClass == match->iface_class))) + return match; + + return NULL; +} + +/* Setup an implicit feedback endpoint from a quirk. Returns 0 if no quirk + * applies. Returns 1 if a quirk was found. + */ +static int audioformat_implicit_fb_quirk(struct snd_usb_audio *chip, + struct audioformat *fmt, + struct usb_host_interface *alts) +{ + const struct snd_usb_implicit_fb_match *p; + unsigned int attr = fmt->ep_attr & USB_ENDPOINT_SYNCTYPE; + + p = find_implicit_fb_entry(chip, playback_implicit_fb_quirks, alts); + if (p) { + switch (p->type) { + case IMPLICIT_FB_GENERIC: + return add_generic_implicit_fb(chip, fmt, alts); + case IMPLICIT_FB_NONE: + return 0; /* No quirk */ + case IMPLICIT_FB_FIXED: + return add_implicit_fb_sync_ep(chip, fmt, p->ep_num, 0, + p->iface, NULL); + } + } + + /* Special handling for devices with capture quirks */ + p = find_implicit_fb_entry(chip, capture_implicit_fb_quirks, alts); + if (p) { + switch (p->type) { + case IMPLICIT_FB_FIXED: + return 0; /* no quirk */ + case IMPLICIT_FB_BOTH: + chip->quirk_flags |= QUIRK_FLAG_PLAYBACK_FIRST; + return add_generic_implicit_fb(chip, fmt, alts); + } + } + + /* Generic UAC2 implicit feedback */ + if (attr == USB_ENDPOINT_SYNC_ASYNC && + alts->desc.bInterfaceClass == USB_CLASS_AUDIO && + alts->desc.bInterfaceProtocol == UAC_VERSION_2 && + alts->desc.bNumEndpoints == 1) { + if (add_generic_uac2_implicit_fb(chip, fmt, + alts->desc.bInterfaceNumber + 1, + alts->desc.bAlternateSetting)) + return 1; + } + + /* Roland/BOSS implicit feedback with vendor spec class */ + if (USB_ID_VENDOR(chip->usb_id) == 0x0582) { + if (add_roland_implicit_fb(chip, fmt, alts) > 0) + return 1; + } + + /* Pioneer devices with vendor spec class */ + if (is_pioneer_implicit_fb(chip, alts)) { + chip->quirk_flags |= QUIRK_FLAG_PLAYBACK_FIRST; + return add_implicit_fb_sync_ep(chip, fmt, + get_endpoint(alts, 1)->bEndpointAddress, + 1, alts->desc.bInterfaceNumber, + alts); + } + + /* Try the generic implicit fb if available */ + if (chip->generic_implicit_fb || + (chip->quirk_flags & QUIRK_FLAG_GENERIC_IMPLICIT_FB)) + return add_generic_implicit_fb(chip, fmt, alts); + + /* No quirk */ + return 0; +} + +/* same for capture, but only handling FIXED entry */ +static int audioformat_capture_quirk(struct snd_usb_audio *chip, + struct audioformat *fmt, + struct usb_host_interface *alts) +{ + const struct snd_usb_implicit_fb_match *p; + + p = find_implicit_fb_entry(chip, capture_implicit_fb_quirks, alts); + if (p && (p->type == IMPLICIT_FB_FIXED || p->type == IMPLICIT_FB_BOTH)) + return add_implicit_fb_sync_ep(chip, fmt, p->ep_num, 0, + p->iface, NULL); + + /* Roland/BOSS need full-duplex streams */ + if (USB_ID_VENDOR(chip->usb_id) == 0x0582) { + if (add_roland_capture_quirk(chip, fmt, alts) > 0) + return 1; + } + + if (is_pioneer_implicit_fb(chip, alts)) + return 1; /* skip the quirk, also don't handle generic sync EP */ + return 0; +} + +/* + * Parse altset and set up implicit feedback endpoint on the audioformat + */ +int snd_usb_parse_implicit_fb_quirk(struct snd_usb_audio *chip, + struct audioformat *fmt, + struct usb_host_interface *alts) +{ + if (chip->quirk_flags & QUIRK_FLAG_SKIP_IMPLICIT_FB) + return 0; + if (fmt->endpoint & USB_DIR_IN) + return audioformat_capture_quirk(chip, fmt, alts); + else + return audioformat_implicit_fb_quirk(chip, fmt, alts); +} + +/* + * Return the score of matching two audioformats. + * Veto the audioformat if: + * - It has no channels for some reason. + * - Requested PCM format is not supported. + * - Requested sample rate is not supported. + */ +static int match_endpoint_audioformats(struct snd_usb_substream *subs, + const struct audioformat *fp, + int rate, int channels, + snd_pcm_format_t pcm_format) +{ + int i, score; + + if (fp->channels < 1) + return 0; + + if (!(fp->formats & pcm_format_to_bits(pcm_format))) + return 0; + + if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) { + if (rate < fp->rate_min || rate > fp->rate_max) + return 0; + } else { + for (i = 0; i < fp->nr_rates; i++) { + if (fp->rate_table[i] == rate) + break; + } + if (i >= fp->nr_rates) + return 0; + } + + score = 1; + if (fp->channels == channels) + score++; + + return score; +} + +static struct snd_usb_substream * +find_matching_substream(struct snd_usb_audio *chip, int stream, int ep_num, + int fmt_type) +{ + struct snd_usb_stream *as; + struct snd_usb_substream *subs; + + list_for_each_entry(as, &chip->pcm_list, list) { + subs = &as->substream[stream]; + if (as->fmt_type == fmt_type && subs->ep_num == ep_num) + return subs; + } + + return NULL; +} + +/* + * Return the audioformat that is suitable for the implicit fb + */ +const struct audioformat * +snd_usb_find_implicit_fb_sync_format(struct snd_usb_audio *chip, + const struct audioformat *target, + const struct snd_pcm_hw_params *params, + int stream, + bool *fixed_rate) +{ + struct snd_usb_substream *subs; + const struct audioformat *fp, *sync_fmt = NULL; + int score, high_score; + + /* Use the original audioformat as fallback for the shared altset */ + if (target->iface == target->sync_iface && + target->altsetting == target->sync_altsetting) + sync_fmt = target; + + subs = find_matching_substream(chip, stream, target->sync_ep, + target->fmt_type); + if (!subs) + goto end; + + high_score = 0; + list_for_each_entry(fp, &subs->fmt_list, list) { + score = match_endpoint_audioformats(subs, fp, + params_rate(params), + params_channels(params), + params_format(params)); + if (score > high_score) { + sync_fmt = fp; + high_score = score; + } + } + + end: + if (fixed_rate) + *fixed_rate = snd_usb_pcm_has_fixed_rate(subs); + return sync_fmt; +} + |