diff options
Diffstat (limited to 'sound/usb')
| -rw-r--r-- | sound/usb/Kconfig | 8 | ||||
| -rw-r--r-- | sound/usb/Makefile | 4 | ||||
| -rw-r--r-- | sound/usb/badd.c | 137 | ||||
| -rw-r--r-- | sound/usb/card.c | 148 | ||||
| -rw-r--r-- | sound/usb/card.h | 4 | ||||
| -rw-r--r-- | sound/usb/clock.c | 4 | ||||
| -rw-r--r-- | sound/usb/endpoint.c | 4 | ||||
| -rw-r--r-- | sound/usb/format.c | 54 | ||||
| -rw-r--r-- | sound/usb/mixer.c | 517 | ||||
| -rw-r--r-- | sound/usb/pcm.c | 70 | ||||
| -rw-r--r-- | sound/usb/pcm.h | 3 | ||||
| -rw-r--r-- | sound/usb/stream.c | 105 | ||||
| -rw-r--r-- | sound/usb/usb_audio_qmi_svc.c | 1357 | ||||
| -rw-r--r-- | sound/usb/usb_audio_qmi_v01.c | 833 | ||||
| -rw-r--r-- | sound/usb/usb_audio_qmi_v01.h | 150 | ||||
| -rw-r--r-- | sound/usb/usbaudio.h | 4 |
16 files changed, 3270 insertions, 132 deletions
diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig index a452ad7cec40..f32cfa40c192 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -162,5 +162,13 @@ config SND_BCD2000 source "sound/usb/line6/Kconfig" +config SND_USB_AUDIO_QMI + tristate "USB Audio QMI Service driver" + depends on MSM_QMI_INTERFACE + help + Starts USB Audio QMI server to communicate with remote entity + to perform operations like enable or disable particular audio + stream on a connected USB device. + endif # SND_USB diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 2d2d122b069f..083887ba0346 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -13,7 +13,8 @@ snd-usb-audio-objs := card.o \ pcm.o \ proc.o \ quirks.o \ - stream.o + stream.o \ + badd.o snd-usbmidi-lib-objs := midi.o @@ -26,3 +27,4 @@ obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/ obj-$(CONFIG_SND_USB_LINE6) += line6/ +obj-$(CONFIG_SND_USB_AUDIO_QMI) += usb_audio_qmi_v01.o usb_audio_qmi_svc.o diff --git a/sound/usb/badd.c b/sound/usb/badd.c new file mode 100644 index 000000000000..cc6c26cbb624 --- /dev/null +++ b/sound/usb/badd.c @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/usb.h> +#include <linux/usb/audio.h> +#include <linux/usb/audio-v2.h> +#include <linux/usb/audio-v3.h> + +struct uac3_input_terminal_descriptor badd_baif_in_term_desc = { + .bLength = UAC3_DT_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + .bTerminalID = BADD_IN_TERM_ID_BAIF, + .bCSourceID = BADD_CLOCK_SOURCE, + .wExTerminalDescrID = 0x0000, + .wTerminalDescrStr = 0x0000 +}; + +struct uac3_input_terminal_descriptor badd_baof_in_term_desc = { + .bLength = UAC3_DT_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + .bTerminalID = BADD_IN_TERM_ID_BAOF, + .wTerminalType = UAC_TERMINAL_STREAMING, + .bAssocTerminal = 0x00, + .bCSourceID = BADD_CLOCK_SOURCE, + .bmControls = 0x00000000, + .wExTerminalDescrID = 0x0000, + .wConnectorsDescrID = 0x0000, + .wTerminalDescrStr = 0x0000 +}; + +struct uac3_output_terminal_descriptor badd_baif_out_term_desc = { + .bLength = UAC3_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = BADD_OUT_TERM_ID_BAIF, + .wTerminalType = UAC_TERMINAL_STREAMING, + .bAssocTerminal = 0x00, /* No associated terminal */ + .bSourceID = BADD_FU_ID_BAIF, + .bCSourceID = BADD_CLOCK_SOURCE, + .bmControls = 0x00000000, /* No controls */ + .wExTerminalDescrID = 0x0000, + .wConnectorsDescrID = 0x0000, + .wTerminalDescrStr = 0x0000 +}; + +struct uac3_output_terminal_descriptor badd_baof_out_term_desc = { + .bLength = UAC3_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = BADD_OUT_TERM_ID_BAOF, + .bSourceID = BADD_FU_ID_BAOF, + .bCSourceID = BADD_CLOCK_SOURCE, + .wExTerminalDescrID = 0x0000, + .wTerminalDescrStr = 0x0000 +}; + +__u8 monoControls[] = { + 0x03, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00}; + +__u8 stereoControls[] = { + 0x03, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00 +}; + +__u8 badd_mu_src_ids[] = {BADD_IN_TERM_ID_BAOF, BADD_FU_ID_BAIOF}; + +struct uac3_mixer_unit_descriptor badd_baiof_mu_desc = { + .bLength = UAC3_DT_MIXER_UNIT_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC3_MIXER_UNIT_V3, + .bUnitID = BADD_MU_ID_BAIOF, + .bNrInPins = 0x02, + .baSourceID = badd_mu_src_ids, + .bmMixerControls = 0x00, + .bmControls = 0x00000000, + .wMixerDescrStr = 0x0000 +}; + +struct uac3_feature_unit_descriptor badd_baif_fu_desc = { + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC3_FEATURE_UNIT_V3, + .bUnitID = BADD_FU_ID_BAIF, + .bSourceID = BADD_IN_TERM_ID_BAIF, + .wFeatureDescrStr = 0x0000 +}; + +struct uac3_feature_unit_descriptor badd_baof_fu_desc = { + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC3_FEATURE_UNIT_V3, + .bUnitID = BADD_FU_ID_BAOF, + .wFeatureDescrStr = 0x0000 +}; + +struct uac3_feature_unit_descriptor badd_baiof_fu_desc = { + .bLength = 0x0f, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC3_FEATURE_UNIT_V3, + .bUnitID = BADD_FU_ID_BAIOF, + .bSourceID = BADD_IN_TERM_ID_BAIF, + .bmaControls = monoControls, + .wFeatureDescrStr = 0x0000 +}; + +struct uac3_clock_source_descriptor badd_clock_desc = { + .bLength = UAC3_DT_CLOCK_SRC_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC3_CLOCK_SOURCE, + .bClockID = BADD_CLOCK_SOURCE, + .bmControls = 0x00000001, + .bReferenceTerminal = 0x00, + .wClockSourceStr = 0x0000 +}; + +void *badd_desc_list[] = { + &badd_baif_in_term_desc, + &badd_baof_in_term_desc, + &badd_baiof_mu_desc, + &badd_baif_fu_desc, + &badd_baof_fu_desc, + &badd_baiof_fu_desc, + &badd_clock_desc +}; + diff --git a/sound/usb/card.c b/sound/usb/card.c index 3ded5fe94cea..1ec81225e534 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -45,6 +45,7 @@ #include <linux/usb/audio.h> #include <linux/usb/audio-v2.h> #include <linux/module.h> +#include <linux/usb/audio-v3.h> #include <sound/control.h> #include <sound/core.h> @@ -110,6 +111,71 @@ static DEFINE_MUTEX(register_mutex); static struct snd_usb_audio *usb_chip[SNDRV_CARDS]; static struct usb_driver usb_audio_driver; +struct snd_usb_substream *find_snd_usb_substream(unsigned int card_num, + unsigned int pcm_idx, unsigned int direction, struct snd_usb_audio + **uchip, void (*disconnect_cb)(struct snd_usb_audio *chip)) +{ + int idx; + struct snd_usb_stream *as; + struct snd_usb_substream *subs = NULL; + struct snd_usb_audio *chip = NULL; + + mutex_lock(®ister_mutex); + /* + * legacy audio snd card number assignment is dynamic. Hence + * search using chip->card->number + */ + for (idx = 0; idx < SNDRV_CARDS; idx++) { + if (!usb_chip[idx]) + continue; + if (usb_chip[idx]->card->number == card_num) { + chip = usb_chip[idx]; + break; + } + } + + if (!chip || atomic_read(&chip->shutdown)) { + pr_debug("%s: instance of usb crad # %d does not exist\n", + __func__, card_num); + goto err; + } + + if (pcm_idx >= chip->pcm_devs) { + pr_err("%s: invalid pcm dev number %u > %d\n", __func__, + pcm_idx, chip->pcm_devs); + goto err; + } + + if (direction > SNDRV_PCM_STREAM_CAPTURE) { + pr_err("%s: invalid direction %u\n", __func__, direction); + goto err; + } + + list_for_each_entry(as, &chip->pcm_list, list) { + if (as->pcm_index == pcm_idx) { + subs = &as->substream[direction]; + if (subs->interface < 0 && !subs->data_endpoint && + !subs->sync_endpoint) { + pr_debug("%s: stream disconnected, bail out\n", + __func__); + subs = NULL; + goto err; + } + goto done; + } + } + +done: + chip->card_num = card_num; + chip->disconnect_cb = disconnect_cb; +err: + *uchip = chip; + if (!subs) + pr_debug("%s: substream instance not found\n", __func__); + mutex_unlock(®ister_mutex); + return subs; +} + /* * disconnect streams * called from usb_audio_disconnect() @@ -215,32 +281,31 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) struct usb_device *dev = chip->dev; struct usb_host_interface *host_iface; struct usb_interface_descriptor *altsd; - void *control_header; + struct usb_interface *usb_iface; int i, protocol; - int rest_bytes; - /* find audiocontrol interface */ - host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0]; - control_header = snd_usb_find_csint_desc(host_iface->extra, - host_iface->extralen, - NULL, UAC_HEADER); - altsd = get_iface_desc(host_iface); - protocol = altsd->bInterfaceProtocol; - - if (!control_header) { - dev_err(&dev->dev, "cannot find UAC_HEADER\n"); + usb_iface = usb_ifnum_to_if(dev, ctrlif); + if (!usb_iface) { + snd_printk(KERN_ERR "%d:%u : does not exist\n", + dev->devnum, ctrlif); return -EINVAL; } - rest_bytes = (void *)(host_iface->extra + host_iface->extralen) - - control_header; - - /* just to be sure -- this shouldn't hit at all */ - if (rest_bytes <= 0) { - dev_err(&dev->dev, "invalid control header\n"); + /* find audiocontrol interface */ + host_iface = &usb_iface->altsetting[0]; + if (!host_iface) { + snd_printk(KERN_ERR "Audio Control interface is not available."); return -EINVAL; } + altsd = get_iface_desc(host_iface); + protocol = altsd->bInterfaceProtocol; + + /* + * UAC 1.0 devices use AC HEADER Desc for linking AS interfaces; + * UAC 2.0 and 3.0 devices use IAD for linking AS interfaces + */ + switch (protocol) { default: dev_warn(&dev->dev, @@ -249,7 +314,27 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) /* fall through */ case UAC_VERSION_1: { - struct uac1_ac_header_descriptor *h1 = control_header; + void *control_header; + struct uac1_ac_header_descriptor *h1; + int rest_bytes; + + control_header = snd_usb_find_csint_desc(host_iface->extra, + host_iface->extralen, NULL, UAC_HEADER); + if (!control_header) { + dev_err(&dev->dev, "cannot find UAC_HEADER\n"); + return -EINVAL; + } + + rest_bytes = (void *)(host_iface->extra + host_iface->extralen) - + control_header; + + /* just to be sure -- this shouldn't hit at all */ + if (rest_bytes <= 0) { + dev_err(&dev->dev, "invalid control header\n"); + return -EINVAL; + } + + h1 = control_header; if (rest_bytes < sizeof(*h1)) { dev_err(&dev->dev, "too short v1 buffer descriptor\n"); @@ -277,10 +362,10 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) break; } - case UAC_VERSION_2: { + case UAC_VERSION_2: + case UAC_VERSION_3: { struct usb_interface_assoc_descriptor *assoc = - usb_ifnum_to_if(dev, ctrlif)->intf_assoc; - + usb_iface->intf_assoc; if (!assoc) { /* * Firmware writers cannot count to three. So to find @@ -297,7 +382,8 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) } if (!assoc) { - dev_err(&dev->dev, "Audio class v2 interfaces need an interface association\n"); + dev_err(&dev->dev, "Audio class V%d interfaces need an interface association\n", + protocol); return -EINVAL; } @@ -329,6 +415,7 @@ static int snd_usb_audio_free(struct snd_usb_audio *chip) list_for_each_entry_safe(ep, n, &chip->ep_list, list) snd_usb_endpoint_free(ep); + mutex_destroy(&chip->dev_lock); mutex_destroy(&chip->mutex); kfree(chip); return 0; @@ -384,6 +471,7 @@ static int snd_usb_audio_create(struct usb_interface *intf, } mutex_init(&chip->mutex); + mutex_init(&chip->dev_lock); init_waitqueue_head(&chip->shutdown_wait); chip->index = idx; chip->dev = dev; @@ -495,6 +583,15 @@ static int usb_audio_probe(struct usb_interface *intf, struct usb_host_interface *alts; int ifnum; u32 id; + struct usb_interface_assoc_descriptor *assoc; + + assoc = intf->intf_assoc; + if (assoc && assoc->bFunctionClass == USB_CLASS_AUDIO && + assoc->bFunctionProtocol == UAC_VERSION_3 && + assoc->bFunctionSubClass == FULL_ADC_PROFILE) { + dev_info(&dev->dev, "No support for full-fledged ADC 3.0 yet!!\n"); + return -EINVAL; + } alts = &intf->altsetting[0]; ifnum = get_iface_desc(alts)->bInterfaceNumber; @@ -583,6 +680,8 @@ static int usb_audio_probe(struct usb_interface *intf, usb_chip[chip->index] = chip; chip->num_interfaces++; usb_set_intfdata(intf, chip); + intf->needs_remote_wakeup = 1; + usb_enable_autosuspend(chip->dev); atomic_dec(&chip->active); mutex_unlock(®ister_mutex); return 0; @@ -615,6 +714,9 @@ static void usb_audio_disconnect(struct usb_interface *intf) card = chip->card; + if (chip->disconnect_cb) + chip->disconnect_cb(chip); + mutex_lock(®ister_mutex); if (atomic_inc_return(&chip->shutdown) == 1) { struct snd_usb_stream *as; diff --git a/sound/usb/card.h b/sound/usb/card.h index b24f2efea1cb..a4dbbeb309bc 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -168,4 +168,8 @@ struct snd_usb_stream { struct list_head list; }; +struct snd_usb_substream *find_snd_usb_substream(unsigned int card_num, + unsigned int pcm_idx, unsigned int direction, struct snd_usb_audio + **uchip, void (*disconnect_cb)(struct snd_usb_audio *chip)); + #endif /* __USBAUDIO_CARD_H */ diff --git a/sound/usb/clock.c b/sound/usb/clock.c index 66294eb64501..2899797610e8 100644 --- a/sound/usb/clock.c +++ b/sound/usb/clock.c @@ -427,6 +427,10 @@ int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface, case UAC_VERSION_2: return set_sample_rate_v2(chip, iface, alts, fmt, rate); + + /* Clock rate is fixed at 48 kHz for BADD devices */ + case UAC_VERSION_3: + return 0; } } diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 66648b4bdd28..1f18719e09aa 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -357,7 +357,7 @@ static void queue_pending_output_urbs(struct snd_usb_endpoint *ep) err = usb_submit_urb(ctx->urb, GFP_ATOMIC); if (err < 0) usb_audio_err(ep->chip, - "Unable to submit urb #%d: %d (urb %p)\n", + "Unable to submit urb #%d: %d (urb %pK)\n", ctx->index, err, ctx->urb); else set_bit(ctx->index, &ep->active_mask); @@ -465,7 +465,7 @@ struct snd_usb_endpoint *snd_usb_add_endpoint(struct snd_usb_audio *chip, ep->iface == alts->desc.bInterfaceNumber && ep->altsetting == alts->desc.bAlternateSetting) { usb_audio_dbg(ep->chip, - "Re-using EP %x in iface %d,%d @%p\n", + "Re-using EP %x in iface %d,%d @%pK\n", ep_num, ep->iface, ep->altsetting, ep); goto __exit_unlock; } diff --git a/sound/usb/format.c b/sound/usb/format.c index fc64232d181b..6e5d33dec07d 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -20,6 +20,7 @@ #include <linux/usb.h> #include <linux/usb/audio.h> #include <linux/usb/audio-v2.h> +#include <linux/usb/audio-v3.h> #include <sound/core.h> #include <sound/pcm.h> @@ -71,6 +72,35 @@ static u64 parse_audio_format_i_type(struct snd_usb_audio *chip, format <<= 1; break; } + + case UAC_VERSION_3: { + switch (fp->maxpacksize) { + case BADD_MAXPSIZE_SYNC_MONO_16: + case BADD_MAXPSIZE_SYNC_STEREO_16: + case BADD_MAXPSIZE_ASYNC_MONO_16: + case BADD_MAXPSIZE_ASYNC_STEREO_16: { + sample_width = BIT_RES_16_BIT; + sample_bytes = SUBSLOTSIZE_16_BIT; + break; + } + + case BADD_MAXPSIZE_SYNC_MONO_24: + case BADD_MAXPSIZE_SYNC_STEREO_24: + case BADD_MAXPSIZE_ASYNC_MONO_24: + case BADD_MAXPSIZE_ASYNC_STEREO_24: { + sample_width = BIT_RES_24_BIT; + sample_bytes = SUBSLOTSIZE_24_BIT; + break; + } + + default: + usb_audio_err(chip, "%u:%d : Invalid wMaxPacketSize\n", + fp->iface, fp->altsetting); + return pcm_formats; + } + format = 1 << format; + break; + } } if ((pcm_formats == 0) && @@ -422,6 +452,22 @@ err: return ret; } +static int badd_set_audio_rate_v3(struct snd_usb_audio *chip, + struct audioformat *fp) +{ + unsigned int rate; + + fp->rate_table = kmalloc(sizeof(int), GFP_KERNEL); + if (fp->rate_table == NULL) + return -ENOMEM; + + fp->nr_rates = 1; + rate = BADD_SAMPLING_RATE; + fp->rate_min = fp->rate_max = fp->rate_table[0] = rate; + fp->rates |= snd_pcm_rate_to_rate_bit(rate); + return 0; +} + /* * parse the format type I and III descriptors */ @@ -471,6 +517,9 @@ static int parse_audio_format_i(struct snd_usb_audio *chip, /* fp->channels is already set in this case */ ret = parse_audio_format_rates_v2(chip, fp); break; + case UAC_VERSION_3: + ret = badd_set_audio_rate_v3(chip, fp); + break; } if (fp->channels < 1) { @@ -558,7 +607,10 @@ int snd_usb_parse_audio_format(struct snd_usb_audio *chip, fmt->bFormatType); return -ENOTSUPP; } - fp->fmt_type = fmt->bFormatType; + if (fp->protocol == UAC_VERSION_3) + fp->fmt_type = UAC_FORMAT_TYPE_I; + else + fp->fmt_type = fmt->bFormatType; if (err < 0) return err; #if 1 diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 9b9d653d5e90..0e32b987e4e8 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -50,6 +50,7 @@ #include <linux/usb.h> #include <linux/usb/audio.h> #include <linux/usb/audio-v2.h> +#include <linux/usb/audio-v3.h> #include <sound/core.h> #include <sound/control.h> @@ -185,6 +186,17 @@ static void *find_audio_control_unit(struct mixer_build *state, /* we just parse the header */ struct uac_feature_unit_descriptor *hdr = NULL; + if (state->mixer->protocol == UAC_VERSION_3) { + int i; + + for (i = 0; i < NUM_BADD_DESCS; i++) { + hdr = (void *)badd_desc_list[i]; + if (hdr->bUnitID == unit) + return hdr; + } + + return NULL; + } while ((hdr = snd_usb_find_desc(state->buffer, state->buflen, hdr, USB_DT_CS_INTERFACE)) != NULL) { if (hdr->bLength >= 4 && @@ -739,7 +751,7 @@ static int __check_input_term(struct mixer_build *state, int id, term->channels = d->bNrChannels; term->chconfig = le16_to_cpu(d->wChannelConfig); term->name = d->iTerminal; - } else { /* UAC_VERSION_2 */ + } else if (state->mixer->protocol == UAC_VERSION_2) { struct uac2_input_terminal_descriptor *d = p1; /* call recursively to verify that the @@ -756,6 +768,24 @@ static int __check_input_term(struct mixer_build *state, int id, term->channels = d->bNrChannels; term->chconfig = le32_to_cpu(d->bmChannelConfig); term->name = d->iTerminal; + } else { /* UAC_VERSION_3 */ + struct uac3_input_terminal_descriptor *d = p1; + + err = __check_input_term(state, + d->bCSourceID, term); + if (err < 0) + return err; + + term->id = id; + term->type = d->wTerminalType; + if (d->wClusterDescrID == CLUSTER_ID_MONO) { + term->channels = NUM_CHANNELS_MONO; + term->chconfig = BADD_CH_CONFIG_MONO; + } else { + term->channels = NUM_CHANNELS_STEREO; + term->chconfig = BADD_CH_CONFIG_STEREO; + } + term->name = d->wTerminalDescrStr; } return 0; case UAC_FEATURE_UNIT: { @@ -773,41 +803,81 @@ static int __check_input_term(struct mixer_build *state, int id, return 0; } case UAC_SELECTOR_UNIT: - case UAC2_CLOCK_SELECTOR: { - struct uac_selector_unit_descriptor *d = p1; - /* call recursively to retrieve the channel info */ - err = __check_input_term(state, d->baSourceID[0], term); - if (err < 0) - return err; - term->type = d->bDescriptorSubtype << 16; /* virtual type */ - term->id = id; - term->name = uac_selector_unit_iSelector(d); + /* UAC3_MIXER_UNIT_V3 */ + case UAC2_CLOCK_SELECTOR: + /* UAC3_CLOCK_SOURCE */ { + if (state->mixer->protocol == UAC_VERSION_3 + && hdr[2] == UAC3_CLOCK_SOURCE) { + struct uac3_clock_source_descriptor *d = p1; + + term->type = d->bDescriptorSubtype << 16; + term->id = id; + term->name = d->wClockSourceStr; + } else if (state->mixer->protocol == UAC_VERSION_3 + && hdr[2] == UAC3_MIXER_UNIT_V3) { + struct uac3_mixer_unit_descriptor *d = p1; + + term->type = d->bDescriptorSubtype << 16; + if (d->wClusterDescrID == CLUSTER_ID_MONO) { + term->channels = NUM_CHANNELS_MONO; + term->chconfig = BADD_CH_CONFIG_MONO; + } else { + term->channels = NUM_CHANNELS_STEREO; + term->chconfig = BADD_CH_CONFIG_STEREO; + } + term->name = d->wMixerDescrStr; + } else { + struct uac_selector_unit_descriptor *d = p1; + /* call recursively to retrieve channel info */ + err = __check_input_term(state, + d->baSourceID[0], term); + if (err < 0) + return err; + /* virtual type */ + term->type = d->bDescriptorSubtype << 16; + term->id = id; + term->name = uac_selector_unit_iSelector(d); + } return 0; } case UAC1_PROCESSING_UNIT: case UAC1_EXTENSION_UNIT: /* UAC2_PROCESSING_UNIT_V2 */ /* UAC2_EFFECT_UNIT */ + /* UAC3_FEATURE_UNIT_V3 */ case UAC2_EXTENSION_UNIT_V2: { - struct uac_processing_unit_descriptor *d = p1; + if (state->mixer->protocol == UAC_VERSION_3) { + struct uac_feature_unit_descriptor *d = p1; + + id = d->bSourceID; + } else { + struct uac_processing_unit_descriptor *d = p1; + + if (state->mixer->protocol == UAC_VERSION_2 && + hdr[2] == UAC2_EFFECT_UNIT) { + /* UAC2/UAC1 unit IDs overlap here in an + * uncompatible way. Ignore this unit + * for now. + */ + return 0; + } - if (state->mixer->protocol == UAC_VERSION_2 && - hdr[2] == UAC2_EFFECT_UNIT) { - /* UAC2/UAC1 unit IDs overlap here in an - * uncompatible way. Ignore this unit for now. - */ + if (d->bNrInPins) { + id = d->baSourceID[0]; + break; /* continue to parse */ + } + /* virtual type */ + term->type = d->bDescriptorSubtype << 16; + term->channels = + uac_processing_unit_bNrChannels(d); + term->chconfig = + uac_processing_unit_wChannelConfig( + d, state->mixer->protocol); + term->name = uac_processing_unit_iProcessing( + d, state->mixer->protocol); return 0; } - - if (d->bNrInPins) { - id = d->baSourceID[0]; - break; /* continue to parse */ - } - term->type = d->bDescriptorSubtype << 16; /* virtual type */ - term->channels = uac_processing_unit_bNrChannels(d); - term->chconfig = uac_processing_unit_wChannelConfig(d, state->mixer->protocol); - term->name = uac_processing_unit_iProcessing(d, state->mixer->protocol); - return 0; + break; } case UAC2_CLOCK_SOURCE: { struct uac_clock_source_descriptor *d = p1; @@ -989,6 +1059,15 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval, cval->res = 1; } break; + case USB_ID(0x1130, 0x1620): /* Logitech Speakers S150 */ + /* This audio device has 2 channels and it explicitly requires the + * host to send SET_CUR command on the volume control of both the + * channels. 7936 = 0x1F00 is the default value. + */ + if (cval->channels == 2) + snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, + (cval->control << 8) | 2, 7936); + break; } } @@ -1086,8 +1165,10 @@ no_res_check: /* USB descriptions contain the dB scale in 1/256 dB unit * while ALSA TLV contains in 1/100 dB unit */ - cval->dBmin = (convert_signed_value(cval, cval->min) * 100) / 256; - cval->dBmax = (convert_signed_value(cval, cval->max) * 100) / 256; + cval->dBmin = + (convert_signed_value(cval, cval->min) * 100) / (cval->res); + cval->dBmax = + (convert_signed_value(cval, cval->max) * 100) / (cval->res); if (cval->dBmin > cval->dBmax) { /* something is wrong; assume it's either from/to 0dB */ if (cval->dBmin < 0) @@ -1279,12 +1360,18 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, struct usb_feature_control_info *ctl_info; unsigned int len = 0; int mapped_name = 0; - int nameid = uac_feature_unit_iFeature(desc); + int nameid; struct snd_kcontrol *kctl; struct usb_mixer_elem_info *cval; const struct usbmix_name_map *map; unsigned int range; + if (state->mixer->protocol == UAC_VERSION_3) + nameid = ((struct uac3_feature_unit_descriptor *) + raw_desc)->wFeatureDescrStr; + else + nameid = uac_feature_unit_iFeature(desc); + control++; /* change from zero-based to 1-based value */ if (control == UAC_FU_GRAPHIC_EQUALIZER) { @@ -1305,7 +1392,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, ctl_info = &audio_feature_info[control-1]; if (state->mixer->protocol == UAC_VERSION_1) cval->val_type = ctl_info->type; - else /* UAC_VERSION_2 */ + else /* UAC_VERSION_2 or UAC_VERSION_3*/ cval->val_type = ctl_info->type_uac2 >= 0 ? ctl_info->type_uac2 : ctl_info->type; @@ -1428,6 +1515,62 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, snd_usb_mixer_add_control(&cval->head, kctl); } +static int find_num_channels(struct mixer_build *state, int dir) +{ + int num_ch = -EINVAL, num, i, j, wMaxPacketSize; + int ctrlif = get_iface_desc(state->mixer->hostif)->bInterfaceNumber; + struct usb_interface *usb_iface = + usb_ifnum_to_if(state->mixer->chip->dev, ctrlif); + struct usb_interface_assoc_descriptor *assoc = usb_iface->intf_assoc; + struct usb_host_interface *alts; + + for (i = 0; i < assoc->bInterfaceCount; i++) { + int intf = assoc->bFirstInterface + i; + + if (intf != ctrlif) { + struct usb_interface *iface = + usb_ifnum_to_if(state->mixer->chip->dev, intf); + + alts = &iface->altsetting[1]; + if (dir == USB_DIR_OUT && + get_endpoint(alts, 0)->bEndpointAddress & + USB_DIR_IN) + continue; + if (dir == USB_DIR_IN && + !(get_endpoint(alts, 0)->bEndpointAddress & + USB_DIR_IN)) + continue; + num = iface->num_altsetting; + for (j = 1; j < num; j++) { + num_ch = NUM_CHANNELS_MONO; + alts = &iface->altsetting[j]; + wMaxPacketSize = le16_to_cpu( + get_endpoint(alts, 0)-> + wMaxPacketSize); + switch (wMaxPacketSize) { + case BADD_MAXPSIZE_SYNC_MONO_16: + case BADD_MAXPSIZE_SYNC_MONO_24: + case BADD_MAXPSIZE_ASYNC_MONO_16: + case BADD_MAXPSIZE_ASYNC_MONO_24: + break; + case BADD_MAXPSIZE_SYNC_STEREO_16: + case BADD_MAXPSIZE_SYNC_STEREO_24: + case BADD_MAXPSIZE_ASYNC_STEREO_16: + case BADD_MAXPSIZE_ASYNC_STEREO_24: + num_ch = NUM_CHANNELS_STEREO; + break; + } + if (num_ch == NUM_CHANNELS_MONO) + continue; + else + break; + } + } + } + + return num_ch; +} + /* * parse a feature unit * @@ -1465,7 +1608,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, unitid); return -EINVAL; } - } else { + } else if (state->mixer->protocol == UAC_VERSION_2) { struct uac2_feature_unit_descriptor *ftr = _ftr; if (hdr->bLength < 6) { usb_audio_err(state->chip, @@ -1482,11 +1625,118 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, unitid); return -EINVAL; } + } else { + struct usb_interface *usb_iface = + usb_ifnum_to_if(state->mixer->chip->dev, + get_iface_desc(state->mixer->hostif)->bInterfaceNumber); + struct usb_interface_assoc_descriptor *assoc = + usb_iface->intf_assoc; + + csize = 4; + switch (unitid) { + case BADD_FU_ID_BAIOF: + channels = NUM_CHANNELS_MONO; + bmaControls = monoControls; + badd_baif_in_term_desc.wClusterDescrID = + CLUSTER_ID_MONO; + break; + + case BADD_FU_ID_BAOF: + switch (assoc->bFunctionSubClass) { + case PROF_HEADPHONE: + case PROF_HEADSET_ADAPTER: + channels = NUM_CHANNELS_STEREO; + bmaControls = stereoControls; + badd_baiof_mu_desc.wClusterDescrID = + CLUSTER_ID_MONO; + break; + case PROF_SPEAKERPHONE: + channels = NUM_CHANNELS_MONO; + bmaControls = monoControls; + badd_baof_in_term_desc.wClusterDescrID = + CLUSTER_ID_MONO; + break; + default: + channels = find_num_channels(state, + USB_DIR_OUT); + if (channels < 0) { + usb_audio_err(state->chip, + "unit %u: Cant find num of channels\n", + unitid); + return channels; + } + + bmaControls = (channels == NUM_CHANNELS_MONO) ? + monoControls : stereoControls; + badd_baof_in_term_desc.wClusterDescrID = + (channels == NUM_CHANNELS_MONO) ? + CLUSTER_ID_MONO : CLUSTER_ID_STEREO; + break; + } + break; + + case BADD_FU_ID_BAIF: + switch (assoc->bFunctionSubClass) { + case PROF_HEADSET: + case PROF_HEADSET_ADAPTER: + case PROF_SPEAKERPHONE: + channels = NUM_CHANNELS_MONO; + bmaControls = monoControls; + badd_baif_in_term_desc.wClusterDescrID = + CLUSTER_ID_MONO; + break; + default: + channels = find_num_channels(state, USB_DIR_IN); + if (channels < 0) { + usb_audio_err(state->chip, + "unit %u: Cant find num of channels\n", + unitid); + return channels; + } + + bmaControls = (channels == NUM_CHANNELS_MONO) ? + monoControls : stereoControls; + badd_baif_in_term_desc.wClusterDescrID = + (channels == NUM_CHANNELS_MONO) ? + CLUSTER_ID_MONO : CLUSTER_ID_STEREO; + break; + } + break; + + default: + usb_audio_err(state->chip, "Invalid unit %u\n", unitid); + return -EINVAL; + } } /* parse the source unit */ - if ((err = parse_audio_unit(state, hdr->bSourceID)) < 0) - return err; + if (state->mixer->protocol != UAC_VERSION_3) { + err = parse_audio_unit(state, hdr->bSourceID); + if (err < 0) + return err; + } else { + struct usb_interface *usb_iface = + usb_ifnum_to_if(state->mixer->chip->dev, + get_iface_desc(state->mixer->hostif)->bInterfaceNumber); + struct usb_interface_assoc_descriptor *assoc = + usb_iface->intf_assoc; + + switch (unitid) { + case BADD_FU_ID_BAOF: + switch (assoc->bFunctionSubClass) { + case PROF_HEADSET: + case PROF_HEADSET_ADAPTER: + hdr->bSourceID = BADD_MU_ID_BAIOF; + break; + default: + hdr->bSourceID = BADD_IN_TERM_ID_BAOF; + break; + } + } + err = parse_audio_unit(state, hdr->bSourceID); + if (err < 0) + return err; + } /* determine the input source type and name */ err = check_input_term(state, hdr->bSourceID, &iterm); @@ -1540,7 +1790,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, build_feature_ctl(state, _ftr, 0, i, &iterm, unitid, 0); } - } else { /* UAC_VERSION_2 */ + } else { /* UAC_VERSION_2 or UAC_VERSION_3*/ for (i = 0; i < ARRAY_SIZE(audio_feature_info); i++) { unsigned int ch_bits = 0; unsigned int ch_read_only = 0; @@ -1658,13 +1908,20 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, int input_pins, num_ins, num_outs; int pin, ich, err; - if (desc->bLength < 11 || !(input_pins = desc->bNrInPins) || - desc->bLength < sizeof(*desc) + desc->bNrInPins || - !(num_outs = uac_mixer_unit_bNrChannels(desc))) { - usb_audio_err(state->chip, - "invalid MIXER UNIT descriptor %d\n", - unitid); - return -EINVAL; + if (state->mixer->protocol == UAC_VERSION_3) { + input_pins = badd_baiof_mu_desc.bNrInPins; + num_outs = + (badd_baiof_mu_desc.wClusterDescrID == CLUSTER_ID_MONO) ? + NUM_CHANNELS_MONO : NUM_CHANNELS_STEREO; + } else { + if (desc->bLength < 11 || !(input_pins = desc->bNrInPins) || + desc->bLength < sizeof(*desc) + desc->bNrInPins || + !(num_outs = uac_mixer_unit_bNrChannels(desc))) { + usb_audio_err(state->chip, + "invalid MIXER UNIT descriptor %d\n", + unitid); + return -EINVAL; + } } num_ins = 0; @@ -1684,9 +1941,14 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, int och, ich_has_controls = 0; for (och = 0; och < num_outs; och++) { - __u8 *c = uac_mixer_unit_bmControls(desc, - state->mixer->protocol); + __u8 *c = NULL; + if (state->mixer->protocol == UAC_VERSION_3) + c = + &(badd_baiof_mu_desc.bmMixerControls); + else + c = uac_mixer_unit_bmControls(desc, + state->mixer->protocol); if (check_matrix_bitmap(c, ich, och, num_outs)) { ich_has_controls = 1; break; @@ -2209,16 +2471,28 @@ static int parse_audio_unit(struct mixer_build *state, int unitid) case UAC_MIXER_UNIT: return parse_audio_mixer_unit(state, unitid, p1); case UAC_SELECTOR_UNIT: + /* UAC3_MIXER_UNIT_V3 has the same value */ case UAC2_CLOCK_SELECTOR: - return parse_audio_selector_unit(state, unitid, p1); + /* UAC3_CLOCK_SOURCE has the same value */ + if (state->mixer->protocol == UAC_VERSION_3 && + p1[2] == UAC3_CLOCK_SOURCE) + return 0; /* NOP */ + else if (state->mixer->protocol == UAC_VERSION_3 + && p1[2] == UAC3_MIXER_UNIT_V3) + return parse_audio_mixer_unit(state, unitid, p1); + else + return parse_audio_selector_unit(state, unitid, p1); case UAC_FEATURE_UNIT: return parse_audio_feature_unit(state, unitid, p1); case UAC1_PROCESSING_UNIT: /* UAC2_EFFECT_UNIT has the same value */ + /* UAC3_FEATURE_UNIT_V3 has the same value */ if (state->mixer->protocol == UAC_VERSION_1) return parse_audio_processing_unit(state, unitid, p1); - else + else if (state->mixer->protocol == UAC_VERSION_2) return 0; /* FIXME - effect units not implemented yet */ + else + return parse_audio_feature_unit(state, unitid, p1); case UAC1_EXTENSION_UNIT: /* UAC2_PROCESSING_UNIT_V2 has the same value */ if (state->mixer->protocol == UAC_VERSION_1) @@ -2256,6 +2530,23 @@ static int snd_usb_mixer_dev_free(struct snd_device *device) return 0; } +static int make_out_term(struct mixer_build state, int wTerminalType) +{ + struct uac3_output_terminal_descriptor *desc = NULL; + + if (wTerminalType == UAC_TERMINAL_STREAMING) + desc = &badd_baif_out_term_desc; + else { + desc = &badd_baof_out_term_desc; + desc->wTerminalType = wTerminalType; + } + set_bit(desc->bTerminalID, state.unitbitmap); + state.oterm.id = desc->bTerminalID; + state.oterm.type = desc->wTerminalType; + state.oterm.name = desc->wTerminalDescrStr; + return parse_audio_unit(&state, desc->bSourceID); +} + /* * create mixer controls * @@ -2264,9 +2555,8 @@ static int snd_usb_mixer_dev_free(struct snd_device *device) static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) { struct mixer_build state; - int err; + int err = -EINVAL; const struct usbmix_ctl_map *map; - void *p; memset(&state, 0, sizeof(state)); state.chip = mixer->chip; @@ -2284,44 +2574,108 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) } } - p = NULL; - while ((p = snd_usb_find_csint_desc(mixer->hostif->extra, - mixer->hostif->extralen, - p, UAC_OUTPUT_TERMINAL)) != NULL) { - if (mixer->protocol == UAC_VERSION_1) { - struct uac1_output_terminal_descriptor *desc = p; - - if (desc->bLength < sizeof(*desc)) - continue; /* invalid descriptor? */ - /* mark terminal ID as visited */ - set_bit(desc->bTerminalID, state.unitbitmap); - state.oterm.id = desc->bTerminalID; - state.oterm.type = le16_to_cpu(desc->wTerminalType); - state.oterm.name = desc->iTerminal; - err = parse_audio_unit(&state, desc->bSourceID); + if (mixer->protocol == UAC_VERSION_3) { + struct usb_interface *usb_iface = + usb_ifnum_to_if(mixer->chip->dev, + get_iface_desc(mixer->hostif)->bInterfaceNumber); + struct usb_interface_assoc_descriptor *assoc = + usb_iface->intf_assoc; + + switch (assoc->bFunctionSubClass) { + case PROF_GENERIC_IO: { + if (assoc->bInterfaceCount == 0x02) { + if (get_endpoint(mixer->hostif, + 0)->bEndpointAddress | USB_DIR_IN) + err = make_out_term(state, + UAC_TERMINAL_STREAMING); + else + err = make_out_term(state, + UAC_OUTPUT_TERMINAL_UNDEFINED); + } else { + err = make_out_term(state, + UAC_OUTPUT_TERMINAL_UNDEFINED); + if (err < 0 && err != -EINVAL) + return err; + err = make_out_term(state, + UAC_TERMINAL_STREAMING); + } + break; + } + + case PROF_HEADPHONE: + err = make_out_term(state, + UAC_OUTPUT_TERMINAL_HEADPHONES); + break; + case PROF_SPEAKER: + err = make_out_term(state, UAC_OUTPUT_TERMINAL_SPEAKER); + break; + case PROF_MICROPHONE: + err = make_out_term(state, UAC_TERMINAL_STREAMING); + break; + case PROF_HEADSET: + case PROF_HEADSET_ADAPTER: + err = make_out_term(state, UAC_BIDIR_TERMINAL_HEADSET); if (err < 0 && err != -EINVAL) return err; - } else { /* UAC_VERSION_2 */ - struct uac2_output_terminal_descriptor *desc = p; - - if (desc->bLength < sizeof(*desc)) - continue; /* invalid descriptor? */ - /* mark terminal ID as visited */ - set_bit(desc->bTerminalID, state.unitbitmap); - state.oterm.id = desc->bTerminalID; - state.oterm.type = le16_to_cpu(desc->wTerminalType); - state.oterm.name = desc->iTerminal; - err = parse_audio_unit(&state, desc->bSourceID); + err = make_out_term(state, UAC_TERMINAL_STREAMING); + break; + case PROF_SPEAKERPHONE: + err = make_out_term(state, + UAC_BIDIR_TERMINAL_SPEAKERPHONE); if (err < 0 && err != -EINVAL) return err; + err = make_out_term(state, UAC_TERMINAL_STREAMING); + break; + } + if (err < 0 && err != -EINVAL) + return err; + } else { + void *p; + + p = NULL; + while ((p = snd_usb_find_csint_desc(mixer->hostif->extra, + mixer->hostif->extralen, p, + UAC_OUTPUT_TERMINAL)) != NULL) { + if (mixer->protocol == UAC_VERSION_1) { + struct uac1_output_terminal_descriptor *desc = + p; + + if (desc->bLength < sizeof(*desc)) + continue; /* invalid descriptor? */ + /* mark terminal ID as visited */ + set_bit(desc->bTerminalID, state.unitbitmap); + state.oterm.id = desc->bTerminalID; + state.oterm.type = + le16_to_cpu(desc->wTerminalType); + state.oterm.name = desc->iTerminal; + err = parse_audio_unit(&state, desc->bSourceID); + if (err < 0 && err != -EINVAL) + return err; + } else { /* UAC_VERSION_2 */ + struct uac2_output_terminal_descriptor *desc = + p; + + if (desc->bLength < sizeof(*desc)) + continue; /* invalid descriptor? */ + /* mark terminal ID as visited */ + set_bit(desc->bTerminalID, state.unitbitmap); + state.oterm.id = desc->bTerminalID; + state.oterm.type = + le16_to_cpu(desc->wTerminalType); + state.oterm.name = desc->iTerminal; + err = parse_audio_unit(&state, desc->bSourceID); + if (err < 0 && err != -EINVAL) + return err; - /* - * For UAC2, use the same approach to also add the - * clock selectors - */ - err = parse_audio_unit(&state, desc->bCSourceID); - if (err < 0 && err != -EINVAL) - return err; + /* + * For UAC2, use the same approach to also add + * the clock selectors + */ + err = parse_audio_unit(&state, + desc->bCSourceID); + if (err < 0 && err != -EINVAL) + return err; + } } } @@ -2572,6 +2926,9 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, case UAC_VERSION_2: mixer->protocol = UAC_VERSION_2; break; + case UAC_VERSION_3: + mixer->protocol = UAC_VERSION_3; + break; } if ((err = snd_usb_mixer_controls(mixer)) < 0 || diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 24df26df81db..a56eb871784b 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -228,7 +228,7 @@ static int start_endpoints(struct snd_usb_substream *subs) if (!test_and_set_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags)) { struct snd_usb_endpoint *ep = subs->data_endpoint; - dev_dbg(&subs->dev->dev, "Starting data EP @%p\n", ep); + dev_dbg(&subs->dev->dev, "Starting data EP @%pK\n", ep); ep->data_subs = subs; err = snd_usb_endpoint_start(ep); @@ -257,7 +257,7 @@ static int start_endpoints(struct snd_usb_substream *subs) } } - dev_dbg(&subs->dev->dev, "Starting sync EP @%p\n", ep); + dev_dbg(&subs->dev->dev, "Starting sync EP @%pK\n", ep); ep->sync_slave = subs->data_endpoint; err = snd_usb_endpoint_start(ep); @@ -566,6 +566,64 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) return 0; } +int snd_usb_enable_audio_stream(struct snd_usb_substream *subs, + bool enable) +{ + struct audioformat *fmt; + struct usb_host_interface *alts; + struct usb_interface *iface; + int ret; + + if (!enable) { + if (subs->interface >= 0) { + usb_set_interface(subs->dev, subs->interface, 0); + subs->altset_idx = 0; + subs->interface = -1; + subs->cur_audiofmt = NULL; + } + + snd_usb_autosuspend(subs->stream->chip); + return 0; + } + + snd_usb_autoresume(subs->stream->chip); + fmt = find_format(subs); + if (!fmt) { + dev_err(&subs->dev->dev, + "cannot set format: format = %#x, rate = %d, channels = %d\n", + subs->pcm_format, subs->cur_rate, subs->channels); + return -EINVAL; + } + + subs->altset_idx = 0; + subs->interface = -1; + if (atomic_read(&subs->stream->chip->shutdown)) { + ret = -ENODEV; + } else { + ret = set_format(subs, fmt); + if (ret < 0) + return ret; + + iface = usb_ifnum_to_if(subs->dev, subs->cur_audiofmt->iface); + alts = &iface->altsetting[subs->cur_audiofmt->altset_idx]; + ret = snd_usb_init_sample_rate(subs->stream->chip, + subs->cur_audiofmt->iface, + alts, + subs->cur_audiofmt, + subs->cur_rate); + if (ret < 0) { + dev_err(&subs->dev->dev, "failed to set rate %d\n", + subs->cur_rate); + return ret; + } + } + + subs->interface = fmt->iface; + subs->altset_idx = fmt->altset_idx; + + return 0; +} + /* * Return the score of matching two audioformats. * Veto the audioformat if: @@ -583,13 +641,13 @@ static int match_endpoint_audioformats(struct snd_usb_substream *subs, if (fp->channels < 1) { dev_dbg(&subs->dev->dev, - "%s: (fmt @%p) no channels\n", __func__, fp); + "%s: (fmt @%pK) no channels\n", __func__, fp); return 0; } if (!(fp->formats & pcm_format_to_bits(pcm_format))) { dev_dbg(&subs->dev->dev, - "%s: (fmt @%p) no match for format %d\n", __func__, + "%s: (fmt @%pK) no match for format %d\n", __func__, fp, pcm_format); return 0; } @@ -602,7 +660,7 @@ static int match_endpoint_audioformats(struct snd_usb_substream *subs, } if (!score) { dev_dbg(&subs->dev->dev, - "%s: (fmt @%p) no match for rate %d\n", __func__, + "%s: (fmt @%pK) no match for rate %d\n", __func__, fp, rate); return 0; } @@ -611,7 +669,7 @@ static int match_endpoint_audioformats(struct snd_usb_substream *subs, score++; dev_dbg(&subs->dev->dev, - "%s: (fmt @%p) score %d\n", __func__, fp, score); + "%s: (fmt @%pK) score %d\n", __func__, fp, score); return score; } diff --git a/sound/usb/pcm.h b/sound/usb/pcm.h index df7a003682ad..d581f94b3fbf 100644 --- a/sound/usb/pcm.h +++ b/sound/usb/pcm.h @@ -9,6 +9,7 @@ void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream); int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface, struct usb_host_interface *alts, struct audioformat *fmt); - +int snd_usb_enable_audio_stream(struct snd_usb_substream *subs, + bool enable); #endif /* __USBAUDIO_PCM_H */ diff --git a/sound/usb/stream.c b/sound/usb/stream.c index bc8e4702e9ed..f1186ba3958c 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -20,6 +20,7 @@ #include <linux/usb.h> #include <linux/usb/audio.h> #include <linux/usb/audio-v2.h> +#include <linux/usb/audio-v3.h> #include <sound/core.h> #include <sound/pcm.h> @@ -69,9 +70,14 @@ static void snd_usb_audio_stream_free(struct snd_usb_stream *stream) static void snd_usb_audio_pcm_free(struct snd_pcm *pcm) { struct snd_usb_stream *stream = pcm->private_data; + struct snd_usb_audio *chip; + if (stream) { + mutex_lock(&stream->chip->dev_lock); + chip = stream->chip; stream->pcm = NULL; snd_usb_audio_stream_free(stream); + mutex_unlock(&chip->dev_lock); } } @@ -280,8 +286,6 @@ static struct snd_pcm_chmap_elem *convert_chmap(int channels, unsigned int bits, 0 /* terminator */ }; struct snd_pcm_chmap_elem *chmap; - const unsigned int *maps; - int c; if (channels > ARRAY_SIZE(chmap->map)) return NULL; @@ -290,27 +294,42 @@ static struct snd_pcm_chmap_elem *convert_chmap(int channels, unsigned int bits, if (!chmap) return NULL; - maps = protocol == UAC_VERSION_2 ? uac2_maps : uac1_maps; chmap->channels = channels; - c = 0; - if (bits) { - for (; bits && *maps; maps++, bits >>= 1) - if (bits & 1) - chmap->map[c++] = *maps; + if (protocol == UAC_VERSION_3) { + switch (channels) { + case 1: + chmap->map[0] = SNDRV_CHMAP_MONO; + break; + case 2: + chmap->map[0] = SNDRV_CHMAP_FL; + chmap->map[1] = SNDRV_CHMAP_FR; + break; + } } else { - /* If we're missing wChannelConfig, then guess something - to make sure the channel map is not skipped entirely */ - if (channels == 1) - chmap->map[c++] = SNDRV_CHMAP_MONO; - else - for (; c < channels && *maps; maps++) - chmap->map[c++] = *maps; + int c = 0; + const unsigned int *maps = + protocol == UAC_VERSION_2 ? uac2_maps : uac1_maps; + + if (bits) { + for (; bits && *maps; maps++, bits >>= 1) + if (bits & 1) + chmap->map[c++] = *maps; + } else { + /* + * If we're missing wChannelConfig, then guess something + * to make sure the channel map is not skipped entirely + */ + if (channels == 1) + chmap->map[c++] = SNDRV_CHMAP_MONO; + else + for (; c < channels && *maps; maps++) + chmap->map[c++] = *maps; + } + for (; c < channels; c++) + chmap->map[c] = SNDRV_CHMAP_UNKNOWN; } - for (; c < channels; c++) - chmap->map[c] = SNDRV_CHMAP_UNKNOWN; - return chmap; } @@ -407,6 +426,9 @@ static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip, struct usb_interface_descriptor *altsd = get_iface_desc(alts); int attributes = 0; + if (protocol == UAC_VERSION_3) + return 0; + csep = snd_usb_find_desc(alts->endpoint[0].extra, alts->endpoint[0].extralen, NULL, USB_DT_CS_ENDPOINT); /* Creamware Noah has this descriptor after the 2nd endpoint */ @@ -627,6 +649,50 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) iface_no, altno, as->bTerminalLink); continue; } + + case UAC_VERSION_3: { + int wMaxPacketSize; + + /* + * Allocate a dummy instance of fmt and set format type + * to UAC_FORMAT_TYPE_I for BADD support; free fmt + * after its last usage + */ + fmt = kzalloc(sizeof(*fmt), GFP_KERNEL); + if (!fmt) + return -ENOMEM; + + fmt->bFormatType = UAC_FORMAT_TYPE_I; + format = UAC_FORMAT_TYPE_I_PCM; + clock = BADD_CLOCK_SOURCE; + wMaxPacketSize = le16_to_cpu(get_endpoint(alts, 0) + ->wMaxPacketSize); + switch (wMaxPacketSize) { + case BADD_MAXPSIZE_SYNC_MONO_16: + case BADD_MAXPSIZE_SYNC_MONO_24: + case BADD_MAXPSIZE_ASYNC_MONO_16: + case BADD_MAXPSIZE_ASYNC_MONO_24: { + num_channels = NUM_CHANNELS_MONO; + chconfig = BADD_CH_CONFIG_MONO; + goto populate_fp; + } + + case BADD_MAXPSIZE_SYNC_STEREO_16: + case BADD_MAXPSIZE_SYNC_STEREO_24: + case BADD_MAXPSIZE_ASYNC_STEREO_16: + case BADD_MAXPSIZE_ASYNC_STEREO_24: { + num_channels = NUM_CHANNELS_STEREO; + chconfig = BADD_CH_CONFIG_STEREO; + goto populate_fp; + } + default: + dev_err(&dev->dev, + "%u:%d: invalid wMaxPacketSize\n", + iface_no, altno); + kfree(fmt); + continue; + } + } } /* get format type */ @@ -660,6 +726,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) fp->maxpacksize * 2) continue; +populate_fp: fp = kzalloc(sizeof(*fp), GFP_KERNEL); if (! fp) { dev_err(&dev->dev, "cannot malloc\n"); @@ -721,6 +788,8 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) continue; } + if (protocol == UAC_VERSION_3) + kfree(fmt); /* Create chmap */ if (fp->channels != num_channels) chconfig = 0; diff --git a/sound/usb/usb_audio_qmi_svc.c b/sound/usb/usb_audio_qmi_svc.c new file mode 100644 index 000000000000..dc66b6016f8d --- /dev/null +++ b/sound/usb/usb_audio_qmi_svc.c @@ -0,0 +1,1357 @@ +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/debugfs.h> +#include <linux/usb/audio.h> +#include <linux/usb/audio-v2.h> +#include <linux/uaccess.h> +#include <sound/pcm.h> +#include <sound/core.h> +#include <sound/asound.h> +#include <linux/usb.h> +#include <linux/qmi_encdec.h> +#include <soc/qcom/msm_qmi_interface.h> +#include <linux/iommu.h> +#include <linux/qcom_iommu.h> +#include <linux/platform_device.h> +#include <linux/usb/audio-v3.h> + +#include "usbaudio.h" +#include "card.h" +#include "helper.h" +#include "pcm.h" +#include "usb_audio_qmi_v01.h" + +#define SND_PCM_CARD_NUM_MASK 0xffff0000 +#define SND_PCM_DEV_NUM_MASK 0xff00 +#define SND_PCM_STREAM_DIRECTION 0xff + +#define PREPEND_SID_TO_IOVA(iova, sid) (u64)(((u64)(iova)) | \ + (((u64)sid) << 32)) + +/* event ring iova base address */ +#define IOVA_BASE 0x1000 + +#define IOVA_DCBA_BASE 0x2000 +#define IOVA_XFER_RING_BASE (IOVA_DCBA_BASE + PAGE_SIZE * (SNDRV_CARDS + 1)) +#define IOVA_XFER_BUF_BASE (IOVA_XFER_RING_BASE + PAGE_SIZE * SNDRV_CARDS * 32) +#define IOVA_XFER_RING_MAX (IOVA_XFER_BUF_BASE - PAGE_SIZE) +#define IOVA_XFER_BUF_MAX (0xfffff000 - PAGE_SIZE) + +#define MAX_XFER_BUFF_LEN (24 * PAGE_SIZE) + +struct iova_info { + struct list_head list; + unsigned long start_iova; + size_t size; + bool in_use; +}; + +struct intf_info { + unsigned long data_xfer_ring_va; + size_t data_xfer_ring_size; + unsigned long sync_xfer_ring_va; + size_t sync_xfer_ring_size; + unsigned long xfer_buf_va; + size_t xfer_buf_size; + phys_addr_t xfer_buf_pa; + u8 *xfer_buf; + u8 intf_num; + u8 pcm_card_num; + u8 pcm_dev_num; + u8 direction; + bool in_use; +}; + +struct uaudio_dev { + struct usb_device *udev; + /* audio control interface */ + struct usb_host_interface *ctrl_intf; + unsigned int card_num; + atomic_t in_use; + struct kref kref; + unsigned long dcba_iova; + size_t dcba_size; + wait_queue_head_t disconnect_wq; + + /* interface specific */ + int num_intf; + struct intf_info *info; +}; + +static struct uaudio_dev uadev[SNDRV_CARDS]; + +struct uaudio_qmi_dev { + struct device *dev; + u32 sid; + u32 intr_num; + struct iommu_domain *domain; + + /* list to keep track of available iova */ + struct list_head dcba_list; + size_t dcba_iova_size; + unsigned long curr_dcba_iova; + struct list_head xfer_ring_list; + size_t xfer_ring_iova_size; + unsigned long curr_xfer_ring_iova; + struct list_head xfer_buf_list; + size_t xfer_buf_iova_size; + unsigned long curr_xfer_buf_iova; + /* bit fields representing pcm card enabled */ + unsigned long card_slot; + /* cache event ring phys addr */ + u64 er_phys_addr; +}; + +static struct uaudio_qmi_dev *uaudio_qdev; + +struct uaudio_qmi_svc { + struct qmi_handle *uaudio_svc_hdl; + void *curr_conn; + struct work_struct recv_msg_work; + struct work_struct qmi_disconnect_work; + struct workqueue_struct *uaudio_wq; + ktime_t t_request_recvd; + ktime_t t_resp_sent; +}; + +static struct uaudio_qmi_svc *uaudio_svc; + +static struct msg_desc uaudio_stream_req_desc = { + .max_msg_len = QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN, + .msg_id = QMI_UAUDIO_STREAM_REQ_V01, + .ei_array = qmi_uaudio_stream_req_msg_v01_ei, +}; + +static struct msg_desc uaudio_stream_resp_desc = { + .max_msg_len = QMI_UAUDIO_STREAM_RESP_MSG_V01_MAX_MSG_LEN, + .msg_id = QMI_UAUDIO_STREAM_RESP_V01, + .ei_array = qmi_uaudio_stream_resp_msg_v01_ei, +}; + +static struct msg_desc uaudio_stream_ind_desc = { + .max_msg_len = QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN, + .msg_id = QMI_UADUIO_STREAM_IND_V01, + .ei_array = qmi_uaudio_stream_ind_msg_v01_ei, +}; + +enum mem_type { + MEM_EVENT_RING, + MEM_DCBA, + MEM_XFER_RING, + MEM_XFER_BUF, +}; + +enum usb_qmi_audio_format { + USB_QMI_PCM_FORMAT_S8 = 0, + USB_QMI_PCM_FORMAT_U8, + USB_QMI_PCM_FORMAT_S16_LE, + USB_QMI_PCM_FORMAT_S16_BE, + USB_QMI_PCM_FORMAT_U16_LE, + USB_QMI_PCM_FORMAT_U16_BE, + USB_QMI_PCM_FORMAT_S24_LE, + USB_QMI_PCM_FORMAT_S24_BE, + USB_QMI_PCM_FORMAT_U24_LE, + USB_QMI_PCM_FORMAT_U24_BE, + USB_QMI_PCM_FORMAT_S24_3LE, + USB_QMI_PCM_FORMAT_S24_3BE, + USB_QMI_PCM_FORMAT_U24_3LE, + USB_QMI_PCM_FORMAT_U24_3BE, + USB_QMI_PCM_FORMAT_S32_LE, + USB_QMI_PCM_FORMAT_S32_BE, + USB_QMI_PCM_FORMAT_U32_LE, + USB_QMI_PCM_FORMAT_U32_BE, +}; + +static unsigned long uaudio_get_iova(unsigned long *curr_iova, + size_t *curr_iova_size, struct list_head *head, size_t size) +{ + struct iova_info *info, *new_info = NULL; + struct list_head *curr_head; + unsigned long va = 0; + size_t tmp_size = size; + bool found = false; + + if (size % PAGE_SIZE) { + pr_err("%s: size %zu is not page size multiple\n", __func__, + size); + goto done; + } + + if (size > *curr_iova_size) { + pr_err("%s: size %zu > curr size %zu\n", __func__, size, + *curr_iova_size); + goto done; + } + if (*curr_iova_size == 0) { + pr_err("%s: iova mapping is full\n", __func__); + goto done; + } + + list_for_each_entry(info, head, list) { + /* exact size iova_info */ + if (!info->in_use && info->size == size) { + info->in_use = true; + va = info->start_iova; + *curr_iova_size -= size; + found = true; + pr_debug("%s: exact size :%zu found\n", __func__, size); + goto done; + } else if (!info->in_use && tmp_size >= info->size) { + if (!new_info) + new_info = info; + pr_debug("%s: partial size: %zu found\n", __func__, + info->size); + tmp_size -= info->size; + if (tmp_size) + continue; + + va = new_info->start_iova; + for (curr_head = &new_info->list; curr_head != + &info->list; curr_head = curr_head->next) { + new_info = list_entry(curr_head, struct + iova_info, list); + new_info->in_use = true; + } + info->in_use = true; + *curr_iova_size -= size; + found = true; + goto done; + } else { + /* iova region in use */ + new_info = NULL; + tmp_size = size; + } + } + + info = kzalloc(sizeof(struct iova_info), GFP_KERNEL); + if (!info) { + va = 0; + goto done; + } + + va = info->start_iova = *curr_iova; + info->size = size; + info->in_use = true; + *curr_iova += size; + *curr_iova_size -= size; + found = true; + list_add_tail(&info->list, head); + +done: + if (!found) + pr_err("%s: unable to find %zu size iova\n", __func__, size); + else + pr_debug("%s: va:%lu curr_iova:%lu curr_iova_size:%zu\n", + __func__, va, *curr_iova, *curr_iova_size); + + return va; +} + +static unsigned long uaudio_iommu_map(enum mem_type mtype, phys_addr_t pa, + size_t size) +{ + unsigned long va = 0; + bool map = true; + int ret; + + switch (mtype) { + case MEM_EVENT_RING: + va = IOVA_BASE; + /* er already mapped */ + if (uaudio_qdev->er_phys_addr == pa) + map = false; + break; + case MEM_DCBA: + va = uaudio_get_iova(&uaudio_qdev->curr_dcba_iova, + &uaudio_qdev->dcba_iova_size, &uaudio_qdev->dcba_list, size); + break; + case MEM_XFER_RING: + va = uaudio_get_iova(&uaudio_qdev->curr_xfer_ring_iova, + &uaudio_qdev->xfer_ring_iova_size, &uaudio_qdev->xfer_ring_list, + size); + break; + case MEM_XFER_BUF: + va = uaudio_get_iova(&uaudio_qdev->curr_xfer_buf_iova, + &uaudio_qdev->xfer_buf_iova_size, &uaudio_qdev->xfer_buf_list, + size); + break; + default: + pr_err("%s: unknown mem type %d\n", __func__, mtype); + } + + if (!va) + map = false; + + if (!map) + goto done; + + pr_debug("%s: map pa %pa to iova %lu for memtype %d\n", __func__, &pa, + va, mtype); + ret = iommu_map(uaudio_qdev->domain, va, pa, size, + IOMMU_READ | IOMMU_WRITE | IOMMU_DEVICE); + if (ret) + pr_err("%s:failed to map pa:%pa iova:%lu memtype:%d ret:%d\n", + __func__, &pa, va, mtype, ret); +done: + return va; +} + +static void uaudio_put_iova(unsigned long va, size_t size, struct list_head + *head, size_t *curr_iova_size) +{ + struct iova_info *info; + size_t tmp_size = size; + bool found = false; + + list_for_each_entry(info, head, list) { + if (info->start_iova == va) { + if (!info->in_use) { + pr_err("%s: va %lu is not in use\n", __func__, + va); + return; + } + found = true; + info->in_use = false; + if (info->size == size) + goto done; + } + + if (found && tmp_size >= info->size) { + info->in_use = false; + tmp_size -= info->size; + if (!tmp_size) + goto done; + } + } + + if (!found) { + pr_err("%s: unable to find the va %lu\n", __func__, va); + return; + } +done: + *curr_iova_size += size; + pr_debug("%s: curr_iova_size %zu\n", __func__, *curr_iova_size); +} + +static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long va, + size_t size) +{ + size_t umap_size; + bool unmap = true; + + if (!va || !size) + return; + + switch (mtype) { + case MEM_EVENT_RING: + if (uaudio_qdev->er_phys_addr) + uaudio_qdev->er_phys_addr = 0; + else + unmap = false; + break; + case MEM_DCBA: + uaudio_put_iova(va, size, &uaudio_qdev->dcba_list, + &uaudio_qdev->dcba_iova_size); + break; + case MEM_XFER_RING: + uaudio_put_iova(va, size, &uaudio_qdev->xfer_ring_list, + &uaudio_qdev->xfer_ring_iova_size); + break; + case MEM_XFER_BUF: + uaudio_put_iova(va, size, &uaudio_qdev->xfer_buf_list, + &uaudio_qdev->xfer_buf_iova_size); + break; + default: + pr_err("%s: unknown mem type %d\n", __func__, mtype); + unmap = false; + } + + if (!unmap) + return; + + pr_debug("%s: unmap iova %lu for memtype %d\n", __func__, va, mtype); + + umap_size = iommu_unmap(uaudio_qdev->domain, va, size); + if (umap_size != size) + pr_err("%s: unmapped size %zu for iova %lu\n", __func__, + umap_size, va); +} + +static int prepare_qmi_response(struct snd_usb_substream *subs, + struct qmi_uaudio_stream_req_msg_v01 *req_msg, + struct qmi_uaudio_stream_resp_msg_v01 *resp, int info_idx) +{ + struct usb_interface *iface; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + struct usb_host_endpoint *ep; + struct uac_format_type_i_continuous_descriptor *fmt; + struct uac_format_type_i_discrete_descriptor *fmt_v1; + struct uac_format_type_i_ext_descriptor *fmt_v2; + struct uac1_as_header_descriptor *as; + int ret = -ENODEV; + int protocol, card_num, pcm_dev_num; + void *hdr_ptr; + u8 *xfer_buf; + u32 len, mult, remainder, xfer_buf_len; + unsigned long va, tr_data_va = 0, tr_sync_va = 0, dcba_va = 0, + xfer_buf_va = 0; + phys_addr_t xhci_pa, xfer_buf_pa; + + iface = usb_ifnum_to_if(subs->dev, subs->interface); + if (!iface) { + pr_err("%s: interface # %d does not exist\n", __func__, + subs->interface); + goto err; + } + + pcm_dev_num = (req_msg->usb_token & SND_PCM_DEV_NUM_MASK) >> 8; + card_num = (req_msg->usb_token & SND_PCM_CARD_NUM_MASK) >> 16; + xfer_buf_len = req_msg->xfer_buff_size; + + alts = &iface->altsetting[subs->altset_idx]; + altsd = get_iface_desc(alts); + protocol = altsd->bInterfaceProtocol; + + /* get format type */ + if (protocol != UAC_VERSION_3) { + fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, + UAC_FORMAT_TYPE); + if (!fmt) { + pr_err("%s: %u:%d : no UAC_FORMAT_TYPE desc\n", + __func__, subs->interface, subs->altset_idx); + goto err; + } + } + + if (!uadev[card_num].ctrl_intf) { + pr_err("%s: audio ctrl intf info not cached\n", __func__); + goto err; + } + + if (protocol != UAC_VERSION_3) { + hdr_ptr = snd_usb_find_csint_desc( + uadev[card_num].ctrl_intf->extra, + uadev[card_num].ctrl_intf->extralen, + NULL, UAC_HEADER); + if (!hdr_ptr) { + pr_err("%s: no UAC_HEADER desc\n", __func__); + goto err; + } + } + + if (protocol == UAC_VERSION_1) { + as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, + UAC_AS_GENERAL); + if (!as) { + pr_err("%s: %u:%d : no UAC_AS_GENERAL desc\n", __func__, + subs->interface, subs->altset_idx); + goto err; + } + resp->data_path_delay = as->bDelay; + resp->data_path_delay_valid = 1; + fmt_v1 = (struct uac_format_type_i_discrete_descriptor *)fmt; + resp->usb_audio_subslot_size = fmt_v1->bSubframeSize; + resp->usb_audio_subslot_size_valid = 1; + + resp->usb_audio_spec_revision = + ((struct uac1_ac_header_descriptor *)hdr_ptr)->bcdADC; + resp->usb_audio_spec_revision_valid = 1; + } else if (protocol == UAC_VERSION_2) { + fmt_v2 = (struct uac_format_type_i_ext_descriptor *)fmt; + resp->usb_audio_subslot_size = fmt_v2->bSubslotSize; + resp->usb_audio_subslot_size_valid = 1; + + resp->usb_audio_spec_revision = + ((struct uac2_ac_header_descriptor *)hdr_ptr)->bcdADC; + resp->usb_audio_spec_revision_valid = 1; + } else if (protocol == UAC_VERSION_3) { + switch (le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize)) { + case BADD_MAXPSIZE_SYNC_MONO_16: + case BADD_MAXPSIZE_SYNC_STEREO_16: + case BADD_MAXPSIZE_ASYNC_MONO_16: + case BADD_MAXPSIZE_ASYNC_STEREO_16: { + resp->usb_audio_subslot_size = SUBSLOTSIZE_16_BIT; + break; + } + + case BADD_MAXPSIZE_SYNC_MONO_24: + case BADD_MAXPSIZE_SYNC_STEREO_24: + case BADD_MAXPSIZE_ASYNC_MONO_24: + case BADD_MAXPSIZE_ASYNC_STEREO_24: { + resp->usb_audio_subslot_size = SUBSLOTSIZE_24_BIT; + break; + } + + default: + pr_err("%d: %u: Invalid wMaxPacketSize\n", + subs->interface, subs->altset_idx); + ret = -EINVAL; + goto err; + } + resp->usb_audio_subslot_size_valid = 1; + } else { + pr_err("%s: unknown protocol version %x\n", __func__, protocol); + goto err; + } + + resp->slot_id = subs->dev->slot_id; + resp->slot_id_valid = 1; + + memcpy(&resp->std_as_opr_intf_desc, &alts->desc, sizeof(alts->desc)); + resp->std_as_opr_intf_desc_valid = 1; + + ep = usb_pipe_endpoint(subs->dev, subs->data_endpoint->pipe); + if (!ep) { + pr_err("%s: data ep # %d context is null\n", __func__, + subs->data_endpoint->ep_num); + goto err; + } + memcpy(&resp->std_as_data_ep_desc, &ep->desc, sizeof(ep->desc)); + resp->std_as_data_ep_desc_valid = 1; + + xhci_pa = usb_get_xfer_ring_dma_addr(subs->dev, ep); + if (!xhci_pa) { + pr_err("%s:failed to get data ep ring dma address\n", __func__); + goto err; + } + + resp->xhci_mem_info.tr_data.pa = xhci_pa; + + if (subs->sync_endpoint) { + ep = usb_pipe_endpoint(subs->dev, subs->sync_endpoint->pipe); + if (!ep) { + pr_debug("%s: implicit fb on data ep\n", __func__); + goto skip_sync_ep; + } + memcpy(&resp->std_as_sync_ep_desc, &ep->desc, sizeof(ep->desc)); + resp->std_as_sync_ep_desc_valid = 1; + + xhci_pa = usb_get_xfer_ring_dma_addr(subs->dev, ep); + if (!xhci_pa) { + pr_err("%s:failed to get sync ep ring dma address\n", + __func__); + goto err; + } + resp->xhci_mem_info.tr_sync.pa = xhci_pa; + } + +skip_sync_ep: + resp->interrupter_num = uaudio_qdev->intr_num; + resp->interrupter_num_valid = 1; + + /* map xhci data structures PA memory to iova */ + + /* event ring */ + ret = usb_sec_event_ring_setup(subs->dev, resp->interrupter_num); + if (ret) { + pr_err("%s: failed to setup sec event ring ret %d\n", __func__, + ret); + goto err; + } + xhci_pa = usb_get_sec_event_ring_dma_addr(subs->dev, + resp->interrupter_num); + if (!xhci_pa) { + pr_err("%s: failed to get sec event ring dma address\n", + __func__); + goto err; + } + + va = uaudio_iommu_map(MEM_EVENT_RING, xhci_pa, PAGE_SIZE); + if (!va) + goto err; + + resp->xhci_mem_info.evt_ring.va = PREPEND_SID_TO_IOVA(va, + uaudio_qdev->sid); + resp->xhci_mem_info.evt_ring.pa = xhci_pa; + resp->xhci_mem_info.evt_ring.size = PAGE_SIZE; + uaudio_qdev->er_phys_addr = xhci_pa; + + /* dcba */ + xhci_pa = usb_get_dcba_dma_addr(subs->dev); + if (!xhci_pa) { + pr_err("%s:failed to get dcba dma address\n", __func__); + goto unmap_er; + } + + if (!uadev[card_num].dcba_iova) { /* mappped per usb device */ + va = uaudio_iommu_map(MEM_DCBA, xhci_pa, PAGE_SIZE); + if (!va) + goto unmap_er; + + uadev[card_num].dcba_iova = va; + uadev[card_num].dcba_size = PAGE_SIZE; + } + + dcba_va = uadev[card_num].dcba_iova; + resp->xhci_mem_info.dcba.va = PREPEND_SID_TO_IOVA(dcba_va, + uaudio_qdev->sid); + resp->xhci_mem_info.dcba.pa = xhci_pa; + resp->xhci_mem_info.dcba.size = PAGE_SIZE; + + /* data transfer ring */ + xhci_pa = resp->xhci_mem_info.tr_data.pa; + va = uaudio_iommu_map(MEM_XFER_RING, xhci_pa, PAGE_SIZE); + if (!va) + goto unmap_dcba; + + tr_data_va = va; + resp->xhci_mem_info.tr_data.va = PREPEND_SID_TO_IOVA(va, + uaudio_qdev->sid); + resp->xhci_mem_info.tr_data.size = PAGE_SIZE; + + /* sync transfer ring */ + if (!resp->xhci_mem_info.tr_sync.pa) + goto skip_sync; + + xhci_pa = resp->xhci_mem_info.tr_sync.pa; + va = uaudio_iommu_map(MEM_XFER_RING, xhci_pa, PAGE_SIZE); + if (!va) + goto unmap_data; + + tr_sync_va = va; + resp->xhci_mem_info.tr_sync.va = PREPEND_SID_TO_IOVA(va, + uaudio_qdev->sid); + resp->xhci_mem_info.tr_sync.size = PAGE_SIZE; + +skip_sync: + /* xfer buffer, multiple of 4K only */ + if (!xfer_buf_len) + xfer_buf_len = PAGE_SIZE; + + mult = xfer_buf_len / PAGE_SIZE; + remainder = xfer_buf_len % PAGE_SIZE; + len = mult * PAGE_SIZE; + len += remainder ? PAGE_SIZE : 0; + + if (len > MAX_XFER_BUFF_LEN) { + pr_err("%s: req buf len %d > max buf len %lu, setting %lu\n", + __func__, len, MAX_XFER_BUFF_LEN, MAX_XFER_BUFF_LEN); + len = MAX_XFER_BUFF_LEN; + } + + xfer_buf = usb_alloc_coherent(subs->dev, len, GFP_KERNEL, &xfer_buf_pa); + if (!xfer_buf) + goto unmap_sync; + + resp->xhci_mem_info.xfer_buff.pa = xfer_buf_pa; + resp->xhci_mem_info.xfer_buff.size = len; + + va = uaudio_iommu_map(MEM_XFER_BUF, xfer_buf_pa, len); + if (!va) + goto unmap_sync; + + xfer_buf_va = va; + resp->xhci_mem_info.xfer_buff.va = PREPEND_SID_TO_IOVA(va, + uaudio_qdev->sid); + + resp->xhci_mem_info_valid = 1; + + if (!atomic_read(&uadev[card_num].in_use)) { + kref_init(&uadev[card_num].kref); + init_waitqueue_head(&uadev[card_num].disconnect_wq); + uadev[card_num].num_intf = + subs->dev->config->desc.bNumInterfaces; + uadev[card_num].info = + kzalloc(sizeof(struct intf_info) * + uadev[card_num].num_intf, GFP_KERNEL); + if (!uadev[card_num].info) { + ret = -ENOMEM; + goto unmap_xfer_buf; + } + uadev[card_num].udev = subs->dev; + atomic_set(&uadev[card_num].in_use, 1); + } else { + kref_get(&uadev[card_num].kref); + } + + uadev[card_num].card_num = card_num; + + /* cache intf specific info to use it for unmap and free xfer buf */ + uadev[card_num].info[info_idx].data_xfer_ring_va = tr_data_va; + uadev[card_num].info[info_idx].data_xfer_ring_size = PAGE_SIZE; + uadev[card_num].info[info_idx].sync_xfer_ring_va = tr_sync_va; + uadev[card_num].info[info_idx].sync_xfer_ring_size = PAGE_SIZE; + uadev[card_num].info[info_idx].xfer_buf_va = xfer_buf_va; + uadev[card_num].info[info_idx].xfer_buf_pa = xfer_buf_pa; + uadev[card_num].info[info_idx].xfer_buf_size = len; + uadev[card_num].info[info_idx].xfer_buf = xfer_buf; + uadev[card_num].info[info_idx].pcm_card_num = card_num; + uadev[card_num].info[info_idx].pcm_dev_num = pcm_dev_num; + uadev[card_num].info[info_idx].direction = subs->direction; + uadev[card_num].info[info_idx].intf_num = subs->interface; + uadev[card_num].info[info_idx].in_use = true; + + set_bit(card_num, &uaudio_qdev->card_slot); + + return 0; + +unmap_xfer_buf: + uaudio_iommu_unmap(MEM_XFER_BUF, xfer_buf_va, len); +unmap_sync: + usb_free_coherent(subs->dev, len, xfer_buf, xfer_buf_pa); + uaudio_iommu_unmap(MEM_XFER_RING, tr_sync_va, PAGE_SIZE); +unmap_data: + uaudio_iommu_unmap(MEM_XFER_RING, tr_data_va, PAGE_SIZE); +unmap_dcba: + uaudio_iommu_unmap(MEM_DCBA, dcba_va, PAGE_SIZE); +unmap_er: + uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE); +err: + return ret; +} + +static void uaudio_dev_intf_cleanup(struct usb_device *udev, + struct intf_info *info) +{ + uaudio_iommu_unmap(MEM_XFER_RING, info->data_xfer_ring_va, + info->data_xfer_ring_size); + info->data_xfer_ring_va = 0; + info->data_xfer_ring_size = 0; + + uaudio_iommu_unmap(MEM_XFER_RING, info->sync_xfer_ring_va, + info->sync_xfer_ring_size); + info->sync_xfer_ring_va = 0; + info->sync_xfer_ring_size = 0; + + uaudio_iommu_unmap(MEM_XFER_BUF, info->xfer_buf_va, + info->xfer_buf_size); + info->xfer_buf_va = 0; + + usb_free_coherent(udev, info->xfer_buf_size, + info->xfer_buf, info->xfer_buf_pa); + info->xfer_buf_size = 0; + info->xfer_buf = NULL; + info->xfer_buf_pa = 0; + + info->in_use = false; +} + +static void uaudio_dev_cleanup(struct uaudio_dev *dev) +{ + int if_idx; + + /* free xfer buffer and unmap xfer ring and buf per interface */ + for (if_idx = 0; if_idx < dev->num_intf; if_idx++) { + if (!dev->info[if_idx].in_use) + continue; + uaudio_dev_intf_cleanup(dev->udev, &dev->info[if_idx]); + pr_debug("%s: release resources: intf# %d card# %d\n", __func__, + dev->info[if_idx].intf_num, dev->card_num); + } + + /* iommu_unmap dcba iova for a usb device */ + uaudio_iommu_unmap(MEM_DCBA, dev->dcba_iova, dev->dcba_size); + + dev->dcba_iova = 0; + dev->dcba_size = 0; + dev->num_intf = 0; + + /* free interface info */ + kfree(dev->info); + dev->info = NULL; + + clear_bit(dev->card_num, &uaudio_qdev->card_slot); + + /* all audio devices are disconnected */ + if (!uaudio_qdev->card_slot) { + uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE); + usb_sec_event_ring_cleanup(dev->udev, uaudio_qdev->intr_num); + pr_debug("%s: all audio devices disconnected\n", __func__); + } + + dev->udev = NULL; +} + +static void uaudio_disconnect_cb(struct snd_usb_audio *chip) +{ + int ret; + struct uaudio_dev *dev; + int card_num = chip->card_num; + struct uaudio_qmi_svc *svc = uaudio_svc; + struct qmi_uaudio_stream_ind_msg_v01 disconnect_ind = {0}; + + pr_debug("%s: for card# %d\n", __func__, card_num); + + if (card_num >= SNDRV_CARDS) { + pr_err("%s: invalid card number\n", __func__); + return; + } + + mutex_lock(&chip->dev_lock); + dev = &uadev[card_num]; + + /* clean up */ + if (!dev->udev) { + pr_debug("%s: no clean up required\n", __func__); + goto done; + } + + if (atomic_read(&dev->in_use)) { + mutex_unlock(&chip->dev_lock); + + pr_debug("%s: sending qmi indication disconnect\n", __func__); + disconnect_ind.dev_event = USB_AUDIO_DEV_DISCONNECT_V01; + disconnect_ind.slot_id = dev->udev->slot_id; + ret = qmi_send_ind(svc->uaudio_svc_hdl, svc->curr_conn, + &uaudio_stream_ind_desc, &disconnect_ind, + sizeof(disconnect_ind)); + if (ret < 0) { + pr_err("%s: qmi send failed wiht err: %d\n", + __func__, ret); + return; + } + + ret = wait_event_interruptible(dev->disconnect_wq, + !atomic_read(&dev->in_use)); + if (ret < 0) { + pr_debug("%s: failed with ret %d\n", __func__, ret); + return; + } + mutex_lock(&chip->dev_lock); + } + + uaudio_dev_cleanup(dev); +done: + mutex_unlock(&chip->dev_lock); +} + +static void uaudio_dev_release(struct kref *kref) +{ + struct uaudio_dev *dev = container_of(kref, struct uaudio_dev, kref); + + pr_debug("%s for dev %pK\n", __func__, dev); + + atomic_set(&dev->in_use, 0); + + clear_bit(dev->card_num, &uaudio_qdev->card_slot); + + /* all audio devices are disconnected */ + if (!uaudio_qdev->card_slot) { + usb_sec_event_ring_cleanup(dev->udev, uaudio_qdev->intr_num); + uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE); + pr_debug("%s: all audio devices disconnected\n", __func__); + } + + wake_up(&dev->disconnect_wq); +} + +/* maps audio format received over QMI to asound.h based pcm format */ +static int map_pcm_format(unsigned int fmt_received) +{ + switch (fmt_received) { + case USB_QMI_PCM_FORMAT_S8: + return SNDRV_PCM_FORMAT_S8; + case USB_QMI_PCM_FORMAT_U8: + return SNDRV_PCM_FORMAT_U8; + case USB_QMI_PCM_FORMAT_S16_LE: + return SNDRV_PCM_FORMAT_S16_LE; + case USB_QMI_PCM_FORMAT_S16_BE: + return SNDRV_PCM_FORMAT_S16_BE; + case USB_QMI_PCM_FORMAT_U16_LE: + return SNDRV_PCM_FORMAT_U16_LE; + case USB_QMI_PCM_FORMAT_U16_BE: + return SNDRV_PCM_FORMAT_U16_BE; + case USB_QMI_PCM_FORMAT_S24_LE: + return SNDRV_PCM_FORMAT_S24_LE; + case USB_QMI_PCM_FORMAT_S24_BE: + return SNDRV_PCM_FORMAT_S24_BE; + case USB_QMI_PCM_FORMAT_U24_LE: + return SNDRV_PCM_FORMAT_U24_LE; + case USB_QMI_PCM_FORMAT_U24_BE: + return SNDRV_PCM_FORMAT_U24_BE; + case USB_QMI_PCM_FORMAT_S24_3LE: + return SNDRV_PCM_FORMAT_S24_3LE; + case USB_QMI_PCM_FORMAT_S24_3BE: + return SNDRV_PCM_FORMAT_S24_3BE; + case USB_QMI_PCM_FORMAT_U24_3LE: + return SNDRV_PCM_FORMAT_U24_3LE; + case USB_QMI_PCM_FORMAT_U24_3BE: + return SNDRV_PCM_FORMAT_U24_3BE; + case USB_QMI_PCM_FORMAT_S32_LE: + return SNDRV_PCM_FORMAT_S32_LE; + case USB_QMI_PCM_FORMAT_S32_BE: + return SNDRV_PCM_FORMAT_S32_BE; + case USB_QMI_PCM_FORMAT_U32_LE: + return SNDRV_PCM_FORMAT_U32_LE; + case USB_QMI_PCM_FORMAT_U32_BE: + return SNDRV_PCM_FORMAT_U32_BE; + default: + return -EINVAL; + } +} + +static int info_idx_from_ifnum(int card_num, int intf_num, bool enable) +{ + int i; + + /* + * default index 0 is used when info is allocated upon + * first enable audio stream req for a pcm device + */ + if (enable && !uadev[card_num].info) + return 0; + + for (i = 0; i < uadev[card_num].num_intf; i++) { + if (enable && !uadev[card_num].info[i].in_use) + return i; + else if (!enable && + uadev[card_num].info[i].intf_num == intf_num) + return i; + } + + return -EINVAL; +} + +static int handle_uaudio_stream_req(void *req_h, void *req) +{ + struct qmi_uaudio_stream_req_msg_v01 *req_msg; + struct qmi_uaudio_stream_resp_msg_v01 resp = {{0}, 0}; + struct snd_usb_substream *subs; + struct snd_usb_audio *chip = NULL; + struct uaudio_qmi_svc *svc = uaudio_svc; + struct intf_info *info; + int pcm_format; + u8 pcm_card_num, pcm_dev_num, direction; + int info_idx = -EINVAL, ret = 0; + + req_msg = (struct qmi_uaudio_stream_req_msg_v01 *)req; + + if (!req_msg->audio_format_valid || !req_msg->bit_rate_valid || + !req_msg->number_of_ch_valid || !req_msg->xfer_buff_size_valid) { + pr_err("%s: invalid request msg\n", __func__); + ret = -EINVAL; + goto response; + } + + direction = req_msg->usb_token & SND_PCM_STREAM_DIRECTION; + pcm_dev_num = (req_msg->usb_token & SND_PCM_DEV_NUM_MASK) >> 8; + pcm_card_num = (req_msg->usb_token & SND_PCM_CARD_NUM_MASK) >> 16; + + pr_debug("%s:card#:%d dev#:%d dir:%d en:%d fmt:%d rate:%d #ch:%d\n", + __func__, pcm_card_num, pcm_dev_num, direction, req_msg->enable, + req_msg->audio_format, req_msg->bit_rate, + req_msg->number_of_ch); + + if (pcm_card_num >= SNDRV_CARDS) { + pr_err("%s: invalid card # %u", __func__, pcm_card_num); + ret = -EINVAL; + goto response; + } + + pcm_format = map_pcm_format(req_msg->audio_format); + if (pcm_format == -EINVAL) { + pr_err("%s: unsupported pcm format received %d\n", + __func__, req_msg->audio_format); + ret = -EINVAL; + goto response; + } + + subs = find_snd_usb_substream(pcm_card_num, pcm_dev_num, direction, + &chip, uaudio_disconnect_cb); + if (!subs || !chip || atomic_read(&chip->shutdown)) { + pr_err("%s: can't find substream for card# %u, dev# %u dir%u\n", + __func__, pcm_card_num, pcm_dev_num, direction); + ret = -ENODEV; + goto response; + } + + mutex_lock(&chip->dev_lock); + info_idx = info_idx_from_ifnum(pcm_card_num, subs->interface, + req_msg->enable); + if (atomic_read(&chip->shutdown) || !subs->stream || !subs->stream->pcm + || !subs->stream->chip) { + ret = -ENODEV; + mutex_unlock(&chip->dev_lock); + goto response; + } + + if (req_msg->enable) { + if (info_idx < 0) { + pr_err("%s interface# %d already in use card# %d\n", + __func__, subs->interface, pcm_card_num); + ret = -EBUSY; + mutex_unlock(&chip->dev_lock); + goto response; + } + } + + subs->pcm_format = pcm_format; + subs->channels = req_msg->number_of_ch; + subs->cur_rate = req_msg->bit_rate; + uadev[pcm_card_num].ctrl_intf = chip->ctrl_intf; + + ret = snd_usb_enable_audio_stream(subs, req_msg->enable); + + if (!ret && req_msg->enable) + ret = prepare_qmi_response(subs, req_msg, &resp, info_idx); + + mutex_unlock(&chip->dev_lock); + +response: + if (!req_msg->enable && ret != -EINVAL) { + if (info_idx >= 0) { + mutex_lock(&chip->dev_lock); + info = &uadev[pcm_card_num].info[info_idx]; + uaudio_dev_intf_cleanup(uadev[pcm_card_num].udev, info); + pr_debug("%s:release resources: intf# %d card# %d\n", + __func__, subs->interface, pcm_card_num); + mutex_unlock(&chip->dev_lock); + } + if (atomic_read(&uadev[pcm_card_num].in_use)) + kref_put(&uadev[pcm_card_num].kref, + uaudio_dev_release); + } + + resp.usb_token = req_msg->usb_token; + resp.usb_token_valid = 1; + resp.internal_status = ret; + resp.internal_status_valid = 1; + resp.status = ret ? USB_AUDIO_STREAM_REQ_FAILURE_V01 : ret; + resp.status_valid = 1; + ret = qmi_send_resp_from_cb(svc->uaudio_svc_hdl, svc->curr_conn, req_h, + &uaudio_stream_resp_desc, &resp, sizeof(resp)); + + svc->t_resp_sent = ktime_get(); + + pr_debug("%s: t_resp sent - t_req recvd (in ms) %lld\n", __func__, + ktime_to_ms(ktime_sub(svc->t_resp_sent, svc->t_request_recvd))); + + return ret; +} + +static int uaudio_qmi_svc_connect_cb(struct qmi_handle *handle, + void *conn_h) +{ + struct uaudio_qmi_svc *svc = uaudio_svc; + + if (svc->uaudio_svc_hdl != handle || !conn_h) { + pr_err("%s: handle mismatch\n", __func__); + return -EINVAL; + } + if (svc->curr_conn) { + pr_err("%s: Service is busy\n", __func__); + return -ECONNREFUSED; + } + svc->curr_conn = conn_h; + return 0; +} + +static void uaudio_qmi_disconnect_work(struct work_struct *w) +{ + struct intf_info *info; + int idx, if_idx; + struct snd_usb_substream *subs; + struct snd_usb_audio *chip = NULL; + + /* find all active intf for set alt 0 and cleanup usb audio dev */ + for (idx = 0; idx < SNDRV_CARDS; idx++) { + if (!atomic_read(&uadev[idx].in_use)) + continue; + + for (if_idx = 0; if_idx < uadev[idx].num_intf; if_idx++) { + if (!uadev[idx].info || !uadev[idx].info[if_idx].in_use) + continue; + info = &uadev[idx].info[if_idx]; + subs = find_snd_usb_substream(info->pcm_card_num, + info->pcm_dev_num, + info->direction, + &chip, + uaudio_disconnect_cb); + if (!subs || !chip || atomic_read(&chip->shutdown)) { + pr_debug("%s:no subs for c#%u, dev#%u dir%u\n", + __func__, info->pcm_card_num, + info->pcm_dev_num, + info->direction); + continue; + } + snd_usb_enable_audio_stream(subs, 0); + } + atomic_set(&uadev[idx].in_use, 0); + mutex_lock(&chip->dev_lock); + uaudio_dev_cleanup(&uadev[idx]); + mutex_unlock(&chip->dev_lock); + } +} + +static int uaudio_qmi_svc_disconnect_cb(struct qmi_handle *handle, + void *conn_h) +{ + struct uaudio_qmi_svc *svc = uaudio_svc; + + if (svc->uaudio_svc_hdl != handle || svc->curr_conn != conn_h) { + pr_err("%s: handle mismatch\n", __func__); + return -EINVAL; + } + + svc->curr_conn = NULL; + queue_work(svc->uaudio_wq, &svc->qmi_disconnect_work); + + return 0; +} + +static int uaudio_qmi_svc_req_cb(struct qmi_handle *handle, void *conn_h, + void *req_h, unsigned int msg_id, void *req) +{ + int ret; + struct uaudio_qmi_svc *svc = uaudio_svc; + + if (svc->uaudio_svc_hdl != handle || svc->curr_conn != conn_h) { + pr_err("%s: handle mismatch\n", __func__); + return -EINVAL; + } + + switch (msg_id) { + case QMI_UAUDIO_STREAM_REQ_V01: + ret = handle_uaudio_stream_req(req_h, req); + break; + + default: + ret = -ENOTSUPP; + break; + } + return ret; +} + +static int uaudio_qmi_svc_req_desc_cb(unsigned int msg_id, + struct msg_desc **req_desc) +{ + int ret; + + pr_debug("%s: msg_id %d\n", __func__, msg_id); + + switch (msg_id) { + case QMI_UAUDIO_STREAM_REQ_V01: + *req_desc = &uaudio_stream_req_desc; + ret = sizeof(struct qmi_uaudio_stream_req_msg_v01); + break; + + default: + ret = -ENOTSUPP; + break; + } + return ret; +} + +static void uaudio_qmi_svc_recv_msg(struct work_struct *w) +{ + int ret; + struct uaudio_qmi_svc *svc = container_of(w, struct uaudio_qmi_svc, + recv_msg_work); + + do { + pr_debug("%s: Notified about a Receive Event", __func__); + } while ((ret = qmi_recv_msg(svc->uaudio_svc_hdl)) == 0); + + if (ret != -ENOMSG) + pr_err("%s: Error receiving message\n", __func__); +} + +static void uaudio_qmi_svc_ntfy(struct qmi_handle *handle, + enum qmi_event_type event, void *priv) +{ + struct uaudio_qmi_svc *svc = uaudio_svc; + + pr_debug("%s: event %d", __func__, event); + + svc->t_request_recvd = ktime_get(); + + switch (event) { + case QMI_RECV_MSG: + queue_work(svc->uaudio_wq, &svc->recv_msg_work); + break; + default: + break; + } +} + +static struct qmi_svc_ops_options uaudio_svc_ops_options = { + .version = 1, + .service_id = UAUDIO_STREAM_SERVICE_ID_V01, + .service_vers = UAUDIO_STREAM_SERVICE_VERS_V01, + .connect_cb = uaudio_qmi_svc_connect_cb, + .disconnect_cb = uaudio_qmi_svc_disconnect_cb, + .req_desc_cb = uaudio_qmi_svc_req_desc_cb, + .req_cb = uaudio_qmi_svc_req_cb, +}; + +static int uaudio_qmi_plat_probe(struct platform_device *pdev) +{ + int ret; + struct device_node *node = pdev->dev.of_node; + + uaudio_qdev = devm_kzalloc(&pdev->dev, sizeof(struct uaudio_qmi_dev), + GFP_KERNEL); + if (!uaudio_qdev) + return -ENOMEM; + + uaudio_qdev->dev = &pdev->dev; + + ret = of_property_read_u32(node, "qcom,usb-audio-stream-id", + &uaudio_qdev->sid); + if (ret) { + dev_err(&pdev->dev, "failed to read sid.\n"); + return -ENODEV; + } + + ret = of_property_read_u32(node, "qcom,usb-audio-intr-num", + &uaudio_qdev->intr_num); + if (ret) { + dev_err(&pdev->dev, "failed to read intr num.\n"); + return -ENODEV; + } + + uaudio_qdev->domain = iommu_domain_alloc(msm_iommu_get_bus(&pdev->dev)); + if (!uaudio_qdev->domain) { + dev_err(&pdev->dev, "failed to callocate iommu domain\n"); + return -ENODEV; + } + + /* attach to external processor iommu */ + ret = iommu_attach_device(uaudio_qdev->domain, &pdev->dev); + if (ret) { + dev_err(&pdev->dev, "failed to attach device ret = %d\n", ret); + goto free_domain; + } + + /* initialize dcba, xfer ring and xfer buf iova list */ + INIT_LIST_HEAD(&uaudio_qdev->dcba_list); + uaudio_qdev->curr_dcba_iova = IOVA_DCBA_BASE; + uaudio_qdev->dcba_iova_size = SNDRV_CARDS * PAGE_SIZE; + + INIT_LIST_HEAD(&uaudio_qdev->xfer_ring_list); + uaudio_qdev->curr_xfer_ring_iova = IOVA_XFER_RING_BASE; + uaudio_qdev->xfer_ring_iova_size = + IOVA_XFER_RING_MAX - IOVA_XFER_RING_BASE; + + INIT_LIST_HEAD(&uaudio_qdev->xfer_buf_list); + uaudio_qdev->curr_xfer_buf_iova = IOVA_XFER_BUF_BASE; + uaudio_qdev->xfer_buf_iova_size = + IOVA_XFER_BUF_MAX - IOVA_XFER_BUF_BASE; + + return 0; + +free_domain: + iommu_domain_free(uaudio_qdev->domain); + return ret; +} + +static int uaudio_qmi_plat_remove(struct platform_device *pdev) +{ + iommu_detach_device(uaudio_qdev->domain, &pdev->dev); + iommu_domain_free(uaudio_qdev->domain); + uaudio_qdev->domain = NULL; + + return 0; +} + +static const struct of_device_id of_uaudio_matach[] = { + { + .compatible = "qcom,usb-audio-qmi-dev", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_uaudio_matach); + +static struct platform_driver uaudio_qmi_driver = { + .probe = uaudio_qmi_plat_probe, + .remove = uaudio_qmi_plat_remove, + .driver = { + .name = "uaudio-qmi", + .of_match_table = of_uaudio_matach, + }, +}; + +static int uaudio_qmi_svc_init(void) +{ + int ret; + struct uaudio_qmi_svc *svc; + + svc = kzalloc(sizeof(struct uaudio_qmi_svc), GFP_KERNEL); + if (!svc) + return -ENOMEM; + + svc->uaudio_wq = create_singlethread_workqueue("uaudio_svc"); + if (!svc->uaudio_wq) { + ret = -ENOMEM; + goto free_svc; + } + + svc->uaudio_svc_hdl = qmi_handle_create(uaudio_qmi_svc_ntfy, NULL); + if (!svc->uaudio_svc_hdl) { + pr_err("%s: Error creating svc_hdl\n", __func__); + ret = -EFAULT; + goto destroy_uaudio_wq; + } + + ret = qmi_svc_register(svc->uaudio_svc_hdl, &uaudio_svc_ops_options); + if (ret < 0) { + pr_err("%s:Error registering uaudio svc %d\n", __func__, ret); + goto destroy_svc_handle; + } + + INIT_WORK(&svc->recv_msg_work, uaudio_qmi_svc_recv_msg); + INIT_WORK(&svc->qmi_disconnect_work, uaudio_qmi_disconnect_work); + + uaudio_svc = svc; + + return 0; + +destroy_svc_handle: + qmi_handle_destroy(svc->uaudio_svc_hdl); +destroy_uaudio_wq: + destroy_workqueue(svc->uaudio_wq); +free_svc: + kfree(svc); + return ret; +} + +static void uaudio_qmi_svc_exit(void) +{ + struct uaudio_qmi_svc *svc = uaudio_svc; + + qmi_svc_unregister(svc->uaudio_svc_hdl); + flush_workqueue(svc->uaudio_wq); + qmi_handle_destroy(svc->uaudio_svc_hdl); + destroy_workqueue(svc->uaudio_wq); + kfree(svc); + uaudio_svc = NULL; +} + +static int __init uaudio_qmi_plat_init(void) +{ + int ret; + + ret = platform_driver_register(&uaudio_qmi_driver); + if (ret) + return ret; + + return uaudio_qmi_svc_init(); +} + +static void __exit uaudio_qmi_plat_exit(void) +{ + uaudio_qmi_svc_exit(); + platform_driver_unregister(&uaudio_qmi_driver); +} + +module_init(uaudio_qmi_plat_init); +module_exit(uaudio_qmi_plat_exit); + +MODULE_DESCRIPTION("USB AUDIO QMI Service Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/usb/usb_audio_qmi_v01.c b/sound/usb/usb_audio_qmi_v01.c new file mode 100644 index 000000000000..beff1aaf4981 --- /dev/null +++ b/sound/usb/usb_audio_qmi_v01.c @@ -0,0 +1,833 @@ + /* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/qmi_encdec.h> + +#include <soc/qcom/msm_qmi_interface.h> + +#include "usb_audio_qmi_v01.h" + +static struct elem_info mem_info_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint64_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct mem_info_v01, + va), + }, + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint64_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct mem_info_v01, + pa), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct mem_info_v01, + size), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info apps_mem_info_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct mem_info_v01), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct apps_mem_info_v01, + evt_ring), + .ei_array = mem_info_v01_ei, + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct mem_info_v01), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct apps_mem_info_v01, + tr_data), + .ei_array = mem_info_v01_ei, + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct mem_info_v01), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct apps_mem_info_v01, + tr_sync), + .ei_array = mem_info_v01_ei, + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct mem_info_v01), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct apps_mem_info_v01, + xfer_buff), + .ei_array = mem_info_v01_ei, + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct mem_info_v01), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct apps_mem_info_v01, + dcba), + .ei_array = mem_info_v01_ei, + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info usb_endpoint_descriptor_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bLength), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bDescriptorType), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bEndpointAddress), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bmAttributes), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + wMaxPacketSize), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bInterval), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bRefresh), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bSynchAddress), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info usb_interface_descriptor_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bLength), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bDescriptorType), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bInterfaceNumber), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bAlternateSetting), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bNumEndpoints), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bInterfaceClass), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bInterfaceSubClass), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bInterfaceProtocol), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + iInterface), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info qmi_uaudio_stream_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + enable), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + usb_token), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + audio_format_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + audio_format), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + number_of_ch_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + number_of_ch), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + bit_rate_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + bit_rate), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + xfer_buff_size_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + xfer_buff_size), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info qmi_uaudio_stream_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + status_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum usb_audio_stream_status_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + status), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + internal_status_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + internal_status), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + slot_id_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + slot_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + usb_token_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + usb_token), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + std_as_opr_intf_desc_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct usb_interface_descriptor_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + std_as_opr_intf_desc), + .ei_array = usb_interface_descriptor_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + std_as_data_ep_desc_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct usb_endpoint_descriptor_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + std_as_data_ep_desc), + .ei_array = usb_endpoint_descriptor_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x16, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + std_as_sync_ep_desc_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct usb_endpoint_descriptor_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x16, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + std_as_sync_ep_desc), + .ei_array = usb_endpoint_descriptor_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + usb_audio_spec_revision_valid), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .is_array = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + usb_audio_spec_revision), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + data_path_delay_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + data_path_delay), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x19, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + usb_audio_subslot_size_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x19, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + usb_audio_subslot_size), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x1A, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + xhci_mem_info_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct apps_mem_info_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x1A, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + xhci_mem_info), + .ei_array = apps_mem_info_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x1B, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + interrupter_num_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x1B, + .offset = offsetof( + struct qmi_uaudio_stream_resp_msg_v01, + interrupter_num), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info qmi_uaudio_stream_ind_msg_v01_ei[] = { + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof( + enum usb_audio_device_indication_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + dev_event), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + slot_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + usb_token_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + usb_token), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + std_as_opr_intf_desc_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct usb_interface_descriptor_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + std_as_opr_intf_desc), + .ei_array = usb_interface_descriptor_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + std_as_data_ep_desc_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct usb_endpoint_descriptor_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + std_as_data_ep_desc), + .ei_array = usb_endpoint_descriptor_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + std_as_sync_ep_desc_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct usb_endpoint_descriptor_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + std_as_sync_ep_desc), + .ei_array = usb_endpoint_descriptor_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + usb_audio_spec_revision_valid), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + usb_audio_spec_revision), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + data_path_delay_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + data_path_delay), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x16, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + usb_audio_subslot_size_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x16, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + usb_audio_subslot_size), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + xhci_mem_info_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct apps_mem_info_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + xhci_mem_info), + .ei_array = apps_mem_info_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + interrupter_num_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + interrupter_num), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; diff --git a/sound/usb/usb_audio_qmi_v01.h b/sound/usb/usb_audio_qmi_v01.h new file mode 100644 index 000000000000..f3b6eb05d5f0 --- /dev/null +++ b/sound/usb/usb_audio_qmi_v01.h @@ -0,0 +1,150 @@ + /* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef USB_QMI_V01_H +#define USB_QMI_V01_H + +#define UAUDIO_STREAM_SERVICE_ID_V01 0x41D +#define UAUDIO_STREAM_SERVICE_VERS_V01 0x01 + +#define QMI_UAUDIO_STREAM_RESP_V01 0x0001 +#define QMI_UAUDIO_STREAM_REQ_V01 0x0001 +#define QMI_UADUIO_STREAM_IND_V01 0x0001 + + +struct mem_info_v01 { + uint64_t va; + uint64_t pa; + uint32_t size; +}; + +struct apps_mem_info_v01 { + struct mem_info_v01 evt_ring; + struct mem_info_v01 tr_data; + struct mem_info_v01 tr_sync; + struct mem_info_v01 xfer_buff; + struct mem_info_v01 dcba; +}; + +struct usb_endpoint_descriptor_v01 { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; + uint8_t bRefresh; + uint8_t bSynchAddress; +}; + +struct usb_interface_descriptor_v01 { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; +}; + +enum usb_audio_stream_status_enum_v01 { + USB_AUDIO_STREAM_STATUS_ENUM_MIN_VAL_V01 = INT_MIN, + USB_AUDIO_STREAM_REQ_SUCCESS_V01 = 0, + USB_AUDIO_STREAM_REQ_FAILURE_V01 = 1, + USB_AUDIO_STREAM_REQ_FAILURE_NOT_FOUND_V01 = 2, + USB_AUDIO_STREAM_REQ_FAILURE_INVALID_PARAM_V01 = 3, + USB_AUDIO_STREAM_REQ_FAILURE_MEMALLOC_V01 = 4, + USB_AUDIO_STREAM_STATUS_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +enum usb_audio_device_indication_enum_v01 { + USB_AUDIO_DEVICE_INDICATION_ENUM_MIN_VAL_V01 = INT_MIN, + USB_AUDIO_DEV_CONNECT_V01 = 0, + USB_AUDIO_DEV_DISCONNECT_V01 = 1, + USB_AUDIO_DEV_SUSPEND_V01 = 2, + USB_AUDIO_DEV_RESUME_V01 = 3, + USB_AUDIO_DEVICE_INDICATION_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +struct qmi_uaudio_stream_req_msg_v01 { + uint8_t enable; + uint32_t usb_token; + uint8_t audio_format_valid; + uint32_t audio_format; + uint8_t number_of_ch_valid; + uint32_t number_of_ch; + uint8_t bit_rate_valid; + uint32_t bit_rate; + uint8_t xfer_buff_size_valid; + uint32_t xfer_buff_size; +}; +#define QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN 39 +extern struct elem_info qmi_uaudio_stream_req_msg_v01_ei[]; + +struct qmi_uaudio_stream_resp_msg_v01 { + struct qmi_response_type_v01 resp; + uint8_t status_valid; + enum usb_audio_stream_status_enum_v01 status; + uint8_t internal_status_valid; + uint32_t internal_status; + uint8_t slot_id_valid; + uint32_t slot_id; + uint8_t usb_token_valid; + uint32_t usb_token; + uint8_t std_as_opr_intf_desc_valid; + struct usb_interface_descriptor_v01 std_as_opr_intf_desc; + uint8_t std_as_data_ep_desc_valid; + struct usb_endpoint_descriptor_v01 std_as_data_ep_desc; + uint8_t std_as_sync_ep_desc_valid; + struct usb_endpoint_descriptor_v01 std_as_sync_ep_desc; + uint8_t usb_audio_spec_revision_valid; + uint16_t usb_audio_spec_revision; + uint8_t data_path_delay_valid; + uint8_t data_path_delay; + uint8_t usb_audio_subslot_size_valid; + uint8_t usb_audio_subslot_size; + uint8_t xhci_mem_info_valid; + struct apps_mem_info_v01 xhci_mem_info; + uint8_t interrupter_num_valid; + uint8_t interrupter_num; +}; +#define QMI_UAUDIO_STREAM_RESP_MSG_V01_MAX_MSG_LEN 191 +extern struct elem_info qmi_uaudio_stream_resp_msg_v01_ei[]; + +struct qmi_uaudio_stream_ind_msg_v01 { + enum usb_audio_device_indication_enum_v01 dev_event; + uint32_t slot_id; + uint8_t usb_token_valid; + uint32_t usb_token; + uint8_t std_as_opr_intf_desc_valid; + struct usb_interface_descriptor_v01 std_as_opr_intf_desc; + uint8_t std_as_data_ep_desc_valid; + struct usb_endpoint_descriptor_v01 std_as_data_ep_desc; + uint8_t std_as_sync_ep_desc_valid; + struct usb_endpoint_descriptor_v01 std_as_sync_ep_desc; + uint8_t usb_audio_spec_revision_valid; + uint16_t usb_audio_spec_revision; + uint8_t data_path_delay_valid; + uint8_t data_path_delay; + uint8_t usb_audio_subslot_size_valid; + uint8_t usb_audio_subslot_size; + uint8_t xhci_mem_info_valid; + struct apps_mem_info_v01 xhci_mem_info; + uint8_t interrupter_num_valid; + uint8_t interrupter_num; +}; +#define QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN 177 +extern struct elem_info qmi_uaudio_stream_ind_msg_v01_ei[]; + +#endif diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 09ecc7afdc4f..2c70e0961e3d 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -60,6 +60,10 @@ struct snd_usb_audio { bool autoclock; /* from the 'autoclock' module param */ struct usb_host_interface *ctrl_intf; /* the audio control interface */ + + struct mutex dev_lock; /* to protect any race with disconnect */ + int card_num; /* cache pcm card number to use upon disconnect */ + void (*disconnect_cb)(struct snd_usb_audio *chip); }; #define USB_AUDIO_IFACE_UNUSED ((void *)-1L) |
