diff options
Diffstat (limited to 'sound/usb')
122 files changed, 33655 insertions, 7785 deletions
diff --git a/sound/usb/6fire/Makefile b/sound/usb/6fire/Makefile index dfce6ec53513..587f25c64e56 100644 --- a/sound/usb/6fire/Makefile +++ b/sound/usb/6fire/Makefile @@ -1,3 +1,4 @@ -snd-usb-6fire-objs += chip.o comm.o midi.o control.o firmware.o pcm.o +# SPDX-License-Identifier: GPL-2.0-only +snd-usb-6fire-y += chip.o comm.o midi.o control.o firmware.o pcm.o obj-$(CONFIG_SND_USB_6FIRE) += snd-usb-6fire.o diff --git a/sound/usb/6fire/chip.c b/sound/usb/6fire/chip.c index 17d5e3ee6d73..5ff78814e687 100644 --- a/sound/usb/6fire/chip.c +++ b/sound/usb/6fire/chip.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Linux driver for TerraTec DMX 6Fire USB * @@ -6,11 +7,6 @@ * Author: Torsten Schenk <torsten.schenk@zoho.com> * Created: Jan 01, 2011 * Copyright: (C) Torsten Schenk - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include "chip.h" @@ -30,7 +26,6 @@ MODULE_AUTHOR("Torsten Schenk <torsten.schenk@zoho.com>"); MODULE_DESCRIPTION("TerraTec DMX 6Fire USB audio driver"); MODULE_LICENSE("GPL v2"); -MODULE_SUPPORTED_DEVICE("{{TerraTec,DMX 6Fire USB}}"); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */ @@ -66,8 +61,10 @@ static void usb6fire_chip_abort(struct sfire_chip *chip) } } -static void usb6fire_chip_destroy(struct sfire_chip *chip) +static void usb6fire_card_free(struct snd_card *card) { + struct sfire_chip *chip = card->private_data; + if (chip) { if (chip->pcm) usb6fire_pcm_destroy(chip); @@ -77,8 +74,6 @@ static void usb6fire_chip_destroy(struct sfire_chip *chip) usb6fire_comm_destroy(chip); if (chip->control) usb6fire_control_destroy(chip); - if (chip->card) - snd_card_free(chip->card); } } @@ -93,24 +88,22 @@ static int usb6fire_chip_probe(struct usb_interface *intf, struct snd_card *card = NULL; /* look if we already serve this card and return if so */ - mutex_lock(®ister_mutex); - for (i = 0; i < SNDRV_CARDS; i++) { - if (devices[i] == device) { - if (chips[i]) - chips[i]->intf_count++; - usb_set_intfdata(intf, chips[i]); - mutex_unlock(®ister_mutex); - return 0; - } else if (!devices[i] && regidx < 0) - regidx = i; - } - if (regidx < 0) { - mutex_unlock(®ister_mutex); - dev_err(&intf->dev, "too many cards registered.\n"); - return -ENODEV; + scoped_guard(mutex, ®ister_mutex) { + for (i = 0; i < SNDRV_CARDS; i++) { + if (devices[i] == device) { + if (chips[i]) + chips[i]->intf_count++; + usb_set_intfdata(intf, chips[i]); + return 0; + } else if (!devices[i] && regidx < 0) + regidx = i; + } + if (regidx < 0) { + dev_err(&intf->dev, "too many cards registered.\n"); + return -ENODEV; + } + devices[regidx] = device; } - devices[regidx] = device; - mutex_unlock(®ister_mutex); /* check, if firmware is present on device, upload it if not */ ret = usb6fire_fw_init(intf); @@ -130,8 +123,8 @@ static int usb6fire_chip_probe(struct usb_interface *intf, dev_err(&intf->dev, "cannot create alsa card.\n"); return ret; } - strcpy(card->driver, "6FireUSB"); - strcpy(card->shortname, "TerraTec DMX6FireUSB"); + strscpy(card->driver, "6FireUSB"); + strscpy(card->shortname, "TerraTec DMX6FireUSB"); sprintf(card->longname, "%s at %d:%d", card->shortname, device->bus->busnum, device->devnum); @@ -141,6 +134,7 @@ static int usb6fire_chip_probe(struct usb_interface *intf, chip->regidx = regidx; chip->intf_count = 1; chip->card = card; + card->private_free = usb6fire_card_free; ret = usb6fire_comm_init(chip); if (ret < 0) @@ -167,7 +161,7 @@ static int usb6fire_chip_probe(struct usb_interface *intf, return 0; destroy_chip: - usb6fire_chip_destroy(chip); + snd_card_free(card); return ret; } @@ -179,14 +173,13 @@ static void usb6fire_chip_disconnect(struct usb_interface *intf) if (chip) { /* if !chip, fw upload has been performed */ chip->intf_count--; if (!chip->intf_count) { - mutex_lock(®ister_mutex); - devices[chip->regidx] = NULL; - chips[chip->regidx] = NULL; - mutex_unlock(®ister_mutex); + scoped_guard(mutex, ®ister_mutex) { + devices[chip->regidx] = NULL; + chips[chip->regidx] = NULL; + } chip->shutdown = true; usb6fire_chip_abort(chip); - usb6fire_chip_destroy(chip); } } } diff --git a/sound/usb/6fire/chip.h b/sound/usb/6fire/chip.h index bde02d105a51..8124259b4c57 100644 --- a/sound/usb/6fire/chip.h +++ b/sound/usb/6fire/chip.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Linux driver for TerraTec DMX 6Fire USB * * Author: Torsten Schenk <torsten.schenk@zoho.com> * Created: Jan 01, 2011 * Copyright: (C) Torsten Schenk - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #ifndef USB6FIRE_CHIP_H #define USB6FIRE_CHIP_H diff --git a/sound/usb/6fire/comm.c b/sound/usb/6fire/comm.c index 161215d78d95..49629d4bb327 100644 --- a/sound/usb/6fire/comm.c +++ b/sound/usb/6fire/comm.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Linux driver for TerraTec DMX 6Fire USB * @@ -6,11 +7,6 @@ * Author: Torsten Schenk <torsten.schenk@zoho.com> * Created: Jan 01, 2011 * Copyright: (C) Torsten Schenk - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include "comm.h" @@ -99,7 +95,7 @@ static int usb6fire_comm_send_buffer(u8 *buffer, struct usb_device *dev) int actual_len; ret = usb_interrupt_msg(dev, usb_sndintpipe(dev, COMM_EP), - buffer, buffer[1] + 2, &actual_len, HZ); + buffer, buffer[1] + 2, &actual_len, 1000); if (ret < 0) return ret; else if (actual_len != buffer[1] + 2) diff --git a/sound/usb/6fire/comm.h b/sound/usb/6fire/comm.h index 780d5ed8e5d8..2447d7ecf179 100644 --- a/sound/usb/6fire/comm.h +++ b/sound/usb/6fire/comm.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Linux driver for TerraTec DMX 6Fire USB * * Author: Torsten Schenk <torsten.schenk@zoho.com> * Created: Jan 01, 2011 * Copyright: (C) Torsten Schenk - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #ifndef USB6FIRE_COMM_H #define USB6FIRE_COMM_H diff --git a/sound/usb/6fire/common.h b/sound/usb/6fire/common.h index b6eb03ed1c2c..3a32c94b5783 100644 --- a/sound/usb/6fire/common.h +++ b/sound/usb/6fire/common.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Linux driver for TerraTec DMX 6Fire USB * * Author: Torsten Schenk <torsten.schenk@zoho.com> * Created: Jan 01, 2011 * Copyright: (C) Torsten Schenk - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #ifndef USB6FIRE_COMMON_H diff --git a/sound/usb/6fire/control.c b/sound/usb/6fire/control.c index 54656eed6e2e..9bd8dcbb68e4 100644 --- a/sound/usb/6fire/control.c +++ b/sound/usb/6fire/control.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Linux driver for TerraTec DMX 6Fire USB * @@ -10,11 +11,6 @@ * Thanks to: * - Holger Ruckdeschel: he found out how to control individual channel * volumes and introduced mute switch - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include <linux/interrupt.h> @@ -401,7 +397,7 @@ static int usb6fire_control_digital_thru_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new vol_elements[] = { +static const struct snd_kcontrol_new vol_elements[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Analog Playback Volume", @@ -441,7 +437,7 @@ static struct snd_kcontrol_new vol_elements[] = { {} }; -static struct snd_kcontrol_new mute_elements[] = { +static const struct snd_kcontrol_new mute_elements[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Analog Playback Switch", @@ -475,7 +471,7 @@ static struct snd_kcontrol_new mute_elements[] = { {} }; -static struct snd_kcontrol_new elements[] = { +static const struct snd_kcontrol_new elements[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Line/Phono Capture Route", @@ -521,7 +517,7 @@ static int usb6fire_control_add_virtual( struct control_runtime *rt, struct snd_card *card, char *name, - struct snd_kcontrol_new *elems) + const struct snd_kcontrol_new *elems) { int ret; int i; @@ -543,7 +539,7 @@ static int usb6fire_control_add_virtual( ret = snd_ctl_add(card, control); if (ret < 0) return ret; - ret = snd_ctl_add_slave(vmaster, control); + ret = snd_ctl_add_follower(vmaster, control); if (ret < 0) return ret; i++; diff --git a/sound/usb/6fire/control.h b/sound/usb/6fire/control.h index 5a40ba143489..ae57704d948f 100644 --- a/sound/usb/6fire/control.h +++ b/sound/usb/6fire/control.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Linux driver for TerraTec DMX 6Fire USB * * Author: Torsten Schenk <torsten.schenk@zoho.com> * Created: Jan 01, 2011 * Copyright: (C) Torsten Schenk - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #ifndef USB6FIRE_CONTROL_H diff --git a/sound/usb/6fire/firmware.c b/sound/usb/6fire/firmware.c index 9520b4cd7038..c51abc54d2f8 100644 --- a/sound/usb/6fire/firmware.c +++ b/sound/usb/6fire/firmware.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Linux driver for TerraTec DMX 6Fire USB * @@ -6,11 +7,6 @@ * Author: Torsten Schenk <torsten.schenk@zoho.com> * Created: Jan 01, 2011 * Copyright: (C) Torsten Schenk - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include <linux/firmware.h> @@ -162,29 +158,17 @@ static int usb6fire_fw_ihex_init(const struct firmware *fw, static int usb6fire_fw_ezusb_write(struct usb_device *device, int type, int value, char *data, int len) { - int ret; - - ret = usb_control_msg(device, usb_sndctrlpipe(device, 0), type, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - value, 0, data, len, HZ); - if (ret < 0) - return ret; - else if (ret != len) - return -EIO; - return 0; + return usb_control_msg_send(device, 0, type, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, 0, data, len, 1000, GFP_KERNEL); } static int usb6fire_fw_ezusb_read(struct usb_device *device, int type, int value, char *data, int len) { - int ret = usb_control_msg(device, usb_rcvctrlpipe(device, 0), type, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, value, - 0, data, len, HZ); - if (ret < 0) - return ret; - else if (ret != len) - return -EIO; - return 0; + return usb_control_msg_recv(device, 0, type, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, 0, data, len, 1000, GFP_KERNEL); } static int usb6fire_fw_fpga_write(struct usb_device *device, @@ -194,7 +178,7 @@ static int usb6fire_fw_fpga_write(struct usb_device *device, int ret; ret = usb_bulk_msg(device, usb_sndbulkpipe(device, FPGA_EP), data, len, - &actual_len, HZ); + &actual_len, 1000); if (ret < 0) return ret; else if (actual_len != len) @@ -234,7 +218,7 @@ static int usb6fire_fw_ezusb_upload( /* upload firmware image */ data = 0x01; /* stop ezusb cpu */ ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1); - if (ret < 0) { + if (ret) { kfree(rec); release_firmware(fw); dev_err(&intf->dev, @@ -246,7 +230,7 @@ static int usb6fire_fw_ezusb_upload( while (usb6fire_fw_ihex_next_record(rec)) { /* write firmware */ ret = usb6fire_fw_ezusb_write(device, 0xa0, rec->address, rec->data, rec->len); - if (ret < 0) { + if (ret) { kfree(rec); release_firmware(fw); dev_err(&intf->dev, @@ -261,7 +245,7 @@ static int usb6fire_fw_ezusb_upload( if (postdata) { /* write data after firmware has been uploaded */ ret = usb6fire_fw_ezusb_write(device, 0xa0, postaddr, postdata, postlen); - if (ret < 0) { + if (ret) { dev_err(&intf->dev, "unable to upload ezusb firmware %s: post urb.\n", fwname); @@ -271,7 +255,7 @@ static int usb6fire_fw_ezusb_upload( data = 0x00; /* resume ezusb cpu */ ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1); - if (ret < 0) { + if (ret) { dev_err(&intf->dev, "unable to upload ezusb firmware %s: end message.\n", fwname); @@ -306,7 +290,7 @@ static int usb6fire_fw_fpga_upload( end = fw->data + fw->size; ret = usb6fire_fw_ezusb_write(device, 8, 0, NULL, 0); - if (ret < 0) { + if (ret) { kfree(buffer); release_firmware(fw); dev_err(&intf->dev, @@ -331,7 +315,7 @@ static int usb6fire_fw_fpga_upload( kfree(buffer); ret = usb6fire_fw_ezusb_write(device, 9, 0, NULL, 0); - if (ret < 0) { + if (ret) { dev_err(&intf->dev, "unable to upload fpga firmware: end urb.\n"); return ret; @@ -367,7 +351,7 @@ int usb6fire_fw_init(struct usb_interface *intf) u8 buffer[12]; ret = usb6fire_fw_ezusb_read(device, 1, 0, buffer, 8); - if (ret < 0) { + if (ret) { dev_err(&intf->dev, "unable to receive device firmware state.\n"); return ret; diff --git a/sound/usb/6fire/firmware.h b/sound/usb/6fire/firmware.h index c109c4f75aba..c29ed9cd2a7b 100644 --- a/sound/usb/6fire/firmware.h +++ b/sound/usb/6fire/firmware.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Linux driver for TerraTec DMX 6Fire USB * * Author: Torsten Schenk * Created: Jan 01, 2011 * Copyright: (C) Torsten Schenk - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #ifndef USB6FIRE_FIRMWARE_H diff --git a/sound/usb/6fire/midi.c b/sound/usb/6fire/midi.c index aa5adbb6eb5d..4d1eeb32c5fe 100644 --- a/sound/usb/6fire/midi.c +++ b/sound/usb/6fire/midi.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Linux driver for TerraTec DMX 6Fire USB * @@ -6,11 +7,6 @@ * Author: Torsten Schenk <torsten.schenk@zoho.com> * Created: Jan 01, 2011 * Copyright: (C) Torsten Schenk - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include <sound/rawmidi.h> @@ -27,9 +23,8 @@ static void usb6fire_midi_out_handler(struct urb *urb) { struct midi_runtime *rt = urb->context; int ret; - unsigned long flags; - spin_lock_irqsave(&rt->out_lock, flags); + guard(spinlock_irqsave)(&rt->out_lock); if (rt->out) { ret = snd_rawmidi_transmit(rt->out, rt->out_buffer + 4, @@ -47,18 +42,14 @@ static void usb6fire_midi_out_handler(struct urb *urb) } else /* no more data to transmit */ rt->out = NULL; } - spin_unlock_irqrestore(&rt->out_lock, flags); } static void usb6fire_midi_in_received( struct midi_runtime *rt, u8 *data, int length) { - unsigned long flags; - - spin_lock_irqsave(&rt->in_lock, flags); + guard(spinlock_irqsave)(&rt->in_lock); if (rt->in) snd_rawmidi_receive(rt->in, data, length); - spin_unlock_irqrestore(&rt->in_lock, flags); } static int usb6fire_midi_out_open(struct snd_rawmidi_substream *alsa_sub) @@ -77,14 +68,11 @@ static void usb6fire_midi_out_trigger( struct midi_runtime *rt = alsa_sub->rmidi->private_data; struct urb *urb = &rt->out_urb; __s8 ret; - unsigned long flags; - spin_lock_irqsave(&rt->out_lock, flags); + guard(spinlock_irqsave)(&rt->out_lock); if (up) { /* start transfer */ - if (rt->out) { /* we are already transmitting so just return */ - spin_unlock_irqrestore(&rt->out_lock, flags); + if (rt->out) /* we are already transmitting so just return */ return; - } ret = snd_rawmidi_transmit(alsa_sub, rt->out_buffer + 4, MIDI_BUFSIZE - 4); @@ -103,7 +91,6 @@ static void usb6fire_midi_out_trigger( } } else if (rt->out == alsa_sub) rt->out = NULL; - spin_unlock_irqrestore(&rt->out_lock, flags); } static void usb6fire_midi_out_drain(struct snd_rawmidi_substream *alsa_sub) @@ -129,14 +116,12 @@ static void usb6fire_midi_in_trigger( struct snd_rawmidi_substream *alsa_sub, int up) { struct midi_runtime *rt = alsa_sub->rmidi->private_data; - unsigned long flags; - spin_lock_irqsave(&rt->in_lock, flags); + guard(spinlock_irqsave)(&rt->in_lock); if (up) rt->in = alsa_sub; else rt->in = NULL; - spin_unlock_irqrestore(&rt->in_lock, flags); } static const struct snd_rawmidi_ops out_ops = { @@ -187,7 +172,7 @@ int usb6fire_midi_init(struct sfire_chip *chip) return ret; } rt->instance->private_data = rt; - strcpy(rt->instance->name, "DMX6FireUSB MIDI"); + strscpy(rt->instance->name, "DMX6FireUSB MIDI"); rt->instance->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; diff --git a/sound/usb/6fire/midi.h b/sound/usb/6fire/midi.h index 84851b9f5559..47640c845903 100644 --- a/sound/usb/6fire/midi.h +++ b/sound/usb/6fire/midi.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Linux driver for TerraTec DMX 6Fire USB * * Author: Torsten Schenk <torsten.schenk@zoho.com> * Created: Jan 01, 2011 * Copyright: (C) Torsten Schenk - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #ifndef USB6FIRE_MIDI_H diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c index f8ef3e2a8ca0..08515da5dcc8 100644 --- a/sound/usb/6fire/pcm.c +++ b/sound/usb/6fire/pcm.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Linux driver for TerraTec DMX 6Fire USB * @@ -6,11 +7,6 @@ * Author: Torsten Schenk <torsten.schenk@zoho.com> * Created: Jan 01, 2011 * Copyright: (C) Torsten Schenk - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include "pcm.h" @@ -179,7 +175,7 @@ static int usb6fire_pcm_stream_start(struct pcm_runtime *rt) } } - /* wait for first out urb to return (sent in in urb handler) */ + /* wait for first out urb to return (sent in urb handler) */ wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond, HZ); if (rt->stream_wait_cond) @@ -293,7 +289,7 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) struct pcm_urb *out_urb = in_urb->peer; struct pcm_runtime *rt = in_urb->chip->pcm; struct pcm_substream *sub; - unsigned long flags; + bool period_elapsed; int total_length = 0; int frame_count; int frame; @@ -317,17 +313,18 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) /* receive our capture data */ sub = &rt->capture; - spin_lock_irqsave(&sub->lock, flags); - if (sub->active) { - usb6fire_pcm_capture(sub, in_urb); - if (sub->period_off >= sub->instance->runtime->period_size) { - sub->period_off %= sub->instance->runtime->period_size; - spin_unlock_irqrestore(&sub->lock, flags); - snd_pcm_period_elapsed(sub->instance); - } else - spin_unlock_irqrestore(&sub->lock, flags); - } else - spin_unlock_irqrestore(&sub->lock, flags); + period_elapsed = false; + scoped_guard(spinlock_irqsave, &sub->lock) { + if (sub->active) { + usb6fire_pcm_capture(sub, in_urb); + if (sub->period_off >= sub->instance->runtime->period_size) { + sub->period_off %= sub->instance->runtime->period_size; + period_elapsed = true; + } + } + } + if (period_elapsed) + snd_pcm_period_elapsed(sub->instance); /* setup out urb structure */ for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { @@ -342,17 +339,18 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) /* now send our playback data (if a free out urb was found) */ sub = &rt->playback; - spin_lock_irqsave(&sub->lock, flags); - if (sub->active) { - usb6fire_pcm_playback(sub, out_urb); - if (sub->period_off >= sub->instance->runtime->period_size) { - sub->period_off %= sub->instance->runtime->period_size; - spin_unlock_irqrestore(&sub->lock, flags); - snd_pcm_period_elapsed(sub->instance); - } else - spin_unlock_irqrestore(&sub->lock, flags); - } else - spin_unlock_irqrestore(&sub->lock, flags); + period_elapsed = false; + scoped_guard(spinlock_irqsave, &sub->lock) { + if (sub->active) { + usb6fire_pcm_playback(sub, out_urb); + if (sub->period_off >= sub->instance->runtime->period_size) { + sub->period_off %= sub->instance->runtime->period_size; + period_elapsed = true; + } + } + } + if (period_elapsed) + snd_pcm_period_elapsed(sub->instance); /* setup the 4th byte of each sample (0x40 for analog channels) */ dest = out_urb->buffer; @@ -396,7 +394,7 @@ static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub) if (rt->panic) return -EPIPE; - mutex_lock(&rt->stream_mutex); + guard(mutex)(&rt->stream_mutex); alsa_rt->hw = pcm_hw; if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) { @@ -412,14 +410,12 @@ static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub) } if (!sub) { - mutex_unlock(&rt->stream_mutex); dev_err(&rt->chip->dev->dev, "invalid stream type.\n"); return -EINVAL; } sub->instance = alsa_sub; sub->active = false; - mutex_unlock(&rt->stream_mutex); return 0; } @@ -427,18 +423,17 @@ static int usb6fire_pcm_close(struct snd_pcm_substream *alsa_sub) { struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub); - unsigned long flags; if (rt->panic) return 0; - mutex_lock(&rt->stream_mutex); + guard(mutex)(&rt->stream_mutex); if (sub) { /* deactivate substream */ - spin_lock_irqsave(&sub->lock, flags); - sub->instance = NULL; - sub->active = false; - spin_unlock_irqrestore(&sub->lock, flags); + scoped_guard(spinlock_irqsave, &sub->lock) { + sub->instance = NULL; + sub->active = false; + } /* all substreams closed? if so, stop streaming */ if (!rt->playback.instance && !rt->capture.instance) { @@ -446,22 +441,9 @@ static int usb6fire_pcm_close(struct snd_pcm_substream *alsa_sub) rt->rate = ARRAY_SIZE(rates); } } - mutex_unlock(&rt->stream_mutex); return 0; } -static int usb6fire_pcm_hw_params(struct snd_pcm_substream *alsa_sub, - struct snd_pcm_hw_params *hw_params) -{ - return snd_pcm_lib_alloc_vmalloc_buffer(alsa_sub, - params_buffer_bytes(hw_params)); -} - -static int usb6fire_pcm_hw_free(struct snd_pcm_substream *alsa_sub) -{ - return snd_pcm_lib_free_vmalloc_buffer(alsa_sub); -} - static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub) { struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); @@ -474,7 +456,7 @@ static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub) if (!sub) return -ENODEV; - mutex_lock(&rt->stream_mutex); + guard(mutex)(&rt->stream_mutex); sub->dma_off = 0; sub->period_off = 0; @@ -483,7 +465,6 @@ static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub) if (alsa_rt->rate == rates[rt->rate]) break; if (rt->rate == ARRAY_SIZE(rates)) { - mutex_unlock(&rt->stream_mutex); dev_err(&rt->chip->dev->dev, "invalid rate %d in prepare.\n", alsa_rt->rate); @@ -491,19 +472,15 @@ static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub) } ret = usb6fire_pcm_set_rate(rt); - if (ret) { - mutex_unlock(&rt->stream_mutex); + if (ret) return ret; - } ret = usb6fire_pcm_stream_start(rt); if (ret) { - mutex_unlock(&rt->stream_mutex); dev_err(&rt->chip->dev->dev, "could not start pcm stream.\n"); return ret; } } - mutex_unlock(&rt->stream_mutex); return 0; } @@ -511,26 +488,22 @@ static int usb6fire_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd) { struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub); struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); - unsigned long flags; if (rt->panic) return -EPIPE; if (!sub) return -ENODEV; + guard(spinlock_irqsave)(&sub->lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - spin_lock_irqsave(&sub->lock, flags); sub->active = true; - spin_unlock_irqrestore(&sub->lock, flags); return 0; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - spin_lock_irqsave(&sub->lock, flags); sub->active = false; - spin_unlock_irqrestore(&sub->lock, flags); return 0; default: @@ -543,28 +516,22 @@ static snd_pcm_uframes_t usb6fire_pcm_pointer( { struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub); struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); - unsigned long flags; snd_pcm_uframes_t ret; if (rt->panic || !sub) return SNDRV_PCM_POS_XRUN; - spin_lock_irqsave(&sub->lock, flags); + guard(spinlock_irqsave)(&sub->lock); ret = sub->dma_off; - spin_unlock_irqrestore(&sub->lock, flags); return ret; } static const struct snd_pcm_ops pcm_ops = { .open = usb6fire_pcm_open, .close = usb6fire_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = usb6fire_pcm_hw_params, - .hw_free = usb6fire_pcm_hw_free, .prepare = usb6fire_pcm_prepare, .trigger = usb6fire_pcm_trigger, .pointer = usb6fire_pcm_pointer, - .page = snd_pcm_lib_get_vmalloc_page, }; static void usb6fire_pcm_init_urb(struct pcm_urb *urb, @@ -660,17 +627,11 @@ int usb6fire_pcm_init(struct sfire_chip *chip) } pcm->private_data = rt; - strcpy(pcm->name, "DMX 6Fire USB"); + strscpy(pcm->name, "DMX 6Fire USB"); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); - if (ret) { - usb6fire_pcm_buffers_destroy(rt); - kfree(rt); - dev_err(&chip->dev->dev, - "error preallocating pcm buffers.\n"); - return ret; - } rt->instance = pcm; chip->pcm = rt; diff --git a/sound/usb/6fire/pcm.h b/sound/usb/6fire/pcm.h index f5779d6182c6..5a092dfd69f5 100644 --- a/sound/usb/6fire/pcm.h +++ b/sound/usb/6fire/pcm.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Linux driver for TerraTec DMX 6Fire USB * * Author: Torsten Schenk <torsten.schenk@zoho.com> * Created: Jan 01, 2011 * Copyright: (C) Torsten Schenk - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #ifndef USB6FIRE_PCM_H diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig index f61b5662bb89..9b890abd96d3 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only # ALSA USB drivers menuconfig SND_USB @@ -14,7 +15,9 @@ config SND_USB_AUDIO select SND_HWDEP select SND_RAWMIDI select SND_PCM + select SND_UMP if SND_USB_AUDIO_MIDI_V2 select BITREVERSE + select SND_USB_AUDIO_USE_MEDIA_CONTROLLER if MEDIA_CONTROLLER && (MEDIA_SUPPORT=y || MEDIA_SUPPORT=SND_USB_AUDIO) help Say Y here to include support for USB audio and USB MIDI devices. @@ -22,6 +25,19 @@ config SND_USB_AUDIO To compile this driver as a module, choose M here: the module will be called snd-usb-audio. +config SND_USB_AUDIO_MIDI_V2 + bool "MIDI 2.0 support by USB Audio driver" + depends on SND_USB_AUDIO + help + Say Y here to include the support for MIDI 2.0 by USB Audio driver. + When the config is set, the driver tries to probe MIDI 2.0 interface + at first, then falls back to MIDI 1.0 interface as default. + The MIDI 2.0 support can be disabled dynamically via midi2_enable + module option, too. + +config SND_USB_AUDIO_USE_MEDIA_CONTROLLER + bool + config SND_USB_UA101 tristate "Edirol UA-101/UA-1000 driver" select SND_PCM @@ -101,25 +117,37 @@ config SND_USB_US122L To compile this driver as a module, choose M here: the module will be called snd-usb-us122l. +config SND_USB_US144MKII + tristate "Tascam US-144MKII USB driver" + depends on X86 || COMPILE_TEST + select SND_RAWMIDI + select SND_PCM + help + Say Y here to include support for Tascam US-144MKII USB Audio/MIDI + interface. + + To compile this driver as a module, choose M here: the module + will be called snd-usb-us144mkii. + config SND_USB_6FIRE - tristate "TerraTec DMX 6Fire USB" - select FW_LOADER - select BITREVERSE - select SND_RAWMIDI - select SND_PCM - select SND_VMASTER - help - Say Y here to include support for TerraTec 6fire DMX USB interface. - - You will need firmware files in order to be able to use the device - after it has been coldstarted. An install script for the firmware - and further help can be found at - http://sixfireusb.sourceforge.net + tristate "TerraTec DMX 6Fire USB" + select FW_LOADER + select BITREVERSE + select SND_RAWMIDI + select SND_PCM + select SND_VMASTER + help + Say Y here to include support for TerraTec 6fire DMX USB interface. + + You will need firmware files in order to be able to use the device + after it has been coldstarted. An install script for the firmware + and further help can be found at + http://sixfireusb.sourceforge.net config SND_USB_HIFACE - tristate "M2Tech hiFace USB-SPDIF driver" - select SND_PCM - help + tristate "M2Tech hiFace USB-SPDIF driver" + select SND_PCM + help Select this option to include support for M2Tech hiFace USB-SPDIF interface. @@ -160,6 +188,21 @@ config SND_BCD2000 To compile this driver as a module, choose M here: the module will be called snd-bcd2000. +config SND_USB_AUDIO_QMI + tristate "Qualcomm Audio Offload driver" + depends on QCOM_QMI_HELPERS && SND_USB_AUDIO && SND_SOC_USB + depends on USB_XHCI_HCD && USB_XHCI_SIDEBAND + help + Say Y here to enable the Qualcomm USB audio offloading feature. + + This module sets up the required QMI stream enable/disable + responses to requests generated by the audio DSP. It passes the + USB transfer resource references, so that the audio DSP can issue + USB transfers to the host controller. + + To compile this driver as a module, choose M here: the module + will be called snd-usb-audio-qmi. + source "sound/usb/line6/Kconfig" endif # SND_USB diff --git a/sound/usb/Makefile b/sound/usb/Makefile index d330f74c90e6..e62794a87e73 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -3,22 +3,30 @@ # Makefile for ALSA # -snd-usb-audio-objs := card.o \ +snd-usb-audio-y := card.o \ clock.o \ endpoint.o \ + fcp.o \ format.o \ helper.o \ + implicit.o \ mixer.o \ mixer_quirks.o \ mixer_scarlett.o \ + mixer_scarlett2.o \ mixer_us16x08.o \ + mixer_s1810c.o \ pcm.o \ power.o \ proc.o \ quirks.o \ - stream.o + stream.o \ + validate.o -snd-usbmidi-lib-objs := midi.o +snd-usb-audio-$(CONFIG_SND_USB_AUDIO_MIDI_V2) += midi2.o +snd-usb-audio-$(CONFIG_SND_USB_AUDIO_USE_MEDIA_CONTROLLER) += media.o + +snd-usbmidi-lib-y := midi.o # Toplevel Module Dependency obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usbmidi-lib.o @@ -27,5 +35,5 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o -obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/ +obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/ qcom/ obj-$(CONFIG_SND_USB_LINE6) += line6/ diff --git a/sound/usb/bcd2000/Makefile b/sound/usb/bcd2000/Makefile index f09ccc0af6ff..e2d916e24787 100644 --- a/sound/usb/bcd2000/Makefile +++ b/sound/usb/bcd2000/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only snd-bcd2000-y := bcd2000.o -obj-$(CONFIG_SND_BCD2000) += snd-bcd2000.o
\ No newline at end of file +obj-$(CONFIG_SND_BCD2000) += snd-bcd2000.o diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c index d6c8b29fe430..bebb48cb9abc 100644 --- a/sound/usb/bcd2000/bcd2000.c +++ b/sound/usb/bcd2000/bcd2000.c @@ -1,17 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Behringer BCD2000 driver * * Copyright (C) 2014 Mario Kicherer (dev@kicherer.org) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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/kernel.h> @@ -34,9 +25,9 @@ static const struct usb_device_id id_table[] = { { }, }; -static unsigned char device_cmd_prefix[] = {0x03, 0x00}; +static const unsigned char device_cmd_prefix[] = {0x03, 0x00}; -static unsigned char bcd2000_init_sequence[] = { +static const unsigned char bcd2000_init_sequence[] = { 0x07, 0x00, 0x00, 0x00, 0x78, 0x48, 0x1c, 0x81, 0xc4, 0x00, 0x00, 0x00, 0x5e, 0x53, 0x4a, 0xf7, 0x18, 0xfa, 0x11, 0xff, 0x6c, 0xf3, 0x90, 0xff, @@ -309,7 +300,7 @@ static int bcd2000_init_midi(struct bcd2000 *bcd2k) if (ret < 0) return ret; - strlcpy(rmidi->name, bcd2k->card->shortname, sizeof(rmidi->name)); + strscpy(rmidi->name, bcd2k->card->shortname, sizeof(rmidi->name)); rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX; rmidi->private_data = bcd2k; @@ -357,7 +348,8 @@ static int bcd2000_init_midi(struct bcd2000 *bcd2k) static void bcd2000_free_usb_related_resources(struct bcd2000 *bcd2k, struct usb_interface *interface) { - /* usb_kill_urb not necessary, urb is aborted automatically */ + usb_kill_urb(bcd2k->midi_out_urb); + usb_kill_urb(bcd2k->midi_in_urb); usb_free_urb(bcd2k->midi_out_urb); usb_free_urb(bcd2k->midi_in_urb); @@ -377,23 +369,19 @@ static int bcd2000_probe(struct usb_interface *interface, char usb_path[32]; int err; - mutex_lock(&devices_mutex); + guard(mutex)(&devices_mutex); for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) if (!test_bit(card_index, devices_used)) break; - if (card_index >= SNDRV_CARDS) { - mutex_unlock(&devices_mutex); + if (card_index >= SNDRV_CARDS) return -ENOENT; - } err = snd_card_new(&interface->dev, index[card_index], id[card_index], THIS_MODULE, sizeof(*bcd2k), &card); - if (err < 0) { - mutex_unlock(&devices_mutex); + if (err < 0) return err; - } bcd2k = card->private_data; bcd2k->dev = interface_to_usbdev(interface); @@ -403,8 +391,8 @@ static int bcd2000_probe(struct usb_interface *interface, snd_card_set_dev(card, &interface->dev); - strncpy(card->driver, "snd-bcd2000", sizeof(card->driver)); - strncpy(card->shortname, "BCD2000", sizeof(card->shortname)); + strscpy(card->driver, "snd-bcd2000", sizeof(card->driver)); + strscpy(card->shortname, "BCD2000", sizeof(card->shortname)); usb_make_path(bcd2k->dev, usb_path, sizeof(usb_path)); snprintf(bcd2k->card->longname, sizeof(bcd2k->card->longname), "Behringer BCD2000 at %s", @@ -421,14 +409,12 @@ static int bcd2000_probe(struct usb_interface *interface, usb_set_intfdata(interface, bcd2k); set_bit(card_index, devices_used); - mutex_unlock(&devices_mutex); return 0; probe_error: dev_info(&bcd2k->dev->dev, PREFIX "error during probing"); bcd2000_free_usb_related_resources(bcd2k, interface); snd_card_free(card); - mutex_unlock(&devices_mutex); return err; } @@ -439,7 +425,7 @@ static void bcd2000_disconnect(struct usb_interface *interface) if (!bcd2k) return; - mutex_lock(&devices_mutex); + guard(mutex)(&devices_mutex); /* make sure that userspace cannot create new requests */ snd_card_disconnect(bcd2k->card); @@ -449,8 +435,6 @@ static void bcd2000_disconnect(struct usb_interface *interface) clear_bit(bcd2k->card_index, devices_used); snd_card_free_when_closed(bcd2k->card); - - mutex_unlock(&devices_mutex); } static struct usb_driver bcd2000_driver = { diff --git a/sound/usb/caiaq/Makefile b/sound/usb/caiaq/Makefile index 388999653aaa..9a99c17a2c1b 100644 --- a/sound/usb/caiaq/Makefile +++ b/sound/usb/caiaq/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only snd-usb-caiaq-y := device.o audio.o midi.o control.o snd-usb-caiaq-$(CONFIG_SND_USB_CAIAQ_INPUT) += input.o diff --git a/sound/usb/caiaq/audio.c b/sound/usb/caiaq/audio.c index c6108a3d7f8f..95d425dd9d70 100644 --- a/sound/usb/caiaq/audio.c +++ b/sound/usb/caiaq/audio.c @@ -1,19 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2006-2008 Daniel Mack, Karsten Wiese - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/device.h> @@ -43,7 +30,7 @@ #define MAKE_CHECKBYTE(cdev,stream,i) \ (stream << 1) | (~(i / (cdev->n_streams * BYTES_PER_SAMPLE_USB)) & 1) -static struct snd_pcm_hardware snd_usb_caiaq_pcm_hardware = { +static const struct snd_pcm_hardware snd_usb_caiaq_pcm_hardware = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER), .formats = SNDRV_PCM_FMTBIT_S24_3BE, @@ -64,29 +51,24 @@ static void activate_substream(struct snd_usb_caiaqdev *cdev, struct snd_pcm_substream *sub) { - spin_lock(&cdev->spinlock); + guard(spinlock)(&cdev->spinlock); if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) cdev->sub_playback[sub->number] = sub; else cdev->sub_capture[sub->number] = sub; - - spin_unlock(&cdev->spinlock); } static void deactivate_substream(struct snd_usb_caiaqdev *cdev, struct snd_pcm_substream *sub) { - unsigned long flags; - spin_lock_irqsave(&cdev->spinlock, flags); + guard(spinlock_irqsave)(&cdev->spinlock); if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) cdev->sub_playback[sub->number] = NULL; else cdev->sub_capture[sub->number] = NULL; - - spin_unlock_irqrestore(&cdev->spinlock, flags); } static int @@ -180,31 +162,16 @@ static int snd_usb_caiaq_substream_close(struct snd_pcm_substream *substream) return 0; } -static int snd_usb_caiaq_pcm_hw_params(struct snd_pcm_substream *sub, - struct snd_pcm_hw_params *hw_params) -{ - return snd_pcm_lib_alloc_vmalloc_buffer(sub, - params_buffer_bytes(hw_params)); -} - static int snd_usb_caiaq_pcm_hw_free(struct snd_pcm_substream *sub) { struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub); deactivate_substream(cdev, sub); - return snd_pcm_lib_free_vmalloc_buffer(sub); + return 0; } -/* this should probably go upstream */ -#if SNDRV_PCM_RATE_5512 != 1 << 0 || SNDRV_PCM_RATE_192000 != 1 << 12 -#error "Change this table" -#endif - -static unsigned int rates[] = { 5512, 8000, 11025, 16000, 22050, 32000, 44100, - 48000, 64000, 88200, 96000, 176400, 192000 }; - static int snd_usb_caiaq_pcm_prepare(struct snd_pcm_substream *substream) { - int bytes_per_sample, bpp, ret, i; + int bytes_per_sample, bpp, ret; int index = substream->number; struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; @@ -253,10 +220,7 @@ static int snd_usb_caiaq_pcm_prepare(struct snd_pcm_substream *substream) /* the first client that opens a stream defines the sample rate * setting for all subsequent calls, until the last client closed. */ - for (i=0; i < ARRAY_SIZE(rates); i++) - if (runtime->rate == rates[i]) - cdev->pcm_info.rates = 1 << i; - + cdev->pcm_info.rates = snd_pcm_rate_to_rate_bit(runtime->rate); snd_pcm_limit_hw_rates(runtime); bytes_per_sample = BYTES_PER_SAMPLE; @@ -316,38 +280,28 @@ snd_usb_caiaq_pcm_pointer(struct snd_pcm_substream *sub) { int index = sub->number; struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub); - snd_pcm_uframes_t ptr; - spin_lock(&cdev->spinlock); + guard(spinlock)(&cdev->spinlock); - if (cdev->input_panic || cdev->output_panic) { - ptr = SNDRV_PCM_POS_XRUN; - goto unlock; - } + if (cdev->input_panic || cdev->output_panic) + return SNDRV_PCM_POS_XRUN; if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) - ptr = bytes_to_frames(sub->runtime, - cdev->audio_out_buf_pos[index]); + return bytes_to_frames(sub->runtime, + cdev->audio_out_buf_pos[index]); else - ptr = bytes_to_frames(sub->runtime, - cdev->audio_in_buf_pos[index]); - -unlock: - spin_unlock(&cdev->spinlock); - return ptr; + return bytes_to_frames(sub->runtime, + cdev->audio_in_buf_pos[index]); } /* operators for both playback and capture */ static const struct snd_pcm_ops snd_usb_caiaq_ops = { .open = snd_usb_caiaq_substream_open, .close = snd_usb_caiaq_substream_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_usb_caiaq_pcm_hw_params, .hw_free = snd_usb_caiaq_pcm_hw_free, .prepare = snd_usb_caiaq_pcm_prepare, .trigger = snd_usb_caiaq_pcm_trigger, .pointer = snd_usb_caiaq_pcm_pointer, - .page = snd_pcm_lib_get_vmalloc_page, }; static void check_for_elapsed_periods(struct snd_usb_caiaqdev *cdev, @@ -635,7 +589,6 @@ static void read_completed(struct urb *urb) struct device *dev; struct urb *out = NULL; int i, frame, len, send_it = 0, outframe = 0; - unsigned long flags; size_t offset = 0; if (urb->status || !info) @@ -672,10 +625,10 @@ static void read_completed(struct urb *urb) offset += len; if (len > 0) { - spin_lock_irqsave(&cdev->spinlock, flags); - fill_out_urb(cdev, out, &out->iso_frame_desc[outframe]); - read_in_urb(cdev, urb, &urb->iso_frame_desc[frame]); - spin_unlock_irqrestore(&cdev->spinlock, flags); + scoped_guard(spinlock_irqsave, &cdev->spinlock) { + fill_out_urb(cdev, out, &out->iso_frame_desc[outframe]); + read_in_urb(cdev, urb, &urb->iso_frame_desc[frame]); + } check_for_elapsed_periods(cdev, cdev->sub_playback); check_for_elapsed_periods(cdev, cdev->sub_capture); send_it = 1; @@ -827,7 +780,7 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev) } cdev->pcm->private_data = cdev; - strlcpy(cdev->pcm->name, cdev->product_name, sizeof(cdev->pcm->name)); + strscpy(cdev->pcm->name, cdev->product_name, sizeof(cdev->pcm->name)); memset(cdev->sub_playback, 0, sizeof(cdev->sub_playback)); memset(cdev->sub_capture, 0, sizeof(cdev->sub_capture)); @@ -843,7 +796,7 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev) case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_SESSIONIO): case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_GUITARRIGMOBILE): cdev->samplerates |= SNDRV_PCM_RATE_192000; - /* fall thru */ + fallthrough; case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO2DJ): case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ): case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ): @@ -856,6 +809,8 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev) &snd_usb_caiaq_ops); snd_pcm_set_ops(cdev->pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usb_caiaq_ops); + snd_pcm_set_managed_buffer_all(cdev->pcm, SNDRV_DMA_TYPE_VMALLOC, + NULL, 0, 0); cdev->data_cb_info = kmalloc_array(N_URBS, sizeof(struct snd_usb_caiaq_cb_info), @@ -890,14 +845,20 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev) return 0; } -void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev) +void snd_usb_caiaq_audio_disconnect(struct snd_usb_caiaqdev *cdev) { struct device *dev = caiaqdev_to_dev(cdev); dev_dbg(dev, "%s(%p)\n", __func__, cdev); stream_stop(cdev); +} + +void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev) +{ + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p)\n", __func__, cdev); free_urbs(cdev->data_urbs_in); free_urbs(cdev->data_urbs_out); kfree(cdev->data_cb_info); } - diff --git a/sound/usb/caiaq/audio.h b/sound/usb/caiaq/audio.h index 869bf6264d6a..07f5d064456c 100644 --- a/sound/usb/caiaq/audio.h +++ b/sound/usb/caiaq/audio.h @@ -3,6 +3,7 @@ #define CAIAQ_AUDIO_H int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev); +void snd_usb_caiaq_audio_disconnect(struct snd_usb_caiaqdev *cdev); void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev); #endif /* CAIAQ_AUDIO_H */ diff --git a/sound/usb/caiaq/control.c b/sound/usb/caiaq/control.c index b7a7c805d63f..af459c49baf4 100644 --- a/sound/usb/caiaq/control.c +++ b/sound/usb/caiaq/control.c @@ -1,20 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2007 Daniel Mack * friendly supported by NI. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/device.h> @@ -176,14 +163,14 @@ struct caiaq_controller { int index; }; -static struct caiaq_controller ak1_controller[] = { +static const struct caiaq_controller ak1_controller[] = { { "LED left", 2 }, { "LED middle", 1 }, { "LED right", 0 }, { "LED ring", 3 } }; -static struct caiaq_controller rk2_controller[] = { +static const struct caiaq_controller rk2_controller[] = { { "LED 1", 5 }, { "LED 2", 4 }, { "LED 3", 3 }, @@ -209,7 +196,7 @@ static struct caiaq_controller rk2_controller[] = { { "LED 7seg_3g", 23 } }; -static struct caiaq_controller rk3_controller[] = { +static const struct caiaq_controller rk3_controller[] = { { "LED 7seg_1a", 0 + 0 }, { "LED 7seg_1b", 0 + 1 }, { "LED 7seg_1c", 0 + 2 }, @@ -257,7 +244,7 @@ static struct caiaq_controller rk3_controller[] = { { "LED pedal", 32 + 8 } }; -static struct caiaq_controller kore_controller[] = { +static const struct caiaq_controller kore_controller[] = { { "LED F1", 8 | CNT_INTVAL }, { "LED F2", 12 | CNT_INTVAL }, { "LED F3", 0 | CNT_INTVAL }, @@ -291,7 +278,7 @@ static struct caiaq_controller kore_controller[] = { { "LED control", 26 | CNT_INTVAL } }; -static struct caiaq_controller a8dj_controller[] = { +static const struct caiaq_controller a8dj_controller[] = { { "Current input mode", 0 | CNT_INTVAL }, { "GND lift for TC Vinyl mode", 24 + 0 }, { "GND lift for TC CD/Line mode", 24 + 1 }, @@ -299,11 +286,11 @@ static struct caiaq_controller a8dj_controller[] = { { "Software lock", 40 } }; -static struct caiaq_controller a4dj_controller[] = { +static const struct caiaq_controller a4dj_controller[] = { { "Current input mode", 0 | CNT_INTVAL } }; -static struct caiaq_controller kontrolx1_controller[] = { +static const struct caiaq_controller kontrolx1_controller[] = { { "LED FX A: ON", 7 | CNT_INTVAL }, { "LED FX A: 1", 6 | CNT_INTVAL }, { "LED FX A: 2", 5 | CNT_INTVAL }, @@ -340,7 +327,7 @@ static struct caiaq_controller kontrolx1_controller[] = { { "LED Deck B: SYNC", 8 | CNT_INTVAL }, }; -static struct caiaq_controller kontrols4_controller[] = { +static const struct caiaq_controller kontrols4_controller[] = { { "LED: Master: Quant", 10 | CNT_INTVAL }, { "LED: Master: Headphone", 11 | CNT_INTVAL }, { "LED: Master: Master", 12 | CNT_INTVAL }, @@ -513,7 +500,7 @@ static struct caiaq_controller kontrols4_controller[] = { { "LED: FX2: Mode", 133 | CNT_INTVAL }, }; -static struct caiaq_controller maschine_controller[] = { +static const struct caiaq_controller maschine_controller[] = { { "LED: Pad 1", 3 | CNT_INTVAL }, { "LED: Pad 2", 2 | CNT_INTVAL }, { "LED: Pad 3", 1 | CNT_INTVAL }, @@ -581,7 +568,7 @@ static struct caiaq_controller maschine_controller[] = { { "Backlight Display", 59 | CNT_INTVAL } }; -static int add_controls(struct caiaq_controller *c, int num, +static int add_controls(const struct caiaq_controller *c, int num, struct snd_usb_caiaqdev *cdev) { int i, ret; diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index f4a72e39ffa9..dfd820483849 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -1,22 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * caiaq.c: ALSA driver for caiaq/NativeInstruments devices * * Copyright (c) 2007 Daniel Mack <daniel@caiaq.de> * Karsten Wiese <fzu@wemgehoertderstaat.de> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/moduleparam.h> @@ -39,20 +26,6 @@ MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>"); MODULE_DESCRIPTION("caiaq USB audio"); MODULE_LICENSE("GPL"); -MODULE_SUPPORTED_DEVICE("{{Native Instruments,RigKontrol2}," - "{Native Instruments,RigKontrol3}," - "{Native Instruments,Kore Controller}," - "{Native Instruments,Kore Controller 2}," - "{Native Instruments,Audio Kontrol 1}," - "{Native Instruments,Audio 2 DJ}," - "{Native Instruments,Audio 4 DJ}," - "{Native Instruments,Audio 8 DJ}," - "{Native Instruments,Traktor Audio 2}," - "{Native Instruments,Session I/O}," - "{Native Instruments,GuitarRig mobile}," - "{Native Instruments,Traktor Kontrol X1}," - "{Native Instruments,Traktor Kontrol S4}," - "{Native Instruments,Maschine Controller}}"); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */ static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */ @@ -200,7 +173,7 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb) break; } #ifdef CONFIG_SND_USB_CAIAQ_INPUT - /* fall through */ + fallthrough; case EP1_CMD_READ_ERP: case EP1_CMD_READ_ANALOG: snd_usb_caiaq_input_dispatch(cdev, buf, urb->actual_length); @@ -403,6 +376,17 @@ static void setup_card(struct snd_usb_caiaqdev *cdev) dev_err(dev, "Unable to set up control system (ret=%d)\n", ret); } +static void card_free(struct snd_card *card) +{ + struct snd_usb_caiaqdev *cdev = caiaqdev(card); + +#ifdef CONFIG_SND_USB_CAIAQ_INPUT + snd_usb_caiaq_input_free(cdev); +#endif + snd_usb_caiaq_audio_free(cdev); + usb_reset_device(cdev->chip.dev); +} + static int create_card(struct usb_device *usb_dev, struct usb_interface *intf, struct snd_card **cardp) @@ -490,9 +474,9 @@ static int init_card(struct snd_usb_caiaqdev *cdev) usb_string(usb_dev, usb_dev->descriptor.iProduct, cdev->product_name, CAIAQ_USB_STR_LEN); - strlcpy(card->driver, MODNAME, sizeof(card->driver)); - strlcpy(card->shortname, cdev->product_name, sizeof(card->shortname)); - strlcpy(card->mixername, cdev->product_name, sizeof(card->mixername)); + strscpy(card->driver, MODNAME, sizeof(card->driver)); + strscpy(card->shortname, cdev->product_name, sizeof(card->shortname)); + strscpy(card->mixername, cdev->product_name, sizeof(card->mixername)); /* if the id was not passed as module option, fill it with a shortened * version of the product string which does not contain any @@ -512,10 +496,11 @@ static int init_card(struct snd_usb_caiaqdev *cdev) } usb_make_path(usb_dev, usbpath, sizeof(usbpath)); - snprintf(card->longname, sizeof(card->longname), "%s %s (%s)", + scnprintf(card->longname, sizeof(card->longname), "%s %s (%s)", cdev->vendor_name, cdev->product_name, usbpath); setup_card(cdev); + card->private_free = card_free; return 0; err_kill_urb: @@ -561,15 +546,14 @@ static void snd_disconnect(struct usb_interface *intf) snd_card_disconnect(card); #ifdef CONFIG_SND_USB_CAIAQ_INPUT - snd_usb_caiaq_input_free(cdev); + snd_usb_caiaq_input_disconnect(cdev); #endif - snd_usb_caiaq_audio_free(cdev); + snd_usb_caiaq_audio_disconnect(cdev); usb_kill_urb(&cdev->ep1_in_urb); usb_kill_urb(&cdev->midi_out_urb); - snd_card_free(card); - usb_reset_device(interface_to_usbdev(intf)); + snd_card_free_when_closed(card); } diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h index 50fea085765b..743eb0387b5f 100644 --- a/sound/usb/caiaq/device.h +++ b/sound/usb/caiaq/device.h @@ -53,7 +53,7 @@ struct caiaq_device_spec { unsigned char num_midi_out; unsigned char num_midi_in; unsigned char data_alignment; -} __attribute__ ((packed)); +} __packed; struct snd_usb_caiaq_cb_info; diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c index e883659ea6e7..a9130891bb69 100644 --- a/sound/usb/caiaq/input.c +++ b/sound/usb/caiaq/input.c @@ -1,19 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2006,2007 Daniel Mack, Tim Ruetz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/device.h> @@ -27,13 +14,13 @@ #include "device.h" #include "input.h" -static unsigned short keycode_ak1[] = { KEY_C, KEY_B, KEY_A }; -static unsigned short keycode_rk2[] = { KEY_1, KEY_2, KEY_3, KEY_4, +static const unsigned short keycode_ak1[] = { KEY_C, KEY_B, KEY_A }; +static const unsigned short keycode_rk2[] = { KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7 }; -static unsigned short keycode_rk3[] = { KEY_1, KEY_2, KEY_3, KEY_4, +static const unsigned short keycode_rk3[] = { KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9 }; -static unsigned short keycode_kore[] = { +static const unsigned short keycode_kore[] = { KEY_FN_F1, /* "menu" */ KEY_FN_F7, /* "lcd backlight */ KEY_FN_F2, /* "control" */ @@ -73,7 +60,7 @@ static unsigned short keycode_kore[] = { #define MASCHINE_PADS (16) #define MASCHINE_PAD(X) ((X) + ABS_PRESSURE) -static unsigned short keycode_maschine[] = { +static const unsigned short keycode_maschine[] = { MASCHINE_BUTTON(40), /* mute */ MASCHINE_BUTTON(39), /* solo */ MASCHINE_BUTTON(38), /* select */ @@ -817,6 +804,7 @@ int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev) default: /* no input methods supported on this device */ + ret = -EINVAL; goto exit_free_idev; } @@ -841,15 +829,21 @@ exit_free_idev: return ret; } -void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *cdev) +void snd_usb_caiaq_input_disconnect(struct snd_usb_caiaqdev *cdev) { if (!cdev || !cdev->input_dev) return; usb_kill_urb(cdev->ep4_in_urb); + input_unregister_device(cdev->input_dev); +} + +void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *cdev) +{ + if (!cdev || !cdev->input_dev) + return; + usb_free_urb(cdev->ep4_in_urb); cdev->ep4_in_urb = NULL; - - input_unregister_device(cdev->input_dev); cdev->input_dev = NULL; } diff --git a/sound/usb/caiaq/input.h b/sound/usb/caiaq/input.h index c42891e7be88..fbe267f85d02 100644 --- a/sound/usb/caiaq/input.h +++ b/sound/usb/caiaq/input.h @@ -4,6 +4,7 @@ void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *cdev, char *buf, unsigned int len); int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev); +void snd_usb_caiaq_input_disconnect(struct snd_usb_caiaqdev *cdev); void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *cdev); #endif diff --git a/sound/usb/caiaq/midi.c b/sound/usb/caiaq/midi.c index f8e5b1b57c4f..c656d0162432 100644 --- a/sound/usb/caiaq/midi.c +++ b/sound/usb/caiaq/midi.c @@ -1,19 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2006,2007 Daniel Mack - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/device.h> @@ -138,7 +125,7 @@ int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *device) if (ret < 0) return ret; - strlcpy(rmidi->name, device->product_name, sizeof(rmidi->name)); + strscpy(rmidi->name, device->product_name, sizeof(rmidi->name)); rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX; rmidi->private_data = device; diff --git a/sound/usb/card.c b/sound/usb/card.c index 746a72e23cf9..270dad84d825 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * (Tentative) USB Audio Driver for ALSA * @@ -9,21 +10,6 @@ * * Audio Class 3.0 support by Ruslan Bilovol <ruslan.bilovol@gmail.com> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * * NOTES: * * - the linked URBs would be preferred but not used so far because of @@ -58,22 +44,21 @@ #include "usbaudio.h" #include "card.h" #include "midi.h" +#include "midi2.h" #include "mixer.h" #include "proc.h" #include "quirks.h" #include "endpoint.h" #include "helper.h" -#include "debug.h" #include "pcm.h" #include "format.h" #include "power.h" #include "stream.h" +#include "media.h" MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); MODULE_DESCRIPTION("USB Audio"); MODULE_LICENSE("GPL"); -MODULE_SUPPORTED_DEVICE("{{Generic,USB Audio}}"); - static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ @@ -84,9 +69,14 @@ static int pid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; static int device_setup[SNDRV_CARDS]; /* device parameter for this card */ static bool ignore_ctl_error; static bool autoclock = true; +static bool lowlatency = true; static char *quirk_alias[SNDRV_CARDS]; +static char *delayed_register[SNDRV_CARDS]; +static bool implicit_fb[SNDRV_CARDS]; +static char *quirk_flags[SNDRV_CARDS]; bool snd_usb_use_vmalloc = true; +bool snd_usb_skip_validation; module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for the USB audio adapter."); @@ -105,10 +95,39 @@ MODULE_PARM_DESC(ignore_ctl_error, "Ignore errors from USB controller for mixer interfaces."); module_param(autoclock, bool, 0444); MODULE_PARM_DESC(autoclock, "Enable auto-clock selection for UAC2 devices (default: yes)."); +module_param(lowlatency, bool, 0444); +MODULE_PARM_DESC(lowlatency, "Enable low latency playback (default: yes)."); module_param_array(quirk_alias, charp, NULL, 0444); MODULE_PARM_DESC(quirk_alias, "Quirk aliases, e.g. 0123abcd:5678beef."); +module_param_array(delayed_register, charp, NULL, 0444); +MODULE_PARM_DESC(delayed_register, "Quirk for delayed registration, given by id:iface, e.g. 0123abcd:4."); +module_param_array(implicit_fb, bool, NULL, 0444); +MODULE_PARM_DESC(implicit_fb, "Apply generic implicit feedback sync mode."); module_param_named(use_vmalloc, snd_usb_use_vmalloc, bool, 0444); MODULE_PARM_DESC(use_vmalloc, "Use vmalloc for PCM intermediate buffers (default: yes)."); +module_param_named(skip_validation, snd_usb_skip_validation, bool, 0444); +MODULE_PARM_DESC(skip_validation, "Skip unit descriptor validation (default: no)."); + +/* protects quirk_flags */ +static DEFINE_MUTEX(quirk_flags_mutex); + +static int param_set_quirkp(const char *val, + const struct kernel_param *kp) +{ + guard(mutex)(&quirk_flags_mutex); + return param_set_charp(val, kp); +} + +static const struct kernel_param_ops param_ops_quirkp = { + .set = param_set_quirkp, + .get = param_get_charp, + .free = param_free_charp, +}; + +#define param_check_quirkp param_check_charp + +module_param_array(quirk_flags, quirkp, NULL, 0644); +MODULE_PARM_DESC(quirk_flags, "Add/modify USB audio quirks"); /* * we keep the snd_usb_audio_t instances by ourselves for merging @@ -118,6 +137,95 @@ MODULE_PARM_DESC(use_vmalloc, "Use vmalloc for PCM intermediate buffers (default static DEFINE_MUTEX(register_mutex); static struct snd_usb_audio *usb_chip[SNDRV_CARDS]; static struct usb_driver usb_audio_driver; +static struct snd_usb_platform_ops *platform_ops; + +/* + * Register platform specific operations that will be notified on events + * which occur in USB SND. The platform driver can utilize this path to + * enable features, such as USB audio offloading, which allows for audio data + * to be queued by an audio DSP. + * + * Only one set of platform operations can be registered to USB SND. The + * platform register operation is protected by the register_mutex. + */ +int snd_usb_register_platform_ops(struct snd_usb_platform_ops *ops) +{ + guard(mutex)(®ister_mutex); + if (platform_ops) + return -EEXIST; + + platform_ops = ops; + return 0; +} +EXPORT_SYMBOL_GPL(snd_usb_register_platform_ops); + +/* + * Unregisters the current set of platform operations. This allows for + * a new set to be registered if required. + * + * The platform unregister operation is protected by the register_mutex. + */ +int snd_usb_unregister_platform_ops(void) +{ + guard(mutex)(®ister_mutex); + platform_ops = NULL; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_usb_unregister_platform_ops); + +/* + * in case the platform driver was not ready at the time of USB SND + * device connect, expose an API to discover all connected USB devices + * so it can populate any dependent resources/structures. + */ +void snd_usb_rediscover_devices(void) +{ + int i; + + guard(mutex)(®ister_mutex); + + if (!platform_ops || !platform_ops->connect_cb) + return; + + for (i = 0; i < SNDRV_CARDS; i++) { + if (usb_chip[i]) + platform_ops->connect_cb(usb_chip[i]); + } +} +EXPORT_SYMBOL_GPL(snd_usb_rediscover_devices); + +/* + * Checks to see if requested audio profile, i.e sample rate, # of + * channels, etc... is supported by the substream associated to the + * USB audio device. + */ +struct snd_usb_stream * +snd_usb_find_suppported_substream(int card_idx, struct snd_pcm_hw_params *params, + int direction) +{ + struct snd_usb_audio *chip; + struct snd_usb_substream *subs; + struct snd_usb_stream *as; + + /* + * Register mutex is held when populating and clearing usb_chip + * array. + */ + guard(mutex)(®ister_mutex); + chip = usb_chip[card_idx]; + + if (chip && enable[card_idx]) { + list_for_each_entry(as, &chip->pcm_list, list) { + subs = &as->substream[direction]; + if (snd_usb_find_substream_format(subs, params)) + return as; + } + } + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_usb_find_suppported_substream); /* * disconnect streams @@ -132,7 +240,6 @@ static void snd_usb_stream_disconnect(struct snd_usb_stream *as) subs = &as->substream[idx]; if (!subs->num_formats) continue; - subs->interface = -1; subs->data_endpoint = NULL; subs->sync_endpoint = NULL; } @@ -180,18 +287,16 @@ static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int int if ((altsd->bInterfaceClass == USB_CLASS_AUDIO || altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) && altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) { - int err = __snd_usbmidi_create(chip->card, iface, - &chip->midi_list, NULL, - chip->usb_id); + int err = snd_usb_midi_v2_create(chip, iface, NULL, + chip->usb_id); if (err < 0) { dev_err(&dev->dev, "%u:%d: cannot create sequencer device\n", ctrlif, interface); return -EINVAL; } - usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L); - - return 0; + return usb_driver_claim_interface(&usb_audio_driver, iface, + USB_AUDIO_IFACE_UNUSED); } if ((altsd->bInterfaceClass != USB_CLASS_AUDIO && @@ -209,9 +314,12 @@ static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int int return -EINVAL; } + snd_usb_add_ctrl_interface_link(chip, interface, ctrlif); + if (! snd_usb_parse_audio_interface(chip, interface)) { usb_set_interface(dev, interface, 0); /* reset the current interface */ - usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L); + return usb_driver_claim_interface(&usb_audio_driver, iface, + USB_AUDIO_IFACE_UNUSED); } return 0; @@ -237,7 +345,7 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) dev_warn(&dev->dev, "unknown interface protocol %#02x, assuming v1\n", protocol); - /* fall through */ + fallthrough; case UAC_VERSION_1: { struct uac1_ac_header_descriptor *h1; @@ -340,6 +448,152 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) } /* + * Profile name preset table + */ +struct usb_audio_device_name { + u32 id; + const char *vendor_name; + const char *product_name; + const char *profile_name; /* override card->longname */ +}; + +#define PROFILE_NAME(vid, pid, vendor, product, profile) \ + { .id = USB_ID(vid, pid), .vendor_name = (vendor), \ + .product_name = (product), .profile_name = (profile) } +#define DEVICE_NAME(vid, pid, vendor, product) \ + PROFILE_NAME(vid, pid, vendor, product, NULL) + +/* vendor/product and profile name presets, sorted in device id order */ +static const struct usb_audio_device_name usb_audio_names[] = { + /* HP Thunderbolt Dock Audio Headset */ + PROFILE_NAME(0x03f0, 0x0269, "HP", "Thunderbolt Dock Audio Headset", + "HP-Thunderbolt-Dock-Audio-Headset"), + /* HP Thunderbolt Dock Audio Module */ + PROFILE_NAME(0x03f0, 0x0567, "HP", "Thunderbolt Dock Audio Module", + "HP-Thunderbolt-Dock-Audio-Module"), + + /* Two entries for Gigabyte TRX40 Aorus Master: + * TRX40 Aorus Master has two USB-audio devices, one for the front + * headphone with ESS SABRE9218 DAC chip, while another for the rest + * I/O (the rear panel and the front mic) with Realtek ALC1220-VB. + * Here we provide two distinct names for making UCM profiles easier. + */ + PROFILE_NAME(0x0414, 0xa000, "Gigabyte", "Aorus Master Front Headphone", + "Gigabyte-Aorus-Master-Front-Headphone"), + PROFILE_NAME(0x0414, 0xa001, "Gigabyte", "Aorus Master Main Audio", + "Gigabyte-Aorus-Master-Main-Audio"), + + /* Gigabyte TRX40 Aorus Pro WiFi */ + PROFILE_NAME(0x0414, 0xa002, + "Realtek", "ALC1220-VB-DT", "Realtek-ALC1220-VB-Desktop"), + + /* Creative/E-Mu devices */ + DEVICE_NAME(0x041e, 0x3010, "Creative Labs", "Sound Blaster MP3+"), + /* Creative/Toshiba Multimedia Center SB-0500 */ + DEVICE_NAME(0x041e, 0x3048, "Toshiba", "SB-0500"), + + /* Logitech Audio Devices */ + DEVICE_NAME(0x046d, 0x0867, "Logitech, Inc.", "Logi-MeetUp"), + DEVICE_NAME(0x046d, 0x0874, "Logitech, Inc.", "Logi-Tap-Audio"), + DEVICE_NAME(0x046d, 0x087c, "Logitech, Inc.", "Logi-Huddle"), + DEVICE_NAME(0x046d, 0x0898, "Logitech, Inc.", "Logi-RB-Audio"), + DEVICE_NAME(0x046d, 0x08d2, "Logitech, Inc.", "Logi-RBM-Audio"), + DEVICE_NAME(0x046d, 0x0990, "Logitech, Inc.", "QuickCam Pro 9000"), + + DEVICE_NAME(0x05e1, 0x0408, "Syntek", "STK1160"), + DEVICE_NAME(0x05e1, 0x0480, "Hauppauge", "Woodbury"), + + /* ASUS ROG Zenith II: this machine has also two devices, one for + * the front headphone and another for the rest + */ + PROFILE_NAME(0x0b05, 0x1915, "ASUS", "Zenith II Front Headphone", + "Zenith-II-Front-Headphone"), + PROFILE_NAME(0x0b05, 0x1916, "ASUS", "Zenith II Main Audio", + "Zenith-II-Main-Audio"), + + /* ASUS ROG Strix */ + PROFILE_NAME(0x0b05, 0x1917, + "Realtek", "ALC1220-VB-DT", "Realtek-ALC1220-VB-Desktop"), + /* ASUS PRIME TRX40 PRO-S */ + PROFILE_NAME(0x0b05, 0x1918, + "Realtek", "ALC1220-VB-DT", "Realtek-ALC1220-VB-Desktop"), + + /* Dell WD15 Dock */ + PROFILE_NAME(0x0bda, 0x4014, "Dell", "WD15 Dock", "Dell-WD15-Dock"), + /* Dell WD19 Dock */ + PROFILE_NAME(0x0bda, 0x402e, "Dell", "WD19 Dock", "Dell-WD15-Dock"), + + DEVICE_NAME(0x0ccd, 0x0028, "TerraTec", "Aureon5.1MkII"), + + /* + * The original product_name is "USB Sound Device", however this name + * is also used by the CM106 based cards, so make it unique. + */ + DEVICE_NAME(0x0d8c, 0x0102, NULL, "ICUSBAUDIO7D"), + DEVICE_NAME(0x0d8c, 0x0103, NULL, "Audio Advantage MicroII"), + + /* MSI TRX40 Creator */ + PROFILE_NAME(0x0db0, 0x0d64, + "Realtek", "ALC1220-VB-DT", "Realtek-ALC1220-VB-Desktop"), + /* MSI TRX40 */ + PROFILE_NAME(0x0db0, 0x543d, + "Realtek", "ALC1220-VB-DT", "Realtek-ALC1220-VB-Desktop"), + + DEVICE_NAME(0x0fd9, 0x0008, "Hauppauge", "HVR-950Q"), + + /* Dock/Stand for HP Engage Go */ + PROFILE_NAME(0x103c, 0x830a, "HP", "HP Engage Go Dock", + "HP-Engage-Go-Dock"), + + /* Stanton/N2IT Final Scratch v1 device ('Scratchamp') */ + DEVICE_NAME(0x103d, 0x0100, "Stanton", "ScratchAmp"), + DEVICE_NAME(0x103d, 0x0101, "Stanton", "ScratchAmp"), + + /* aka. Serato Scratch Live DJ Box */ + DEVICE_NAME(0x13e5, 0x0001, "Rane", "SL-1"), + + /* Lenovo ThinkStation P620 Rear Line-in, Line-out and Microphone */ + PROFILE_NAME(0x17aa, 0x1046, "Lenovo", "ThinkStation P620 Rear", + "Lenovo-ThinkStation-P620-Rear"), + /* Lenovo ThinkStation P620 Internal Speaker + Front Headset */ + PROFILE_NAME(0x17aa, 0x104d, "Lenovo", "ThinkStation P620 Main", + "Lenovo-ThinkStation-P620-Main"), + + /* Asrock TRX40 Creator */ + PROFILE_NAME(0x26ce, 0x0a01, + "Realtek", "ALC1220-VB-DT", "Realtek-ALC1220-VB-Desktop"), + + DEVICE_NAME(0x2040, 0x7200, "Hauppauge", "HVR-950Q"), + DEVICE_NAME(0x2040, 0x7201, "Hauppauge", "HVR-950Q-MXL"), + DEVICE_NAME(0x2040, 0x7210, "Hauppauge", "HVR-950Q"), + DEVICE_NAME(0x2040, 0x7211, "Hauppauge", "HVR-950Q-MXL"), + DEVICE_NAME(0x2040, 0x7213, "Hauppauge", "HVR-950Q"), + DEVICE_NAME(0x2040, 0x7217, "Hauppauge", "HVR-950Q"), + DEVICE_NAME(0x2040, 0x721b, "Hauppauge", "HVR-950Q"), + DEVICE_NAME(0x2040, 0x721e, "Hauppauge", "HVR-950Q"), + DEVICE_NAME(0x2040, 0x721f, "Hauppauge", "HVR-950Q"), + DEVICE_NAME(0x2040, 0x7240, "Hauppauge", "HVR-850"), + DEVICE_NAME(0x2040, 0x7260, "Hauppauge", "HVR-950Q"), + DEVICE_NAME(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), + DEVICE_NAME(0x2040, 0x7280, "Hauppauge", "HVR-950Q"), + DEVICE_NAME(0x2040, 0x7281, "Hauppauge", "HVR-950Q-MXL"), + DEVICE_NAME(0x2040, 0x8200, "Hauppauge", "Woodbury"), + + { } /* terminator */ +}; + +static const struct usb_audio_device_name * +lookup_device_name(u32 id) +{ + static const struct usb_audio_device_name *p; + + for (p = usb_audio_names; p->id; p++) + if (p->id == id) + return p; + return NULL; +} + +/* * free the chip instance * * here we have to do not much, since pcm and controls are already freed @@ -349,10 +603,9 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) static void snd_usb_audio_free(struct snd_card *card) { struct snd_usb_audio *chip = card->private_data; - struct snd_usb_endpoint *ep, *n; - list_for_each_entry_safe(ep, n, &chip->ep_list, list) - snd_usb_endpoint_free(ep); + snd_usb_endpoint_free_all(chip); + snd_usb_midi_v2_free_all(chip); mutex_destroy(&chip->mutex); if (!atomic_read(&chip->shutdown)) @@ -364,10 +617,16 @@ static void usb_audio_make_shortname(struct usb_device *dev, const struct snd_usb_audio_quirk *quirk) { struct snd_card *card = chip->card; - - if (quirk && quirk->product_name && *quirk->product_name) { - strlcpy(card->shortname, quirk->product_name, - sizeof(card->shortname)); + const struct usb_audio_device_name *preset; + const char *s = NULL; + + preset = lookup_device_name(chip->usb_id); + if (preset && preset->product_name) + s = preset->product_name; + else if (quirk && quirk->product_name) + s = quirk->product_name; + if (s && *s) { + strscpy(card->shortname, s, sizeof(card->shortname)); return; } @@ -376,9 +635,10 @@ static void usb_audio_make_shortname(struct usb_device *dev, usb_string(dev, dev->descriptor.iProduct, card->shortname, sizeof(card->shortname)) <= 0) { /* no name available from anywhere, so use ID */ - sprintf(card->shortname, "USB Device %#04x:%#04x", - USB_ID_VENDOR(chip->usb_id), - USB_ID_PRODUCT(chip->usb_id)); + scnprintf(card->shortname, sizeof(card->shortname), + "USB Device %#04x:%#04x", + USB_ID_VENDOR(chip->usb_id), + USB_ID_PRODUCT(chip->usb_id)); } strim(card->shortname); @@ -389,27 +649,35 @@ static void usb_audio_make_longname(struct usb_device *dev, const struct snd_usb_audio_quirk *quirk) { struct snd_card *card = chip->card; + const struct usb_audio_device_name *preset; + const char *s = NULL; int len; + preset = lookup_device_name(chip->usb_id); + /* shortcut - if any pre-defined string is given, use it */ - if (quirk && quirk->profile_name && *quirk->profile_name) { - strlcpy(card->longname, quirk->profile_name, - sizeof(card->longname)); + if (preset && preset->profile_name) + s = preset->profile_name; + if (s && *s) { + strscpy(card->longname, s, sizeof(card->longname)); return; } - if (quirk && quirk->vendor_name && *quirk->vendor_name) { - len = strlcpy(card->longname, quirk->vendor_name, sizeof(card->longname)); + if (preset && preset->vendor_name) + s = preset->vendor_name; + else if (quirk && quirk->vendor_name) + s = quirk->vendor_name; + *card->longname = 0; + if (s && *s) { + strscpy(card->longname, s, sizeof(card->longname)); } else { /* retrieve the vendor and device strings as longname */ if (dev->descriptor.iManufacturer) - len = usb_string(dev, dev->descriptor.iManufacturer, - card->longname, sizeof(card->longname)); - else - len = 0; + usb_string(dev, dev->descriptor.iManufacturer, + card->longname, sizeof(card->longname)); /* we don't really care if there isn't any vendor string */ } - if (len > 0) { + if (*card->longname) { strim(card->longname); if (*card->longname) strlcat(card->longname, " ", sizeof(card->longname)); @@ -443,6 +711,31 @@ static void usb_audio_make_longname(struct usb_device *dev, } } +static void snd_usb_init_quirk_flags(int idx, struct snd_usb_audio *chip) +{ + size_t i; + + guard(mutex)(&quirk_flags_mutex); + + /* old style option found: the position-based integer value */ + if (quirk_flags[idx] && + !kstrtou32(quirk_flags[idx], 0, &chip->quirk_flags)) { + snd_usb_apply_flag_dbg("module param", chip, chip->quirk_flags); + return; + } + + /* take the default quirk from the quirk table */ + snd_usb_init_quirk_flags_table(chip); + + /* add or correct quirk bits from options */ + for (i = 0; i < ARRAY_SIZE(quirk_flags); i++) { + if (!quirk_flags[i] || !*quirk_flags[i]) + break; + + snd_usb_init_quirk_flags_parse_string(chip, quirk_flags[i]); + } +} + /* * create a chip instance and set its names. */ @@ -463,7 +756,6 @@ static int snd_usb_audio_create(struct usb_interface *intf, case USB_SPEED_LOW: case USB_SPEED_FULL: case USB_SPEED_HIGH: - case USB_SPEED_WIRELESS: case USB_SPEED_SUPER: case USB_SPEED_SUPER_PLUS: break; @@ -486,7 +778,9 @@ static int snd_usb_audio_create(struct usb_interface *intf, chip->dev = dev; chip->card = card; chip->setup = device_setup[idx]; + chip->generic_implicit_fb = implicit_fb[idx]; chip->autoclock = autoclock; + chip->lowlatency = lowlatency; atomic_set(&chip->active, 1); /* avoid autopm during probing */ atomic_set(&chip->usage_count, 0); atomic_set(&chip->shutdown, 0); @@ -494,14 +788,19 @@ static int snd_usb_audio_create(struct usb_interface *intf, chip->usb_id = usb_id; INIT_LIST_HEAD(&chip->pcm_list); INIT_LIST_HEAD(&chip->ep_list); + INIT_LIST_HEAD(&chip->iface_ref_list); + INIT_LIST_HEAD(&chip->clock_ref_list); INIT_LIST_HEAD(&chip->midi_list); + INIT_LIST_HEAD(&chip->midi_v2_list); INIT_LIST_HEAD(&chip->mixer_list); + snd_usb_init_quirk_flags(idx, chip); + card->private_free = snd_usb_audio_free; - strcpy(card->driver, "USB-Audio"); - sprintf(component, "USB%04x:%04x", - USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id)); + strscpy(card->driver, "USB-Audio"); + scnprintf(component, sizeof(component), "USB%04x:%04x", + USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id)); snd_component_add(card, component); usb_audio_make_shortname(dev, chip, quirk); @@ -535,8 +834,40 @@ static bool get_alias_id(struct usb_device *dev, unsigned int *id) return false; } +static int check_delayed_register_option(struct snd_usb_audio *chip) +{ + int i; + unsigned int id, inum; + + for (i = 0; i < ARRAY_SIZE(delayed_register); i++) { + if (delayed_register[i] && + sscanf(delayed_register[i], "%x:%x", &id, &inum) == 2 && + id == chip->usb_id) + return inum; + } + + return -1; +} + static const struct usb_device_id usb_audio_ids[]; /* defined below */ +/* look for the last interface that matches with our ids and remember it */ +static void find_last_interface(struct snd_usb_audio *chip) +{ + struct usb_host_config *config = chip->dev->actconfig; + struct usb_interface *intf; + int i; + + if (!config) + return; + for (i = 0; i < config->desc.bNumInterfaces; i++) { + intf = config->interface[i]; + if (usb_match_id(intf, usb_audio_ids)) + chip->last_iface = intf->altsetting[0].desc.bInterfaceNumber; + } + usb_audio_dbg(chip, "Found last interface = %d\n", chip->last_iface); +} + /* look for the corresponding quirk */ static const struct snd_usb_audio_quirk * get_alias_quirk(struct usb_device *dev, unsigned int id) @@ -555,6 +886,24 @@ get_alias_quirk(struct usb_device *dev, unsigned int id) return NULL; } +/* register card if we reach to the last interface or to the specified + * one given via option + */ +static int try_to_register_card(struct snd_usb_audio *chip, int ifnum) +{ + struct usb_interface *iface; + + if (check_delayed_register_option(chip) == ifnum || + chip->last_iface == ifnum) + return snd_card_register(chip->card); + + iface = usb_ifnum_to_if(chip->dev, chip->last_iface); + if (iface && usb_interface_claimed(iface)) + return snd_card_register(chip->card); + + return 0; +} + /* * probe the active usb device * @@ -585,6 +934,8 @@ static int usb_audio_probe(struct usb_interface *intf, quirk = get_alias_quirk(dev, id); if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum) return -ENXIO; + if (quirk && quirk->ifnum == QUIRK_NODEV_INTERFACE) + return -ENODEV; err = snd_usb_apply_boot_quirk(dev, intf, quirk, id); if (err < 0) @@ -596,7 +947,7 @@ static int usb_audio_probe(struct usb_interface *intf, /* check whether it's already registered */ chip = NULL; - mutex_lock(®ister_mutex); + guard(mutex)(®ister_mutex); for (i = 0; i < SNDRV_CARDS; i++) { if (usb_chip[i] && usb_chip[i]->dev == dev) { if (atomic_read(&usb_chip[i]->shutdown)) { @@ -610,6 +961,10 @@ static int usb_audio_probe(struct usb_interface *intf, } } if (! chip) { + err = snd_usb_apply_boot_quirk_once(dev, intf, quirk, id); + if (err < 0) + goto __error; + /* it's a fresh one. * now look for an empty slot and create a new card instance */ @@ -622,7 +977,6 @@ static int usb_audio_probe(struct usb_interface *intf, id, &chip); if (err < 0) goto __error; - chip->pm_intf = intf; break; } else if (vid[i] != -1 || pid[i] != -1) { dev_info(&dev->dev, @@ -638,9 +992,23 @@ static int usb_audio_probe(struct usb_interface *intf, err = -ENODEV; goto __error; } + find_last_interface(chip); + } + + if (chip->num_interfaces >= MAX_CARD_INTERFACES) { + dev_info(&dev->dev, "Too many interfaces assigned to the single USB-audio card\n"); + err = -EINVAL; + goto __error; } + dev_set_drvdata(&dev->dev, chip); + if (ignore_ctl_error) + chip->quirk_flags |= QUIRK_FLAG_IGNORE_CTL_ERROR; + + if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) + usb_disable_autosuspend(interface_to_usbdev(intf)); + /* * For devices with more than one control interface, we assume the * first contains the audio controls. We might need a more specific @@ -649,7 +1017,6 @@ static int usb_audio_probe(struct usb_interface *intf, if (!chip->ctrl_intf) chip->ctrl_intf = alts; - chip->txfr_quirk = 0; err = 1; /* continue */ if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) { /* need some special handlings */ @@ -663,24 +1030,47 @@ static int usb_audio_probe(struct usb_interface *intf, err = snd_usb_create_streams(chip, ifnum); if (err < 0) goto __error; - err = snd_usb_create_mixer(chip, ifnum, ignore_ctl_error); + err = snd_usb_create_mixer(chip, ifnum); if (err < 0) goto __error; } - /* we are allowed to call snd_card_register() many times */ - err = snd_card_register(chip->card); + if (chip->need_delayed_register) { + dev_info(&dev->dev, + "Found post-registration device assignment: %08x:%02x\n", + chip->usb_id, ifnum); + chip->need_delayed_register = false; /* clear again */ + } + + err = try_to_register_card(chip, ifnum); if (err < 0) - goto __error; + goto __error_no_register; + + if (chip->quirk_flags & QUIRK_FLAG_SHARE_MEDIA_DEVICE) { + /* don't want to fail when snd_media_device_create() fails */ + snd_media_device_create(chip, intf); + } + + if (quirk) + chip->quirk_type = quirk->type; usb_chip[chip->index] = chip; + chip->intf[chip->num_interfaces] = intf; chip->num_interfaces++; usb_set_intfdata(intf, chip); atomic_dec(&chip->active); - mutex_unlock(®ister_mutex); + + if (platform_ops && platform_ops->connect_cb) + platform_ops->connect_cb(chip); + return 0; __error: + /* in the case of error in secondary interface, still try to register */ + if (chip) + try_to_register_card(chip, ifnum); + + __error_no_register: if (chip) { /* chip->active is inside the chip->card object, * decrement before memory is possibly returned. @@ -689,7 +1079,6 @@ static int usb_audio_probe(struct usb_interface *intf, if (!chip->num_interfaces) snd_card_free(chip->card); } - mutex_unlock(®ister_mutex); return err; } @@ -697,18 +1086,17 @@ static int usb_audio_probe(struct usb_interface *intf, * we need to take care of counter, since disconnection can be called also * many times as well as usb_audio_probe(). */ -static void usb_audio_disconnect(struct usb_interface *intf) +static bool __usb_audio_disconnect(struct usb_interface *intf, + struct snd_usb_audio *chip, + struct snd_card *card) { - struct snd_usb_audio *chip = usb_get_intfdata(intf); - struct snd_card *card; struct list_head *p; - if (chip == (void *)-1L) - return; + guard(mutex)(®ister_mutex); - card = chip->card; + if (platform_ops && platform_ops->disconnect_cb) + platform_ops->disconnect_cb(chip); - mutex_lock(®ister_mutex); if (atomic_inc_return(&chip->shutdown) == 1) { struct snd_usb_stream *as; struct snd_usb_endpoint *ep; @@ -732,20 +1120,43 @@ static void usb_audio_disconnect(struct usb_interface *intf) list_for_each(p, &chip->midi_list) { snd_usbmidi_disconnect(p); } + snd_usb_midi_v2_disconnect_all(chip); + /* + * Nice to check quirk && quirk->shares_media_device and + * then call the snd_media_device_delete(). Don't have + * access to the quirk here. snd_media_device_delete() + * accesses mixer_list + */ + snd_media_device_delete(chip); + /* release mixer resources */ list_for_each_entry(mixer, &chip->mixer_list, list) { snd_usb_mixer_disconnect(mixer); } } + if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) + usb_enable_autosuspend(interface_to_usbdev(intf)); + chip->num_interfaces--; - if (chip->num_interfaces <= 0) { - usb_chip[chip->index] = NULL; - mutex_unlock(®ister_mutex); + if (chip->num_interfaces > 0) + return false; + + usb_chip[chip->index] = NULL; + return true; +} + +static void usb_audio_disconnect(struct usb_interface *intf) +{ + struct snd_usb_audio *chip = usb_get_intfdata(intf); + struct snd_card *card; + + if (chip == USB_AUDIO_IFACE_UNUSED) + return; + + card = chip->card; + if (__usb_audio_disconnect(intf, chip, card)) snd_card_free_when_closed(card); - } else { - mutex_unlock(®ister_mutex); - } } /* lock the shutdown (disconnect) task and autoresume */ @@ -768,6 +1179,7 @@ int snd_usb_lock_shutdown(struct snd_usb_audio *chip) wake_up(&chip->shutdown_wait); return err; } +EXPORT_SYMBOL_GPL(snd_usb_lock_shutdown); /* autosuspend and unlock the shutdown */ void snd_usb_unlock_shutdown(struct snd_usb_audio *chip) @@ -776,56 +1188,80 @@ void snd_usb_unlock_shutdown(struct snd_usb_audio *chip) if (atomic_dec_and_test(&chip->usage_count)) wake_up(&chip->shutdown_wait); } - -#ifdef CONFIG_PM +EXPORT_SYMBOL_GPL(snd_usb_unlock_shutdown); int snd_usb_autoresume(struct snd_usb_audio *chip) { + int i, err; + if (atomic_read(&chip->shutdown)) return -EIO; - if (atomic_inc_return(&chip->active) == 1) - return usb_autopm_get_interface(chip->pm_intf); + if (atomic_inc_return(&chip->active) != 1) + return 0; + + for (i = 0; i < chip->num_interfaces; i++) { + err = usb_autopm_get_interface(chip->intf[i]); + if (err < 0) { + /* rollback */ + while (--i >= 0) + usb_autopm_put_interface(chip->intf[i]); + atomic_dec(&chip->active); + return err; + } + } return 0; } +EXPORT_SYMBOL_GPL(snd_usb_autoresume); void snd_usb_autosuspend(struct snd_usb_audio *chip) { + int i; + if (atomic_read(&chip->shutdown)) return; - if (atomic_dec_and_test(&chip->active)) - usb_autopm_put_interface(chip->pm_intf); + if (!atomic_dec_and_test(&chip->active)) + return; + + for (i = 0; i < chip->num_interfaces; i++) + usb_autopm_put_interface(chip->intf[i]); } +EXPORT_SYMBOL_GPL(snd_usb_autosuspend); static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) { struct snd_usb_audio *chip = usb_get_intfdata(intf); struct snd_usb_stream *as; + struct snd_usb_endpoint *ep; struct usb_mixer_interface *mixer; struct list_head *p; - if (chip == (void *)-1L) + if (chip == USB_AUDIO_IFACE_UNUSED) return 0; - chip->autosuspended = !!PMSG_IS_AUTO(message); - if (!chip->autosuspended) - snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); if (!chip->num_suspended_intf++) { - list_for_each_entry(as, &chip->pcm_list, list) { - snd_pcm_suspend_all(as->pcm); + list_for_each_entry(as, &chip->pcm_list, list) snd_usb_pcm_suspend(as); - as->substream[0].need_setup_ep = - as->substream[1].need_setup_ep = true; - } + list_for_each_entry(ep, &chip->ep_list, list) + snd_usb_endpoint_suspend(ep); list_for_each(p, &chip->midi_list) snd_usbmidi_suspend(p); list_for_each_entry(mixer, &chip->mixer_list, list) snd_usb_mixer_suspend(mixer); + snd_usb_midi_v2_suspend_all(chip); + } + + if (!PMSG_IS_AUTO(message) && !chip->system_suspend) { + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); + chip->system_suspend = chip->num_suspended_intf; } + if (platform_ops && platform_ops->suspend_cb) + platform_ops->suspend_cb(intf, message); + return 0; } -static int __usb_audio_resume(struct usb_interface *intf, bool reset_resume) +static int usb_audio_resume(struct usb_interface *intf) { struct snd_usb_audio *chip = usb_get_intfdata(intf); struct snd_usb_stream *as; @@ -833,12 +1269,12 @@ static int __usb_audio_resume(struct usb_interface *intf, bool reset_resume) struct list_head *p; int err = 0; - if (chip == (void *)-1L) - return 0; - if (--chip->num_suspended_intf) + if (chip == USB_AUDIO_IFACE_UNUSED) return 0; atomic_inc(&chip->active); /* avoid autopm */ + if (chip->num_suspended_intf > 1) + goto out; list_for_each_entry(as, &chip->pcm_list, list) { err = snd_usb_pcm_resume(as); @@ -851,7 +1287,7 @@ static int __usb_audio_resume(struct usb_interface *intf, bool reset_resume) * we just notify and restart the mixers */ list_for_each_entry(mixer, &chip->mixer_list, list) { - err = snd_usb_mixer_resume(mixer, reset_resume); + err = snd_usb_mixer_resume(mixer); if (err < 0) goto err_out; } @@ -860,30 +1296,23 @@ static int __usb_audio_resume(struct usb_interface *intf, bool reset_resume) snd_usbmidi_resume(p); } - if (!chip->autosuspended) + snd_usb_midi_v2_resume_all(chip); + + if (platform_ops && platform_ops->resume_cb) + platform_ops->resume_cb(intf); + + out: + if (chip->num_suspended_intf == chip->system_suspend) { snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); - chip->autosuspended = 0; + chip->system_suspend = 0; + } + chip->num_suspended_intf--; err_out: atomic_dec(&chip->active); /* allow autopm after this point */ return err; } -static int usb_audio_resume(struct usb_interface *intf) -{ - return __usb_audio_resume(intf, false); -} - -static int usb_audio_reset_resume(struct usb_interface *intf) -{ - return __usb_audio_resume(intf, true); -} -#else -#define usb_audio_suspend NULL -#define usb_audio_resume NULL -#define usb_audio_reset_resume NULL -#endif /* CONFIG_PM */ - static const struct usb_device_id usb_audio_ids [] = { #include "quirks-table.h" { .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS), @@ -903,7 +1332,7 @@ static struct usb_driver usb_audio_driver = { .disconnect = usb_audio_disconnect, .suspend = usb_audio_suspend, .resume = usb_audio_resume, - .reset_resume = usb_audio_reset_resume, + .reset_resume = usb_audio_resume, .id_table = usb_audio_ids, .supports_autosuspend = 1, }; diff --git a/sound/usb/card.h b/sound/usb/card.h index ac785d15ced4..94404c24d240 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -14,13 +14,21 @@ struct audioformat { u64 formats; /* ALSA format bits */ unsigned int channels; /* # channels */ unsigned int fmt_type; /* USB audio format type (1-3) */ + unsigned int fmt_bits; /* number of significant bits */ + unsigned int fmt_sz; /* overall audio sub frame/slot size */ unsigned int frame_size; /* samples per frame for non-audio */ - int iface; /* interface number */ + unsigned char iface; /* interface number */ unsigned char altsetting; /* corresponding alternate setting */ - unsigned char altset_idx; /* array index of altenate setting */ + unsigned char ep_idx; /* endpoint array index */ + unsigned char altset_idx; /* array index of alternate setting */ unsigned char attributes; /* corresponding attributes of cs endpoint */ unsigned char endpoint; /* endpoint */ unsigned char ep_attr; /* endpoint attributes */ + bool implicit_fb; /* implicit feedback endpoint */ + unsigned char sync_ep; /* sync endpoint number */ + unsigned char sync_iface; /* sync EP interface */ + unsigned char sync_altsetting; /* sync EP alternate setting */ + unsigned char sync_ep_idx; /* sync EP array index */ unsigned char datainterval; /* log_2 of data packet interval */ unsigned char protocol; /* UAC_VERSION_1/2/3 */ unsigned int maxpacksize; /* max. packet size */ @@ -36,6 +44,8 @@ struct audioformat { }; struct snd_usb_substream; +struct snd_usb_iface_ref; +struct snd_usb_clock_ref; struct snd_usb_endpoint; struct snd_usb_power_domain; @@ -46,26 +56,36 @@ struct snd_urb_ctx { struct snd_usb_endpoint *ep; int index; /* index for urb array */ int packets; /* number of packets per urb */ + int queued; /* queued data bytes by this urb */ int packet_size[MAX_PACKS_HS]; /* size of packets for next submission */ struct list_head ready_list; }; struct snd_usb_endpoint { struct snd_usb_audio *chip; + struct snd_usb_iface_ref *iface_ref; + struct snd_usb_clock_ref *clock_ref; - int use_count; + int opened; /* open refcount; protect with chip->mutex */ + atomic_t running; /* running status */ int ep_num; /* the referenced endpoint number */ int type; /* SND_USB_ENDPOINT_TYPE_* */ - unsigned long flags; - void (*prepare_data_urb) (struct snd_usb_substream *subs, - struct urb *urb); + unsigned char iface; /* interface number */ + unsigned char altsetting; /* corresponding alternate setting */ + unsigned char ep_idx; /* endpoint array index */ + + atomic_t state; /* running state */ + + int (*prepare_data_urb) (struct snd_usb_substream *subs, + struct urb *urb, + bool in_stream_lock); void (*retire_data_urb) (struct snd_usb_substream *subs, struct urb *urb); struct snd_usb_substream *data_subs; - struct snd_usb_endpoint *sync_master; - struct snd_usb_endpoint *sync_slave; + struct snd_usb_endpoint *sync_source; + struct snd_usb_endpoint *sync_sink; struct snd_urb_ctx urb[MAX_URBS]; @@ -73,16 +93,22 @@ struct snd_usb_endpoint { uint32_t packet_size[MAX_PACKS_HS]; int packets; } next_packet[MAX_URBS]; - int next_packet_read_pos, next_packet_write_pos; - struct list_head ready_playback_urbs; + unsigned int next_packet_head; /* ring buffer offset to read */ + unsigned int next_packet_queued; /* queued items in the ring buffer */ + struct list_head ready_playback_urbs; /* playback URB FIFO for implicit fb */ unsigned int nurbs; /* # urbs */ unsigned long active_mask; /* bitmask of active urbs */ unsigned long unlink_mask; /* bitmask of unlinked urbs */ + atomic_t submitted_urbs; /* currently submitted urbs */ char *syncbuf; /* sync buffer for all sync URBs */ dma_addr_t sync_dma; /* DMA address of syncbuf */ unsigned int pipe; /* the data i/o pipe */ + unsigned int packsize[2]; /* small/large packet sizes in samples */ + unsigned int sample_rem; /* remainder from division fs/pps */ + unsigned int sample_accum; /* sample accumulator */ + unsigned int pps; /* packets per second */ unsigned int freqn; /* nominal sampling rate in fs/fps in Q16.16 format */ unsigned int freqm; /* momentary sampling rate in fs/fps in Q16.16 format */ int freqshift; /* how much to shift the feedback value to get Q16.16 */ @@ -100,40 +126,53 @@ struct snd_usb_endpoint { unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */ unsigned char silence_value; unsigned int stride; - int iface, altsetting; int skip_packets; /* quirks for devices to ignore the first n packets in a stream */ + bool implicit_fb_sync; /* syncs with implicit feedback */ + bool lowlatency_playback; /* low-latency playback mode */ + bool need_setup; /* (re-)need for hw_params? */ + bool need_prepare; /* (re-)need for prepare? */ + bool fixed_rate; /* skip rate setup */ + + /* for hw constraints */ + const struct audioformat *cur_audiofmt; + unsigned int cur_rate; + snd_pcm_format_t cur_format; + unsigned int cur_channels; + unsigned int cur_frame_bytes; + unsigned int cur_period_frames; + unsigned int cur_period_bytes; + unsigned int cur_buffer_periods; spinlock_t lock; struct list_head list; }; +struct media_ctl; + struct snd_usb_substream { struct snd_usb_stream *stream; struct usb_device *dev; struct snd_pcm_substream *pcm_substream; int direction; /* playback or capture */ - int interface; /* current interface */ int endpoint; /* assigned endpoint */ - struct audioformat *cur_audiofmt; /* current audioformat pointer (for hw_params callback) */ + const struct audioformat *cur_audiofmt; /* current audioformat pointer (for hw_params callback) */ struct snd_usb_power_domain *str_pd; /* UAC3 Power Domain for streaming path */ - snd_pcm_format_t pcm_format; /* current audio format (for hw_params callback) */ - unsigned int channels; /* current number of channels (for hw_params callback) */ unsigned int channels_max; /* max channels in the all audiofmts */ - unsigned int cur_rate; /* current rate (for hw_params callback) */ - unsigned int period_bytes; /* current period bytes (for hw_params callback) */ - unsigned int period_frames; /* current frames per period */ - unsigned int buffer_periods; /* current periods per buffer */ - unsigned int altset_idx; /* USB data format: index of alternate setting */ unsigned int txfr_quirk:1; /* allow sub-frame alignment */ unsigned int tx_length_quirk:1; /* add length specifier to transfers */ unsigned int fmt_type; /* USB audio format type (1-3) */ unsigned int pkt_offset_adj; /* Bytes to drop from beginning of packets (for non-compliant devices) */ + unsigned int stream_offset_adj; /* Bytes to drop from beginning of stream (for non-compliant devices) */ + unsigned int opened:1; /* pcm device opened */ unsigned int running: 1; /* running status */ + unsigned int period_elapsed_pending; /* delay period handling */ + unsigned int buffer_bytes; /* buffer size in bytes */ + unsigned int inflight_bytes; /* in-flight data bytes on buffer (for playback) */ unsigned int hwptr_done; /* processed byte position in the buffer */ - unsigned int transfer_done; /* processed frames since last period update */ + unsigned int transfer_done; /* processed frames since last period update */ unsigned int frame_limit; /* limits number of packets in URB */ /* data and sync endpoints for this stream */ @@ -141,17 +180,14 @@ struct snd_usb_substream { struct snd_usb_endpoint *data_endpoint; struct snd_usb_endpoint *sync_endpoint; unsigned long flags; - bool need_setup_ep; /* (re)configure EP at prepare? */ unsigned int speed; /* USB_SPEED_XXX */ u64 formats; /* format bitmasks (all or'ed) */ unsigned int num_formats; /* number of supported audio formats (list) */ struct list_head fmt_list; /* format list */ - struct snd_pcm_hw_constraint_list rate_list; /* limited rates */ spinlock_t lock; - int last_frame_number; /* stored frame number */ - int last_delay; /* stored delay */ + unsigned int last_frame_number; /* stored frame number */ struct { int marker; @@ -160,6 +196,8 @@ struct snd_usb_substream { } dsd_dop; bool trigger_tstamp_pending_update; /* trigger timestamp being updated from initial estimate */ + bool lowlatency_playback; /* low-latency playback mode */ + struct media_ctl *media_ctl; }; struct snd_usb_stream { @@ -171,4 +209,19 @@ struct snd_usb_stream { struct list_head list; }; +struct snd_usb_platform_ops { + void (*connect_cb)(struct snd_usb_audio *chip); + void (*disconnect_cb)(struct snd_usb_audio *chip); + void (*suspend_cb)(struct usb_interface *intf, pm_message_t message); + void (*resume_cb)(struct usb_interface *intf); +}; + +struct snd_usb_stream * +snd_usb_find_suppported_substream(int card_idx, struct snd_pcm_hw_params *params, + int direction); + +int snd_usb_register_platform_ops(struct snd_usb_platform_ops *ops); +int snd_usb_unregister_platform_ops(void); + +void snd_usb_rediscover_devices(void); #endif /* __USBAUDIO_CARD_H */ diff --git a/sound/usb/clock.c b/sound/usb/clock.c index db5e39d67a90..842ba5b801ea 100644 --- a/sound/usb/clock.c +++ b/sound/usb/clock.c @@ -1,20 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Clock domain and sample rate management functions - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * */ #include <linux/bitops.h> @@ -35,95 +21,116 @@ #include "clock.h" #include "quirks.h" +union uac23_clock_source_desc { + struct uac_clock_source_descriptor v2; + struct uac3_clock_source_descriptor v3; +}; + +union uac23_clock_selector_desc { + struct uac_clock_selector_descriptor v2; + struct uac3_clock_selector_descriptor v3; +}; + +union uac23_clock_multiplier_desc { + struct uac_clock_multiplier_descriptor v2; + struct uac_clock_multiplier_descriptor v3; +}; + +/* check whether the descriptor bLength has the minimal length */ +#define DESC_LENGTH_CHECK(p, proto) \ + ((proto) == UAC_VERSION_3 ? \ + ((p)->v3.bLength >= sizeof((p)->v3)) : \ + ((p)->v2.bLength >= sizeof((p)->v2))) + +#define GET_VAL(p, proto, field) \ + ((proto) == UAC_VERSION_3 ? (p)->v3.field : (p)->v2.field) + static void *find_uac_clock_desc(struct usb_host_interface *iface, int id, - bool (*validator)(void *, int), u8 type) + bool (*validator)(void *, int, int), + u8 type, int proto) { void *cs = NULL; while ((cs = snd_usb_find_csint_desc(iface->extra, iface->extralen, cs, type))) { - if (validator(cs, id)) + if (validator(cs, id, proto)) return cs; } return NULL; } -static bool validate_clock_source_v2(void *p, int id) +static bool validate_clock_source(void *p, int id, int proto) { - struct uac_clock_source_descriptor *cs = p; - return cs->bLength == sizeof(*cs) && cs->bClockID == id; -} + union uac23_clock_source_desc *cs = p; -static bool validate_clock_source_v3(void *p, int id) -{ - struct uac3_clock_source_descriptor *cs = p; - return cs->bLength == sizeof(*cs) && cs->bClockID == id; + if (!DESC_LENGTH_CHECK(cs, proto)) + return false; + return GET_VAL(cs, proto, bClockID) == id; } -static bool validate_clock_selector_v2(void *p, int id) +static bool validate_clock_selector(void *p, int id, int proto) { - struct uac_clock_selector_descriptor *cs = p; - return cs->bLength >= sizeof(*cs) && cs->bClockID == id && - cs->bLength == 7 + cs->bNrInPins; -} - -static bool validate_clock_selector_v3(void *p, int id) -{ - struct uac3_clock_selector_descriptor *cs = p; - return cs->bLength >= sizeof(*cs) && cs->bClockID == id && - cs->bLength == 11 + cs->bNrInPins; + union uac23_clock_selector_desc *cs = p; + + if (!DESC_LENGTH_CHECK(cs, proto)) + return false; + if (GET_VAL(cs, proto, bClockID) != id) + return false; + /* additional length check for baCSourceID array (in bNrInPins size) + * and two more fields (which sizes depend on the protocol) + */ + if (proto == UAC_VERSION_3) + return cs->v3.bLength >= sizeof(cs->v3) + cs->v3.bNrInPins + + 4 /* bmControls */ + 2 /* wCSelectorDescrStr */; + else + return cs->v2.bLength >= sizeof(cs->v2) + cs->v2.bNrInPins + + 1 /* bmControls */ + 1 /* iClockSelector */; } -static bool validate_clock_multiplier_v2(void *p, int id) +static bool validate_clock_multiplier(void *p, int id, int proto) { - struct uac_clock_multiplier_descriptor *cs = p; - return cs->bLength == sizeof(*cs) && cs->bClockID == id; -} + union uac23_clock_multiplier_desc *cs = p; -static bool validate_clock_multiplier_v3(void *p, int id) -{ - struct uac3_clock_multiplier_descriptor *cs = p; - return cs->bLength == sizeof(*cs) && cs->bClockID == id; + if (!DESC_LENGTH_CHECK(cs, proto)) + return false; + return GET_VAL(cs, proto, bClockID) == id; } -#define DEFINE_FIND_HELPER(name, obj, validator, type) \ -static obj *name(struct usb_host_interface *iface, int id) \ -{ \ - return find_uac_clock_desc(iface, id, validator, type); \ +#define DEFINE_FIND_HELPER(name, obj, validator, type2, type3) \ +static obj *name(struct snd_usb_audio *chip, int id, \ + const struct audioformat *fmt) \ +{ \ + struct usb_host_interface *ctrl_intf = \ + snd_usb_find_ctrl_interface(chip, fmt->iface); \ + return find_uac_clock_desc(ctrl_intf, id, validator, \ + fmt->protocol == UAC_VERSION_3 ? (type3) : (type2), \ + fmt->protocol); \ } DEFINE_FIND_HELPER(snd_usb_find_clock_source, - struct uac_clock_source_descriptor, - validate_clock_source_v2, UAC2_CLOCK_SOURCE); -DEFINE_FIND_HELPER(snd_usb_find_clock_source_v3, - struct uac3_clock_source_descriptor, - validate_clock_source_v3, UAC3_CLOCK_SOURCE); - + union uac23_clock_source_desc, validate_clock_source, + UAC2_CLOCK_SOURCE, UAC3_CLOCK_SOURCE); DEFINE_FIND_HELPER(snd_usb_find_clock_selector, - struct uac_clock_selector_descriptor, - validate_clock_selector_v2, UAC2_CLOCK_SELECTOR); -DEFINE_FIND_HELPER(snd_usb_find_clock_selector_v3, - struct uac3_clock_selector_descriptor, - validate_clock_selector_v3, UAC3_CLOCK_SELECTOR); - + union uac23_clock_selector_desc, validate_clock_selector, + UAC2_CLOCK_SELECTOR, UAC3_CLOCK_SELECTOR); DEFINE_FIND_HELPER(snd_usb_find_clock_multiplier, - struct uac_clock_multiplier_descriptor, - validate_clock_multiplier_v2, UAC2_CLOCK_MULTIPLIER); -DEFINE_FIND_HELPER(snd_usb_find_clock_multiplier_v3, - struct uac3_clock_multiplier_descriptor, - validate_clock_multiplier_v3, UAC3_CLOCK_MULTIPLIER); + union uac23_clock_multiplier_desc, validate_clock_multiplier, + UAC2_CLOCK_MULTIPLIER, UAC3_CLOCK_MULTIPLIER); -static int uac_clock_selector_get_val(struct snd_usb_audio *chip, int selector_id) +static int uac_clock_selector_get_val(struct snd_usb_audio *chip, + int selector_id, int iface_no) { + struct usb_host_interface *ctrl_intf; unsigned char buf; int ret; + ctrl_intf = snd_usb_find_ctrl_interface(chip, iface_no); ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), UAC2_CS_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, UAC2_CX_CLOCK_SELECTOR << 8, - snd_usb_ctrl_intf(chip) | (selector_id << 8), + snd_usb_ctrl_intf(ctrl_intf) | (selector_id << 8), &buf, sizeof(buf)); if (ret < 0) @@ -132,16 +139,18 @@ static int uac_clock_selector_get_val(struct snd_usb_audio *chip, int selector_i return buf; } -static int uac_clock_selector_set_val(struct snd_usb_audio *chip, int selector_id, - unsigned char pin) +static int uac_clock_selector_set_val(struct snd_usb_audio *chip, + int selector_id, unsigned char pin, int iface_no) { + struct usb_host_interface *ctrl_intf; int ret; + ctrl_intf = snd_usb_find_ctrl_interface(chip, iface_no); ret = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC2_CS_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, UAC2_CX_CLOCK_SELECTOR << 8, - snd_usb_ctrl_intf(chip) | (selector_id << 8), + snd_usb_ctrl_intf(ctrl_intf) | (selector_id << 8), &pin, sizeof(pin)); if (ret < 0) return ret; @@ -153,7 +162,7 @@ static int uac_clock_selector_set_val(struct snd_usb_audio *chip, int selector_i return -EINVAL; } - ret = uac_clock_selector_get_val(chip, selector_id); + ret = uac_clock_selector_get_val(chip, selector_id, iface_no); if (ret < 0) return ret; @@ -167,58 +176,127 @@ static int uac_clock_selector_set_val(struct snd_usb_audio *chip, int selector_i return ret; } +static bool uac_clock_source_is_valid_quirk(struct snd_usb_audio *chip, + const struct audioformat *fmt, + int source_id) +{ + bool ret = false; + int count; + unsigned char data; + struct usb_device *dev = chip->dev; + union uac23_clock_source_desc *cs_desc; + struct usb_host_interface *ctrl_intf; + + ctrl_intf = snd_usb_find_ctrl_interface(chip, fmt->iface); + cs_desc = snd_usb_find_clock_source(chip, source_id, fmt); + if (!cs_desc) + return false; + + if (fmt->protocol == UAC_VERSION_2) { + /* + * Assume the clock is valid if clock source supports only one + * single sample rate, the terminal is connected directly to it + * (there is no clock selector) and clock type is internal. + * This is to deal with some Denon DJ controllers that always + * reports that clock is invalid. + */ + if (fmt->nr_rates == 1 && + (fmt->clock & 0xff) == cs_desc->v2.bClockID && + (cs_desc->v2.bmAttributes & 0x3) != + UAC_CLOCK_SOURCE_TYPE_EXT) + return true; + } + + /* + * MOTU MicroBook IIc + * Sample rate changes takes more than 2 seconds for this device. Clock + * validity request returns false during that period. + */ + if (chip->usb_id == USB_ID(0x07fd, 0x0004)) { + count = 0; + + while ((!ret) && (count < 50)) { + int err; + + msleep(100); + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + UAC2_CS_CONTROL_CLOCK_VALID << 8, + snd_usb_ctrl_intf(ctrl_intf) | (source_id << 8), + &data, sizeof(data)); + if (err < 0) { + dev_warn(&dev->dev, + "%s(): cannot get clock validity for id %d\n", + __func__, source_id); + return false; + } + + ret = !!data; + count++; + } + } + + return ret; +} + static bool uac_clock_source_is_valid(struct snd_usb_audio *chip, - int protocol, + const struct audioformat *fmt, int source_id) { int err; unsigned char data; struct usb_device *dev = chip->dev; u32 bmControls; + union uac23_clock_source_desc *cs_desc; + struct usb_host_interface *ctrl_intf; - if (protocol == UAC_VERSION_3) { - struct uac3_clock_source_descriptor *cs_desc = - snd_usb_find_clock_source_v3(chip->ctrl_intf, source_id); - - if (!cs_desc) - return 0; - bmControls = le32_to_cpu(cs_desc->bmControls); - } else { /* UAC_VERSION_1/2 */ - struct uac_clock_source_descriptor *cs_desc = - snd_usb_find_clock_source(chip->ctrl_intf, source_id); + ctrl_intf = snd_usb_find_ctrl_interface(chip, fmt->iface); + cs_desc = snd_usb_find_clock_source(chip, source_id, fmt); + if (!cs_desc) + return false; - if (!cs_desc) - return 0; - bmControls = cs_desc->bmControls; - } + if (fmt->protocol == UAC_VERSION_3) + bmControls = le32_to_cpu(cs_desc->v3.bmControls); + else + bmControls = cs_desc->v2.bmControls; /* If a clock source can't tell us whether it's valid, we assume it is */ if (!uac_v2v3_control_is_readable(bmControls, UAC2_CS_CONTROL_CLOCK_VALID)) - return 1; + return true; err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, UAC2_CS_CONTROL_CLOCK_VALID << 8, - snd_usb_ctrl_intf(chip) | (source_id << 8), + snd_usb_ctrl_intf(ctrl_intf) | (source_id << 8), &data, sizeof(data)); if (err < 0) { dev_warn(&dev->dev, "%s(): cannot get clock validity for id %d\n", __func__, source_id); - return 0; + return false; } - return !!data; + if (data) + return true; + else + return uac_clock_source_is_valid_quirk(chip, fmt, source_id); } -static int __uac_clock_find_source(struct snd_usb_audio *chip, int entity_id, +static int __uac_clock_find_source(struct snd_usb_audio *chip, + const struct audioformat *fmt, int entity_id, unsigned long *visited, bool validate) { - struct uac_clock_source_descriptor *source; - struct uac_clock_selector_descriptor *selector; - struct uac_clock_multiplier_descriptor *multiplier; + union uac23_clock_source_desc *source; + union uac23_clock_selector_desc *selector; + union uac23_clock_multiplier_desc *multiplier; + int ret, i, cur, err, pins, clock_id; + const u8 *sources; + int proto = fmt->protocol; + bool readable, writeable; + u32 bmControls; entity_id &= 0xff; @@ -229,11 +307,11 @@ static int __uac_clock_find_source(struct snd_usb_audio *chip, int entity_id, return -EINVAL; } - /* first, see if the ID we're looking for is a clock source already */ - source = snd_usb_find_clock_source(chip->ctrl_intf, entity_id); + /* first, see if the ID we're looking at is a clock source already */ + source = snd_usb_find_clock_source(chip, entity_id, fmt); if (source) { - entity_id = source->bClockID; - if (validate && !uac_clock_source_is_valid(chip, UAC_VERSION_2, + entity_id = GET_VAL(source, proto, bClockID); + if (validate && !uac_clock_source_is_valid(chip, fmt, entity_id)) { usb_audio_err(chip, "clock source %d is not valid, cannot use\n", @@ -243,135 +321,97 @@ static int __uac_clock_find_source(struct snd_usb_audio *chip, int entity_id, return entity_id; } - selector = snd_usb_find_clock_selector(chip->ctrl_intf, entity_id); + selector = snd_usb_find_clock_selector(chip, entity_id, fmt); if (selector) { - int ret, i, cur; - - /* the entity ID we are looking for is a selector. - * find out what it currently selects */ - ret = uac_clock_selector_get_val(chip, selector->bClockID); - if (ret < 0) - return ret; - - /* Selector values are one-based */ - - if (ret > selector->bNrInPins || ret < 1) { - usb_audio_err(chip, - "%s(): selector reported illegal value, id %d, ret %d\n", - __func__, selector->bClockID, ret); - - return -EINVAL; - } - - cur = ret; - ret = __uac_clock_find_source(chip, selector->baCSourceID[ret - 1], - visited, validate); - if (!validate || ret > 0 || !chip->autoclock) - return ret; - - /* The current clock source is invalid, try others. */ - for (i = 1; i <= selector->bNrInPins; i++) { - int err; - - if (i == cur) - continue; - - ret = __uac_clock_find_source(chip, selector->baCSourceID[i - 1], - visited, true); - if (ret < 0) - continue; - - err = uac_clock_selector_set_val(chip, entity_id, i); - if (err < 0) - continue; - - usb_audio_info(chip, - "found and selected valid clock source %d\n", - ret); - return ret; - } - - return -ENXIO; - } - - /* FIXME: multipliers only act as pass-thru element for now */ - multiplier = snd_usb_find_clock_multiplier(chip->ctrl_intf, entity_id); - if (multiplier) - return __uac_clock_find_source(chip, multiplier->bCSourceID, - visited, validate); - - return -EINVAL; -} - -static int __uac3_clock_find_source(struct snd_usb_audio *chip, int entity_id, - unsigned long *visited, bool validate) -{ - struct uac3_clock_source_descriptor *source; - struct uac3_clock_selector_descriptor *selector; - struct uac3_clock_multiplier_descriptor *multiplier; - - entity_id &= 0xff; - - if (test_and_set_bit(entity_id, visited)) { - usb_audio_warn(chip, - "%s(): recursive clock topology detected, id %d.\n", - __func__, entity_id); - return -EINVAL; - } - - /* first, see if the ID we're looking for is a clock source already */ - source = snd_usb_find_clock_source_v3(chip->ctrl_intf, entity_id); - if (source) { - entity_id = source->bClockID; - if (validate && !uac_clock_source_is_valid(chip, UAC_VERSION_3, - entity_id)) { - usb_audio_err(chip, - "clock source %d is not valid, cannot use\n", - entity_id); - return -ENXIO; + pins = GET_VAL(selector, proto, bNrInPins); + clock_id = GET_VAL(selector, proto, bClockID); + sources = GET_VAL(selector, proto, baCSourceID); + cur = 0; + + if (proto == UAC_VERSION_3) + bmControls = le32_to_cpu(*(__le32 *)(&selector->v3.baCSourceID[0] + pins)); + else + bmControls = *(__u8 *)(&selector->v2.baCSourceID[0] + pins); + + readable = uac_v2v3_control_is_readable(bmControls, + UAC2_CX_CLOCK_SELECTOR); + writeable = uac_v2v3_control_is_writeable(bmControls, + UAC2_CX_CLOCK_SELECTOR); + + if (pins == 1) { + ret = 1; + goto find_source; } - return entity_id; - } - selector = snd_usb_find_clock_selector_v3(chip->ctrl_intf, entity_id); - if (selector) { - int ret, i, cur; + /* for now just warn about buggy device */ + if (!readable) + usb_audio_warn(chip, + "%s(): clock selector control is not readable, id %d\n", + __func__, clock_id); - /* the entity ID we are looking for is a selector. + /* the entity ID we are looking at is a selector. * find out what it currently selects */ - ret = uac_clock_selector_get_val(chip, selector->bClockID); - if (ret < 0) - return ret; + ret = uac_clock_selector_get_val(chip, clock_id, fmt->iface); + if (ret < 0) { + if (!chip->autoclock) + return ret; + goto find_others; + } /* Selector values are one-based */ - if (ret > selector->bNrInPins || ret < 1) { + if (ret > pins || ret < 1) { usb_audio_err(chip, "%s(): selector reported illegal value, id %d, ret %d\n", - __func__, selector->bClockID, ret); + __func__, clock_id, ret); - return -EINVAL; + if (!chip->autoclock) + return -EINVAL; + goto find_others; } + find_source: cur = ret; - ret = __uac3_clock_find_source(chip, selector->baCSourceID[ret - 1], - visited, validate); + ret = __uac_clock_find_source(chip, fmt, + sources[ret - 1], + visited, validate); + if (ret > 0) { + /* Skip setting clock selector again for some devices */ + if (chip->quirk_flags & QUIRK_FLAG_SKIP_CLOCK_SELECTOR || + !writeable) + return ret; + err = uac_clock_selector_set_val(chip, entity_id, cur, fmt->iface); + if (err < 0) { + if (pins == 1) { + usb_audio_dbg(chip, + "%s(): selector returned an error, " + "assuming a firmware bug, id %d, ret %d\n", + __func__, clock_id, err); + return ret; + } + return err; + } + } + if (!validate || ret > 0 || !chip->autoclock) return ret; - /* The current clock source is invalid, try others. */ - for (i = 1; i <= selector->bNrInPins; i++) { - int err; + find_others: + if (!writeable) + return -ENXIO; + /* The current clock source is invalid, try others. */ + for (i = 1; i <= pins; i++) { if (i == cur) continue; - ret = __uac3_clock_find_source(chip, selector->baCSourceID[i - 1], - visited, true); + ret = __uac_clock_find_source(chip, fmt, + sources[i - 1], + visited, true); if (ret < 0) continue; - err = uac_clock_selector_set_val(chip, entity_id, i); + err = uac_clock_selector_set_val(chip, entity_id, i, fmt->iface); if (err < 0) continue; @@ -385,11 +425,11 @@ static int __uac3_clock_find_source(struct snd_usb_audio *chip, int entity_id, } /* FIXME: multipliers only act as pass-thru element for now */ - multiplier = snd_usb_find_clock_multiplier_v3(chip->ctrl_intf, - entity_id); + multiplier = snd_usb_find_clock_multiplier(chip, entity_id, fmt); if (multiplier) - return __uac3_clock_find_source(chip, multiplier->bCSourceID, - visited, validate); + return __uac_clock_find_source(chip, fmt, + GET_VAL(multiplier, proto, bCSourceID), + visited, validate); return -EINVAL; } @@ -405,37 +445,29 @@ static int __uac3_clock_find_source(struct snd_usb_audio *chip, int entity_id, * * Returns the clock source UnitID (>=0) on success, or an error. */ -int snd_usb_clock_find_source(struct snd_usb_audio *chip, int protocol, - int entity_id, bool validate) +int snd_usb_clock_find_source(struct snd_usb_audio *chip, + const struct audioformat *fmt, bool validate) { DECLARE_BITMAP(visited, 256); memset(visited, 0, sizeof(visited)); - switch (protocol) { + switch (fmt->protocol) { case UAC_VERSION_2: - return __uac_clock_find_source(chip, entity_id, visited, - validate); case UAC_VERSION_3: - return __uac3_clock_find_source(chip, entity_id, visited, + return __uac_clock_find_source(chip, fmt, fmt->clock, visited, validate); default: return -EINVAL; } } -static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt, int rate) +static int set_sample_rate_v1(struct snd_usb_audio *chip, + const struct audioformat *fmt, int rate) { struct usb_device *dev = chip->dev; - unsigned int ep; unsigned char data[3]; int err, crate; - if (get_iface_desc(alts)->bNumEndpoints < 1) - return -EINVAL; - ep = get_endpoint(alts, 0)->bEndpointAddress; - /* if endpoint doesn't have sampling rate control, bail out */ if (!(fmt->attributes & UAC_EP_CS_ATTR_SAMPLE_RATE)) return 0; @@ -445,17 +477,17 @@ static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface, data[2] = rate >> 16; err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT, - UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, - data, sizeof(data)); + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, + fmt->endpoint, data, sizeof(data)); if (err < 0) { dev_err(&dev->dev, "%d:%d: cannot set freq %d to ep %#x\n", - iface, fmt->altsetting, rate, ep); + fmt->iface, fmt->altsetting, rate, fmt->endpoint); return err; } /* Don't check the sample rate for devices which we know don't * support reading */ - if (snd_usb_get_sample_rate_quirk(chip)) + if (chip->quirk_flags & QUIRK_FLAG_GET_SAMPLE_RATE) return 0; /* the firmware is likely buggy, don't repeat to fail too many times */ if (chip->sample_rate_read_error > 2) @@ -463,16 +495,22 @@ static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface, err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR, USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN, - UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, - data, sizeof(data)); + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, + fmt->endpoint, data, sizeof(data)); if (err < 0) { dev_err(&dev->dev, "%d:%d: cannot get freq at ep %#x\n", - iface, fmt->altsetting, ep); + fmt->iface, fmt->altsetting, fmt->endpoint); chip->sample_rate_read_error++; return 0; /* some devices don't support reading */ } crate = data[0] | (data[1] << 8) | (data[2] << 16); + if (!crate) { + dev_info(&dev->dev, "failed to read current rate; disabling the check\n"); + chip->sample_rate_read_error = 3; /* three strikes, see above */ + return 0; + } + if (crate != rate) { dev_warn(&dev->dev, "current rate %d is different from the runtime rate %d\n", crate, rate); // runtime->rate = crate; @@ -487,11 +525,13 @@ static int get_sample_rate_v2v3(struct snd_usb_audio *chip, int iface, struct usb_device *dev = chip->dev; __le32 data; int err; + struct usb_host_interface *ctrl_intf; + ctrl_intf = snd_usb_find_ctrl_interface(chip, iface); err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, UAC2_CS_CONTROL_SAM_FREQ << 8, - snd_usb_ctrl_intf(chip) | (clock << 8), + snd_usb_ctrl_intf(ctrl_intf) | (clock << 8), &data, sizeof(data)); if (err < 0) { dev_warn(&dev->dev, "%d:%d: cannot get freq (v2/v3): err %d\n", @@ -502,23 +542,65 @@ static int get_sample_rate_v2v3(struct snd_usb_audio *chip, int iface, return le32_to_cpu(data); } -static int set_sample_rate_v2v3(struct snd_usb_audio *chip, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt, int rate) +/* + * Try to set the given sample rate: + * + * Return 0 if the clock source is read-only, the actual rate on success, + * or a negative error code. + * + * This function gets called from format.c to validate each sample rate, too. + * Hence no message is shown upon error + */ +int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip, + const struct audioformat *fmt, + int clock, int rate) { - struct usb_device *dev = chip->dev; - __le32 data; - int err, cur_rate, prev_rate; - int clock; bool writeable; u32 bmControls; + __le32 data; + int err; + union uac23_clock_source_desc *cs_desc; + struct usb_host_interface *ctrl_intf; + + ctrl_intf = snd_usb_find_ctrl_interface(chip, fmt->iface); + cs_desc = snd_usb_find_clock_source(chip, clock, fmt); + + if (!cs_desc) + return 0; + + if (fmt->protocol == UAC_VERSION_3) + bmControls = le32_to_cpu(cs_desc->v3.bmControls); + else + bmControls = cs_desc->v2.bmControls; + + writeable = uac_v2v3_control_is_writeable(bmControls, + UAC2_CS_CONTROL_SAM_FREQ); + if (!writeable) + return 0; + + data = cpu_to_le32(rate); + err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC2_CS_CUR, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + UAC2_CS_CONTROL_SAM_FREQ << 8, + snd_usb_ctrl_intf(ctrl_intf) | (clock << 8), + &data, sizeof(data)); + if (err < 0) + return err; + + return get_sample_rate_v2v3(chip, fmt->iface, fmt->altsetting, clock); +} + +static int set_sample_rate_v2v3(struct snd_usb_audio *chip, + const struct audioformat *fmt, int rate) +{ + int cur_rate, prev_rate; + int clock; /* First, try to find a valid clock. This may trigger * automatic clock selection if the current clock is not * valid. */ - clock = snd_usb_clock_find_source(chip, fmt->protocol, - fmt->clock, true); + clock = snd_usb_clock_find_source(chip, fmt, true); if (clock < 0) { /* We did not find a valid clock, but that might be * because the current sample rate does not match an @@ -526,86 +608,66 @@ static int set_sample_rate_v2v3(struct snd_usb_audio *chip, int iface, * and we will do another validation after setting the * rate. */ - clock = snd_usb_clock_find_source(chip, fmt->protocol, - fmt->clock, false); + clock = snd_usb_clock_find_source(chip, fmt, false); + + /* Hardcoded sample rates */ + if (chip->quirk_flags & QUIRK_FLAG_IGNORE_CLOCK_SOURCE) + return 0; + if (clock < 0) return clock; } - prev_rate = get_sample_rate_v2v3(chip, iface, fmt->altsetting, clock); + prev_rate = get_sample_rate_v2v3(chip, fmt->iface, fmt->altsetting, clock); if (prev_rate == rate) goto validation; - if (fmt->protocol == UAC_VERSION_3) { - struct uac3_clock_source_descriptor *cs_desc; - - cs_desc = snd_usb_find_clock_source_v3(chip->ctrl_intf, clock); - bmControls = le32_to_cpu(cs_desc->bmControls); - } else { - struct uac_clock_source_descriptor *cs_desc; - - cs_desc = snd_usb_find_clock_source(chip->ctrl_intf, clock); - bmControls = cs_desc->bmControls; + cur_rate = snd_usb_set_sample_rate_v2v3(chip, fmt, clock, rate); + if (cur_rate < 0) { + usb_audio_err(chip, + "%d:%d: cannot set freq %d (v2/v3): err %d\n", + fmt->iface, fmt->altsetting, rate, cur_rate); + return cur_rate; } - writeable = uac_v2v3_control_is_writeable(bmControls, - UAC2_CS_CONTROL_SAM_FREQ); - if (writeable) { - data = cpu_to_le32(rate); - err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - UAC2_CS_CONTROL_SAM_FREQ << 8, - snd_usb_ctrl_intf(chip) | (clock << 8), - &data, sizeof(data)); - if (err < 0) { - usb_audio_err(chip, - "%d:%d: cannot set freq %d (v2/v3): err %d\n", - iface, fmt->altsetting, rate, err); - return err; - } - - cur_rate = get_sample_rate_v2v3(chip, iface, - fmt->altsetting, clock); - } else { + if (!cur_rate) cur_rate = prev_rate; - } if (cur_rate != rate) { - if (!writeable) { - usb_audio_warn(chip, - "%d:%d: freq mismatch (RO clock): req %d, clock runs @%d\n", - iface, fmt->altsetting, rate, cur_rate); - return -ENXIO; - } usb_audio_dbg(chip, - "current rate %d is different from the runtime rate %d\n", - cur_rate, rate); + "%d:%d: freq mismatch: req %d, clock runs @%d\n", + fmt->iface, fmt->altsetting, rate, cur_rate); + /* continue processing */ } - /* Some devices doesn't respond to sample rate changes while the - * interface is active. */ - if (rate != prev_rate) { - usb_set_interface(dev, iface, 0); - snd_usb_set_interface_quirk(dev); - usb_set_interface(dev, iface, fmt->altsetting); - snd_usb_set_interface_quirk(dev); + /* FIXME - TEAC devices require the immediate interface setup */ + if (USB_ID_VENDOR(chip->usb_id) == 0x0644) { + bool cur_base_48k = (rate % 48000 == 0); + bool prev_base_48k = (prev_rate % 48000 == 0); + if (cur_base_48k != prev_base_48k) { + usb_set_interface(chip->dev, fmt->iface, fmt->altsetting); + if (chip->quirk_flags & QUIRK_FLAG_IFACE_DELAY) + msleep(50); + } } validation: /* validate clock after rate change */ - if (!uac_clock_source_is_valid(chip, fmt->protocol, clock)) + if (!uac_clock_source_is_valid(chip, fmt, clock)) return -ENXIO; return 0; } -int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt, int rate) +int snd_usb_init_sample_rate(struct snd_usb_audio *chip, + const struct audioformat *fmt, int rate) { + usb_audio_dbg(chip, "%d:%d Set sample rate %d, clock %d\n", + fmt->iface, fmt->altsetting, rate, fmt->clock); + switch (fmt->protocol) { case UAC_VERSION_1: default: - return set_sample_rate_v1(chip, iface, alts, fmt, rate); + return set_sample_rate_v1(chip, fmt, rate); case UAC_VERSION_3: if (chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { @@ -614,9 +676,9 @@ int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface, else return 0; } - /* fall through */ + fallthrough; case UAC_VERSION_2: - return set_sample_rate_v2v3(chip, iface, alts, fmt, rate); + return set_sample_rate_v2v3(chip, fmt, rate); } } diff --git a/sound/usb/clock.h b/sound/usb/clock.h index 076e31b79ee0..ed9fc2dc0510 100644 --- a/sound/usb/clock.h +++ b/sound/usb/clock.h @@ -2,11 +2,14 @@ #ifndef __USBAUDIO_CLOCK_H #define __USBAUDIO_CLOCK_H -int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt, int rate); +int snd_usb_init_sample_rate(struct snd_usb_audio *chip, + const struct audioformat *fmt, int rate); -int snd_usb_clock_find_source(struct snd_usb_audio *chip, int protocol, - int entity_id, bool validate); +int snd_usb_clock_find_source(struct snd_usb_audio *chip, + const struct audioformat *fmt, bool validate); + +int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip, + const struct audioformat *fmt, + int clock, int rate); #endif /* __USBAUDIO_CLOCK_H */ diff --git a/sound/usb/debug.h b/sound/usb/debug.h deleted file mode 100644 index 7dd983c35001..000000000000 --- a/sound/usb/debug.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __USBAUDIO_DEBUG_H -#define __USBAUDIO_DEBUG_H - -/* - * h/w constraints - */ - -#ifdef HW_CONST_DEBUG -#define hwc_debug(fmt, args...) printk(KERN_DEBUG fmt, ##args) -#else -#define hwc_debug(fmt, args...) do { } while(0) -#endif - -#endif /* __USBAUDIO_DEBUG_H */ - diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index d86be8bfe412..cc15624ecaff 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -1,18 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * */ #include <linux/gfp.h> @@ -31,10 +18,33 @@ #include "card.h" #include "endpoint.h" #include "pcm.h" +#include "clock.h" #include "quirks.h" -#define EP_FLAG_RUNNING 1 -#define EP_FLAG_STOPPING 2 +enum { + EP_STATE_STOPPED, + EP_STATE_RUNNING, + EP_STATE_STOPPING, +}; + +/* interface refcounting */ +struct snd_usb_iface_ref { + unsigned char iface; + bool need_setup; + int opened; + int altset; + struct list_head list; +}; + +/* clock refcounting */ +struct snd_usb_clock_ref { + unsigned char clock; + atomic_t locked; + int opened; + int rate; + bool need_setup; + struct list_head list; +}; /* * snd_usb_endpoint is a model that abstracts everything related to an @@ -86,12 +96,13 @@ static inline unsigned get_usb_high_speed_rate(unsigned int rate) */ static void release_urb_ctx(struct snd_urb_ctx *u) { - if (u->buffer_size) + if (u->urb && u->buffer_size) usb_free_coherent(u->ep->chip->dev, u->buffer_size, u->urb->transfer_buffer, u->urb->transfer_dma); usb_free_urb(u->urb); u->urb = NULL; + u->buffer_size = 0; } static const char *usb_error_string(int err) @@ -119,6 +130,16 @@ static const char *usb_error_string(int err) } } +static inline bool ep_state_running(struct snd_usb_endpoint *ep) +{ + return atomic_read(&ep->state) == EP_STATE_RUNNING; +} + +static inline bool ep_state_update(struct snd_usb_endpoint *ep, int old, int new) +{ + return atomic_try_cmpxchg(&ep->state, &old, new); +} + /** * snd_usb_endpoint_implicit_feedback_sink: Report endpoint usage type * @@ -129,58 +150,129 @@ static const char *usb_error_string(int err) */ int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep) { - return ep->sync_master && - ep->sync_master->type == SND_USB_ENDPOINT_TYPE_DATA && - ep->type == SND_USB_ENDPOINT_TYPE_DATA && - usb_pipeout(ep->pipe); + return ep->implicit_fb_sync && usb_pipeout(ep->pipe); } /* - * For streaming based on information derived from sync endpoints, - * prepare_outbound_urb_sizes() will call next_packet_size() to - * determine the number of samples to be sent in the next packet. + * Return the number of samples to be sent in the next packet + * for streaming based on information derived from sync endpoints * - * For implicit feedback, next_packet_size() is unused. + * This won't be used for implicit feedback which takes the packet size + * returned from the sync source */ -int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep) +static int slave_next_packet_size(struct snd_usb_endpoint *ep, + unsigned int avail) { - unsigned long flags; + unsigned int phase; + int ret; + + if (ep->fill_max) + return ep->maxframesize; + + guard(spinlock_irqsave)(&ep->lock); + phase = (ep->phase & 0xffff) + (ep->freqm << ep->datainterval); + ret = min(phase >> 16, ep->maxframesize); + if (avail && ret >= avail) + ret = -EAGAIN; + else + ep->phase = phase; + return ret; +} + +/* + * Return the number of samples to be sent in the next packet + * for adaptive and synchronous endpoints + */ +static int next_packet_size(struct snd_usb_endpoint *ep, unsigned int avail) +{ + unsigned int sample_accum; int ret; if (ep->fill_max) return ep->maxframesize; - spin_lock_irqsave(&ep->lock, flags); - ep->phase = (ep->phase & 0xffff) - + (ep->freqm << ep->datainterval); - ret = min(ep->phase >> 16, ep->maxframesize); - spin_unlock_irqrestore(&ep->lock, flags); + sample_accum = ep->sample_accum + ep->sample_rem; + if (sample_accum >= ep->pps) { + sample_accum -= ep->pps; + ret = ep->packsize[1]; + } else { + ret = ep->packsize[0]; + } + if (avail && ret >= avail) + ret = -EAGAIN; + else + ep->sample_accum = sample_accum; return ret; } +/* + * snd_usb_endpoint_next_packet_size: Return the number of samples to be sent + * in the next packet + * + * If the size is equal or exceeds @avail, don't proceed but return -EAGAIN + * Exception: @avail = 0 for skipping the check. + */ +int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *ctx, int idx, + unsigned int avail) +{ + unsigned int packet; + + packet = ctx->packet_size[idx]; + if (packet) { + if (avail && packet >= avail) + return -EAGAIN; + return packet; + } + + if (ep->sync_source) + return slave_next_packet_size(ep, avail); + else + return next_packet_size(ep, avail); +} + +static void call_retire_callback(struct snd_usb_endpoint *ep, + struct urb *urb) +{ + struct snd_usb_substream *data_subs; + + data_subs = READ_ONCE(ep->data_subs); + if (data_subs && ep->retire_data_urb) + ep->retire_data_urb(data_subs, urb); +} + static void retire_outbound_urb(struct snd_usb_endpoint *ep, struct snd_urb_ctx *urb_ctx) { - if (ep->retire_data_urb) - ep->retire_data_urb(ep->data_subs, urb_ctx->urb); + call_retire_callback(ep, urb_ctx->urb); } +static void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, + struct snd_usb_endpoint *sender, + const struct urb *urb); + static void retire_inbound_urb(struct snd_usb_endpoint *ep, struct snd_urb_ctx *urb_ctx) { struct urb *urb = urb_ctx->urb; + struct snd_usb_endpoint *sync_sink; if (unlikely(ep->skip_packets > 0)) { ep->skip_packets--; return; } - if (ep->sync_slave) - snd_usb_handle_sync_urb(ep->sync_slave, ep, urb); + sync_sink = READ_ONCE(ep->sync_sink); + if (sync_sink) + snd_usb_handle_sync_urb(sync_sink, ep, urb); - if (ep->retire_data_urb) - ep->retire_data_urb(ep->data_subs, urb); + call_retire_callback(ep, urb); +} + +static inline bool has_tx_length_quirk(struct snd_usb_audio *chip) +{ + return chip->quirk_flags & QUIRK_FLAG_TX_LENGTH; } static void prepare_silent_urb(struct snd_usb_endpoint *ep, @@ -193,7 +285,7 @@ static void prepare_silent_urb(struct snd_usb_endpoint *ep, int i; /* For tx_length_quirk, put packet length at start of packet */ - if (ep->chip->tx_length_quirk) + if (has_tx_length_quirk(ep->chip)) extra = sizeof(packet_length); for (i = 0; i < ctx->packets; ++i) { @@ -201,11 +293,7 @@ static void prepare_silent_urb(struct snd_usb_endpoint *ep, unsigned int length; int counts; - if (ctx->packet_size[i]) - counts = ctx->packet_size[i]; - else - counts = snd_usb_endpoint_next_packet_size(ep); - + counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, 0); length = counts * ep->stride; /* number of silent bytes */ offset = offs * ep->stride + extra * i; urb->iso_frame_desc[i].offset = offset; @@ -222,27 +310,29 @@ static void prepare_silent_urb(struct snd_usb_endpoint *ep, urb->number_of_packets = ctx->packets; urb->transfer_buffer_length = offs * ep->stride + ctx->packets * extra; + ctx->queued = 0; } /* * Prepare a PLAYBACK urb for submission to the bus. */ -static void prepare_outbound_urb(struct snd_usb_endpoint *ep, - struct snd_urb_ctx *ctx) +static int prepare_outbound_urb(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *ctx, + bool in_stream_lock) { struct urb *urb = ctx->urb; unsigned char *cp = urb->transfer_buffer; + struct snd_usb_substream *data_subs; urb->dev = ep->chip->dev; /* we need to set this at each time */ switch (ep->type) { case SND_USB_ENDPOINT_TYPE_DATA: - if (ep->prepare_data_urb) { - ep->prepare_data_urb(ep->data_subs, urb); - } else { - /* no data provider, so send silence */ - prepare_silent_urb(ep, ctx); - } + data_subs = READ_ONCE(ep->data_subs); + if (data_subs && ep->prepare_data_urb) + return ep->prepare_data_urb(data_subs, urb, in_stream_lock); + /* no data provider, so send silence */ + prepare_silent_urb(ep, ctx); break; case SND_USB_ENDPOINT_TYPE_SYNC: @@ -271,13 +361,14 @@ static void prepare_outbound_urb(struct snd_usb_endpoint *ep, break; } + return 0; } /* * Prepare a CAPTURE or SYNC urb for submission to the bus. */ -static inline void prepare_inbound_urb(struct snd_usb_endpoint *ep, - struct snd_urb_ctx *urb_ctx) +static int prepare_inbound_urb(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *urb_ctx) { int i, offs; struct urb *urb = urb_ctx->urb; @@ -302,11 +393,57 @@ static inline void prepare_inbound_urb(struct snd_usb_endpoint *ep, urb->iso_frame_desc[0].offset = 0; break; } + return 0; +} + +/* notify an error as XRUN to the assigned PCM data substream */ +static void notify_xrun(struct snd_usb_endpoint *ep) +{ + struct snd_usb_substream *data_subs; + struct snd_pcm_substream *psubs; + + data_subs = READ_ONCE(ep->data_subs); + if (!data_subs) + return; + psubs = data_subs->pcm_substream; + if (psubs && psubs->runtime && + psubs->runtime->state == SNDRV_PCM_STATE_RUNNING) + snd_pcm_stop_xrun(psubs); +} + +static struct snd_usb_packet_info * +next_packet_fifo_enqueue(struct snd_usb_endpoint *ep) +{ + struct snd_usb_packet_info *p; + + p = ep->next_packet + (ep->next_packet_head + ep->next_packet_queued) % + ARRAY_SIZE(ep->next_packet); + ep->next_packet_queued++; + return p; +} + +static struct snd_usb_packet_info * +next_packet_fifo_dequeue(struct snd_usb_endpoint *ep) +{ + struct snd_usb_packet_info *p; + + p = ep->next_packet + ep->next_packet_head; + ep->next_packet_head++; + ep->next_packet_head %= ARRAY_SIZE(ep->next_packet); + ep->next_packet_queued--; + return p; +} + +static void push_back_to_ready_list(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *ctx) +{ + guard(spinlock_irqsave)(&ep->lock); + list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); } /* * Send output urbs that have been prepared previously. URBs are dequeued - * from ep->ready_playback_urbs and in case there there aren't any available + * from ep->ready_playback_urbs and in case there aren't any available * or there are no packets that have been prepared, this function does * nothing. * @@ -314,52 +451,77 @@ static inline void prepare_inbound_urb(struct snd_usb_endpoint *ep, * is that host controllers don't guarantee the order in which they return * inbound and outbound packets to their submitters. * - * This function is only used for implicit feedback endpoints. For endpoints - * driven by dedicated sync endpoints, URBs are immediately re-submitted - * from their completion handler. + * This function is used both for implicit feedback endpoints and in low- + * latency playback mode. */ -static void queue_pending_output_urbs(struct snd_usb_endpoint *ep) +int snd_usb_queue_pending_output_urbs(struct snd_usb_endpoint *ep, + bool in_stream_lock) { - while (test_bit(EP_FLAG_RUNNING, &ep->flags)) { + bool implicit_fb = snd_usb_endpoint_implicit_feedback_sink(ep); - unsigned long flags; - struct snd_usb_packet_info *uninitialized_var(packet); + while (ep_state_running(ep)) { + struct snd_usb_packet_info *packet; struct snd_urb_ctx *ctx = NULL; int err, i; - spin_lock_irqsave(&ep->lock, flags); - if (ep->next_packet_read_pos != ep->next_packet_write_pos) { - packet = ep->next_packet + ep->next_packet_read_pos; - ep->next_packet_read_pos++; - ep->next_packet_read_pos %= MAX_URBS; - - /* take URB out of FIFO */ - if (!list_empty(&ep->ready_playback_urbs)) + scoped_guard(spinlock_irqsave, &ep->lock) { + if ((!implicit_fb || ep->next_packet_queued > 0) && + !list_empty(&ep->ready_playback_urbs)) { + /* take URB out of FIFO */ ctx = list_first_entry(&ep->ready_playback_urbs, - struct snd_urb_ctx, ready_list); + struct snd_urb_ctx, ready_list); + list_del_init(&ctx->ready_list); + if (implicit_fb) + packet = next_packet_fifo_dequeue(ep); + } } - spin_unlock_irqrestore(&ep->lock, flags); if (ctx == NULL) - return; - - list_del_init(&ctx->ready_list); + break; /* copy over the length information */ - for (i = 0; i < packet->packets; i++) - ctx->packet_size[i] = packet->packet_size[i]; + if (implicit_fb) { + for (i = 0; i < packet->packets; i++) + ctx->packet_size[i] = packet->packet_size[i]; + } /* call the data handler to fill in playback data */ - prepare_outbound_urb(ep, ctx); + err = prepare_outbound_urb(ep, ctx, in_stream_lock); + /* can be stopped during prepare callback */ + if (unlikely(!ep_state_running(ep))) + break; + if (err < 0) { + /* push back to ready list again for -EAGAIN */ + if (err == -EAGAIN) { + push_back_to_ready_list(ep, ctx); + break; + } - 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", - ctx->index, err, ctx->urb); + if (!in_stream_lock) + notify_xrun(ep); + return -EPIPE; + } + + if (!atomic_read(&ep->chip->shutdown)) + err = usb_submit_urb(ctx->urb, GFP_ATOMIC); else - set_bit(ctx->index, &ep->active_mask); + err = -ENODEV; + if (err < 0) { + if (!atomic_read(&ep->chip->shutdown)) { + usb_audio_err(ep->chip, + "Unable to submit urb #%d: %d at %s\n", + ctx->index, err, __func__); + if (!in_stream_lock) + notify_xrun(ep); + } + return -EPIPE; + } + + set_bit(ctx->index, &ep->active_mask); + atomic_inc(&ep->submitted_urbs); } + + return 0; } /* @@ -369,8 +531,6 @@ static void snd_complete_urb(struct urb *urb) { struct snd_urb_ctx *ctx = urb->context; struct snd_usb_endpoint *ep = ctx->ep; - struct snd_pcm_substream *substream; - unsigned long flags; int err; if (unlikely(urb->status == -ENOENT || /* unlinked */ @@ -382,138 +542,440 @@ static void snd_complete_urb(struct urb *urb) if (unlikely(atomic_read(&ep->chip->shutdown))) goto exit_clear; - if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags))) + if (unlikely(!ep_state_running(ep))) goto exit_clear; if (usb_pipeout(ep->pipe)) { retire_outbound_urb(ep, ctx); /* can be stopped during retire callback */ - if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags))) + if (unlikely(!ep_state_running(ep))) goto exit_clear; - if (snd_usb_endpoint_implicit_feedback_sink(ep)) { - spin_lock_irqsave(&ep->lock, flags); - list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); - spin_unlock_irqrestore(&ep->lock, flags); - queue_pending_output_urbs(ep); - - goto exit_clear; + /* in low-latency and implicit-feedback modes, push back the + * URB to ready list at first, then process as much as possible + */ + if (ep->lowlatency_playback || + snd_usb_endpoint_implicit_feedback_sink(ep)) { + push_back_to_ready_list(ep, ctx); + clear_bit(ctx->index, &ep->active_mask); + snd_usb_queue_pending_output_urbs(ep, false); + /* decrement at last, and check xrun */ + if (atomic_dec_and_test(&ep->submitted_urbs) && + !snd_usb_endpoint_implicit_feedback_sink(ep)) + notify_xrun(ep); + return; } - prepare_outbound_urb(ep, ctx); + /* in non-lowlatency mode, no error handling for prepare */ + prepare_outbound_urb(ep, ctx, false); + /* can be stopped during prepare callback */ + if (unlikely(!ep_state_running(ep))) + goto exit_clear; } else { retire_inbound_urb(ep, ctx); /* can be stopped during retire callback */ - if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags))) + if (unlikely(!ep_state_running(ep))) goto exit_clear; prepare_inbound_urb(ep, ctx); } - err = usb_submit_urb(urb, GFP_ATOMIC); + if (!atomic_read(&ep->chip->shutdown)) + err = usb_submit_urb(urb, GFP_ATOMIC); + else + err = -ENODEV; if (err == 0) return; - usb_audio_err(ep->chip, "cannot submit urb (err = %d)\n", err); - if (ep->data_subs && ep->data_subs->pcm_substream) { - substream = ep->data_subs->pcm_substream; - snd_pcm_stop_xrun(substream); + if (!atomic_read(&ep->chip->shutdown)) { + usb_audio_err(ep->chip, "cannot submit urb (err = %d)\n", err); + notify_xrun(ep); } exit_clear: clear_bit(ctx->index, &ep->active_mask); + atomic_dec(&ep->submitted_urbs); +} + +/* + * Find or create a refcount object for the given interface + * + * The objects are released altogether in snd_usb_endpoint_free_all() + */ +static struct snd_usb_iface_ref * +iface_ref_find(struct snd_usb_audio *chip, int iface) +{ + struct snd_usb_iface_ref *ip; + + list_for_each_entry(ip, &chip->iface_ref_list, list) + if (ip->iface == iface) + return ip; + + ip = kzalloc(sizeof(*ip), GFP_KERNEL); + if (!ip) + return NULL; + ip->iface = iface; + list_add_tail(&ip->list, &chip->iface_ref_list); + return ip; } +/* Similarly, a refcount object for clock */ +static struct snd_usb_clock_ref * +clock_ref_find(struct snd_usb_audio *chip, int clock) +{ + struct snd_usb_clock_ref *ref; + + list_for_each_entry(ref, &chip->clock_ref_list, list) + if (ref->clock == clock) + return ref; + + ref = kzalloc(sizeof(*ref), GFP_KERNEL); + if (!ref) + return NULL; + ref->clock = clock; + atomic_set(&ref->locked, 0); + list_add_tail(&ref->list, &chip->clock_ref_list); + return ref; +} + +/* + * Get the existing endpoint object corresponding EP + * Returns NULL if not present. + */ +struct snd_usb_endpoint * +snd_usb_get_endpoint(struct snd_usb_audio *chip, int ep_num) +{ + struct snd_usb_endpoint *ep; + + list_for_each_entry(ep, &chip->ep_list, list) { + if (ep->ep_num == ep_num) + return ep; + } + + return NULL; +} + +#define ep_type_name(type) \ + (type == SND_USB_ENDPOINT_TYPE_DATA ? "data" : "sync") + /** * snd_usb_add_endpoint: Add an endpoint to an USB audio chip * * @chip: The chip - * @alts: The USB host interface * @ep_num: The number of the endpoint to use - * @direction: SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE * @type: SND_USB_ENDPOINT_TYPE_DATA or SND_USB_ENDPOINT_TYPE_SYNC * * If the requested endpoint has not been added to the given chip before, - * a new instance is created. Otherwise, a pointer to the previoulsy - * created instance is returned. In case of any error, NULL is returned. + * a new instance is created. + * + * Returns zero on success or a negative error code. * - * New endpoints will be added to chip->ep_list and must be freed by - * calling snd_usb_endpoint_free(). + * New endpoints will be added to chip->ep_list and freed by + * calling snd_usb_endpoint_free_all(). * * For SND_USB_ENDPOINT_TYPE_SYNC, the caller needs to guarantee that * bNumEndpoints > 1 beforehand. */ -struct snd_usb_endpoint *snd_usb_add_endpoint(struct snd_usb_audio *chip, - struct usb_host_interface *alts, - int ep_num, int direction, int type) +int snd_usb_add_endpoint(struct snd_usb_audio *chip, int ep_num, int type) { struct snd_usb_endpoint *ep; - int is_playback = direction == SNDRV_PCM_STREAM_PLAYBACK; - - if (WARN_ON(!alts)) - return NULL; - - mutex_lock(&chip->mutex); - - list_for_each_entry(ep, &chip->ep_list, list) { - if (ep->ep_num == ep_num && - 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", - ep_num, ep->iface, ep->altsetting, ep); - goto __exit_unlock; - } - } + bool is_playback; - usb_audio_dbg(chip, "Creating new %s %s endpoint #%x\n", - is_playback ? "playback" : "capture", - type == SND_USB_ENDPOINT_TYPE_DATA ? "data" : "sync", - ep_num); + ep = snd_usb_get_endpoint(chip, ep_num); + if (ep) + return 0; + usb_audio_dbg(chip, "Creating new %s endpoint #%x\n", + ep_type_name(type), + ep_num); ep = kzalloc(sizeof(*ep), GFP_KERNEL); if (!ep) - goto __exit_unlock; + return -ENOMEM; ep->chip = chip; spin_lock_init(&ep->lock); ep->type = type; ep->ep_num = ep_num; - ep->iface = alts->desc.bInterfaceNumber; - ep->altsetting = alts->desc.bAlternateSetting; INIT_LIST_HEAD(&ep->ready_playback_urbs); - ep_num &= USB_ENDPOINT_NUMBER_MASK; + atomic_set(&ep->submitted_urbs, 0); + is_playback = ((ep_num & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT); + ep_num &= USB_ENDPOINT_NUMBER_MASK; if (is_playback) ep->pipe = usb_sndisocpipe(chip->dev, ep_num); else ep->pipe = usb_rcvisocpipe(chip->dev, ep_num); - if (type == SND_USB_ENDPOINT_TYPE_SYNC) { - if (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && - get_endpoint(alts, 1)->bRefresh >= 1 && - get_endpoint(alts, 1)->bRefresh <= 9) - ep->syncinterval = get_endpoint(alts, 1)->bRefresh; - else if (snd_usb_get_speed(chip->dev) == USB_SPEED_FULL) - ep->syncinterval = 1; - else if (get_endpoint(alts, 1)->bInterval >= 1 && - get_endpoint(alts, 1)->bInterval <= 16) - ep->syncinterval = get_endpoint(alts, 1)->bInterval - 1; - else - ep->syncinterval = 3; + list_add_tail(&ep->list, &chip->ep_list); + return 0; +} + +/* Set up syncinterval and maxsyncsize for a sync EP */ +static void endpoint_set_syncinterval(struct snd_usb_audio *chip, + struct snd_usb_endpoint *ep) +{ + struct usb_host_interface *alts; + struct usb_endpoint_descriptor *desc; - ep->syncmaxsize = le16_to_cpu(get_endpoint(alts, 1)->wMaxPacketSize); + alts = snd_usb_get_host_interface(chip, ep->iface, ep->altsetting); + if (!alts) + return; + + desc = get_endpoint(alts, ep->ep_idx); + if (desc->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && + desc->bRefresh >= 1 && desc->bRefresh <= 9) + ep->syncinterval = desc->bRefresh; + else if (snd_usb_get_speed(chip->dev) == USB_SPEED_FULL) + ep->syncinterval = 1; + else if (desc->bInterval >= 1 && desc->bInterval <= 16) + ep->syncinterval = desc->bInterval - 1; + else + ep->syncinterval = 3; + + ep->syncmaxsize = le16_to_cpu(desc->wMaxPacketSize); +} + +static bool endpoint_compatible(struct snd_usb_endpoint *ep, + const struct audioformat *fp, + const struct snd_pcm_hw_params *params) +{ + if (!ep->opened) + return false; + if (ep->cur_audiofmt != fp) + return false; + if (ep->cur_rate != params_rate(params) || + ep->cur_format != params_format(params) || + ep->cur_period_frames != params_period_size(params) || + ep->cur_buffer_periods != params_periods(params)) + return false; + return true; +} + +/* + * Check whether the given fp and hw params are compatible with the current + * setup of the target EP for implicit feedback sync + */ +bool snd_usb_endpoint_compatible(struct snd_usb_audio *chip, + struct snd_usb_endpoint *ep, + const struct audioformat *fp, + const struct snd_pcm_hw_params *params) +{ + guard(mutex)(&chip->mutex); + return endpoint_compatible(ep, fp, params); +} + +/* + * snd_usb_endpoint_open: Open the endpoint + * + * Called from hw_params to assign the endpoint to the substream. + * It's reference-counted, and only the first opener is allowed to set up + * arbitrary parameters. The later opener must be compatible with the + * former opened parameters. + * The endpoint needs to be closed via snd_usb_endpoint_close() later. + * + * Note that this function doesn't configure the endpoint. The substream + * needs to set it up later via snd_usb_endpoint_set_params() and + * snd_usb_endpoint_prepare(). + */ +struct snd_usb_endpoint * +snd_usb_endpoint_open(struct snd_usb_audio *chip, + const struct audioformat *fp, + const struct snd_pcm_hw_params *params, + bool is_sync_ep, + bool fixed_rate) +{ + struct snd_usb_endpoint *ep; + int ep_num = is_sync_ep ? fp->sync_ep : fp->endpoint; + + guard(mutex)(&chip->mutex); + ep = snd_usb_get_endpoint(chip, ep_num); + if (!ep) { + usb_audio_err(chip, "Cannot find EP 0x%x to open\n", ep_num); + return NULL; } - list_add_tail(&ep->list, &chip->ep_list); + if (!ep->opened) { + if (is_sync_ep) { + ep->iface = fp->sync_iface; + ep->altsetting = fp->sync_altsetting; + ep->ep_idx = fp->sync_ep_idx; + } else { + ep->iface = fp->iface; + ep->altsetting = fp->altsetting; + ep->ep_idx = fp->ep_idx; + } + usb_audio_dbg(chip, "Open EP 0x%x, iface=%d:%d, idx=%d\n", + ep_num, ep->iface, ep->altsetting, ep->ep_idx); + + ep->iface_ref = iface_ref_find(chip, ep->iface); + if (!ep->iface_ref) + return NULL; + + if (fp->protocol != UAC_VERSION_1) { + ep->clock_ref = clock_ref_find(chip, fp->clock); + if (!ep->clock_ref) + return NULL; + ep->clock_ref->opened++; + } + + ep->cur_audiofmt = fp; + ep->cur_channels = fp->channels; + ep->cur_rate = params_rate(params); + ep->cur_format = params_format(params); + ep->cur_frame_bytes = snd_pcm_format_physical_width(ep->cur_format) * + ep->cur_channels / 8; + ep->cur_period_frames = params_period_size(params); + ep->cur_period_bytes = ep->cur_period_frames * ep->cur_frame_bytes; + ep->cur_buffer_periods = params_periods(params); + + if (ep->type == SND_USB_ENDPOINT_TYPE_SYNC) + endpoint_set_syncinterval(chip, ep); + + ep->implicit_fb_sync = fp->implicit_fb; + ep->need_setup = true; + ep->need_prepare = true; + ep->fixed_rate = fixed_rate; + + usb_audio_dbg(chip, " channels=%d, rate=%d, format=%s, period_bytes=%d, periods=%d, implicit_fb=%d\n", + ep->cur_channels, ep->cur_rate, + snd_pcm_format_name(ep->cur_format), + ep->cur_period_bytes, ep->cur_buffer_periods, + ep->implicit_fb_sync); + + } else { + if (WARN_ON(!ep->iface_ref)) + return NULL; + + if (!endpoint_compatible(ep, fp, params)) { + usb_audio_err(chip, "Incompatible EP setup for 0x%x\n", + ep_num); + return NULL; + } + + usb_audio_dbg(chip, "Reopened EP 0x%x (count %d)\n", + ep_num, ep->opened); + } -__exit_unlock: - mutex_unlock(&chip->mutex); + if (!ep->iface_ref->opened++) + ep->iface_ref->need_setup = true; + ep->opened++; return ep; } /* + * snd_usb_endpoint_set_sync: Link data and sync endpoints + * + * Pass NULL to sync_ep to unlink again + */ +void snd_usb_endpoint_set_sync(struct snd_usb_audio *chip, + struct snd_usb_endpoint *data_ep, + struct snd_usb_endpoint *sync_ep) +{ + data_ep->sync_source = sync_ep; +} + +/* + * Set data endpoint callbacks and the assigned data stream + * + * Called at PCM trigger and cleanups. + * Pass NULL to deactivate each callback. + */ +void snd_usb_endpoint_set_callback(struct snd_usb_endpoint *ep, + int (*prepare)(struct snd_usb_substream *subs, + struct urb *urb, + bool in_stream_lock), + void (*retire)(struct snd_usb_substream *subs, + struct urb *urb), + struct snd_usb_substream *data_subs) +{ + ep->prepare_data_urb = prepare; + ep->retire_data_urb = retire; + if (data_subs) + ep->lowlatency_playback = data_subs->lowlatency_playback; + else + ep->lowlatency_playback = false; + WRITE_ONCE(ep->data_subs, data_subs); +} + +static int endpoint_set_interface(struct snd_usb_audio *chip, + struct snd_usb_endpoint *ep, + bool set) +{ + int altset = set ? ep->altsetting : 0; + int err; + int retries = 0; + const int max_retries = 5; + + if (ep->iface_ref->altset == altset) + return 0; + /* already disconnected? */ + if (unlikely(atomic_read(&chip->shutdown))) + return -ENODEV; + + usb_audio_dbg(chip, "Setting usb interface %d:%d for EP 0x%x\n", + ep->iface, altset, ep->ep_num); +retry: + err = usb_set_interface(chip->dev, ep->iface, altset); + if (err < 0) { + if (err == -EPROTO && ++retries <= max_retries) { + msleep(5 * (1 << (retries - 1))); + goto retry; + } + usb_audio_err_ratelimited( + chip, "%d:%d: usb_set_interface failed (%d)\n", + ep->iface, altset, err); + return err; + } + + if (chip->quirk_flags & QUIRK_FLAG_IFACE_DELAY) + msleep(50); + ep->iface_ref->altset = altset; + return 0; +} + +/* + * snd_usb_endpoint_close: Close the endpoint + * + * Unreference the already opened endpoint via snd_usb_endpoint_open(). + */ +void snd_usb_endpoint_close(struct snd_usb_audio *chip, + struct snd_usb_endpoint *ep) +{ + guard(mutex)(&chip->mutex); + usb_audio_dbg(chip, "Closing EP 0x%x (count %d)\n", + ep->ep_num, ep->opened); + + if (!--ep->iface_ref->opened && + !(chip->quirk_flags & QUIRK_FLAG_IFACE_SKIP_CLOSE)) + endpoint_set_interface(chip, ep, false); + + if (!--ep->opened) { + if (ep->clock_ref) { + if (!--ep->clock_ref->opened) + ep->clock_ref->rate = 0; + } + ep->iface = 0; + ep->altsetting = 0; + ep->cur_audiofmt = NULL; + ep->cur_rate = 0; + ep->iface_ref = NULL; + ep->clock_ref = NULL; + usb_audio_dbg(chip, "EP 0x%x closed\n", ep->ep_num); + } +} + +/* Prepare for suspening EP, called from the main suspend handler */ +void snd_usb_endpoint_suspend(struct snd_usb_endpoint *ep) +{ + ep->need_prepare = true; + if (ep->iface_ref) + ep->iface_ref->need_setup = true; + if (ep->clock_ref) + ep->clock_ref->rate = 0; +} + +/* * wait until all urbs are processed. */ static int wait_clear_urbs(struct snd_usb_endpoint *ep) @@ -521,8 +983,11 @@ static int wait_clear_urbs(struct snd_usb_endpoint *ep) unsigned long end_time = jiffies + msecs_to_jiffies(1000); int alive; + if (atomic_read(&ep->state) != EP_STATE_STOPPING) + return 0; + do { - alive = bitmap_weight(&ep->active_mask, ep->nurbs); + alive = atomic_read(&ep->submitted_urbs); if (!alive) break; @@ -533,12 +998,11 @@ static int wait_clear_urbs(struct snd_usb_endpoint *ep) usb_audio_err(ep->chip, "timeout: still %d active urbs on EP #%x\n", alive, ep->ep_num); - clear_bit(EP_FLAG_STOPPING, &ep->flags); - ep->data_subs = NULL; - ep->sync_slave = NULL; - ep->retire_data_urb = NULL; - ep->prepare_data_urb = NULL; + if (ep_state_update(ep, EP_STATE_STOPPING, EP_STATE_STOPPED)) { + ep->sync_sink = NULL; + snd_usb_endpoint_set_callback(ep, NULL, NULL, NULL); + } return 0; } @@ -548,25 +1012,33 @@ static int wait_clear_urbs(struct snd_usb_endpoint *ep) */ void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep) { - if (ep && test_bit(EP_FLAG_STOPPING, &ep->flags)) + if (ep) wait_clear_urbs(ep); } /* - * unlink active urbs. + * Stop active urbs + * + * This function moves the EP to STOPPING state if it's being RUNNING. */ -static int deactivate_urbs(struct snd_usb_endpoint *ep, bool force) +static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) { unsigned int i; - if (!force && atomic_read(&ep->chip->shutdown)) /* to be sure... */ - return -EBADFD; + if (!force && atomic_read(&ep->running)) + return -EBUSY; - clear_bit(EP_FLAG_RUNNING, &ep->flags); + if (!ep_state_update(ep, EP_STATE_RUNNING, EP_STATE_STOPPING)) + return 0; - INIT_LIST_HEAD(&ep->ready_playback_urbs); - ep->next_packet_read_pos = 0; - ep->next_packet_write_pos = 0; + scoped_guard(spinlock_irqsave, &ep->lock) { + INIT_LIST_HEAD(&ep->ready_playback_urbs); + ep->next_packet_head = 0; + ep->next_packet_queued = 0; + } + + if (keep_pending) + return 0; for (i = 0; i < ep->nurbs; i++) { if (test_bit(i, &ep->active_mask)) { @@ -583,61 +1055,61 @@ static int deactivate_urbs(struct snd_usb_endpoint *ep, bool force) /* * release an endpoint's urbs */ -static void release_urbs(struct snd_usb_endpoint *ep, int force) +static int release_urbs(struct snd_usb_endpoint *ep, bool force) { - int i; + int i, err; /* route incoming urbs to nirvana */ - ep->retire_data_urb = NULL; - ep->prepare_data_urb = NULL; + snd_usb_endpoint_set_callback(ep, NULL, NULL, NULL); + + /* stop and unlink urbs */ + err = stop_urbs(ep, force, false); + if (err) + return err; - /* stop urbs */ - deactivate_urbs(ep, force); wait_clear_urbs(ep); for (i = 0; i < ep->nurbs; i++) release_urb_ctx(&ep->urb[i]); - if (ep->syncbuf) - usb_free_coherent(ep->chip->dev, SYNC_URBS * 4, - ep->syncbuf, ep->sync_dma); + usb_free_coherent(ep->chip->dev, SYNC_URBS * 4, + ep->syncbuf, ep->sync_dma); ep->syncbuf = NULL; ep->nurbs = 0; + return 0; } /* * configure a data endpoint */ -static int data_ep_set_params(struct snd_usb_endpoint *ep, - snd_pcm_format_t pcm_format, - unsigned int channels, - unsigned int period_bytes, - unsigned int frames_per_period, - unsigned int periods_per_buffer, - struct audioformat *fmt, - struct snd_usb_endpoint *sync_ep) +static int data_ep_set_params(struct snd_usb_endpoint *ep) { + struct snd_usb_audio *chip = ep->chip; unsigned int maxsize, minsize, packs_per_ms, max_packs_per_urb; unsigned int max_packs_per_period, urbs_per_period, urb_packs; unsigned int max_urbs, i; - int frame_bits = snd_pcm_format_physical_width(pcm_format) * channels; - int tx_length_quirk = (ep->chip->tx_length_quirk && + const struct audioformat *fmt = ep->cur_audiofmt; + int frame_bits = ep->cur_frame_bytes * 8; + int tx_length_quirk = (has_tx_length_quirk(chip) && usb_pipeout(ep->pipe)); - if (pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE && fmt->dsd_dop) { + usb_audio_dbg(chip, "Setting params for data EP 0x%x, pipe 0x%x\n", + ep->ep_num, ep->pipe); + + if (ep->cur_format == SNDRV_PCM_FORMAT_DSD_U16_LE && fmt->dsd_dop) { /* * When operating in DSD DOP mode, the size of a sample frame * in hardware differs from the actual physical format width * because we need to make room for the DOP markers. */ - frame_bits += channels << 3; + frame_bits += ep->cur_channels << 3; } ep->datainterval = fmt->datainterval; ep->stride = frame_bits >> 3; - switch (pcm_format) { + switch (ep->cur_format) { case SNDRV_PCM_FORMAT_U8: ep->silence_value = 0x80; break; @@ -690,16 +1162,16 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep, else ep->curpacksize = maxsize; - if (snd_usb_get_speed(ep->chip->dev) != USB_SPEED_FULL) { + if (snd_usb_get_speed(chip->dev) != USB_SPEED_FULL) { packs_per_ms = 8 >> ep->datainterval; max_packs_per_urb = MAX_PACKS_HS; } else { packs_per_ms = 1; max_packs_per_urb = MAX_PACKS; } - if (sync_ep && !snd_usb_endpoint_implicit_feedback_sink(ep)) + if (ep->sync_source && !ep->implicit_fb_sync) max_packs_per_urb = min(max_packs_per_urb, - 1U << sync_ep->syncinterval); + 1U << ep->sync_source->syncinterval); max_packs_per_urb = max(1u, max_packs_per_urb >> ep->datainterval); /* @@ -710,26 +1182,11 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep, * Playback endpoints with implicit sync much use the same parameters * as their corresponding capture endpoint. */ - if (usb_pipein(ep->pipe) || - snd_usb_endpoint_implicit_feedback_sink(ep)) { + if (usb_pipein(ep->pipe) || ep->implicit_fb_sync) { - urb_packs = packs_per_ms; - /* - * Wireless devices can poll at a max rate of once per 4ms. - * For dataintervals less than 5, increase the packet count to - * allow the host controller to use bursting to fill in the - * gaps. - */ - if (snd_usb_get_speed(ep->chip->dev) == USB_SPEED_WIRELESS) { - int interval = ep->datainterval; - while (interval < 5) { - urb_packs <<= 1; - ++interval; - } - } /* make capture URBs <= 1 ms and smaller than a period */ - urb_packs = min(max_packs_per_urb, urb_packs); - while (urb_packs > 1 && urb_packs * maxsize >= period_bytes) + urb_packs = min(max_packs_per_urb, packs_per_ms); + while (urb_packs > 1 && urb_packs * maxsize >= ep->cur_period_bytes) urb_packs >>= 1; ep->nurbs = MAX_URBS; @@ -744,12 +1201,12 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep, minsize = (ep->freqn >> (16 - ep->datainterval)) * (frame_bits >> 3); /* with sync from device, assume it can be 12% lower */ - if (sync_ep) + if (ep->sync_source) minsize -= minsize >> 3; minsize = max(minsize, 1u); /* how many packets will contain an entire ALSA period? */ - max_packs_per_period = DIV_ROUND_UP(period_bytes, minsize); + max_packs_per_period = DIV_ROUND_UP(ep->cur_period_bytes, minsize); /* how many URBs will contain a period? */ urbs_per_period = DIV_ROUND_UP(max_packs_per_period, @@ -758,13 +1215,13 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep, urb_packs = DIV_ROUND_UP(max_packs_per_period, urbs_per_period); /* limit the number of frames in a single URB */ - ep->max_urb_frames = DIV_ROUND_UP(frames_per_period, - urbs_per_period); + ep->max_urb_frames = DIV_ROUND_UP(ep->cur_period_frames, + urbs_per_period); /* try to use enough URBs to contain an entire ALSA buffer */ max_urbs = min((unsigned) MAX_URBS, MAX_QUEUE * packs_per_ms / urb_packs); - ep->nurbs = min(max_urbs, urbs_per_period * periods_per_buffer); + ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods); } /* allocate and initialize data urbs */ @@ -782,7 +1239,7 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep, goto out_of_memory; u->urb->transfer_buffer = - usb_alloc_coherent(ep->chip->dev, u->buffer_size, + usb_alloc_coherent(chip->dev, u->buffer_size, GFP_KERNEL, &u->urb->transfer_dma); if (!u->urb->transfer_buffer) goto out_of_memory; @@ -797,7 +1254,7 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep, return 0; out_of_memory: - release_urbs(ep, 0); + release_urbs(ep, false); return -ENOMEM; } @@ -806,13 +1263,18 @@ out_of_memory: */ static int sync_ep_set_params(struct snd_usb_endpoint *ep) { + struct snd_usb_audio *chip = ep->chip; int i; - ep->syncbuf = usb_alloc_coherent(ep->chip->dev, SYNC_URBS * 4, + usb_audio_dbg(chip, "Setting params for sync EP 0x%x, pipe 0x%x\n", + ep->ep_num, ep->pipe); + + ep->syncbuf = usb_alloc_coherent(chip->dev, SYNC_URBS * 4, GFP_KERNEL, &ep->sync_dma); if (!ep->syncbuf) return -ENOMEM; + ep->nurbs = SYNC_URBS; for (i = 0; i < SYNC_URBS; i++) { struct snd_urb_ctx *u = &ep->urb[i]; u->index = i; @@ -832,62 +1294,79 @@ static int sync_ep_set_params(struct snd_usb_endpoint *ep) u->urb->complete = snd_complete_urb; } - ep->nurbs = SYNC_URBS; - return 0; out_of_memory: - release_urbs(ep, 0); + release_urbs(ep, false); return -ENOMEM; } -/** +/* update the rate of the referred clock; return the actual rate */ +static int update_clock_ref_rate(struct snd_usb_audio *chip, + struct snd_usb_endpoint *ep) +{ + struct snd_usb_clock_ref *clock = ep->clock_ref; + int rate = ep->cur_rate; + + if (!clock || clock->rate == rate) + return rate; + if (clock->rate) { + if (atomic_read(&clock->locked)) + return clock->rate; + if (clock->rate != rate) { + usb_audio_err(chip, "Mismatched sample rate %d vs %d for EP 0x%x\n", + clock->rate, rate, ep->ep_num); + return clock->rate; + } + } + clock->rate = rate; + clock->need_setup = true; + return rate; +} + +/* * snd_usb_endpoint_set_params: configure an snd_usb_endpoint * - * @ep: the snd_usb_endpoint to configure - * @pcm_format: the audio fomat. - * @channels: the number of audio channels. - * @period_bytes: the number of bytes in one alsa period. - * @period_frames: the number of frames in one alsa period. - * @buffer_periods: the number of periods in one alsa buffer. - * @rate: the frame rate. - * @fmt: the USB audio format information - * @sync_ep: the sync endpoint to use, if any - * + * It's called either from hw_params callback. * Determine the number of URBs to be used on this endpoint. * An endpoint must be configured before it can be started. * An endpoint that is already running can not be reconfigured. */ -int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep, - snd_pcm_format_t pcm_format, - unsigned int channels, - unsigned int period_bytes, - unsigned int period_frames, - unsigned int buffer_periods, - unsigned int rate, - struct audioformat *fmt, - struct snd_usb_endpoint *sync_ep) +int snd_usb_endpoint_set_params(struct snd_usb_audio *chip, + struct snd_usb_endpoint *ep) { + const struct audioformat *fmt = ep->cur_audiofmt; int err; - if (ep->use_count != 0) { - usb_audio_warn(ep->chip, - "Unable to change format on ep #%x: already in use\n", - ep->ep_num); - return -EBUSY; - } + guard(mutex)(&chip->mutex); + if (!ep->need_setup) + return 0; /* release old buffers, if any */ - release_urbs(ep, 0); + err = release_urbs(ep, false); + if (err < 0) + return err; ep->datainterval = fmt->datainterval; ep->maxpacksize = fmt->maxpacksize; ep->fill_max = !!(fmt->attributes & UAC_EP_CS_ATTR_FILL_MAX); - if (snd_usb_get_speed(ep->chip->dev) == USB_SPEED_FULL) - ep->freqn = get_usb_full_speed_rate(rate); - else - ep->freqn = get_usb_high_speed_rate(rate); + if (snd_usb_get_speed(chip->dev) == USB_SPEED_FULL) { + ep->freqn = get_usb_full_speed_rate(ep->cur_rate); + ep->pps = 1000 >> ep->datainterval; + } else { + ep->freqn = get_usb_high_speed_rate(ep->cur_rate); + ep->pps = 8000 >> ep->datainterval; + } + + ep->sample_rem = ep->cur_rate % ep->pps; + ep->packsize[0] = ep->cur_rate / ep->pps; + ep->packsize[1] = (ep->cur_rate + (ep->pps - 1)) / ep->pps; + if (ep->packsize[1] > ep->maxpacksize) { + usb_audio_dbg(chip, "Too small maxpacksize %u for rate %u / pps %u\n", + ep->maxpacksize, ep->cur_rate, ep->pps); + return -EINVAL; + } /* calculate the frequency in 16.16 format */ ep->freqm = ep->freqn; @@ -897,9 +1376,7 @@ int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep, switch (ep->type) { case SND_USB_ENDPOINT_TYPE_DATA: - err = data_ep_set_params(ep, pcm_format, channels, - period_bytes, period_frames, - buffer_periods, fmt, sync_ep); + err = data_ep_set_params(ep); break; case SND_USB_ENDPOINT_TYPE_SYNC: err = sync_ep_set_params(ep); @@ -908,19 +1385,153 @@ int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep, err = -EINVAL; } - usb_audio_dbg(ep->chip, - "Setting params for ep #%x (type %d, %d urbs), ret=%d\n", - ep->ep_num, ep->type, ep->nurbs, err); + usb_audio_dbg(chip, "Set up %d URBS, ret=%d\n", ep->nurbs, err); + + if (err < 0) + return err; + + /* some unit conversions in runtime */ + ep->maxframesize = ep->maxpacksize / ep->cur_frame_bytes; + ep->curframesize = ep->curpacksize / ep->cur_frame_bytes; + + err = update_clock_ref_rate(chip, ep); + if (err >= 0) { + ep->need_setup = false; + err = 0; + } return err; } +static int init_sample_rate(struct snd_usb_audio *chip, + struct snd_usb_endpoint *ep) +{ + struct snd_usb_clock_ref *clock = ep->clock_ref; + int rate, err; + + rate = update_clock_ref_rate(chip, ep); + if (rate < 0) + return rate; + if (clock && !clock->need_setup) + return 0; + + if (!ep->fixed_rate) { + err = snd_usb_init_sample_rate(chip, ep->cur_audiofmt, rate); + if (err < 0) { + if (clock) + clock->rate = 0; /* reset rate */ + return err; + } + } + + if (clock) + clock->need_setup = false; + return 0; +} + +/* + * snd_usb_endpoint_prepare: Prepare the endpoint + * + * This function sets up the EP to be fully usable state. + * It's called either from prepare callback. + * The function checks need_setup flag, and performs nothing unless needed, + * so it's safe to call this multiple times. + * + * This returns zero if unchanged, 1 if the configuration has changed, + * or a negative error code. + */ +int snd_usb_endpoint_prepare(struct snd_usb_audio *chip, + struct snd_usb_endpoint *ep) +{ + bool iface_first; + int err = 0; + + guard(mutex)(&chip->mutex); + if (WARN_ON(!ep->iface_ref)) + return 0; + if (!ep->need_prepare) + return 0; + + /* If the interface has been already set up, just set EP parameters */ + if (!ep->iface_ref->need_setup) { + /* sample rate setup of UAC1 is per endpoint, and we need + * to update at each EP configuration + */ + if (ep->cur_audiofmt->protocol == UAC_VERSION_1) { + err = init_sample_rate(chip, ep); + if (err < 0) + return err; + } + goto done; + } + + /* Need to deselect altsetting at first */ + endpoint_set_interface(chip, ep, false); + + /* Some UAC1 devices (e.g. Yamaha THR10) need the host interface + * to be set up before parameter setups + */ + iface_first = ep->cur_audiofmt->protocol == UAC_VERSION_1; + /* Workaround for devices that require the interface setup at first like UAC1 */ + if (chip->quirk_flags & QUIRK_FLAG_SET_IFACE_FIRST) + iface_first = true; + if (iface_first) { + err = endpoint_set_interface(chip, ep, true); + if (err < 0) + return err; + } + + err = snd_usb_init_pitch(chip, ep->cur_audiofmt); + if (err < 0) + return err; + + err = init_sample_rate(chip, ep); + if (err < 0) + return err; + + err = snd_usb_select_mode_quirk(chip, ep->cur_audiofmt); + if (err < 0) + return err; + + /* for UAC2/3, enable the interface altset here at last */ + if (!iface_first) { + err = endpoint_set_interface(chip, ep, true); + if (err < 0) + return err; + } + + ep->iface_ref->need_setup = false; + + done: + ep->need_prepare = false; + return 1; +} +EXPORT_SYMBOL_GPL(snd_usb_endpoint_prepare); + +/* get the current rate set to the given clock by any endpoint */ +int snd_usb_endpoint_get_clock_rate(struct snd_usb_audio *chip, int clock) +{ + struct snd_usb_clock_ref *ref; + int rate = 0; + + if (!clock) + return 0; + guard(mutex)(&chip->mutex); + list_for_each_entry(ref, &chip->clock_ref_list, list) { + if (ref->clock == clock) { + rate = ref->rate; + break; + } + } + return rate; +} + /** * snd_usb_endpoint_start: start an snd_usb_endpoint * * @ep: the endpoint to start * - * A call to this function will increment the use count of the endpoint. + * A call to this function will increment the running count of the endpoint. * In case it is not already running, the URBs for this endpoint will be * submitted. Otherwise, this function does nothing. * @@ -930,22 +1541,31 @@ int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep, */ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep) { + bool is_playback = usb_pipeout(ep->pipe); int err; unsigned int i; if (atomic_read(&ep->chip->shutdown)) return -EBADFD; + if (ep->sync_source) + WRITE_ONCE(ep->sync_source->sync_sink, ep); + + usb_audio_dbg(ep->chip, "Starting %s EP 0x%x (running %d)\n", + ep_type_name(ep->type), ep->ep_num, + atomic_read(&ep->running)); + /* already running? */ - if (++ep->use_count != 1) + if (atomic_inc_return(&ep->running) != 1) return 0; - /* just to be sure */ - deactivate_urbs(ep, false); + if (ep->clock_ref) + atomic_inc(&ep->clock_ref->locked); ep->active_mask = 0; ep->unlink_mask = 0; ep->phase = 0; + ep->sample_accum = 0; snd_usb_endpoint_start_quirk(ep); @@ -956,15 +1576,14 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep) * from that context. */ - set_bit(EP_FLAG_RUNNING, &ep->flags); - - if (snd_usb_endpoint_implicit_feedback_sink(ep)) { - for (i = 0; i < ep->nurbs; i++) { - struct snd_urb_ctx *ctx = ep->urb + i; - list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); - } + if (!ep_state_update(ep, EP_STATE_STOPPED, EP_STATE_RUNNING)) + goto __error; - return 0; + if (snd_usb_endpoint_implicit_feedback_sink(ep) && + !(ep->chip->quirk_flags & QUIRK_FLAG_PLAYBACK_FIRST)) { + usb_audio_dbg(ep->chip, "No URB submission due to implicit fb sync\n"); + i = 0; + goto fill_rest; } for (i = 0; i < ep->nurbs; i++) { @@ -973,28 +1592,55 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep) if (snd_BUG_ON(!urb)) goto __error; - if (usb_pipeout(ep->pipe)) { - prepare_outbound_urb(ep, urb->context); - } else { - prepare_inbound_urb(ep, urb->context); + if (is_playback) + err = prepare_outbound_urb(ep, urb->context, true); + else + err = prepare_inbound_urb(ep, urb->context); + if (err < 0) { + /* stop filling at applptr */ + if (err == -EAGAIN) + break; + usb_audio_dbg(ep->chip, + "EP 0x%x: failed to prepare urb: %d\n", + ep->ep_num, err); + goto __error; } - err = usb_submit_urb(urb, GFP_ATOMIC); + if (!atomic_read(&ep->chip->shutdown)) + err = usb_submit_urb(urb, GFP_ATOMIC); + else + err = -ENODEV; if (err < 0) { - usb_audio_err(ep->chip, - "cannot submit urb %d, error %d: %s\n", - i, err, usb_error_string(err)); + if (!atomic_read(&ep->chip->shutdown)) + usb_audio_err(ep->chip, + "cannot submit urb %d, error %d: %s\n", + i, err, usb_error_string(err)); goto __error; } set_bit(i, &ep->active_mask); + atomic_inc(&ep->submitted_urbs); + } + + if (!i) { + usb_audio_dbg(ep->chip, "XRUN at starting EP 0x%x\n", + ep->ep_num); + goto __error; + } + + usb_audio_dbg(ep->chip, "%d URBs submitted for EP 0x%x\n", + i, ep->ep_num); + + fill_rest: + /* put the remaining URBs to ready list */ + if (is_playback) { + for (; i < ep->nurbs; i++) + push_back_to_ready_list(ep, ep->urb + i); } return 0; __error: - clear_bit(EP_FLAG_RUNNING, &ep->flags); - ep->use_count--; - deactivate_urbs(ep, false); + snd_usb_endpoint_stop(ep, false); return -EPIPE; } @@ -1002,8 +1648,9 @@ __error: * snd_usb_endpoint_stop: stop an snd_usb_endpoint * * @ep: the endpoint to stop (may be NULL) + * @keep_pending: keep in-flight URBs * - * A call to this function will decrement the use count of the endpoint. + * A call to this function will decrement the running count of the endpoint. * In case the last user has requested the endpoint stop, the URBs will * actually be deactivated. * @@ -1012,40 +1659,32 @@ __error: * The caller needs to synchronize the pending stop operation via * snd_usb_endpoint_sync_pending_stop(). */ -void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep) +void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep, bool keep_pending) { if (!ep) return; - if (snd_BUG_ON(ep->use_count == 0)) - return; - - if (--ep->use_count == 0) { - deactivate_urbs(ep, false); - set_bit(EP_FLAG_STOPPING, &ep->flags); - } -} + usb_audio_dbg(ep->chip, "Stopping %s EP 0x%x (running %d)\n", + ep_type_name(ep->type), ep->ep_num, + atomic_read(&ep->running)); -/** - * snd_usb_endpoint_deactivate: deactivate an snd_usb_endpoint - * - * @ep: the endpoint to deactivate - * - * If the endpoint is not currently in use, this functions will - * deactivate its associated URBs. - * - * In case of any active users, this functions does nothing. - */ -void snd_usb_endpoint_deactivate(struct snd_usb_endpoint *ep) -{ - if (!ep) - return; - - if (ep->use_count != 0) + if (snd_BUG_ON(!atomic_read(&ep->running))) return; - deactivate_urbs(ep, true); - wait_clear_urbs(ep); + if (!atomic_dec_return(&ep->running)) { + if (ep->sync_source) + WRITE_ONCE(ep->sync_source->sync_sink, NULL); + stop_urbs(ep, false, keep_pending); + if (ep->clock_ref) + atomic_dec(&ep->clock_ref->locked); + + if (ep->chip->quirk_flags & QUIRK_FLAG_FORCE_IFACE_RESET && + usb_pipeout(ep->pipe)) { + ep->need_prepare = true; + if (ep->iface_ref) + ep->iface_ref->need_setup = true; + } + } } /** @@ -1053,27 +1692,37 @@ void snd_usb_endpoint_deactivate(struct snd_usb_endpoint *ep) * * @ep: the endpoint to release * - * This function does not care for the endpoint's use count but will tear + * This function does not care for the endpoint's running count but will tear * down all the streaming URBs immediately. */ void snd_usb_endpoint_release(struct snd_usb_endpoint *ep) { - release_urbs(ep, 1); + release_urbs(ep, true); } /** - * snd_usb_endpoint_free: Free the resources of an snd_usb_endpoint - * - * @ep: the endpoint to free + * snd_usb_endpoint_free_all: Free the resources of an snd_usb_endpoint + * @chip: The chip * - * This free all resources of the given ep. + * This free all endpoints and those resources */ -void snd_usb_endpoint_free(struct snd_usb_endpoint *ep) +void snd_usb_endpoint_free_all(struct snd_usb_audio *chip) { - kfree(ep); + struct snd_usb_endpoint *ep, *en; + struct snd_usb_iface_ref *ip, *in; + struct snd_usb_clock_ref *cp, *cn; + + list_for_each_entry_safe(ep, en, &chip->ep_list, list) + kfree(ep); + + list_for_each_entry_safe(ip, in, &chip->iface_ref_list, list) + kfree(ip); + + list_for_each_entry_safe(cp, cn, &chip->clock_ref_list, list) + kfree(cp); } -/** +/* * snd_usb_handle_sync_urb: parse an USB sync packet * * @ep: the endpoint to handle the packet @@ -1083,9 +1732,9 @@ void snd_usb_endpoint_free(struct snd_usb_endpoint *ep) * This function is called from the context of an endpoint that received * the packet and is used to let another endpoint object handle the payload. */ -void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, - struct snd_usb_endpoint *sender, - const struct urb *urb) +static void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, + struct snd_usb_endpoint *sender, + const struct urb *urb) { int shift; unsigned int f; @@ -1100,7 +1749,7 @@ void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, * will take care of them later. */ if (snd_usb_endpoint_implicit_feedback_sink(ep) && - ep->use_count != 0) { + atomic_read(&ep->running)) { /* implicit feedback case */ int i, bytes = 0; @@ -1122,7 +1771,16 @@ void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, return; spin_lock_irqsave(&ep->lock, flags); - out_packet = ep->next_packet + ep->next_packet_write_pos; + if (ep->next_packet_queued >= ARRAY_SIZE(ep->next_packet)) { + spin_unlock_irqrestore(&ep->lock, flags); + usb_audio_err(ep->chip, + "next package FIFO overflow EP 0x%x\n", + ep->ep_num); + notify_xrun(ep); + return; + } + + out_packet = next_packet_fifo_enqueue(ep); /* * Iterate through the inbound packet and prepare the lengths @@ -1143,10 +1801,8 @@ void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, out_packet->packet_size[i] = 0; } - ep->next_packet_write_pos++; - ep->next_packet_write_pos %= MAX_URBS; spin_unlock_irqrestore(&ep->lock, flags); - queue_pending_output_urbs(ep); + snd_usb_queue_pending_output_urbs(ep, false); return; } @@ -1216,9 +1872,8 @@ void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, * If the frequency looks valid, set it. * This value is referred to in prepare_playback_urb(). */ - spin_lock_irqsave(&ep->lock, flags); + guard(spinlock_irqsave)(&ep->lock); ep->freqm = f; - spin_unlock_irqrestore(&ep->lock, flags); } else { /* * Out of range; maybe the shift value is wrong. diff --git a/sound/usb/endpoint.h b/sound/usb/endpoint.h index 63a39d4fa8d8..ba70f52f6860 100644 --- a/sound/usb/endpoint.h +++ b/sound/usb/endpoint.h @@ -5,33 +5,53 @@ #define SND_USB_ENDPOINT_TYPE_DATA 0 #define SND_USB_ENDPOINT_TYPE_SYNC 1 -struct snd_usb_endpoint *snd_usb_add_endpoint(struct snd_usb_audio *chip, - struct usb_host_interface *alts, - int ep_num, int direction, int type); - -int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep, - snd_pcm_format_t pcm_format, - unsigned int channels, - unsigned int period_bytes, - unsigned int period_frames, - unsigned int buffer_periods, - unsigned int rate, - struct audioformat *fmt, - struct snd_usb_endpoint *sync_ep); - -int snd_usb_endpoint_start(struct snd_usb_endpoint *ep); -void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep); +struct snd_usb_endpoint *snd_usb_get_endpoint(struct snd_usb_audio *chip, + int ep_num); + +int snd_usb_add_endpoint(struct snd_usb_audio *chip, int ep_num, int type); + +struct snd_usb_endpoint * +snd_usb_endpoint_open(struct snd_usb_audio *chip, + const struct audioformat *fp, + const struct snd_pcm_hw_params *params, + bool is_sync_ep, + bool fixed_rate); +void snd_usb_endpoint_close(struct snd_usb_audio *chip, + struct snd_usb_endpoint *ep); +int snd_usb_endpoint_set_params(struct snd_usb_audio *chip, + struct snd_usb_endpoint *ep); +int snd_usb_endpoint_prepare(struct snd_usb_audio *chip, + struct snd_usb_endpoint *ep); +int snd_usb_endpoint_get_clock_rate(struct snd_usb_audio *chip, int clock); + +bool snd_usb_endpoint_compatible(struct snd_usb_audio *chip, + struct snd_usb_endpoint *ep, + const struct audioformat *fp, + const struct snd_pcm_hw_params *params); + +void snd_usb_endpoint_set_sync(struct snd_usb_audio *chip, + struct snd_usb_endpoint *data_ep, + struct snd_usb_endpoint *sync_ep); +void snd_usb_endpoint_set_callback(struct snd_usb_endpoint *ep, + int (*prepare)(struct snd_usb_substream *subs, + struct urb *urb, + bool in_stream_lock), + void (*retire)(struct snd_usb_substream *subs, + struct urb *urb), + struct snd_usb_substream *data_subs); + +int snd_usb_endpoint_start(struct snd_usb_endpoint *ep); +void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep, bool keep_pending); void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep); -int snd_usb_endpoint_activate(struct snd_usb_endpoint *ep); -void snd_usb_endpoint_deactivate(struct snd_usb_endpoint *ep); +void snd_usb_endpoint_suspend(struct snd_usb_endpoint *ep); void snd_usb_endpoint_release(struct snd_usb_endpoint *ep); -void snd_usb_endpoint_free(struct snd_usb_endpoint *ep); +void snd_usb_endpoint_free_all(struct snd_usb_audio *chip); int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep); -int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep); - -void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, - struct snd_usb_endpoint *sender, - const struct urb *urb); +int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *ctx, int idx, + unsigned int avail); +int snd_usb_queue_pending_output_urbs(struct snd_usb_endpoint *ep, + bool in_stream_lock); #endif /* __USBAUDIO_ENDPOINT_H */ diff --git a/sound/usb/fcp.c b/sound/usb/fcp.c new file mode 100644 index 000000000000..11e9a96b46ff --- /dev/null +++ b/sound/usb/fcp.c @@ -0,0 +1,1127 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Focusrite Control Protocol Driver for ALSA + * + * Copyright (c) 2024-2025 by Geoffrey D. Bennett <g at b4.vu> + */ +/* + * DOC: Theory of Operation + * + * The Focusrite Control Protocol (FCP) driver provides a minimal + * kernel interface that allows a user-space driver (primarily + * fcp-server) to communicate with Focusrite USB audio interfaces + * using their vendor-specific protocol. This protocol is used by + * Scarlett 2nd Gen, 3rd Gen, 4th Gen, Clarett USB, Clarett+, and + * Vocaster series devices. + * + * Unlike the existing scarlett2 driver which implements all controls + * in kernel space, this driver takes a lighter-weight approach by + * moving most functionality to user space. The only control + * implemented in kernel space is the Level Meter, since it requires + * frequent polling of volatile data. + * + * The driver provides an hwdep interface that allows the user-space + * driver to: + * - Initialise the protocol + * - Send arbitrary FCP commands to the device + * - Receive notifications from the device + * - Configure the Level Meter control + * + * Usage Flow + * ---------- + * 1. Open the hwdep device (requires CAP_SYS_RAWIO) + * 2. Get protocol version using FCP_IOCTL_PVERSION + * 3. Initialise protocol using FCP_IOCTL_INIT + * 4. Send commands using FCP_IOCTL_CMD + * 5. Receive notifications using read() + * 6. Optionally set up the Level Meter control using + * FCP_IOCTL_SET_METER_MAP + * 7. Optionally add labels to the Level Meter control using + * FCP_IOCTL_SET_METER_LABELS + * + * Level Meter + * ----------- + * The Level Meter is implemented as an ALSA control that provides + * real-time level monitoring. When the control is read, the driver + * requests the current meter levels from the device, translates the + * levels using the configured mapping, and returns the result to the + * user. The mapping between device meters and the ALSA control's + * channels is configured with FCP_IOCTL_SET_METER_MAP. + * + * Labels for the Level Meter channels can be set using + * FCP_IOCTL_SET_METER_LABELS and read by applications through the + * control's TLV data. The labels are transferred as a sequence of + * null-terminated strings. + */ + +#include <linux/slab.h> +#include <linux/usb.h> + +#include <sound/control.h> +#include <sound/hwdep.h> +#include <sound/tlv.h> + +#include <uapi/sound/fcp.h> + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" + +#include "fcp.h" + +/* notify waiting to send to *file */ +struct fcp_notify { + wait_queue_head_t queue; + u32 event; + spinlock_t lock; +}; + +struct fcp_data { + struct usb_mixer_interface *mixer; + + struct mutex mutex; /* serialise access to the device */ + struct completion cmd_done; /* wait for command completion */ + struct file *file; /* hwdep file */ + + struct fcp_notify notify; + + u8 bInterfaceNumber; + u8 bEndpointAddress; + u16 wMaxPacketSize; + u8 bInterval; + + uint16_t step0_resp_size; + uint16_t step2_resp_size; + uint32_t init1_opcode; + uint32_t init2_opcode; + + u8 init; + u16 seq; + + u8 num_meter_slots; + s16 *meter_level_map; + __le32 *meter_levels; + struct snd_kcontrol *meter_ctl; + + unsigned int *meter_labels_tlv; + int meter_labels_tlv_size; +}; + +/*** USB Interactions ***/ + +/* FCP Command ACK notification bit */ +#define FCP_NOTIFY_ACK 1 + +/* Vendor-specific USB control requests */ +#define FCP_USB_REQ_STEP0 0 +#define FCP_USB_REQ_CMD_TX 2 +#define FCP_USB_REQ_CMD_RX 3 + +/* Focusrite Control Protocol opcodes that the kernel side needs to + * know about + */ +#define FCP_USB_REBOOT 0x00000003 +#define FCP_USB_GET_METER 0x00001001 +#define FCP_USB_FLASH_ERASE 0x00004002 +#define FCP_USB_FLASH_WRITE 0x00004004 + +#define FCP_USB_METER_LEVELS_GET_MAGIC 1 + +#define FCP_SEGMENT_APP_GOLD 0 + +/* Forward declarations */ +static int fcp_init(struct usb_mixer_interface *mixer, + void *step0_resp, void *step2_resp); + +/* FCP command request/response format */ +struct fcp_usb_packet { + __le32 opcode; + __le16 size; + __le16 seq; + __le32 error; + __le32 pad; + u8 data[]; +}; + +static void fcp_fill_request_header(struct fcp_data *private, + struct fcp_usb_packet *req, + u32 opcode, u16 req_size) +{ + /* sequence must go up by 1 for each request */ + u16 seq = private->seq++; + + req->opcode = cpu_to_le32(opcode); + req->size = cpu_to_le16(req_size); + req->seq = cpu_to_le16(seq); + req->error = 0; + req->pad = 0; +} + +static int fcp_usb_tx(struct usb_device *dev, int interface, + void *buf, u16 size) +{ + return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + FCP_USB_REQ_CMD_TX, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + 0, interface, buf, size); +} + +static int fcp_usb_rx(struct usb_device *dev, int interface, + void *buf, u16 size) +{ + return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + FCP_USB_REQ_CMD_RX, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + 0, interface, buf, size); +} + +/* Send an FCP command and get the response */ +static int fcp_usb(struct usb_mixer_interface *mixer, u32 opcode, + const void *req_data, u16 req_size, + void *resp_data, u16 resp_size) +{ + struct fcp_data *private = mixer->private_data; + struct usb_device *dev = mixer->chip->dev; + struct fcp_usb_packet *req __free(kfree) = NULL; + struct fcp_usb_packet *resp __free(kfree) = NULL; + size_t req_buf_size = struct_size(req, data, req_size); + size_t resp_buf_size = struct_size(resp, data, resp_size); + int retries = 0; + const int max_retries = 5; + int err; + + if (!mixer->urb) + return -ENODEV; + + req = kmalloc(req_buf_size, GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kmalloc(resp_buf_size, GFP_KERNEL); + if (!resp) + return -ENOMEM; + + /* build request message */ + fcp_fill_request_header(private, req, opcode, req_size); + if (req_size) + memcpy(req->data, req_data, req_size); + + /* send the request and retry on EPROTO */ +retry: + err = fcp_usb_tx(dev, private->bInterfaceNumber, req, req_buf_size); + if (err == -EPROTO && ++retries <= max_retries) { + msleep(1 << (retries - 1)); + goto retry; + } + + if (err != req_buf_size) { + usb_audio_err(mixer->chip, + "FCP request %08x failed: %d\n", opcode, err); + return -EINVAL; + } + + if (!wait_for_completion_timeout(&private->cmd_done, + msecs_to_jiffies(1000))) { + usb_audio_err(mixer->chip, + "FCP request %08x timed out\n", opcode); + + return -ETIMEDOUT; + } + + /* send a second message to get the response */ + err = fcp_usb_rx(dev, private->bInterfaceNumber, resp, resp_buf_size); + + /* validate the response */ + + if (err < 0) { + + /* ESHUTDOWN and EPROTO are valid responses to a + * reboot request + */ + if (opcode == FCP_USB_REBOOT && + (err == -ESHUTDOWN || err == -EPROTO)) + return 0; + + usb_audio_err(mixer->chip, + "FCP read response %08x failed: %d\n", + opcode, err); + return -EINVAL; + } + + if (err < sizeof(*resp)) { + usb_audio_err(mixer->chip, + "FCP response %08x too short: %d\n", + opcode, err); + return -EINVAL; + } + + if (req->seq != resp->seq) { + usb_audio_err(mixer->chip, + "FCP response %08x seq mismatch %d/%d\n", + opcode, + le16_to_cpu(req->seq), le16_to_cpu(resp->seq)); + return -EINVAL; + } + + if (req->opcode != resp->opcode) { + usb_audio_err(mixer->chip, + "FCP response %08x opcode mismatch %08x\n", + opcode, le32_to_cpu(resp->opcode)); + return -EINVAL; + } + + if (resp->error) { + usb_audio_err(mixer->chip, + "FCP response %08x error %d\n", + opcode, le32_to_cpu(resp->error)); + return -EINVAL; + } + + if (err != resp_buf_size) { + usb_audio_err(mixer->chip, + "FCP response %08x buffer size mismatch %d/%zu\n", + opcode, err, resp_buf_size); + return -EINVAL; + } + + if (resp_size != le16_to_cpu(resp->size)) { + usb_audio_err(mixer->chip, + "FCP response %08x size mismatch %d/%d\n", + opcode, resp_size, le16_to_cpu(resp->size)); + return -EINVAL; + } + + if (resp_data && resp_size > 0) + memcpy(resp_data, resp->data, resp_size); + + return 0; +} + +static int fcp_reinit(struct usb_mixer_interface *mixer) +{ + struct fcp_data *private = mixer->private_data; + void *step0_resp __free(kfree) = NULL; + void *step2_resp __free(kfree) = NULL; + + if (mixer->urb) + return 0; + + step0_resp = kmalloc(private->step0_resp_size, GFP_KERNEL); + if (!step0_resp) + return -ENOMEM; + step2_resp = kmalloc(private->step2_resp_size, GFP_KERNEL); + if (!step2_resp) + return -ENOMEM; + + return fcp_init(mixer, step0_resp, step2_resp); +} + +/*** Control Functions ***/ + +/* helper function to create a new control */ +static int fcp_add_new_ctl(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *ncontrol, + int index, int channels, const char *name, + struct snd_kcontrol **kctl_return) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + int err; + + elem = kzalloc(sizeof(*elem), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + /* We set USB_MIXER_BESPOKEN type, so that the core USB mixer code + * ignores them for resume and other operations. + * Also, the head.id field is set to 0, as we don't use this field. + */ + elem->head.mixer = mixer; + elem->control = index; + elem->head.id = 0; + elem->channels = channels; + elem->val_type = USB_MIXER_BESPOKEN; + + kctl = snd_ctl_new1(ncontrol, elem); + if (!kctl) { + kfree(elem); + return -ENOMEM; + } + kctl->private_free = snd_usb_mixer_elem_free; + + strscpy(kctl->id.name, name, sizeof(kctl->id.name)); + + err = snd_usb_mixer_add_control(&elem->head, kctl); + if (err < 0) + return err; + + if (kctl_return) + *kctl_return = kctl; + + return 0; +} + +/*** Level Meter Control ***/ + +static int fcp_meter_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 4095; + uinfo->value.integer.step = 1; + return 0; +} + +static int fcp_meter_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct fcp_data *private = mixer->private_data; + int num_meter_slots, resp_size; + __le32 *resp = private->meter_levels; + int i, err = 0; + + struct { + __le16 pad; + __le16 num_meters; + __le32 magic; + } __packed req; + + guard(mutex)(&private->mutex); + + err = fcp_reinit(mixer); + if (err < 0) + return err; + + num_meter_slots = private->num_meter_slots; + resp_size = num_meter_slots * sizeof(u32); + + req.pad = 0; + req.num_meters = cpu_to_le16(num_meter_slots); + req.magic = cpu_to_le32(FCP_USB_METER_LEVELS_GET_MAGIC); + err = fcp_usb(mixer, FCP_USB_GET_METER, + &req, sizeof(req), resp, resp_size); + if (err < 0) + return err; + + /* copy & translate from resp[] using meter_level_map[] */ + for (i = 0; i < elem->channels; i++) { + int idx = private->meter_level_map[i]; + int value = idx < 0 ? 0 : le32_to_cpu(resp[idx]); + + ucontrol->value.integer.value[i] = value; + } + + return 0; +} + +static int fcp_meter_tlv_callback(struct snd_kcontrol *kctl, + int op_flag, unsigned int size, + unsigned int __user *tlv) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct fcp_data *private = mixer->private_data; + + guard(mutex)(&private->mutex); + + if (op_flag == SNDRV_CTL_TLV_OP_READ) { + if (private->meter_labels_tlv_size == 0) + return 0; + + if (size > private->meter_labels_tlv_size) + size = private->meter_labels_tlv_size; + + if (copy_to_user(tlv, private->meter_labels_tlv, size)) + return -EFAULT; + + return size; + } + + return -EINVAL; +} + +static const struct snd_kcontrol_new fcp_meter_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fcp_meter_ctl_info, + .get = fcp_meter_ctl_get, + .tlv = { .c = fcp_meter_tlv_callback }, +}; + +/*** hwdep interface ***/ + +/* FCP initialisation */ +static int fcp_ioctl_init(struct usb_mixer_interface *mixer, + struct fcp_init __user *arg) +{ + struct fcp_init init; + struct usb_device *dev = mixer->chip->dev; + struct fcp_data *private = mixer->private_data; + void *resp __free(kfree) = NULL; + void *step2_resp; + int err, buf_size; + + if (usb_pipe_type_check(dev, usb_sndctrlpipe(dev, 0))) + return -EINVAL; + + /* Get initialisation parameters */ + if (copy_from_user(&init, arg, sizeof(init))) + return -EFAULT; + + /* Validate the response sizes */ + if (init.step0_resp_size < 1 || + init.step0_resp_size > 255 || + init.step2_resp_size < 1 || + init.step2_resp_size > 255) + return -EINVAL; + + /* Allocate response buffer */ + buf_size = init.step0_resp_size + init.step2_resp_size; + + resp = kmalloc(buf_size, GFP_KERNEL); + if (!resp) + return -ENOMEM; + + private->step0_resp_size = init.step0_resp_size; + private->step2_resp_size = init.step2_resp_size; + private->init1_opcode = init.init1_opcode; + private->init2_opcode = init.init2_opcode; + + step2_resp = resp + private->step0_resp_size; + + err = fcp_init(mixer, resp, step2_resp); + if (err < 0) + return err; + + if (copy_to_user(arg->resp, resp, buf_size)) + return -EFAULT; + + return 0; +} + +/* Check that the command is allowed + * Don't permit erasing/writing segment 0 (App_Gold) + */ +static int fcp_validate_cmd(u32 opcode, void *data, u16 size) +{ + if (opcode == FCP_USB_FLASH_ERASE) { + struct { + __le32 segment_num; + __le32 pad; + } __packed *req = data; + + if (size != sizeof(*req)) + return -EINVAL; + + if (le32_to_cpu(req->segment_num) == FCP_SEGMENT_APP_GOLD) + return -EPERM; + + if (req->pad != 0) + return -EINVAL; + + } else if (opcode == FCP_USB_FLASH_WRITE) { + struct { + __le32 segment_num; + __le32 offset; + __le32 pad; + u8 data[]; + } __packed *req = data; + + if (size < sizeof(*req)) + return -EINVAL; + + if (le32_to_cpu(req->segment_num) == FCP_SEGMENT_APP_GOLD) + return -EPERM; + + if (req->pad != 0) + return -EINVAL; + } + + return 0; +} + +/* Execute an FCP command specified by the user */ +static int fcp_ioctl_cmd(struct usb_mixer_interface *mixer, + struct fcp_cmd __user *arg) +{ + struct fcp_cmd cmd; + int err, buf_size; + void *data __free(kfree) = NULL; + + /* get opcode and request/response size */ + if (copy_from_user(&cmd, arg, sizeof(cmd))) + return -EFAULT; + + /* validate request and response sizes */ + if (cmd.req_size > 4096 || cmd.resp_size > 4096) + return -EINVAL; + + /* reinit if needed */ + err = fcp_reinit(mixer); + if (err < 0) + return err; + + /* allocate request/response buffer */ + buf_size = max(cmd.req_size, cmd.resp_size); + + if (buf_size > 0) { + data = kmalloc(buf_size, GFP_KERNEL); + if (!data) + return -ENOMEM; + } + + /* copy request from user */ + if (cmd.req_size > 0) + if (copy_from_user(data, arg->data, cmd.req_size)) + return -EFAULT; + + /* check that the command is allowed */ + err = fcp_validate_cmd(cmd.opcode, data, cmd.req_size); + if (err < 0) + return err; + + /* send request, get response */ + err = fcp_usb(mixer, cmd.opcode, + data, cmd.req_size, data, cmd.resp_size); + if (err < 0) + return err; + + /* copy response to user */ + if (cmd.resp_size > 0) + if (copy_to_user(arg->data, data, cmd.resp_size)) + return -EFAULT; + + return 0; +} + +/* Validate the Level Meter map passed by the user */ +static int validate_meter_map(const s16 *map, int map_size, int meter_slots) +{ + int i; + + for (i = 0; i < map_size; i++) + if (map[i] < -1 || map[i] >= meter_slots) + return -EINVAL; + + return 0; +} + +/* Set the Level Meter map and add the control */ +static int fcp_ioctl_set_meter_map(struct usb_mixer_interface *mixer, + struct fcp_meter_map __user *arg) +{ + struct fcp_meter_map map; + struct fcp_data *private = mixer->private_data; + s16 *tmp_map __free(kfree) = NULL; + int err; + + if (copy_from_user(&map, arg, sizeof(map))) + return -EFAULT; + + /* Don't allow changing the map size or meter slots once set */ + if (private->meter_ctl) { + struct usb_mixer_elem_info *elem = + private->meter_ctl->private_data; + + if (map.map_size != elem->channels || + map.meter_slots != private->num_meter_slots) + return -EINVAL; + } + + /* Validate the map size */ + if (map.map_size < 1 || map.map_size > 255 || + map.meter_slots < 1 || map.meter_slots > 255) + return -EINVAL; + + /* Allocate and copy the map data */ + tmp_map = memdup_array_user(arg->map, map.map_size, sizeof(s16)); + if (IS_ERR(tmp_map)) + return PTR_ERR(tmp_map); + + err = validate_meter_map(tmp_map, map.map_size, map.meter_slots); + if (err < 0) + return err; + + /* If the control doesn't exist, create it */ + if (!private->meter_ctl) { + s16 *new_map __free(kfree) = NULL; + __le32 *meter_levels __free(kfree) = NULL; + + /* Allocate buffer for the map */ + new_map = kmalloc_array(map.map_size, sizeof(s16), GFP_KERNEL); + if (!new_map) + return -ENOMEM; + + /* Allocate buffer for reading meter levels */ + meter_levels = kmalloc_array(map.meter_slots, sizeof(__le32), + GFP_KERNEL); + if (!meter_levels) + return -ENOMEM; + + /* Create the Level Meter control */ + err = fcp_add_new_ctl(mixer, &fcp_meter_ctl, 0, map.map_size, + "Level Meter", &private->meter_ctl); + if (err < 0) + return err; + + /* Success; save the pointers in private and don't free them */ + private->meter_level_map = new_map; + private->meter_levels = meter_levels; + private->num_meter_slots = map.meter_slots; + new_map = NULL; + meter_levels = NULL; + } + + /* Install the new map */ + memcpy(private->meter_level_map, tmp_map, map.map_size * sizeof(s16)); + + return 0; +} + +/* Set the Level Meter labels */ +static int fcp_ioctl_set_meter_labels(struct usb_mixer_interface *mixer, + struct fcp_meter_labels __user *arg) +{ + struct fcp_meter_labels labels; + struct fcp_data *private = mixer->private_data; + unsigned int *tlv_data; + unsigned int tlv_size, data_size; + + if (copy_from_user(&labels, arg, sizeof(labels))) + return -EFAULT; + + /* Remove existing labels if size is zero */ + if (!labels.labels_size) { + + /* Clear TLV read/callback bits if labels were present */ + if (private->meter_labels_tlv) { + private->meter_ctl->vd[0].access &= + ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); + snd_ctl_notify(mixer->chip->card, + SNDRV_CTL_EVENT_MASK_INFO, + &private->meter_ctl->id); + } + + kfree(private->meter_labels_tlv); + private->meter_labels_tlv = NULL; + private->meter_labels_tlv_size = 0; + + return 0; + } + + /* Validate size */ + if (labels.labels_size > 4096) + return -EINVAL; + + /* Calculate padded data size */ + data_size = ALIGN(labels.labels_size, sizeof(unsigned int)); + + /* Calculate total TLV size including header */ + tlv_size = sizeof(unsigned int) * 2 + data_size; + + /* Allocate, set up TLV header, and copy the labels data */ + tlv_data = kzalloc(tlv_size, GFP_KERNEL); + if (!tlv_data) + return -ENOMEM; + tlv_data[0] = SNDRV_CTL_TLVT_FCP_CHANNEL_LABELS; + tlv_data[1] = data_size; + if (copy_from_user(&tlv_data[2], arg->labels, labels.labels_size)) { + kfree(tlv_data); + return -EFAULT; + } + + /* Set TLV read/callback bits if labels weren't present */ + if (!private->meter_labels_tlv) { + private->meter_ctl->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + snd_ctl_notify(mixer->chip->card, + SNDRV_CTL_EVENT_MASK_INFO, + &private->meter_ctl->id); + } + + /* Swap in the new labels */ + kfree(private->meter_labels_tlv); + private->meter_labels_tlv = tlv_data; + private->meter_labels_tlv_size = tlv_size; + + return 0; +} + +static int fcp_hwdep_open(struct snd_hwdep *hw, struct file *file) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct fcp_data *private = mixer->private_data; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + private->file = file; + + return 0; +} + +static int fcp_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct fcp_data *private = mixer->private_data; + void __user *argp = (void __user *)arg; + + guard(mutex)(&private->mutex); + + switch (cmd) { + + case FCP_IOCTL_PVERSION: + return put_user(FCP_HWDEP_VERSION, + (int __user *)argp) ? -EFAULT : 0; + break; + + case FCP_IOCTL_INIT: + return fcp_ioctl_init(mixer, argp); + + case FCP_IOCTL_CMD: + if (!private->init) + return -EINVAL; + return fcp_ioctl_cmd(mixer, argp); + + case FCP_IOCTL_SET_METER_MAP: + if (!private->init) + return -EINVAL; + return fcp_ioctl_set_meter_map(mixer, argp); + + case FCP_IOCTL_SET_METER_LABELS: + if (!private->init) + return -EINVAL; + if (!private->meter_ctl) + return -EINVAL; + return fcp_ioctl_set_meter_labels(mixer, argp); + + default: + return -ENOIOCTLCMD; + } + + /* not reached */ +} + +static long fcp_hwdep_read(struct snd_hwdep *hw, char __user *buf, + long count, loff_t *offset) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct fcp_data *private = mixer->private_data; + long ret = 0; + u32 event; + + if (count < sizeof(event)) + return -EINVAL; + + ret = wait_event_interruptible(private->notify.queue, + private->notify.event); + if (ret) + return ret; + + scoped_guard(spinlock_irqsave, &private->notify.lock) { + event = private->notify.event; + private->notify.event = 0; + } + + if (copy_to_user(buf, &event, sizeof(event))) + return -EFAULT; + + return sizeof(event); +} + +static __poll_t fcp_hwdep_poll(struct snd_hwdep *hw, + struct file *file, + poll_table *wait) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct fcp_data *private = mixer->private_data; + __poll_t mask = 0; + + poll_wait(file, &private->notify.queue, wait); + + if (private->notify.event) + mask |= EPOLLIN | EPOLLRDNORM; + + return mask; +} + +static int fcp_hwdep_release(struct snd_hwdep *hw, struct file *file) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct fcp_data *private = mixer->private_data; + + if (!private) + return 0; + + private->file = NULL; + + return 0; +} + +static int fcp_hwdep_init(struct usb_mixer_interface *mixer) +{ + struct snd_hwdep *hw; + int err; + + err = snd_hwdep_new(mixer->chip->card, "Focusrite Control", 0, &hw); + if (err < 0) + return err; + + hw->private_data = mixer; + hw->exclusive = 1; + hw->ops.open = fcp_hwdep_open; + hw->ops.ioctl = fcp_hwdep_ioctl; + hw->ops.ioctl_compat = fcp_hwdep_ioctl; + hw->ops.read = fcp_hwdep_read; + hw->ops.poll = fcp_hwdep_poll; + hw->ops.release = fcp_hwdep_release; + + return 0; +} + +/*** Cleanup ***/ + +static void fcp_cleanup_urb(struct usb_mixer_interface *mixer) +{ + if (!mixer->urb) + return; + + usb_kill_urb(mixer->urb); + kfree(mixer->urb->transfer_buffer); + usb_free_urb(mixer->urb); + mixer->urb = NULL; +} + +static void fcp_private_free(struct usb_mixer_interface *mixer) +{ + struct fcp_data *private = mixer->private_data; + + fcp_cleanup_urb(mixer); + + kfree(private->meter_level_map); + kfree(private->meter_levels); + kfree(private->meter_labels_tlv); + kfree(private); + mixer->private_data = NULL; +} + +static void fcp_private_suspend(struct usb_mixer_interface *mixer) +{ + fcp_cleanup_urb(mixer); +} + +/*** Callbacks ***/ + +static void fcp_notify(struct urb *urb) +{ + struct usb_mixer_interface *mixer = urb->context; + struct fcp_data *private = mixer->private_data; + int len = urb->actual_length; + int ustatus = urb->status; + u32 data; + + if (ustatus != 0 || len != 8) + goto requeue; + + data = le32_to_cpu(*(__le32 *)urb->transfer_buffer); + + /* Handle command acknowledgement */ + if (data & FCP_NOTIFY_ACK) { + complete(&private->cmd_done); + data &= ~FCP_NOTIFY_ACK; + } + + if (data) { + scoped_guard(spinlock_irqsave, &private->notify.lock) { + private->notify.event |= data; + } + + wake_up_interruptible(&private->notify.queue); + } + +requeue: + if (ustatus != -ENOENT && + ustatus != -ECONNRESET && + ustatus != -ESHUTDOWN) { + urb->dev = mixer->chip->dev; + usb_submit_urb(urb, GFP_ATOMIC); + } else { + complete(&private->cmd_done); + } +} + +/* Submit a URB to receive notifications from the device */ +static int fcp_init_notify(struct usb_mixer_interface *mixer) +{ + struct usb_device *dev = mixer->chip->dev; + struct fcp_data *private = mixer->private_data; + unsigned int pipe = usb_rcvintpipe(dev, private->bEndpointAddress); + void *transfer_buffer; + int err; + + /* Already set up */ + if (mixer->urb) + return 0; + + if (usb_pipe_type_check(dev, pipe)) + return -EINVAL; + + mixer->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!mixer->urb) + return -ENOMEM; + + transfer_buffer = kmalloc(private->wMaxPacketSize, GFP_KERNEL); + if (!transfer_buffer) { + usb_free_urb(mixer->urb); + mixer->urb = NULL; + return -ENOMEM; + } + + usb_fill_int_urb(mixer->urb, dev, pipe, + transfer_buffer, private->wMaxPacketSize, + fcp_notify, mixer, private->bInterval); + + init_completion(&private->cmd_done); + + err = usb_submit_urb(mixer->urb, GFP_KERNEL); + if (err) { + usb_audio_err(mixer->chip, + "%s: usb_submit_urb failed: %d\n", + __func__, err); + kfree(transfer_buffer); + usb_free_urb(mixer->urb); + mixer->urb = NULL; + } + + return err; +} + +/*** Initialisation ***/ + +static int fcp_init(struct usb_mixer_interface *mixer, + void *step0_resp, void *step2_resp) +{ + struct fcp_data *private = mixer->private_data; + struct usb_device *dev = mixer->chip->dev; + int err; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + FCP_USB_REQ_STEP0, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + 0, private->bInterfaceNumber, + step0_resp, private->step0_resp_size); + if (err < 0) + return err; + + err = fcp_init_notify(mixer); + if (err < 0) + return err; + + private->seq = 0; + private->init = 1; + + err = fcp_usb(mixer, private->init1_opcode, NULL, 0, NULL, 0); + if (err < 0) + return err; + + err = fcp_usb(mixer, private->init2_opcode, + NULL, 0, step2_resp, private->step2_resp_size); + if (err < 0) + return err; + + return 0; +} + +static int fcp_init_private(struct usb_mixer_interface *mixer) +{ + struct fcp_data *private = + kzalloc(sizeof(struct fcp_data), GFP_KERNEL); + + if (!private) + return -ENOMEM; + + mutex_init(&private->mutex); + init_waitqueue_head(&private->notify.queue); + spin_lock_init(&private->notify.lock); + + mixer->private_data = private; + mixer->private_free = fcp_private_free; + mixer->private_suspend = fcp_private_suspend; + + private->mixer = mixer; + + return 0; +} + +/* Look through the interface descriptors for the Focusrite Control + * interface (bInterfaceClass = 255 Vendor Specific Class) and set + * bInterfaceNumber, bEndpointAddress, wMaxPacketSize, and bInterval + * in private + */ +static int fcp_find_fc_interface(struct usb_mixer_interface *mixer) +{ + struct snd_usb_audio *chip = mixer->chip; + struct fcp_data *private = mixer->private_data; + struct usb_host_config *config = chip->dev->actconfig; + int i; + + for (i = 0; i < config->desc.bNumInterfaces; i++) { + struct usb_interface *intf = config->interface[i]; + struct usb_interface_descriptor *desc = + &intf->altsetting[0].desc; + struct usb_endpoint_descriptor *epd; + + if (desc->bInterfaceClass != 255) + continue; + + epd = get_endpoint(intf->altsetting, 0); + private->bInterfaceNumber = desc->bInterfaceNumber; + private->bEndpointAddress = usb_endpoint_num(epd); + private->wMaxPacketSize = le16_to_cpu(epd->wMaxPacketSize); + private->bInterval = epd->bInterval; + return 0; + } + + usb_audio_err(chip, "Focusrite vendor-specific interface not found\n"); + return -EINVAL; +} + +int snd_fcp_init(struct usb_mixer_interface *mixer) +{ + struct snd_usb_audio *chip = mixer->chip; + int err; + + /* only use UAC_VERSION_2 */ + if (!mixer->protocol) + return 0; + + err = fcp_init_private(mixer); + if (err < 0) + return err; + + err = fcp_find_fc_interface(mixer); + if (err < 0) + return err; + + err = fcp_hwdep_init(mixer); + if (err < 0) + return err; + + usb_audio_info(chip, + "Focusrite Control Protocol Driver ready (pid=0x%04x); " + "report any issues to " + "https://github.com/geoffreybennett/fcp-support/issues", + USB_ID_PRODUCT(chip->usb_id)); + + return err; +} diff --git a/sound/usb/fcp.h b/sound/usb/fcp.h new file mode 100644 index 000000000000..d58cc7db75b1 --- /dev/null +++ b/sound/usb/fcp.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBAUDIO_FCP_H +#define __USBAUDIO_FCP_H + +int snd_fcp_init(struct usb_mixer_interface *mixer); + +#endif /* __USBAUDIO_FCP_H */ diff --git a/sound/usb/format.c b/sound/usb/format.c index fd13ac11b136..ec95a063beb1 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -1,18 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * */ #include <linux/init.h> @@ -29,7 +16,6 @@ #include "card.h" #include "quirks.h" #include "helper.h" -#include "debug.h" #include "clock.h" #include "format.h" @@ -53,6 +39,12 @@ static u64 parse_audio_format_i_type(struct snd_usb_audio *chip, case UAC_VERSION_1: default: { struct uac_format_type_i_discrete_descriptor *fmt = _fmt; + if (format >= 64) { + usb_audio_info(chip, + "%u:%d: invalid format type 0x%llx is detected, processed as PCM\n", + fp->iface, fp->altsetting, format); + format = UAC_FORMAT_TYPE_I_PCM; + } sample_width = fmt->bBitResolution; sample_bytes = fmt->bSubframeSize; format = 1ULL << format; @@ -68,6 +60,8 @@ static u64 parse_audio_format_i_type(struct snd_usb_audio *chip, pcm_formats |= SNDRV_PCM_FMTBIT_SPECIAL; /* flag potentially raw DSD capable altsettings */ fp->dsd_raw = true; + /* clear special format bit to avoid "unsupported format" msg below */ + format &= ~UAC2_FORMAT_TYPE_I_RAW_DATA; } format <<= 1; @@ -79,22 +73,28 @@ static u64 parse_audio_format_i_type(struct snd_usb_audio *chip, sample_width = as->bBitResolution; sample_bytes = as->bSubslotSize; - if (format & UAC3_FORMAT_TYPE_I_RAW_DATA) + if (format & UAC3_FORMAT_TYPE_I_RAW_DATA) { pcm_formats |= SNDRV_PCM_FMTBIT_SPECIAL; + /* clear special format bit to avoid "unsupported format" msg below */ + format &= ~UAC3_FORMAT_TYPE_I_RAW_DATA; + } format <<= 1; break; } } + fp->fmt_bits = sample_width; + fp->fmt_sz = sample_bytes; + if ((pcm_formats == 0) && - (format == 0 || format == (1 << UAC_FORMAT_TYPE_I_UNDEFINED))) { + (format == 0 || format == BIT(UAC_FORMAT_TYPE_I_UNDEFINED))) { /* some devices don't define this correctly... */ usb_audio_info(chip, "%u:%d : format type 0 is detected, processed as PCM\n", fp->iface, fp->altsetting); - format = 1 << UAC_FORMAT_TYPE_I_PCM; + format = BIT(UAC_FORMAT_TYPE_I_PCM); } - if (format & (1 << UAC_FORMAT_TYPE_I_PCM)) { + if (format & BIT(UAC_FORMAT_TYPE_I_PCM)) { if (((chip->usb_id == USB_ID(0x0582, 0x0016)) || /* Edirol SD-90 */ (chip->usb_id == USB_ID(0x0582, 0x000c))) && @@ -134,7 +134,7 @@ static u64 parse_audio_format_i_type(struct snd_usb_audio *chip, break; } } - if (format & (1 << UAC_FORMAT_TYPE_I_PCM8)) { + if (format & BIT(UAC_FORMAT_TYPE_I_PCM8)) { /* Dallas DS4201 workaround: it advertises U8 format, but really supports S8. */ if (chip->usb_id == USB_ID(0x04fa, 0x4201)) @@ -142,15 +142,12 @@ static u64 parse_audio_format_i_type(struct snd_usb_audio *chip, else pcm_formats |= SNDRV_PCM_FMTBIT_U8; } - if (format & (1 << UAC_FORMAT_TYPE_I_IEEE_FLOAT)) { + if (format & BIT(UAC_FORMAT_TYPE_I_IEEE_FLOAT)) pcm_formats |= SNDRV_PCM_FMTBIT_FLOAT_LE; - } - if (format & (1 << UAC_FORMAT_TYPE_I_ALAW)) { + if (format & BIT(UAC_FORMAT_TYPE_I_ALAW)) pcm_formats |= SNDRV_PCM_FMTBIT_A_LAW; - } - if (format & (1 << UAC_FORMAT_TYPE_I_MULAW)) { + if (format & BIT(UAC_FORMAT_TYPE_I_MULAW)) pcm_formats |= SNDRV_PCM_FMTBIT_MU_LAW; - } if (format & ~0x3f) { usb_audio_info(chip, "%u:%d : unsupported format bits %#llx\n", @@ -162,6 +159,36 @@ static u64 parse_audio_format_i_type(struct snd_usb_audio *chip, return pcm_formats; } +static int set_fixed_rate(struct audioformat *fp, int rate, int rate_bits) +{ + kfree(fp->rate_table); + fp->rate_table = kmalloc(sizeof(int), GFP_KERNEL); + if (!fp->rate_table) + return -ENOMEM; + fp->nr_rates = 1; + fp->rate_min = rate; + fp->rate_max = rate; + fp->rates = rate_bits; + fp->rate_table[0] = rate; + return 0; +} + +/* set up rate_min, rate_max and rates from the rate table */ +static void set_rate_table_min_max(struct audioformat *fp) +{ + unsigned int rate; + int i; + + fp->rate_min = INT_MAX; + fp->rate_max = 0; + fp->rates = 0; + for (i = 0; i < fp->nr_rates; i++) { + rate = fp->rate_table[i]; + fp->rate_min = min(fp->rate_min, rate); + fp->rate_max = max(fp->rate_max, rate); + fp->rates |= snd_pcm_rate_to_rate_bit(rate); + } +} /* * parse the format descriptor and stores the possible sample rates @@ -197,16 +224,17 @@ static int parse_audio_format_rates_v1(struct snd_usb_audio *chip, struct audiof return -ENOMEM; fp->nr_rates = 0; - fp->rate_min = fp->rate_max = 0; for (r = 0, idx = offset + 1; r < nr_rates; r++, idx += 3) { unsigned int rate = combine_triple(&fmt[idx]); if (!rate) continue; /* C-Media CM6501 mislabels its 96 kHz altsetting */ /* Terratec Aureon 7.1 USB C-Media 6206, too */ + /* Ozone Z90 USB C-Media, too */ if (rate == 48000 && nr_rates == 1 && (chip->usb_id == USB_ID(0x0d8c, 0x0201) || chip->usb_id == USB_ID(0x0d8c, 0x0102) || + chip->usb_id == USB_ID(0x0d8c, 0x0078) || chip->usb_id == USB_ID(0x0ccd, 0x00b1)) && fp->altsetting == 5 && fp->maxpacksize == 392) rate = 96000; @@ -216,27 +244,111 @@ static int parse_audio_format_rates_v1(struct snd_usb_audio *chip, struct audiof chip->usb_id == USB_ID(0x041e, 0x4068))) rate = 8000; - fp->rate_table[fp->nr_rates] = rate; - if (!fp->rate_min || rate < fp->rate_min) - fp->rate_min = rate; - if (!fp->rate_max || rate > fp->rate_max) - fp->rate_max = rate; - fp->rates |= snd_pcm_rate_to_rate_bit(rate); - fp->nr_rates++; + fp->rate_table[fp->nr_rates++] = rate; } if (!fp->nr_rates) { - hwc_debug("All rates were zero. Skipping format!\n"); + usb_audio_info(chip, + "%u:%d: All rates were zero\n", + fp->iface, fp->altsetting); return -EINVAL; } + set_rate_table_min_max(fp); } else { /* continuous rates */ fp->rates = SNDRV_PCM_RATE_CONTINUOUS; fp->rate_min = combine_triple(&fmt[offset + 1]); fp->rate_max = combine_triple(&fmt[offset + 4]); } + + /* Jabra Evolve 65 headset */ + if (chip->usb_id == USB_ID(0x0b0e, 0x030b) || + chip->usb_id == USB_ID(0x0b0e, 0x030c)) { + /* only 48kHz for playback while keeping 16kHz for capture */ + if (fp->nr_rates != 1) + return set_fixed_rate(fp, 48000, SNDRV_PCM_RATE_48000); + } + return 0; } + +/* + * Presonus Studio 1810c supports a limited set of sampling + * rates per altsetting but reports the full set each time. + * If we don't filter out the unsupported rates and attempt + * to configure the card, it will hang refusing to do any + * further audio I/O until a hard reset is performed. + * + * The list of supported rates per altsetting (set of available + * I/O channels) is described in the owner's manual, section 2.2. + */ +static bool s1810c_valid_sample_rate(struct audioformat *fp, + unsigned int rate) +{ + switch (fp->altsetting) { + case 1: + /* All ADAT ports available */ + return rate <= 48000; + case 2: + /* Half of ADAT ports available */ + return (rate == 88200 || rate == 96000); + case 3: + /* Analog I/O only (no S/PDIF nor ADAT) */ + return rate >= 176400; + default: + return false; + } + return false; +} + +/* + * Many Focusrite devices supports a limited set of sampling rates per + * altsetting. Maximum rate is exposed in the last 4 bytes of Format Type + * descriptor which has a non-standard bLength = 10. + */ +static bool focusrite_valid_sample_rate(struct snd_usb_audio *chip, + struct audioformat *fp, + unsigned int rate) +{ + struct usb_host_interface *alts; + unsigned char *fmt; + unsigned int max_rate; + + alts = snd_usb_get_host_interface(chip, fp->iface, fp->altsetting); + if (!alts) + return true; + + fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, UAC_FORMAT_TYPE); + if (!fmt) + return true; + + if (fmt[0] == 10) { /* bLength */ + max_rate = combine_quad(&fmt[6]); + + switch (max_rate) { + case 192000: + if (rate == 176400 || rate == 192000) + return true; + fallthrough; + case 96000: + if (rate == 88200 || rate == 96000) + return true; + fallthrough; + case 48000: + return (rate == 44100 || rate == 48000); + default: + usb_audio_info(chip, + "%u:%d : unexpected max rate: %u\n", + fp->iface, fp->altsetting, max_rate); + + return true; + } + } + + return true; +} + /* * Helper function to walk the array of sample rate triplets reported by * the device. The problem is that we need to parse whole array first to @@ -249,8 +361,6 @@ static int parse_uac2_sample_rate_range(struct snd_usb_audio *chip, { int i, nr_rates = 0; - fp->rates = fp->rate_min = fp->rate_max = 0; - for (i = 0; i < nr_triplets; i++) { int min = combine_quad(&data[2 + 12 * i]); int max = combine_quad(&data[6 + 12 * i]); @@ -273,20 +383,30 @@ static int parse_uac2_sample_rate_range(struct snd_usb_audio *chip, } for (rate = min; rate <= max; rate += res) { + + /* Filter out invalid rates on Presonus Studio 1810c */ + if (chip->usb_id == USB_ID(0x194f, 0x010c) && + !s1810c_valid_sample_rate(fp, rate)) + goto skip_rate; + /* Filter out invalid rates on Presonus Studio 1824c */ + if (chip->usb_id == USB_ID(0x194f, 0x010d) && + !s1810c_valid_sample_rate(fp, rate)) + goto skip_rate; + + /* Filter out invalid rates on Focusrite devices */ + if (USB_ID_VENDOR(chip->usb_id) == 0x1235 && + !focusrite_valid_sample_rate(chip, fp, rate)) + goto skip_rate; + if (fp->rate_table) fp->rate_table[nr_rates] = rate; - if (!fp->rate_min || rate < fp->rate_min) - fp->rate_min = rate; - if (!fp->rate_max || rate > fp->rate_max) - fp->rate_max = rate; - fp->rates |= snd_pcm_rate_to_rate_bit(rate); - nr_rates++; if (nr_rates >= MAX_NR_RATES) { usb_audio_err(chip, "invalid uac2 rates\n"); break; } +skip_rate: /* avoid endless loop */ if (res == 0) break; @@ -296,6 +416,137 @@ static int parse_uac2_sample_rate_range(struct snd_usb_audio *chip, return nr_rates; } +/* Line6 Helix series and the Rode Rodecaster Pro don't support the + * UAC2_CS_RANGE usb function call. Return a static table of known + * clock rates. + */ +static int line6_parse_audio_format_rates_quirk(struct snd_usb_audio *chip, + struct audioformat *fp) +{ + switch (chip->usb_id) { + case USB_ID(0x0e41, 0x4241): /* Line6 Helix */ + case USB_ID(0x0e41, 0x4242): /* Line6 Helix Rack */ + case USB_ID(0x0e41, 0x4244): /* Line6 Helix LT */ + case USB_ID(0x0e41, 0x4246): /* Line6 HX-Stomp */ + case USB_ID(0x0e41, 0x4253): /* Line6 HX-Stomp XL */ + case USB_ID(0x0e41, 0x4247): /* Line6 Pod Go */ + case USB_ID(0x0e41, 0x4248): /* Line6 Helix >= fw 2.82 */ + case USB_ID(0x0e41, 0x4249): /* Line6 Helix Rack >= fw 2.82 */ + case USB_ID(0x0e41, 0x424a): /* Line6 Helix LT >= fw 2.82 */ + case USB_ID(0x0e41, 0x424b): /* Line6 Pod Go */ + case USB_ID(0x19f7, 0x0011): /* Rode Rodecaster Pro */ + return set_fixed_rate(fp, 48000, SNDRV_PCM_RATE_48000); + } + + return -ENODEV; +} + +/* check whether the given altsetting is supported for the already set rate */ +static bool check_valid_altsetting_v2v3(struct snd_usb_audio *chip, int iface, + int altsetting) +{ + struct usb_device *dev = chip->dev; + __le64 raw_data = 0; + u64 data; + int err; + + /* we assume 64bit is enough for any altsettings */ + if (snd_BUG_ON(altsetting >= 64 - 8)) + return false; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + UAC2_AS_VAL_ALT_SETTINGS << 8, + iface, &raw_data, sizeof(raw_data)); + if (err < 0) + return false; + + data = le64_to_cpu(raw_data); + /* first byte contains the bitmap size */ + if ((data & 0xff) * 8 < altsetting) + return false; + if (data & (1ULL << (altsetting + 8))) + return true; + + return false; +} + +/* + * Validate each sample rate with the altsetting + * Rebuild the rate table if only partial values are valid + */ +static int validate_sample_rate_table_v2v3(struct snd_usb_audio *chip, + struct audioformat *fp, + int clock) +{ + struct usb_device *dev = chip->dev; + struct usb_host_interface *alts; + unsigned int *table; + unsigned int nr_rates; + int i, err; + u32 bmControls; + + /* performing the rate verification may lead to unexpected USB bus + * behavior afterwards by some unknown reason. Do this only for the + * known devices. + */ + if (!(chip->quirk_flags & QUIRK_FLAG_VALIDATE_RATES)) + return 0; /* don't perform the validation as default */ + + alts = snd_usb_get_host_interface(chip, fp->iface, fp->altsetting); + if (!alts) + return 0; + + if (fp->protocol == UAC_VERSION_3) { + struct uac3_as_header_descriptor *as = snd_usb_find_csint_desc( + alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); + bmControls = le32_to_cpu(as->bmControls); + } else { + struct uac2_as_header_descriptor *as = snd_usb_find_csint_desc( + alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); + bmControls = as->bmControls; + } + + if (!uac_v2v3_control_is_readable(bmControls, + UAC2_AS_VAL_ALT_SETTINGS)) + return 0; + + table = kcalloc(fp->nr_rates, sizeof(*table), GFP_KERNEL); + if (!table) + return -ENOMEM; + + /* clear the interface altsetting at first */ + usb_set_interface(dev, fp->iface, 0); + + nr_rates = 0; + for (i = 0; i < fp->nr_rates; i++) { + err = snd_usb_set_sample_rate_v2v3(chip, fp, clock, + fp->rate_table[i]); + if (err < 0) + continue; + + if (check_valid_altsetting_v2v3(chip, fp->iface, fp->altsetting)) + table[nr_rates++] = fp->rate_table[i]; + } + + if (!nr_rates) { + usb_audio_dbg(chip, + "No valid sample rate available for %d:%d, assuming a firmware bug\n", + fp->iface, fp->altsetting); + nr_rates = fp->nr_rates; /* continue as is */ + } + + if (fp->nr_rates == nr_rates) { + kfree(table); + return 0; + } + + kfree(fp->rate_table); + fp->rate_table = table; + fp->nr_rates = nr_rates; + return 0; +} + /* * parse the format descriptor and stores the possible sample rates * on the audioformat table (audio class v2 and v3). @@ -305,10 +556,11 @@ static int parse_audio_format_rates_v2v3(struct snd_usb_audio *chip, { struct usb_device *dev = chip->dev; unsigned char tmp[2], *data; - int nr_triplets, data_size, ret = 0; - int clock = snd_usb_clock_find_source(chip, fp->protocol, - fp->clock, false); + int nr_triplets, data_size, ret = 0, ret_l6; + int clock = snd_usb_clock_find_source(chip, fp, false); + struct usb_host_interface *ctrl_intf; + ctrl_intf = snd_usb_find_ctrl_interface(chip, fp->iface); if (clock < 0) { dev_err(&dev->dev, "%s(): unable to find clock source (clock %d)\n", @@ -320,13 +572,26 @@ static int parse_audio_format_rates_v2v3(struct snd_usb_audio *chip, ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, UAC2_CS_CONTROL_SAM_FREQ << 8, - snd_usb_ctrl_intf(chip) | (clock << 8), + snd_usb_ctrl_intf(ctrl_intf) | (clock << 8), tmp, sizeof(tmp)); if (ret < 0) { - dev_err(&dev->dev, - "%s(): unable to retrieve number of sample rates (clock %d)\n", + /* line6 helix devices don't support UAC2_CS_CONTROL_SAM_FREQ call */ + ret_l6 = line6_parse_audio_format_rates_quirk(chip, fp); + if (ret_l6 == -ENODEV) { + /* no line6 device found continue showing the error */ + dev_err(&dev->dev, + "%s(): unable to retrieve number of sample rates (clock %d)\n", + __func__, clock); + goto err; + } + if (ret_l6 == 0) { + dev_info(&dev->dev, + "%s(): unable to retrieve number of sample rates: set it to a predefined value (clock %d).\n", __func__, clock); + return 0; + } + ret = ret_l6; goto err; } @@ -342,7 +607,7 @@ static int parse_audio_format_rates_v2v3(struct snd_usb_audio *chip, ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, UAC2_CS_CONTROL_SAM_FREQ << 8, - snd_usb_ctrl_intf(chip) | (clock << 8), + snd_usb_ctrl_intf(ctrl_intf) | (clock << 8), data, data_size); if (ret < 0) { @@ -376,6 +641,12 @@ static int parse_audio_format_rates_v2v3(struct snd_usb_audio *chip, * allocated, so the rates will be stored */ parse_uac2_sample_rate_range(chip, fp, nr_triplets, data); + ret = validate_sample_rate_table_v2v3(chip, fp, clock); + if (ret < 0) + goto err_free; + + set_rate_table_min_max(fp); + err_free: kfree(data); err: diff --git a/sound/usb/helper.c b/sound/usb/helper.c index 7712e2b84183..497d2b27fb59 100644 --- a/sound/usb/helper.c +++ b/sound/usb/helper.c @@ -1,18 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * */ #include <linux/init.h> @@ -75,6 +62,7 @@ void *snd_usb_find_csint_desc(void *buffer, int buflen, void *after, u8 dsubtype } return NULL; } +EXPORT_SYMBOL_GPL(snd_usb_find_csint_desc); /* * Wrapper for usb_control_msg(). @@ -88,6 +76,9 @@ int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, __u8 request, void *buf = NULL; int timeout; + if (usb_pipe_type_check(dev, pipe)) + return -EINVAL; + if (size > 0) { buf = kmemdup(data, size, GFP_KERNEL); if (!buf) @@ -118,7 +109,6 @@ unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip, { switch (snd_usb_get_speed(chip->dev)) { case USB_SPEED_HIGH: - case USB_SPEED_WIRELESS: case USB_SPEED_SUPER: case USB_SPEED_SUPER_PLUS: if (get_endpoint(alts, 0)->bInterval >= 1 && @@ -131,3 +121,47 @@ unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip, return 0; } +struct usb_host_interface * +snd_usb_get_host_interface(struct snd_usb_audio *chip, int ifnum, int altsetting) +{ + struct usb_interface *iface; + + iface = usb_ifnum_to_if(chip->dev, ifnum); + if (!iface) + return NULL; + return usb_altnum_to_altsetting(iface, altsetting); +} + +int snd_usb_add_ctrl_interface_link(struct snd_usb_audio *chip, int ifnum, + int ctrlif) +{ + struct usb_device *dev = chip->dev; + struct usb_host_interface *host_iface; + + if (chip->num_intf_to_ctrl >= MAX_CARD_INTERFACES) { + dev_info(&dev->dev, "Too many interfaces assigned to the single USB-audio card\n"); + return -EINVAL; + } + + /* find audiocontrol interface */ + host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0]; + + chip->intf_to_ctrl[chip->num_intf_to_ctrl].interface = ifnum; + chip->intf_to_ctrl[chip->num_intf_to_ctrl].ctrl_intf = host_iface; + chip->num_intf_to_ctrl++; + + return 0; +} + +struct usb_host_interface *snd_usb_find_ctrl_interface(struct snd_usb_audio *chip, + int ifnum) +{ + int i; + + for (i = 0; i < chip->num_intf_to_ctrl; ++i) + if (chip->intf_to_ctrl[i].interface == ifnum) + return chip->intf_to_ctrl[i].ctrl_intf; + + /* Fallback to first audiocontrol interface */ + return chip->ctrl_intf; +} diff --git a/sound/usb/helper.h b/sound/usb/helper.h index d338bd0e0ca6..0372e050b3dc 100644 --- a/sound/usb/helper.h +++ b/sound/usb/helper.h @@ -14,6 +14,15 @@ int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip, struct usb_host_interface *alts); +struct usb_host_interface * +snd_usb_get_host_interface(struct snd_usb_audio *chip, int ifnum, int altsetting); + +int snd_usb_add_ctrl_interface_link(struct snd_usb_audio *chip, int ifnum, + int ctrlif); + +struct usb_host_interface *snd_usb_find_ctrl_interface(struct snd_usb_audio *chip, + int ifnum); + /* * retrieve usb_interface descriptor from the host interface * (conditional for compatibility with the older API) @@ -25,9 +34,13 @@ unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip, #define snd_usb_get_speed(dev) ((dev)->speed) -static inline int snd_usb_ctrl_intf(struct snd_usb_audio *chip) +static inline int snd_usb_ctrl_intf(struct usb_host_interface *ctrl_intf) { - return get_iface_desc(chip->ctrl_intf)->bInterfaceNumber; + return get_iface_desc(ctrl_intf)->bInterfaceNumber; } +/* in validate.c */ +bool snd_usb_validate_audio_desc(void *p, int protocol); +bool snd_usb_validate_midi_desc(void *p); + #endif /* __USBAUDIO_HELPER_H */ diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile index 463b136d1d89..997c1558d0cb 100644 --- a/sound/usb/hiface/Makefile +++ b/sound/usb/hiface/Makefile @@ -1,2 +1,3 @@ -snd-usb-hiface-objs := chip.o pcm.o +# SPDX-License-Identifier: GPL-2.0-only +snd-usb-hiface-y := chip.o pcm.o obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c index 2670d646bda9..bce28f683666 100644 --- a/sound/usb/hiface/chip.c +++ b/sound/usb/hiface/chip.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Linux driver for M2Tech hiFace compatible devices * @@ -7,11 +8,6 @@ * Antonio Ospite <ao2@amarulasolutions.com> * * The driver is based on the work done in TerraTec DMX 6Fire USB - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include <linux/module.h> @@ -25,23 +21,6 @@ MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>"); MODULE_AUTHOR("Antonio Ospite <ao2@amarulasolutions.com>"); MODULE_DESCRIPTION("M2Tech hiFace USB-SPDIF audio driver"); MODULE_LICENSE("GPL v2"); -MODULE_SUPPORTED_DEVICE("{{M2Tech,Young}," - "{M2Tech,hiFace}," - "{M2Tech,North Star}," - "{M2Tech,W4S Young}," - "{M2Tech,Corrson}," - "{M2Tech,AUDIA}," - "{M2Tech,SL Audio}," - "{M2Tech,Empirical}," - "{M2Tech,Rockna}," - "{M2Tech,Pathos}," - "{M2Tech,Metronome}," - "{M2Tech,CAD}," - "{M2Tech,Audio Esclusive}," - "{M2Tech,Rotel}," - "{M2Tech,Eeaudio}," - "{The Chord Company,CHORD}," - "{AVA Group A/S,Vitus}}"); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */ @@ -84,12 +63,12 @@ static int hiface_chip_create(struct usb_interface *intf, return ret; } - strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver)); + strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); if (quirk && quirk->device_name) - strlcpy(card->shortname, quirk->device_name, sizeof(card->shortname)); + strscpy(card->shortname, quirk->device_name, sizeof(card->shortname)); else - strlcpy(card->shortname, "M2Tech generic audio", sizeof(card->shortname)); + strscpy(card->shortname, "M2Tech generic audio", sizeof(card->shortname)); strlcat(card->longname, card->shortname, sizeof(card->longname)); len = strlcat(card->longname, " at ", sizeof(card->longname)); @@ -122,7 +101,7 @@ static int hiface_chip_probe(struct usb_interface *intf, /* check whether the card is already registered */ chip = NULL; - mutex_lock(®ister_mutex); + guard(mutex)(®ister_mutex); for (i = 0; i < SNDRV_CARDS; i++) if (enable[i]) @@ -130,13 +109,12 @@ static int hiface_chip_probe(struct usb_interface *intf, if (i >= SNDRV_CARDS) { dev_err(&device->dev, "no available " CARD_NAME " audio device\n"); - ret = -ENODEV; - goto err; + return -ENODEV; } ret = hiface_chip_create(intf, device, i, quirk, &chip); if (ret < 0) - goto err; + return ret; ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0); if (ret < 0) @@ -148,15 +126,11 @@ static int hiface_chip_probe(struct usb_interface *intf, goto err_chip_destroy; } - mutex_unlock(®ister_mutex); - usb_set_intfdata(intf, chip); return 0; err_chip_destroy: snd_card_free(chip->card); -err: - mutex_unlock(®ister_mutex); return ret; } diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h index 189a1371b7c4..eb7a5cfb4d06 100644 --- a/sound/usb/hiface/chip.h +++ b/sound/usb/hiface/chip.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Linux driver for M2Tech hiFace compatible devices * @@ -7,11 +8,6 @@ * Antonio Ospite <ao2@amarulasolutions.com> * * The driver is based on the work done in TerraTec DMX 6Fire USB - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #ifndef HIFACE_CHIP_H diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c index e1fbb9cc9ea7..27cd427fbaa5 100644 --- a/sound/usb/hiface/pcm.c +++ b/sound/usb/hiface/pcm.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Linux driver for M2Tech hiFace compatible devices * @@ -7,11 +8,6 @@ * Antonio Ospite <ao2@amarulasolutions.com> * * The driver is based on the work done in TerraTec DMX 6Fire USB - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include <linux/slab.h> @@ -160,16 +156,14 @@ static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate) * This control message doesn't have any ack from the * other side */ - ret = usb_control_msg(device, usb_sndctrlpipe(device, 0), - HIFACE_SET_RATE_REQUEST, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - rate_value, 0, NULL, 0, 100); - if (ret < 0) { + ret = usb_control_msg_send(device, 0, + HIFACE_SET_RATE_REQUEST, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + rate_value, 0, NULL, 0, 100, GFP_KERNEL); + if (ret) dev_err(&device->dev, "Error setting samplerate %d.\n", rate); - return ret; - } - return 0; + return ret; } static struct pcm_substream *hiface_pcm_get_substream(struct snd_pcm_substream @@ -231,7 +225,7 @@ static int hiface_pcm_stream_start(struct pcm_runtime *rt) } } - /* wait for first out urb to return (sent in in urb handler) */ + /* wait for first out urb to return (sent in urb handler) */ wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond, HZ); if (rt->stream_wait_cond) { @@ -311,7 +305,6 @@ static void hiface_pcm_out_urb_handler(struct urb *usb_urb) struct pcm_runtime *rt = out_urb->chip->pcm; struct pcm_substream *sub; bool do_period_elapsed = false; - unsigned long flags; int ret; if (rt->panic || rt->stream_state == STREAM_STOPPING) @@ -331,13 +324,12 @@ static void hiface_pcm_out_urb_handler(struct urb *usb_urb) /* now send our playback data (if a free out urb was found) */ sub = &rt->playback; - spin_lock_irqsave(&sub->lock, flags); - if (sub->active) - do_period_elapsed = hiface_pcm_playback(sub, out_urb); - else - memset(out_urb->buffer, 0, PCM_PACKET_SIZE); - - spin_unlock_irqrestore(&sub->lock, flags); + scoped_guard(spinlock_irqsave, &sub->lock) { + if (sub->active) + do_period_elapsed = hiface_pcm_playback(sub, out_urb); + else + memset(out_urb->buffer, 0, PCM_PACKET_SIZE); + } if (do_period_elapsed) snd_pcm_period_elapsed(sub->instance); @@ -362,7 +354,7 @@ static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub) if (rt->panic) return -EPIPE; - mutex_lock(&rt->stream_mutex); + guard(mutex)(&rt->stream_mutex); alsa_rt->hw = pcm_hw; if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) @@ -370,7 +362,6 @@ static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub) if (!sub) { struct device *device = &rt->chip->dev->dev; - mutex_unlock(&rt->stream_mutex); dev_err(device, "Invalid stream type\n"); return -EINVAL; } @@ -383,15 +374,12 @@ static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub) ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_extra_rates); - if (ret < 0) { - mutex_unlock(&rt->stream_mutex); + if (ret < 0) return ret; - } } sub->instance = alsa_sub; sub->active = false; - mutex_unlock(&rt->stream_mutex); return 0; } @@ -399,38 +387,22 @@ static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub) { struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub); - unsigned long flags; if (rt->panic) return 0; - mutex_lock(&rt->stream_mutex); + guard(mutex)(&rt->stream_mutex); if (sub) { hiface_pcm_stream_stop(rt); /* deactivate substream */ - spin_lock_irqsave(&sub->lock, flags); + guard(spinlock_irqsave)(&sub->lock); sub->instance = NULL; sub->active = false; - spin_unlock_irqrestore(&sub->lock, flags); - } - mutex_unlock(&rt->stream_mutex); return 0; } -static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub, - struct snd_pcm_hw_params *hw_params) -{ - return snd_pcm_lib_alloc_vmalloc_buffer(alsa_sub, - params_buffer_bytes(hw_params)); -} - -static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub) -{ - return snd_pcm_lib_free_vmalloc_buffer(alsa_sub); -} - static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub) { struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); @@ -443,7 +415,7 @@ static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub) if (!sub) return -ENODEV; - mutex_lock(&rt->stream_mutex); + guard(mutex)(&rt->stream_mutex); hiface_pcm_stream_stop(rt); @@ -453,17 +425,12 @@ static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub) if (rt->stream_state == STREAM_DISABLED) { ret = hiface_pcm_set_rate(rt, alsa_rt->rate); - if (ret) { - mutex_unlock(&rt->stream_mutex); + if (ret) return ret; - } ret = hiface_pcm_stream_start(rt); - if (ret) { - mutex_unlock(&rt->stream_mutex); + if (ret) return ret; - } } - mutex_unlock(&rt->stream_mutex); return 0; } @@ -480,16 +447,16 @@ static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd) switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - spin_lock_irq(&sub->lock); - sub->active = true; - spin_unlock_irq(&sub->lock); + scoped_guard(spinlock_irq, &sub->lock) { + sub->active = true; + } return 0; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - spin_lock_irq(&sub->lock); - sub->active = false; - spin_unlock_irq(&sub->lock); + scoped_guard(spinlock_irq, &sub->lock) { + sub->active = false; + } return 0; default: @@ -501,28 +468,22 @@ static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub) { struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub); struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); - unsigned long flags; snd_pcm_uframes_t dma_offset; if (rt->panic || !sub) return SNDRV_PCM_POS_XRUN; - spin_lock_irqsave(&sub->lock, flags); + guard(spinlock_irqsave)(&sub->lock); dma_offset = sub->dma_off; - spin_unlock_irqrestore(&sub->lock, flags); return bytes_to_frames(alsa_sub->runtime, dma_offset); } static const struct snd_pcm_ops pcm_ops = { .open = hiface_pcm_open, .close = hiface_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = hiface_pcm_hw_params, - .hw_free = hiface_pcm_hw_free, .prepare = hiface_pcm_prepare, .trigger = hiface_pcm_trigger, .pointer = hiface_pcm_pointer, - .page = snd_pcm_lib_get_vmalloc_page, }; static int hiface_pcm_init_urb(struct pcm_urb *urb, @@ -554,9 +515,8 @@ void hiface_pcm_abort(struct hiface_chip *chip) if (rt) { rt->panic = true; - mutex_lock(&rt->stream_mutex); + guard(mutex)(&rt->stream_mutex); hiface_pcm_stream_stop(rt); - mutex_unlock(&rt->stream_mutex); } } @@ -604,24 +564,31 @@ int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq) ret = hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP, hiface_pcm_out_urb_handler); if (ret < 0) - return ret; + goto error; } ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm); if (ret < 0) { - kfree(rt); dev_err(&chip->dev->dev, "Cannot create pcm instance\n"); - return ret; + goto error; } pcm->private_data = rt; pcm->private_free = hiface_pcm_free; - strlcpy(pcm->name, "USB-SPDIF Audio", sizeof(pcm->name)); + strscpy(pcm->name, "USB-SPDIF Audio", sizeof(pcm->name)); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, + NULL, 0, 0); rt->instance = pcm; chip->pcm = rt; return 0; + +error: + for (i = 0; i < PCM_N_URBS; i++) + kfree(rt->out_urbs[i].buffer); + kfree(rt); + return ret; } diff --git a/sound/usb/hiface/pcm.h b/sound/usb/hiface/pcm.h index 77edd7c12e19..afe74e978e59 100644 --- a/sound/usb/hiface/pcm.h +++ b/sound/usb/hiface/pcm.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Linux driver for M2Tech hiFace compatible devices * @@ -7,11 +8,6 @@ * Antonio Ospite <ao2@amarulasolutions.com> * * The driver is based on the work done in TerraTec DMX 6Fire USB - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #ifndef HIFACE_PCM_H diff --git a/sound/usb/implicit.c b/sound/usb/implicit.c new file mode 100644 index 000000000000..77f06da93151 --- /dev/null +++ b/sound/usb/implicit.c @@ -0,0 +1,494 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Special handling for implicit feedback mode +// + +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/usb/audio.h> +#include <linux/usb/audio-v2.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include "usbaudio.h" +#include "card.h" +#include "helper.h" +#include "pcm.h" +#include "implicit.h" + +enum { + IMPLICIT_FB_NONE, + IMPLICIT_FB_GENERIC, + IMPLICIT_FB_FIXED, + IMPLICIT_FB_BOTH, /* generic playback + capture (for BOSS) */ +}; + +struct snd_usb_implicit_fb_match { + unsigned int id; + unsigned int iface_class; + unsigned int ep_num; + unsigned int iface; + int type; +}; + +#define IMPLICIT_FB_GENERIC_DEV(vend, prod) \ + { .id = USB_ID(vend, prod), .type = IMPLICIT_FB_GENERIC } +#define IMPLICIT_FB_FIXED_DEV(vend, prod, ep, ifnum) \ + { .id = USB_ID(vend, prod), .type = IMPLICIT_FB_FIXED, .ep_num = (ep),\ + .iface = (ifnum) } +#define IMPLICIT_FB_BOTH_DEV(vend, prod, ep, ifnum) \ + { .id = USB_ID(vend, prod), .type = IMPLICIT_FB_BOTH, .ep_num = (ep),\ + .iface = (ifnum) } +#define IMPLICIT_FB_SKIP_DEV(vend, prod) \ + { .id = USB_ID(vend, prod), .type = IMPLICIT_FB_NONE } + +/* Implicit feedback quirk table for playback */ +static const struct snd_usb_implicit_fb_match playback_implicit_fb_quirks[] = { + /* Fixed EP */ + /* FIXME: check the availability of generic matching */ + IMPLICIT_FB_FIXED_DEV(0x0763, 0x2030, 0x81, 3), /* M-Audio Fast Track C400 */ + IMPLICIT_FB_FIXED_DEV(0x0763, 0x2031, 0x81, 3), /* M-Audio Fast Track C600 */ + IMPLICIT_FB_FIXED_DEV(0x0763, 0x2080, 0x81, 2), /* M-Audio FastTrack Ultra */ + IMPLICIT_FB_FIXED_DEV(0x0763, 0x2081, 0x81, 2), /* M-Audio FastTrack Ultra */ + IMPLICIT_FB_FIXED_DEV(0x2466, 0x8010, 0x81, 2), /* Fractal Audio Axe-Fx III */ + IMPLICIT_FB_FIXED_DEV(0x31e9, 0x0001, 0x81, 2), /* Solid State Logic SSL2 */ + IMPLICIT_FB_FIXED_DEV(0x31e9, 0x0002, 0x81, 2), /* Solid State Logic SSL2+ */ + IMPLICIT_FB_FIXED_DEV(0x0499, 0x172f, 0x81, 2), /* Steinberg UR22C */ + IMPLICIT_FB_FIXED_DEV(0x0d9a, 0x00df, 0x81, 2), /* RTX6001 */ + IMPLICIT_FB_FIXED_DEV(0x19f7, 0x000a, 0x84, 3), /* RODE AI-1 */ + IMPLICIT_FB_FIXED_DEV(0x22f0, 0x0006, 0x81, 3), /* Allen&Heath Qu-16 */ + IMPLICIT_FB_FIXED_DEV(0x1686, 0xf029, 0x82, 2), /* Zoom UAC-2 */ + IMPLICIT_FB_FIXED_DEV(0x2466, 0x8003, 0x86, 2), /* Fractal Audio Axe-Fx II */ + IMPLICIT_FB_FIXED_DEV(0x0499, 0x172a, 0x86, 2), /* Yamaha MODX */ + + /* Special matching */ + { .id = USB_ID(0x07fd, 0x0004), .iface_class = USB_CLASS_AUDIO, + .type = IMPLICIT_FB_NONE }, /* MicroBook IIc */ + /* ep = 0x84, ifnum = 0 */ + { .id = USB_ID(0x07fd, 0x0004), .iface_class = USB_CLASS_VENDOR_SPEC, + .type = IMPLICIT_FB_FIXED, + .ep_num = 0x84, .iface = 0 }, /* MOTU MicroBook II */ + + {} /* terminator */ +}; + +/* Implicit feedback quirk table for capture: only FIXED type */ +static const struct snd_usb_implicit_fb_match capture_implicit_fb_quirks[] = { + {} /* terminator */ +}; + +/* set up sync EP information on the audioformat */ +static int add_implicit_fb_sync_ep(struct snd_usb_audio *chip, + struct audioformat *fmt, + int ep, int ep_idx, int ifnum, + const struct usb_host_interface *alts) +{ + struct usb_interface *iface; + + if (!alts) { + iface = usb_ifnum_to_if(chip->dev, ifnum); + if (!iface || iface->num_altsetting < 2) + return 0; + alts = &iface->altsetting[1]; + } + + fmt->sync_ep = ep; + fmt->sync_iface = ifnum; + fmt->sync_altsetting = alts->desc.bAlternateSetting; + fmt->sync_ep_idx = ep_idx; + fmt->implicit_fb = 1; + usb_audio_dbg(chip, + "%d:%d: added %s implicit_fb sync_ep %x, iface %d:%d\n", + fmt->iface, fmt->altsetting, + (ep & USB_DIR_IN) ? "playback" : "capture", + fmt->sync_ep, fmt->sync_iface, fmt->sync_altsetting); + return 1; +} + +/* Check whether the given UAC2 iface:altset points to an implicit fb source */ +static int add_generic_uac2_implicit_fb(struct snd_usb_audio *chip, + struct audioformat *fmt, + unsigned int ifnum, + unsigned int altsetting) +{ + struct usb_host_interface *alts; + struct usb_endpoint_descriptor *epd; + + alts = snd_usb_get_host_interface(chip, ifnum, altsetting); + if (!alts) + return 0; + if (alts->desc.bInterfaceClass != USB_CLASS_AUDIO || + alts->desc.bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING || + alts->desc.bInterfaceProtocol != UAC_VERSION_2 || + alts->desc.bNumEndpoints < 1) + return 0; + epd = get_endpoint(alts, 0); + if (!usb_endpoint_is_isoc_in(epd) || + (epd->bmAttributes & USB_ENDPOINT_USAGE_MASK) != + USB_ENDPOINT_USAGE_IMPLICIT_FB) + return 0; + return add_implicit_fb_sync_ep(chip, fmt, epd->bEndpointAddress, 0, + ifnum, alts); +} + +static bool roland_sanity_check_iface(struct usb_host_interface *alts) +{ + if (alts->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC || + (alts->desc.bInterfaceSubClass != 2 && + alts->desc.bInterfaceProtocol != 2) || + alts->desc.bNumEndpoints < 1) + return false; + return true; +} + +/* Like the UAC2 case above, but specific to Roland with vendor class and hack */ +static int add_roland_implicit_fb(struct snd_usb_audio *chip, + struct audioformat *fmt, + struct usb_host_interface *alts) +{ + struct usb_endpoint_descriptor *epd; + + if (!roland_sanity_check_iface(alts)) + return 0; + /* only when both streams are with ASYNC type */ + epd = get_endpoint(alts, 0); + if (!usb_endpoint_is_isoc_out(epd) || + (epd->bmAttributes & USB_ENDPOINT_SYNCTYPE) != USB_ENDPOINT_SYNC_ASYNC) + return 0; + + /* check capture EP */ + alts = snd_usb_get_host_interface(chip, + alts->desc.bInterfaceNumber + 1, + alts->desc.bAlternateSetting); + if (!alts || !roland_sanity_check_iface(alts)) + return 0; + epd = get_endpoint(alts, 0); + if (!usb_endpoint_is_isoc_in(epd) || + (epd->bmAttributes & USB_ENDPOINT_SYNCTYPE) != USB_ENDPOINT_SYNC_ASYNC) + return 0; + chip->quirk_flags |= QUIRK_FLAG_PLAYBACK_FIRST; + return add_implicit_fb_sync_ep(chip, fmt, epd->bEndpointAddress, 0, + alts->desc.bInterfaceNumber, alts); +} + +/* capture quirk for Roland device; always full-duplex */ +static int add_roland_capture_quirk(struct snd_usb_audio *chip, + struct audioformat *fmt, + struct usb_host_interface *alts) +{ + struct usb_endpoint_descriptor *epd; + + if (!roland_sanity_check_iface(alts)) + return 0; + epd = get_endpoint(alts, 0); + if (!usb_endpoint_is_isoc_in(epd) || + (epd->bmAttributes & USB_ENDPOINT_SYNCTYPE) != USB_ENDPOINT_SYNC_ASYNC) + return 0; + + alts = snd_usb_get_host_interface(chip, + alts->desc.bInterfaceNumber - 1, + alts->desc.bAlternateSetting); + if (!alts || !roland_sanity_check_iface(alts)) + return 0; + epd = get_endpoint(alts, 0); + if (!usb_endpoint_is_isoc_out(epd)) + return 0; + return add_implicit_fb_sync_ep(chip, fmt, epd->bEndpointAddress, 0, + alts->desc.bInterfaceNumber, alts); +} + +/* Playback and capture EPs on Pioneer devices share the same iface/altset + * for the implicit feedback operation + */ +static bool is_pioneer_implicit_fb(struct snd_usb_audio *chip, + struct usb_host_interface *alts) + +{ + struct usb_endpoint_descriptor *epd; + + if (USB_ID_VENDOR(chip->usb_id) != 0x2b73 && + USB_ID_VENDOR(chip->usb_id) != 0x08e4) + return false; + if (alts->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC) + return false; + if (alts->desc.bNumEndpoints != 2) + return false; + + epd = get_endpoint(alts, 0); + if (!usb_endpoint_is_isoc_out(epd) || + (epd->bmAttributes & USB_ENDPOINT_SYNCTYPE) != USB_ENDPOINT_SYNC_ASYNC) + return false; + + epd = get_endpoint(alts, 1); + if (!usb_endpoint_is_isoc_in(epd) || + (epd->bmAttributes & USB_ENDPOINT_SYNCTYPE) != USB_ENDPOINT_SYNC_ASYNC || + ((epd->bmAttributes & USB_ENDPOINT_USAGE_MASK) != + USB_ENDPOINT_USAGE_DATA && + (epd->bmAttributes & USB_ENDPOINT_USAGE_MASK) != + USB_ENDPOINT_USAGE_IMPLICIT_FB)) + return false; + + return true; +} + +static int __add_generic_implicit_fb(struct snd_usb_audio *chip, + struct audioformat *fmt, + int iface, int altset) +{ + struct usb_host_interface *alts; + struct usb_endpoint_descriptor *epd; + + alts = snd_usb_get_host_interface(chip, iface, altset); + if (!alts) + return 0; + + if ((alts->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC && + alts->desc.bInterfaceClass != USB_CLASS_AUDIO) || + alts->desc.bNumEndpoints < 1) + return 0; + epd = get_endpoint(alts, 0); + if (!usb_endpoint_is_isoc_in(epd) || + (epd->bmAttributes & USB_ENDPOINT_SYNCTYPE) != USB_ENDPOINT_SYNC_ASYNC) + return 0; + return add_implicit_fb_sync_ep(chip, fmt, epd->bEndpointAddress, 0, + iface, alts); +} + +/* More generic quirk: look for the sync EP next to the data EP */ +static int add_generic_implicit_fb(struct snd_usb_audio *chip, + struct audioformat *fmt, + struct usb_host_interface *alts) +{ + if ((fmt->ep_attr & USB_ENDPOINT_SYNCTYPE) != USB_ENDPOINT_SYNC_ASYNC) + return 0; + + if (__add_generic_implicit_fb(chip, fmt, + alts->desc.bInterfaceNumber + 1, + alts->desc.bAlternateSetting)) + return 1; + return __add_generic_implicit_fb(chip, fmt, + alts->desc.bInterfaceNumber - 1, + alts->desc.bAlternateSetting); +} + +static const struct snd_usb_implicit_fb_match * +find_implicit_fb_entry(struct snd_usb_audio *chip, + const struct snd_usb_implicit_fb_match *match, + const struct usb_host_interface *alts) +{ + for (; match->id; match++) + if (match->id == chip->usb_id && + (!match->iface_class || + (alts->desc.bInterfaceClass == match->iface_class))) + return match; + + return NULL; +} + +/* Setup an implicit feedback endpoint from a quirk. Returns 0 if no quirk + * applies. Returns 1 if a quirk was found. + */ +static int audioformat_implicit_fb_quirk(struct snd_usb_audio *chip, + struct audioformat *fmt, + struct usb_host_interface *alts) +{ + const struct snd_usb_implicit_fb_match *p; + unsigned int attr = fmt->ep_attr & USB_ENDPOINT_SYNCTYPE; + + p = find_implicit_fb_entry(chip, playback_implicit_fb_quirks, alts); + if (p) { + switch (p->type) { + case IMPLICIT_FB_GENERIC: + return add_generic_implicit_fb(chip, fmt, alts); + case IMPLICIT_FB_NONE: + return 0; /* No quirk */ + case IMPLICIT_FB_FIXED: + return add_implicit_fb_sync_ep(chip, fmt, p->ep_num, 0, + p->iface, NULL); + } + } + + /* Special handling for devices with capture quirks */ + p = find_implicit_fb_entry(chip, capture_implicit_fb_quirks, alts); + if (p) { + switch (p->type) { + case IMPLICIT_FB_FIXED: + return 0; /* no quirk */ + case IMPLICIT_FB_BOTH: + chip->quirk_flags |= QUIRK_FLAG_PLAYBACK_FIRST; + return add_generic_implicit_fb(chip, fmt, alts); + } + } + + /* Generic UAC2 implicit feedback */ + if (attr == USB_ENDPOINT_SYNC_ASYNC && + alts->desc.bInterfaceClass == USB_CLASS_AUDIO && + alts->desc.bInterfaceProtocol == UAC_VERSION_2 && + alts->desc.bNumEndpoints == 1) { + if (add_generic_uac2_implicit_fb(chip, fmt, + alts->desc.bInterfaceNumber + 1, + alts->desc.bAlternateSetting)) + return 1; + } + + /* Roland/BOSS implicit feedback with vendor spec class */ + if (USB_ID_VENDOR(chip->usb_id) == 0x0582) { + if (add_roland_implicit_fb(chip, fmt, alts) > 0) + return 1; + } + + /* Pioneer devices with vendor spec class */ + if (is_pioneer_implicit_fb(chip, alts)) { + chip->quirk_flags |= QUIRK_FLAG_PLAYBACK_FIRST; + return add_implicit_fb_sync_ep(chip, fmt, + get_endpoint(alts, 1)->bEndpointAddress, + 1, alts->desc.bInterfaceNumber, + alts); + } + + /* Try the generic implicit fb if available */ + if (chip->generic_implicit_fb || + (chip->quirk_flags & QUIRK_FLAG_GENERIC_IMPLICIT_FB)) + return add_generic_implicit_fb(chip, fmt, alts); + + /* No quirk */ + return 0; +} + +/* same for capture, but only handling FIXED entry */ +static int audioformat_capture_quirk(struct snd_usb_audio *chip, + struct audioformat *fmt, + struct usb_host_interface *alts) +{ + const struct snd_usb_implicit_fb_match *p; + + p = find_implicit_fb_entry(chip, capture_implicit_fb_quirks, alts); + if (p && (p->type == IMPLICIT_FB_FIXED || p->type == IMPLICIT_FB_BOTH)) + return add_implicit_fb_sync_ep(chip, fmt, p->ep_num, 0, + p->iface, NULL); + + /* Roland/BOSS need full-duplex streams */ + if (USB_ID_VENDOR(chip->usb_id) == 0x0582) { + if (add_roland_capture_quirk(chip, fmt, alts) > 0) + return 1; + } + + if (is_pioneer_implicit_fb(chip, alts)) + return 1; /* skip the quirk, also don't handle generic sync EP */ + return 0; +} + +/* + * Parse altset and set up implicit feedback endpoint on the audioformat + */ +int snd_usb_parse_implicit_fb_quirk(struct snd_usb_audio *chip, + struct audioformat *fmt, + struct usb_host_interface *alts) +{ + if (chip->quirk_flags & QUIRK_FLAG_SKIP_IMPLICIT_FB) + return 0; + if (fmt->endpoint & USB_DIR_IN) + return audioformat_capture_quirk(chip, fmt, alts); + else + return audioformat_implicit_fb_quirk(chip, fmt, alts); +} + +/* + * Return the score of matching two audioformats. + * Veto the audioformat if: + * - It has no channels for some reason. + * - Requested PCM format is not supported. + * - Requested sample rate is not supported. + */ +static int match_endpoint_audioformats(struct snd_usb_substream *subs, + const struct audioformat *fp, + int rate, int channels, + snd_pcm_format_t pcm_format) +{ + int i, score; + + if (fp->channels < 1) + return 0; + + if (!(fp->formats & pcm_format_to_bits(pcm_format))) + return 0; + + if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) { + if (rate < fp->rate_min || rate > fp->rate_max) + return 0; + } else { + for (i = 0; i < fp->nr_rates; i++) { + if (fp->rate_table[i] == rate) + break; + } + if (i >= fp->nr_rates) + return 0; + } + + score = 1; + if (fp->channels == channels) + score++; + + return score; +} + +static struct snd_usb_substream * +find_matching_substream(struct snd_usb_audio *chip, int stream, int ep_num, + int fmt_type) +{ + struct snd_usb_stream *as; + struct snd_usb_substream *subs; + + list_for_each_entry(as, &chip->pcm_list, list) { + subs = &as->substream[stream]; + if (as->fmt_type == fmt_type && subs->ep_num == ep_num) + return subs; + } + + return NULL; +} + +/* + * Return the audioformat that is suitable for the implicit fb + */ +const struct audioformat * +snd_usb_find_implicit_fb_sync_format(struct snd_usb_audio *chip, + const struct audioformat *target, + const struct snd_pcm_hw_params *params, + int stream, + bool *fixed_rate) +{ + struct snd_usb_substream *subs; + const struct audioformat *fp, *sync_fmt = NULL; + int score, high_score; + + /* Use the original audioformat as fallback for the shared altset */ + if (target->iface == target->sync_iface && + target->altsetting == target->sync_altsetting) + sync_fmt = target; + + subs = find_matching_substream(chip, stream, target->sync_ep, + target->fmt_type); + if (!subs) + goto end; + + high_score = 0; + list_for_each_entry(fp, &subs->fmt_list, list) { + score = match_endpoint_audioformats(subs, fp, + params_rate(params), + params_channels(params), + params_format(params)); + if (score > high_score) { + sync_fmt = fp; + high_score = score; + } + } + + end: + if (fixed_rate) + *fixed_rate = snd_usb_pcm_has_fixed_rate(subs); + return sync_fmt; +} + diff --git a/sound/usb/implicit.h b/sound/usb/implicit.h new file mode 100644 index 000000000000..7f1577b6c4d3 --- /dev/null +++ b/sound/usb/implicit.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef __USBAUDIO_IMPLICIT_H +#define __USBAUDIO_IMPLICIT_H + +int snd_usb_parse_implicit_fb_quirk(struct snd_usb_audio *chip, + struct audioformat *fmt, + struct usb_host_interface *alts); +const struct audioformat * +snd_usb_find_implicit_fb_sync_format(struct snd_usb_audio *chip, + const struct audioformat *target, + const struct snd_pcm_hw_params *params, + int stream, bool *fixed_rate); + +#endif /* __USBAUDIO_IMPLICIT_H */ diff --git a/sound/usb/line6/Kconfig b/sound/usb/line6/Kconfig index 39b400392d71..ab557e5eee9d 100644 --- a/sound/usb/line6/Kconfig +++ b/sound/usb/line6/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only config SND_USB_LINE6 tristate select SND_RAWMIDI diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index d8a14d769f48..9ef4faa006a0 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) */ #include <linux/slab.h> @@ -149,8 +145,6 @@ void line6_capture_check_period(struct snd_line6_pcm *line6pcm, int length) static void audio_in_callback(struct urb *urb) { int i, index, length = 0, shutdown = 0; - unsigned long flags; - struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context; line6pcm->in.last_frame = urb->start_frame; @@ -160,7 +154,7 @@ static void audio_in_callback(struct urb *urb) if (urb == line6pcm->in.urbs[index]) break; - spin_lock_irqsave(&line6pcm->in.lock, flags); + guard(spinlock_irqsave)(&line6pcm->in.lock); for (i = 0; i < LINE6_ISO_PACKETS; ++i) { char *fbuf; @@ -215,8 +209,6 @@ static void audio_in_callback(struct urb *urb) test_bit(LINE6_STREAM_PCM, &line6pcm->in.running)) line6_capture_check_period(line6pcm, length); } - - spin_unlock_irqrestore(&line6pcm->in.lock, flags); } /* open capture callback */ @@ -251,7 +243,6 @@ static int snd_line6_capture_close(struct snd_pcm_substream *substream) const struct snd_pcm_ops snd_line6_capture_ops = { .open = snd_line6_capture_open, .close = snd_line6_capture_close, - .ioctl = snd_pcm_lib_ioctl, .hw_params = snd_line6_hw_params, .hw_free = snd_line6_hw_free, .prepare = snd_line6_prepare, @@ -291,6 +282,8 @@ int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm) urb->interval = LINE6_ISO_INTERVAL; urb->error_count = 0; urb->complete = audio_in_callback; + if (usb_urb_ep_type_check(urb)) + return -EINVAL; } return 0; diff --git a/sound/usb/line6/capture.h b/sound/usb/line6/capture.h index b67ccc39fd25..90572dae134e 100644 --- a/sound/usb/line6/capture.h +++ b/sound/usb/line6/capture.h @@ -1,12 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) */ #ifndef CAPTURE_H diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index c1376bfdc90b..e97368c31417 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) */ #include <linux/kernel.h> @@ -24,7 +20,7 @@ #include "midi.h" #include "playback.h" -#define DRIVER_AUTHOR "Markus Grabner <grabner@icg.tugraz.at>" +#define DRIVER_AUTHOR "Markus Grabner <line6@grabner-graz.at>" #define DRIVER_DESC "Line 6 USB Driver" /* @@ -101,7 +97,7 @@ static void line6_stop_listen(struct usb_line6 *line6) /* Send raw message in pieces of wMaxPacketSize bytes. */ -static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, +int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, int size) { int i, done = 0; @@ -117,12 +113,12 @@ static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, retval = usb_interrupt_msg(line6->usbdev, usb_sndintpipe(line6->usbdev, properties->ep_ctrl_w), (char *)frag_buf, frag_size, - &partial, LINE6_TIMEOUT * HZ); + &partial, LINE6_TIMEOUT); } else { retval = usb_bulk_msg(line6->usbdev, usb_sndbulkpipe(line6->usbdev, properties->ep_ctrl_w), (char *)frag_buf, frag_size, - &partial, LINE6_TIMEOUT * HZ); + &partial, LINE6_TIMEOUT); } if (retval) { @@ -136,6 +132,7 @@ static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, return done; } +EXPORT_SYMBOL_GPL(line6_send_raw_message); /* Notification of completion of asynchronous request transmission. @@ -196,17 +193,6 @@ static int line6_send_raw_message_async_part(struct message *msg, } /* - Setup and start timer. -*/ -void line6_start_timer(struct timer_list *timer, unsigned long msecs, - void (*function)(struct timer_list *t)) -{ - timer->function = function; - mod_timer(timer, jiffies + msecs_to_jiffies(msecs)); -} -EXPORT_SYMBOL_GPL(line6_start_timer); - -/* Asynchronously send raw message. */ int line6_send_raw_message_async(struct usb_line6 *line6, const char *buffer, @@ -216,7 +202,7 @@ int line6_send_raw_message_async(struct usb_line6 *line6, const char *buffer, struct urb *urb; /* create message: */ - msg = kmalloc(sizeof(struct message), GFP_ATOMIC); + msg = kzalloc(sizeof(struct message), GFP_ATOMIC); if (msg == NULL) return -ENOMEM; @@ -306,21 +292,26 @@ static void line6_data_received(struct urb *urb) return; if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { - done = - line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length); + scoped_guard(spinlock_irqsave, &line6->line6midi->lock) { + done = + line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length); - if (done < urb->actual_length) { - line6_midibuf_ignore(mb, done); - dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n", - done, urb->actual_length); + if (done < urb->actual_length) { + line6_midibuf_ignore(mb, done); + dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n", + done, urb->actual_length); + } } for (;;) { - done = - line6_midibuf_read(mb, line6->buffer_message, - LINE6_MIDI_MESSAGE_MAXLEN); - - if (done == 0) + scoped_guard(spinlock_irqsave, &line6->line6midi->lock) { + done = + line6_midibuf_read(mb, line6->buffer_message, + LINE6_MIDI_MESSAGE_MAXLEN, + LINE6_MIDIBUF_READ_RX); + } + + if (done <= 0) break; line6->message_length = done; @@ -351,66 +342,63 @@ int line6_read_data(struct usb_line6 *line6, unsigned address, void *data, { struct usb_device *usbdev = line6->usbdev; int ret; - unsigned char len; + u8 len; unsigned count; if (address > 0xffff || datalen > 0xff) return -EINVAL; /* query the serial number: */ - ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, - (datalen << 8) | 0x21, address, - NULL, 0, LINE6_TIMEOUT * HZ); - - if (ret < 0) { + ret = usb_control_msg_send(usbdev, 0, 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + (datalen << 8) | 0x21, address, NULL, 0, + LINE6_TIMEOUT, GFP_KERNEL); + if (ret) { dev_err(line6->ifcdev, "read request failed (error %d)\n", ret); - return ret; + goto exit; } /* Wait for data length. We'll get 0xff until length arrives. */ for (count = 0; count < LINE6_READ_WRITE_MAX_RETRIES; count++) { mdelay(LINE6_READ_WRITE_STATUS_DELAY); - ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | - USB_DIR_IN, - 0x0012, 0x0000, &len, 1, - LINE6_TIMEOUT * HZ); - if (ret < 0) { + ret = usb_control_msg_recv(usbdev, 0, 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x0012, 0x0000, &len, 1, + LINE6_TIMEOUT, GFP_KERNEL); + if (ret) { dev_err(line6->ifcdev, "receive length failed (error %d)\n", ret); - return ret; + goto exit; } if (len != 0xff) break; } + ret = -EIO; if (len == 0xff) { dev_err(line6->ifcdev, "read failed after %d retries\n", count); - return -EIO; + goto exit; } else if (len != datalen) { /* should be equal or something went wrong */ dev_err(line6->ifcdev, "length mismatch (expected %d, got %d)\n", - (int)datalen, (int)len); - return -EIO; + (int)datalen, len); + goto exit; } /* receive the result: */ - ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x0013, 0x0000, data, datalen, - LINE6_TIMEOUT * HZ); - - if (ret < 0) { + ret = usb_control_msg_recv(usbdev, 0, 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x0013, 0x0000, data, datalen, LINE6_TIMEOUT, + GFP_KERNEL); + if (ret) dev_err(line6->ifcdev, "read failed (error %d)\n", ret); - return ret; - } - return 0; +exit: + return ret; } EXPORT_SYMBOL_GPL(line6_read_data); @@ -422,53 +410,54 @@ int line6_write_data(struct usb_line6 *line6, unsigned address, void *data, { struct usb_device *usbdev = line6->usbdev; int ret; - unsigned char status; + unsigned char *status; int count; if (address > 0xffff || datalen > 0xffff) return -EINVAL; - ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, - 0x0022, address, data, datalen, - LINE6_TIMEOUT * HZ); + status = kmalloc(1, GFP_KERNEL); + if (!status) + return -ENOMEM; - if (ret < 0) { + ret = usb_control_msg_send(usbdev, 0, 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + 0x0022, address, data, datalen, LINE6_TIMEOUT, + GFP_KERNEL); + if (ret) { dev_err(line6->ifcdev, "write request failed (error %d)\n", ret); - return ret; + goto exit; } for (count = 0; count < LINE6_READ_WRITE_MAX_RETRIES; count++) { mdelay(LINE6_READ_WRITE_STATUS_DELAY); - ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), - 0x67, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | - USB_DIR_IN, - 0x0012, 0x0000, - &status, 1, LINE6_TIMEOUT * HZ); - - if (ret < 0) { + ret = usb_control_msg_recv(usbdev, 0, 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x0012, 0x0000, status, 1, LINE6_TIMEOUT, + GFP_KERNEL); + if (ret) { dev_err(line6->ifcdev, "receiving status failed (error %d)\n", ret); - return ret; + goto exit; } - if (status != 0xff) + if (*status != 0xff) break; } - if (status == 0xff) { + if (*status == 0xff) { dev_err(line6->ifcdev, "write failed after %d retries\n", count); - return -EIO; - } else if (status != 0) { + ret = -EIO; + } else if (*status != 0) { dev_err(line6->ifcdev, "write failed (error %d)\n", ret); - return -EIO; + ret = -EIO; } - - return 0; +exit: + kfree(status); + return ret; } EXPORT_SYMBOL_GPL(line6_write_data); @@ -555,6 +544,7 @@ static int line6_hwdep_open(struct snd_hwdep *hw, struct file *file) /* NOTE: hwdep layer provides atomicity here */ line6->messages.active = 1; + line6->messages.nonblock = file->f_flags & O_NONBLOCK ? 1 : 0; return 0; } @@ -584,6 +574,9 @@ line6_hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, while (kfifo_len(&line6->messages.fifo) == 0) { mutex_unlock(&line6->messages.read_lock); + if (line6->messages.nonblock) + return -EAGAIN; + rv = wait_event_interruptible( line6->messages.wait_queue, kfifo_len(&line6->messages.fifo) != 0); @@ -631,11 +624,23 @@ line6_hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count, return rv; } +static __poll_t +line6_hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) +{ + struct usb_line6 *line6 = hwdep->private_data; + + poll_wait(file, &line6->messages.wait_queue, wait); + + guard(mutex)(&line6->messages.read_lock); + return kfifo_len(&line6->messages.fifo) == 0 ? 0 : EPOLLIN | EPOLLRDNORM; +} + static const struct snd_hwdep_ops hwdep_ops = { .open = line6_hwdep_open, .release = line6_hwdep_release, .read = line6_hwdep_read, .write = line6_hwdep_write, + .poll = line6_hwdep_poll, }; /* Insert into circular buffer */ @@ -668,7 +673,7 @@ static int line6_hwdep_init(struct usb_line6 *line6) err = snd_hwdep_new(line6->card, "config", 0, &hwdep); if (err < 0) goto end; - strcpy(hwdep->name, "config"); + strscpy(hwdep->name, "config"); hwdep->iface = SNDRV_HWDEP_IFACE_LINE6; hwdep->ops = hwdep_ops; hwdep->private_data = line6; @@ -683,7 +688,7 @@ static int line6_init_cap_control(struct usb_line6 *line6) int ret; /* initialize USB buffers: */ - line6->buffer_listen = kmalloc(LINE6_BUFSIZE_LISTEN, GFP_KERNEL); + line6->buffer_listen = kzalloc(LINE6_BUFSIZE_LISTEN, GFP_KERNEL); if (!line6->buffer_listen) return -ENOMEM; @@ -692,9 +697,13 @@ static int line6_init_cap_control(struct usb_line6 *line6) return -ENOMEM; if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { - line6->buffer_message = kmalloc(LINE6_MIDI_MESSAGE_MAXLEN, GFP_KERNEL); + line6->buffer_message = kzalloc(LINE6_MIDI_MESSAGE_MAXLEN, GFP_KERNEL); if (!line6->buffer_message) return -ENOMEM; + + ret = line6_init_midi(line6); + if (ret < 0) + return ret; } else { ret = line6_hwdep_init(line6); if (ret < 0) @@ -710,6 +719,15 @@ static int line6_init_cap_control(struct usb_line6 *line6) return 0; } +static void line6_startup_work(struct work_struct *work) +{ + struct usb_line6 *line6 = + container_of(work, struct usb_line6, startup_work.work); + + if (line6->startup) + line6->startup(line6); +} + /* Probe USB device. */ @@ -745,10 +763,11 @@ int line6_probe(struct usb_interface *interface, line6->properties = properties; line6->usbdev = usbdev; line6->ifcdev = &interface->dev; + INIT_DELAYED_WORK(&line6->startup_work, line6_startup_work); - strcpy(card->id, properties->id); - strcpy(card->driver, driver_name); - strcpy(card->shortname, properties->name); + strscpy(card->id, properties->id); + strscpy(card->driver, driver_name); + strscpy(card->shortname, properties->name); sprintf(card->longname, "Line 6 %s at USB %s", properties->name, dev_name(line6->ifcdev)); card->private_free = line6_destruct; @@ -815,6 +834,8 @@ void line6_disconnect(struct usb_interface *interface) if (WARN_ON(usbdev != line6->usbdev)) return; + cancel_delayed_work_sync(&line6->startup_work); + if (line6->urb_listen != NULL) line6_stop_listen(line6); @@ -849,10 +870,8 @@ int line6_suspend(struct usb_interface *interface, pm_message_t message) if (line6->properties->capabilities & LINE6_CAP_CONTROL) line6_stop_listen(line6); - if (line6pcm != NULL) { - snd_pcm_suspend_all(line6pcm->pcm); + if (line6pcm != NULL) line6pcm->flags = 0; - } return 0; } diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 61425597eb61..5736ad4256a5 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -1,12 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) */ #ifndef DRIVER_H @@ -31,7 +27,7 @@ #define LINE6_FALLBACK_INTERVAL 10 #define LINE6_FALLBACK_MAXPACKETSIZE 16 -#define LINE6_TIMEOUT 1 +#define LINE6_TIMEOUT 1000 #define LINE6_BUFSIZE_LISTEN 64 #define LINE6_MIDI_MESSAGE_MAXLEN 256 @@ -68,17 +64,10 @@ #define LINE6_CHANNEL_MASK 0x0f -#define CHECK_STARTUP_PROGRESS(x, n) \ -do { \ - if ((x) >= (n)) \ - return; \ - x = (n); \ -} while (0) - extern const unsigned char line6_midi_id[3]; -static const int SYSEX_DATA_OFS = sizeof(line6_midi_id) + 3; -static const int SYSEX_EXTRA_SIZE = sizeof(line6_midi_id) + 4; +#define SYSEX_DATA_OFS (sizeof(line6_midi_id) + 3) +#define SYSEX_EXTRA_SIZE (sizeof(line6_midi_id) + 4) /* Common properties of Line 6 devices. @@ -119,6 +108,8 @@ enum { LINE6_CAP_CONTROL_MIDI = 1 << 4, /* device provides low-level information */ LINE6_CAP_CONTROL_INFO = 1 << 5, + /* device provides hardware monitoring volume control */ + LINE6_CAP_HWMON_CTL = 1 << 6, }; /* @@ -174,15 +165,20 @@ struct usb_line6 { struct mutex read_lock; wait_queue_head_t wait_queue; unsigned int active:1; + unsigned int nonblock:1; STRUCT_KFIFO_REC_2(LINE6_BUFSIZE_LISTEN * LINE6_RAW_MESSAGES_MAXCOUNT) fifo; } messages; + /* Work for delayed PCM startup */ + struct delayed_work startup_work; + /* If MIDI is supported, buffer_message contains the pre-processed data; * otherwise the data is only in urb_listen (buffer_incoming). */ void (*process_message)(struct usb_line6 *); void (*disconnect)(struct usb_line6 *line6); + void (*startup)(struct usb_line6 *line6); }; extern char *line6_alloc_sysex_buffer(struct usb_line6 *line6, int code1, @@ -191,14 +187,12 @@ extern int line6_read_data(struct usb_line6 *line6, unsigned address, void *data, unsigned datalen); extern int line6_read_serial_number(struct usb_line6 *line6, u32 *serial_number); +extern int line6_send_raw_message(struct usb_line6 *line6, + const char *buffer, int size); extern int line6_send_raw_message_async(struct usb_line6 *line6, const char *buffer, int size); extern int line6_send_sysex_message(struct usb_line6 *line6, const char *buffer, int size); -extern ssize_t line6_set_raw(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count); -extern void line6_start_timer(struct timer_list *timer, unsigned long msecs, - void (*function)(struct timer_list *t)); extern int line6_version_request_async(struct usb_line6 *line6); extern int line6_write_data(struct usb_line6 *line6, unsigned address, void *data, unsigned datalen); diff --git a/sound/usb/line6/midi.c b/sound/usb/line6/midi.c index e2cf55c53ea8..4731293728e6 100644 --- a/sound/usb/line6/midi.c +++ b/sound/usb/line6/midi.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) */ #include <linux/slab.h> @@ -48,7 +44,8 @@ static void line6_midi_transmit(struct snd_rawmidi_substream *substream) int req, done; for (;;) { - req = min(line6_midibuf_bytes_free(mb), line6->max_packet_size); + req = min3(line6_midibuf_bytes_free(mb), line6->max_packet_size, + LINE6_FALLBACK_MAXPACKETSIZE); done = snd_rawmidi_transmit_peek(substream, chunk, req); if (done == 0) @@ -60,7 +57,8 @@ static void line6_midi_transmit(struct snd_rawmidi_substream *substream) for (;;) { done = line6_midibuf_read(mb, chunk, - LINE6_FALLBACK_MAXPACKETSIZE); + LINE6_FALLBACK_MAXPACKETSIZE, + LINE6_MIDIBUF_READ_TX); if (done == 0) break; @@ -74,7 +72,6 @@ static void line6_midi_transmit(struct snd_rawmidi_substream *substream) */ static void midi_sent(struct urb *urb) { - unsigned long flags; int status; int num; struct usb_line6 *line6 = (struct usb_line6 *)urb->context; @@ -86,7 +83,7 @@ static void midi_sent(struct urb *urb) if (status == -ESHUTDOWN) return; - spin_lock_irqsave(&line6->line6midi->lock, flags); + guard(spinlock_irqsave)(&line6->line6midi->lock); num = --line6->line6midi->num_active_send_urbs; if (num == 0) { @@ -96,8 +93,6 @@ static void midi_sent(struct urb *urb) if (num == 0) wake_up(&line6->line6midi->send_wait); - - spin_unlock_irqrestore(&line6->line6midi->lock, flags); } /* @@ -160,17 +155,14 @@ static int line6_midi_output_close(struct snd_rawmidi_substream *substream) static void line6_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) { - unsigned long flags; struct usb_line6 *line6 = line6_rawmidi_substream_midi(substream)->line6; line6->line6midi->substream_transmit = substream; - spin_lock_irqsave(&line6->line6midi->lock, flags); + guard(spinlock_irqsave)(&line6->line6midi->lock); if (line6->line6midi->num_active_send_urbs == 0) line6_midi_transmit(substream); - - spin_unlock_irqrestore(&line6->line6midi->lock, flags); } static void line6_midi_output_drain(struct snd_rawmidi_substream *substream) @@ -230,8 +222,8 @@ static int snd_line6_new_midi(struct usb_line6 *line6, return err; rmidi = *rmidi_ret; - strcpy(rmidi->id, line6->properties->id); - strcpy(rmidi->name, line6->properties->name); + strscpy(rmidi->id, line6->properties->id); + strscpy(rmidi->name, line6->properties->name); rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | diff --git a/sound/usb/line6/midi.h b/sound/usb/line6/midi.h index cf82d69e2747..3409c742c173 100644 --- a/sound/usb/line6/midi.h +++ b/sound/usb/line6/midi.h @@ -1,12 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) */ #ifndef MIDI_H diff --git a/sound/usb/line6/midibuf.c b/sound/usb/line6/midibuf.c index 36a610ba342e..57fca134b337 100644 --- a/sound/usb/line6/midibuf.c +++ b/sound/usb/line6/midibuf.c @@ -1,18 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) */ #include <linux/slab.h> #include "midibuf.h" + static int midibuf_message_length(unsigned char code) { int message_length; @@ -24,12 +21,7 @@ static int midibuf_message_length(unsigned char code) message_length = length[(code >> 4) - 8]; } else { - /* - Note that according to the MIDI specification 0xf2 is - the "Song Position Pointer", but this is used by Line 6 - to send sysex messages to the host. - */ - static const int length[] = { -1, 2, -1, 2, -1, -1, 1, 1, 1, 1, + static const int length[] = { -1, 2, 2, 2, -1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1 }; message_length = length[code & 0x0f]; @@ -129,7 +121,7 @@ int line6_midibuf_write(struct midi_buffer *this, unsigned char *data, } int line6_midibuf_read(struct midi_buffer *this, unsigned char *data, - int length) + int length, int read_type) { int bytes_used; int length1, length2; @@ -152,9 +144,22 @@ int line6_midibuf_read(struct midi_buffer *this, unsigned char *data, length1 = this->size - this->pos_read; - /* check MIDI command length */ command = this->buf[this->pos_read]; + /* + PODxt always has status byte lower nibble set to 0010, + when it means to send 0000, so we correct if here so + that control/program changes come on channel 1 and + sysex message status byte is correct + */ + if (read_type == LINE6_MIDIBUF_READ_RX) { + if (command == 0xb2 || command == 0xc2 || command == 0xf2) { + unsigned char fixed = command & 0xf0; + this->buf[this->pos_read] = fixed; + command = fixed; + } + } + /* check MIDI command length */ if (command & 0x80) { midi_length = midibuf_message_length(command); this->command_prev = command; @@ -163,7 +168,7 @@ int line6_midibuf_read(struct midi_buffer *this, unsigned char *data, int midi_length_prev = midibuf_message_length(this->command_prev); - if (midi_length_prev > 0) { + if (midi_length_prev > 1) { midi_length = midi_length_prev - 1; repeat = 1; } else diff --git a/sound/usb/line6/midibuf.h b/sound/usb/line6/midibuf.h index 6ea21ffb6627..1dae5fac9dde 100644 --- a/sound/usb/line6/midibuf.h +++ b/sound/usb/line6/midibuf.h @@ -1,17 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) */ #ifndef MIDIBUF_H #define MIDIBUF_H +#define LINE6_MIDIBUF_READ_TX 0 +#define LINE6_MIDIBUF_READ_RX 1 + struct midi_buffer { unsigned char *buf; int size; @@ -27,7 +26,7 @@ extern void line6_midibuf_destroy(struct midi_buffer *mb); extern int line6_midibuf_ignore(struct midi_buffer *mb, int length); extern int line6_midibuf_init(struct midi_buffer *mb, int size, int split); extern int line6_midibuf_read(struct midi_buffer *mb, unsigned char *data, - int length); + int length, int read_type); extern void line6_midibuf_reset(struct midi_buffer *mb); extern int line6_midibuf_write(struct midi_buffer *mb, unsigned char *data, int length); diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 72c6f8e82a7e..f61d9f6cf754 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) */ #include <linux/slab.h> @@ -186,11 +182,10 @@ static void line6_buffer_release(struct snd_line6_pcm *line6pcm, static int line6_stream_start(struct snd_line6_pcm *line6pcm, int direction, int type) { - unsigned long flags; struct line6_pcm_stream *pstr = get_stream(line6pcm, direction); int ret = 0; - spin_lock_irqsave(&pstr->lock, flags); + guard(spinlock_irqsave)(&pstr->lock); if (!test_and_set_bit(type, &pstr->running) && !(pstr->active_urbs || pstr->unlink_urbs)) { pstr->count = 0; @@ -203,7 +198,6 @@ static int line6_stream_start(struct snd_line6_pcm *line6pcm, int direction, if (ret < 0) clear_bit(type, &pstr->running); - spin_unlock_irqrestore(&pstr->lock, flags); return ret; } @@ -211,21 +205,20 @@ static int line6_stream_start(struct snd_line6_pcm *line6pcm, int direction, static void line6_stream_stop(struct snd_line6_pcm *line6pcm, int direction, int type) { - unsigned long flags; struct line6_pcm_stream *pstr = get_stream(line6pcm, direction); - spin_lock_irqsave(&pstr->lock, flags); - clear_bit(type, &pstr->running); - if (!pstr->running) { - spin_unlock_irqrestore(&pstr->lock, flags); - line6_unlink_audio_urbs(line6pcm, pstr); - spin_lock_irqsave(&pstr->lock, flags); - if (direction == SNDRV_PCM_STREAM_CAPTURE) { - line6pcm->prev_fbuf = NULL; - line6pcm->prev_fsize = 0; - } + scoped_guard(spinlock_irqsave, &pstr->lock) { + clear_bit(type, &pstr->running); + if (pstr->running) + return; + } + + line6_unlink_audio_urbs(line6pcm, pstr); + if (direction == SNDRV_PCM_STREAM_CAPTURE) { + guard(spinlock_irqsave)(&pstr->lock); + line6pcm->prev_fbuf = NULL; + line6pcm->prev_fsize = 0; } - spin_unlock_irqrestore(&pstr->lock, flags); } /* common PCM trigger callback */ @@ -299,6 +292,28 @@ snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream) return pstr->pos_done; } +/* Stop and release duplex streams */ +static void __line6_pcm_release(struct snd_line6_pcm *line6pcm, int type) +{ + struct line6_pcm_stream *pstr; + int dir; + + for (dir = 0; dir < 2; dir++) + line6_stream_stop(line6pcm, dir, type); + for (dir = 0; dir < 2; dir++) { + pstr = get_stream(line6pcm, dir); + line6_buffer_release(line6pcm, pstr, type); + } +} + +/* Stop and release duplex streams */ +void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type) +{ + guard(mutex)(&line6pcm->state_mutex); + __line6_pcm_release(line6pcm, type); +} +EXPORT_SYMBOL_GPL(line6_pcm_release); + /* Acquire and optionally start duplex streams: * type is either LINE6_STREAM_IMPULSE or LINE6_STREAM_MONITOR */ @@ -308,7 +323,7 @@ int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type, bool start) int ret = 0, dir; /* TODO: We should assert SNDRV_PCM_STREAM_PLAYBACK/CAPTURE == 0/1 */ - mutex_lock(&line6pcm->state_mutex); + guard(mutex)(&line6pcm->state_mutex); for (dir = 0; dir < 2; dir++) { pstr = get_stream(line6pcm, dir); ret = line6_buffer_acquire(line6pcm, pstr, dir, type); @@ -325,30 +340,12 @@ int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type, bool start) } } error: - mutex_unlock(&line6pcm->state_mutex); if (ret < 0) - line6_pcm_release(line6pcm, type); + __line6_pcm_release(line6pcm, type); return ret; } EXPORT_SYMBOL_GPL(line6_pcm_acquire); -/* Stop and release duplex streams */ -void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type) -{ - struct line6_pcm_stream *pstr; - int dir; - - mutex_lock(&line6pcm->state_mutex); - for (dir = 0; dir < 2; dir++) - line6_stream_stop(line6pcm, dir, type); - for (dir = 0; dir < 2; dir++) { - pstr = get_stream(line6pcm, dir); - line6_buffer_release(line6pcm, pstr, type); - } - mutex_unlock(&line6pcm->state_mutex); -} -EXPORT_SYMBOL_GPL(line6_pcm_release); - /* common PCM hw_params callback */ int snd_line6_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) @@ -357,23 +354,14 @@ int snd_line6_hw_params(struct snd_pcm_substream *substream, struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream); - mutex_lock(&line6pcm->state_mutex); + guard(mutex)(&line6pcm->state_mutex); ret = line6_buffer_acquire(line6pcm, pstr, substream->stream, LINE6_STREAM_PCM); if (ret < 0) - goto error; - - ret = snd_pcm_lib_malloc_pages(substream, - params_buffer_bytes(hw_params)); - if (ret < 0) { - line6_buffer_release(line6pcm, pstr, LINE6_STREAM_PCM); - goto error; - } + return ret; pstr->period = params_period_bytes(hw_params); - error: - mutex_unlock(&line6pcm->state_mutex); - return ret; + return 0; } /* common PCM hw_free callback */ @@ -382,10 +370,9 @@ int snd_line6_hw_free(struct snd_pcm_substream *substream) struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream); - mutex_lock(&line6pcm->state_mutex); + guard(mutex)(&line6pcm->state_mutex); line6_buffer_release(line6pcm, pstr, LINE6_STREAM_PCM); - mutex_unlock(&line6pcm->state_mutex); - return snd_pcm_lib_free_pages(substream); + return 0; } @@ -497,7 +484,7 @@ static int snd_line6_new_pcm(struct usb_line6 *line6, struct snd_pcm **pcm_ret) if (err < 0) return err; pcm = *pcm_ret; - strcpy(pcm->name, line6->properties->name); + strscpy(pcm->name, line6->properties->name); /* set operators */ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, @@ -505,10 +492,8 @@ static int snd_line6_new_pcm(struct usb_line6 *line6, struct snd_pcm **pcm_ret) snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_line6_capture_ops); /* pre-allocation of buffers */ - snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data - (GFP_KERNEL), 64 * 1024, - 128 * 1024); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + NULL, 64 * 1024, 128 * 1024); return 0; } @@ -554,13 +539,6 @@ int line6_init_pcm(struct usb_line6 *line6, line6pcm->volume_monitor = 255; line6pcm->line6 = line6; - line6pcm->max_packet_size_in = - usb_maxpacket(line6->usbdev, - usb_rcvisocpipe(line6->usbdev, ep_read), 0); - line6pcm->max_packet_size_out = - usb_maxpacket(line6->usbdev, - usb_sndisocpipe(line6->usbdev, ep_write), 1); - spin_lock_init(&line6pcm->out.lock); spin_lock_init(&line6pcm->in.lock); line6pcm->impulse_period = LINE6_IMPULSE_DEFAULT_PERIOD; @@ -570,6 +548,18 @@ int line6_init_pcm(struct usb_line6 *line6, pcm->private_data = line6pcm; pcm->private_free = line6_cleanup_pcm; + line6pcm->max_packet_size_in = + usb_maxpacket(line6->usbdev, + usb_rcvisocpipe(line6->usbdev, ep_read)); + line6pcm->max_packet_size_out = + usb_maxpacket(line6->usbdev, + usb_sndisocpipe(line6->usbdev, ep_write)); + if (!line6pcm->max_packet_size_in || !line6pcm->max_packet_size_out) { + dev_err(line6pcm->line6->ifcdev, + "cannot get proper max packet size\n"); + return -EINVAL; + } + err = line6_create_audio_out_urbs(line6pcm); if (err < 0) return err; @@ -596,7 +586,7 @@ int snd_line6_prepare(struct snd_pcm_substream *substream) struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream); - mutex_lock(&line6pcm->state_mutex); + guard(mutex)(&line6pcm->state_mutex); if (!pstr->running) line6_wait_clear_audio_urbs(line6pcm, pstr); @@ -610,6 +600,5 @@ int snd_line6_prepare(struct snd_pcm_substream *substream) line6pcm->in.bytes = 0; } - mutex_unlock(&line6pcm->state_mutex); return 0; } diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index bb0c9cbf2a78..a15913bf2a7a 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -1,12 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) */ /* diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index dec89d2beb57..9f26f66e6792 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) */ #include <linux/slab.h> @@ -396,7 +392,6 @@ static int snd_line6_playback_close(struct snd_pcm_substream *substream) const struct snd_pcm_ops snd_line6_playback_ops = { .open = snd_line6_playback_open, .close = snd_line6_playback_close, - .ioctl = snd_pcm_lib_ioctl, .hw_params = snd_line6_hw_params, .hw_free = snd_line6_hw_free, .prepare = snd_line6_prepare, @@ -436,6 +431,8 @@ int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm) urb->interval = LINE6_ISO_INTERVAL; urb->error_count = 0; urb->complete = audio_out_callback; + if (usb_urb_ep_type_check(urb)) + return -EINVAL; } return 0; diff --git a/sound/usb/line6/playback.h b/sound/usb/line6/playback.h index d8d3b8a07a72..2e0ec0ade0bf 100644 --- a/sound/usb/line6/playback.h +++ b/sound/usb/line6/playback.h @@ -1,12 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) */ #ifndef PLAYBACK_H diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c index 020c81818951..6f948c3e8f9e 100644 --- a/sound/usb/line6/pod.c +++ b/sound/usb/line6/pod.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) */ #include <linux/slab.h> @@ -39,11 +35,9 @@ Stages of POD startup procedure */ enum { - POD_STARTUP_INIT = 1, POD_STARTUP_VERSIONREQ, - POD_STARTUP_WORKQUEUE, POD_STARTUP_SETUP, - POD_STARTUP_LAST = POD_STARTUP_SETUP - 1 + POD_STARTUP_DONE, }; enum { @@ -63,12 +57,6 @@ struct usb_line6_pod { /* Instrument monitor level */ int monitor_level; - /* Timer for device initialization */ - struct timer_list startup_timer; - - /* Work handler for device initialization */ - struct work_struct startup_work; - /* Current progress in startup procedure */ int startup_progress; @@ -82,6 +70,8 @@ struct usb_line6_pod { int device_id; }; +#define line6_to_pod(x) container_of(x, struct usb_line6_pod, line6) + #define POD_SYSEX_CODE 3 /* *INDENT-OFF* */ @@ -120,7 +110,7 @@ enum { POD_BUSY_MIDISEND }; -static struct snd_ratden pod_ratden = { +static const struct snd_ratden pod_ratden = { .num_min = 78125, .num_max = 78125, .num_step = 1, @@ -169,14 +159,11 @@ static struct line6_pcm_properties pod_pcm_properties = { .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ }; + static const char pod_version_header[] = { - 0xf2, 0x7e, 0x7f, 0x06, 0x02 + 0xf0, 0x7e, 0x7f, 0x06, 0x02 }; -/* forward declarations: */ -static void pod_startup2(struct timer_list *t); -static void pod_startup3(struct usb_line6_pod *pod); - static char *pod_alloc_sysex_buffer(struct usb_line6_pod *pod, int code, int size) { @@ -189,14 +176,17 @@ static char *pod_alloc_sysex_buffer(struct usb_line6_pod *pod, int code, */ static void line6_pod_process_message(struct usb_line6 *line6) { - struct usb_line6_pod *pod = (struct usb_line6_pod *) line6; + struct usb_line6_pod *pod = line6_to_pod(line6); const unsigned char *buf = pod->line6.buffer_message; if (memcmp(buf, pod_version_header, sizeof(pod_version_header)) == 0) { pod->firmware_version = buf[13] * 100 + buf[14] * 10 + buf[15]; pod->device_id = ((int)buf[8] << 16) | ((int)buf[9] << 8) | (int) buf[10]; - pod_startup3(pod); + if (pod->startup_progress == POD_STARTUP_VERSIONREQ) { + pod->startup_progress = POD_STARTUP_SETUP; + schedule_delayed_work(&line6->startup_work, 0); + } return; } @@ -246,7 +236,7 @@ static ssize_t serial_number_show(struct device *dev, struct snd_card *card = dev_to_snd_card(dev); struct usb_line6_pod *pod = card->private_data; - return sprintf(buf, "%u\n", pod->serial_number); + return sysfs_emit(buf, "%u\n", pod->serial_number); } /* @@ -258,8 +248,8 @@ static ssize_t firmware_version_show(struct device *dev, struct snd_card *card = dev_to_snd_card(dev); struct usb_line6_pod *pod = card->private_data; - return sprintf(buf, "%d.%02d\n", pod->firmware_version / 100, - pod->firmware_version % 100); + return sysfs_emit(buf, "%d.%02d\n", pod->firmware_version / 100, + pod->firmware_version % 100); } /* @@ -271,7 +261,7 @@ static ssize_t device_id_show(struct device *dev, struct snd_card *card = dev_to_snd_card(dev); struct usb_line6_pod *pod = card->private_data; - return sprintf(buf, "%d\n", pod->device_id); + return sysfs_emit(buf, "%d\n", pod->device_id); } /* @@ -281,46 +271,27 @@ static ssize_t device_id_show(struct device *dev, context). After the last one has finished, the device is ready to use. */ -static void pod_startup1(struct usb_line6_pod *pod) -{ - CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_INIT); - - /* delay startup procedure: */ - line6_start_timer(&pod->startup_timer, POD_STARTUP_DELAY, pod_startup2); -} - -static void pod_startup2(struct timer_list *t) -{ - struct usb_line6_pod *pod = from_timer(pod, t, startup_timer); - struct usb_line6 *line6 = &pod->line6; - - CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_VERSIONREQ); - - /* request firmware version: */ - line6_version_request_async(line6); -} - -static void pod_startup3(struct usb_line6_pod *pod) -{ - CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_WORKQUEUE); - - /* schedule work for global work queue: */ - schedule_work(&pod->startup_work); -} - -static void pod_startup4(struct work_struct *work) +static void pod_startup(struct usb_line6 *line6) { - struct usb_line6_pod *pod = - container_of(work, struct usb_line6_pod, startup_work); - struct usb_line6 *line6 = &pod->line6; - - CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_SETUP); - - /* serial number: */ - line6_read_serial_number(&pod->line6, &pod->serial_number); - - /* ALSA audio interface: */ - snd_card_register(line6->card); + struct usb_line6_pod *pod = line6_to_pod(line6); + + switch (pod->startup_progress) { + case POD_STARTUP_VERSIONREQ: + /* request firmware version: */ + line6_version_request_async(line6); + break; + case POD_STARTUP_SETUP: + /* serial number: */ + line6_read_serial_number(&pod->line6, &pod->serial_number); + + /* ALSA audio interface: */ + if (snd_card_register(line6->card)) + dev_err(line6->ifcdev, "Failed to register POD card.\n"); + pod->startup_progress = POD_STARTUP_DONE; + break; + default: + break; + } } /* POD special files: */ @@ -356,7 +327,7 @@ static int snd_pod_control_monitor_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); - struct usb_line6_pod *pod = (struct usb_line6_pod *)line6pcm->line6; + struct usb_line6_pod *pod = line6_to_pod(line6pcm->line6); ucontrol->value.integer.value[0] = pod->monitor_level; return 0; @@ -367,7 +338,7 @@ static int snd_pod_control_monitor_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); - struct usb_line6_pod *pod = (struct usb_line6_pod *)line6pcm->line6; + struct usb_line6_pod *pod = line6_to_pod(line6pcm->line6); if (ucontrol->value.integer.value[0] == pod->monitor_level) return 0; @@ -390,41 +361,22 @@ static const struct snd_kcontrol_new pod_control_monitor = { }; /* - POD device disconnected. -*/ -static void line6_pod_disconnect(struct usb_line6 *line6) -{ - struct usb_line6_pod *pod = (struct usb_line6_pod *)line6; - - del_timer_sync(&pod->startup_timer); - cancel_work_sync(&pod->startup_work); -} - -/* Try to init POD device. */ static int pod_init(struct usb_line6 *line6, const struct usb_device_id *id) { int err; - struct usb_line6_pod *pod = (struct usb_line6_pod *) line6; + struct usb_line6_pod *pod = line6_to_pod(line6); line6->process_message = line6_pod_process_message; - line6->disconnect = line6_pod_disconnect; - - timer_setup(&pod->startup_timer, NULL, 0); - INIT_WORK(&pod->startup_work, pod_startup4); + line6->startup = pod_startup; /* create sysfs entries: */ err = snd_card_add_dev_attr(line6->card, &pod_dev_attr_group); if (err < 0) return err; - /* initialize MIDI subsystem: */ - err = line6_init_midi(line6); - if (err < 0) - return err; - /* initialize PCM subsystem: */ err = line6_init_pcm(line6, &pod_pcm_properties); if (err < 0) @@ -446,7 +398,8 @@ static int pod_init(struct usb_line6 *line6, pod->monitor_level = POD_SYSTEM_INVALID; /* initiate startup procedure: */ - pod_startup1(pod); + schedule_delayed_work(&line6->startup_work, + msecs_to_jiffies(POD_STARTUP_DELAY)); } return 0; diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 36ed9c85c0eb..ea1324c22f46 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -1,20 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Line 6 Pod HD * * Copyright (C) 2011 Stefan Hajnoczi <stefanha@gmail.com> * Copyright (C) 2015 Andrej Krutak <dev@andree.sk> * Copyright (C) 2017 Hans P. Moller <hmoller@uc.cl> - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * */ #include <linux/usb.h> #include <linux/slab.h> #include <linux/module.h> #include <sound/core.h> +#include <sound/control.h> #include <sound/pcm.h> #include "driver.h" @@ -22,48 +19,34 @@ #define PODHD_STARTUP_DELAY 500 -/* - * Stages of POD startup procedure - */ -enum { - PODHD_STARTUP_INIT = 1, - PODHD_STARTUP_SCHEDULE_WORKQUEUE, - PODHD_STARTUP_SETUP, - PODHD_STARTUP_LAST = PODHD_STARTUP_SETUP - 1 -}; - enum { LINE6_PODHD300, LINE6_PODHD400, - LINE6_PODHD500_0, - LINE6_PODHD500_1, + LINE6_PODHD500, LINE6_PODX3, LINE6_PODX3LIVE, LINE6_PODHD500X, - LINE6_PODHDDESKTOP + LINE6_PODHDDESKTOP, + LINE6_PODHDPROX, }; struct usb_line6_podhd { /* Generic Line 6 USB data */ struct usb_line6 line6; - /* Timer for device initialization */ - struct timer_list startup_timer; - - /* Work handler for device initialization */ - struct work_struct startup_work; - - /* Current progress in startup procedure */ - int startup_progress; - /* Serial number of device */ u32 serial_number; /* Firmware version */ int firmware_version; + + /* Monitor level */ + int monitor_level; }; -static struct snd_ratden podhd_ratden = { +#define line6_to_podhd(x) container_of(x, struct usb_line6_podhd, line6) + +static const struct snd_ratden podhd_ratden = { .num_min = 48000, .num_max = 48000, .num_step = 1, @@ -158,17 +141,13 @@ static struct line6_pcm_properties podx3_pcm_properties = { }; static struct usb_driver podhd_driver; -static void podhd_startup_start_workqueue(struct timer_list *t); -static void podhd_startup_workqueue(struct work_struct *work); -static int podhd_startup_finalize(struct usb_line6_podhd *pod); - static ssize_t serial_number_show(struct device *dev, struct device_attribute *attr, char *buf) { struct snd_card *card = dev_to_snd_card(dev); struct usb_line6_podhd *pod = card->private_data; - return sprintf(buf, "%u\n", pod->serial_number); + return sysfs_emit(buf, "%u\n", pod->serial_number); } static ssize_t firmware_version_show(struct device *dev, @@ -177,7 +156,7 @@ static ssize_t firmware_version_show(struct device *dev, struct snd_card *card = dev_to_snd_card(dev); struct usb_line6_podhd *pod = card->private_data; - return sprintf(buf, "%06x\n", pod->firmware_version); + return sysfs_emit(buf, "%06x\n", pod->firmware_version); } static DEVICE_ATTR_RO(firmware_version); @@ -202,26 +181,6 @@ static const struct attribute_group podhd_dev_attr_group = { * audio nor bulk interfaces to work. */ -static void podhd_startup(struct usb_line6_podhd *pod) -{ - CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_INIT); - - /* delay startup procedure: */ - line6_start_timer(&pod->startup_timer, PODHD_STARTUP_DELAY, - podhd_startup_start_workqueue); -} - -static void podhd_startup_start_workqueue(struct timer_list *t) -{ - struct usb_line6_podhd *pod = from_timer(pod, t, startup_timer); - - CHECK_STARTUP_PROGRESS(pod->startup_progress, - PODHD_STARTUP_SCHEDULE_WORKQUEUE); - - /* schedule work for global work queue: */ - schedule_work(&pod->startup_work); -} - static int podhd_dev_start(struct usb_line6_podhd *pod) { int ret; @@ -229,24 +188,24 @@ static int podhd_dev_start(struct usb_line6_podhd *pod) int i; struct usb_device *usbdev = pod->line6.usbdev; - ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + ret = usb_control_msg_send(usbdev, 0, 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, 0x11, 0, - NULL, 0, LINE6_TIMEOUT * HZ); - if (ret < 0) { + NULL, 0, LINE6_TIMEOUT, GFP_KERNEL); + if (ret) { dev_err(pod->line6.ifcdev, "read request failed (error %d)\n", ret); - return ret; + goto exit; } /* NOTE: looks like some kind of ping message */ - ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, + ret = usb_control_msg_recv(usbdev, 0, 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, 0x11, 0x0, - &init_bytes, 3, LINE6_TIMEOUT * HZ); - if (ret < 0) { + init_bytes, 3, LINE6_TIMEOUT, GFP_KERNEL); + if (ret) { dev_err(pod->line6.ifcdev, "receive length failed (error %d)\n", ret); - return ret; + goto exit; } pod->firmware_version = @@ -255,51 +214,35 @@ static int podhd_dev_start(struct usb_line6_podhd *pod) for (i = 0; i <= 16; i++) { ret = line6_read_data(&pod->line6, 0xf000 + 0x08 * i, init_bytes, 8); if (ret < 0) - return ret; + goto exit; } - ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + ret = usb_control_msg_send(usbdev, 0, USB_REQ_SET_FEATURE, USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_DIR_OUT, 1, 0, - NULL, 0, LINE6_TIMEOUT * HZ); - if (ret < 0) - return ret; - - return 0; + NULL, 0, LINE6_TIMEOUT, GFP_KERNEL); +exit: + return ret; } -static void podhd_startup_workqueue(struct work_struct *work) +static void podhd_startup(struct usb_line6 *line6) { - struct usb_line6_podhd *pod = - container_of(work, struct usb_line6_podhd, startup_work); - - CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_SETUP); + struct usb_line6_podhd *pod = line6_to_podhd(line6); podhd_dev_start(pod); line6_read_serial_number(&pod->line6, &pod->serial_number); - - podhd_startup_finalize(pod); -} - -static int podhd_startup_finalize(struct usb_line6_podhd *pod) -{ - struct usb_line6 *line6 = &pod->line6; - - /* ALSA audio interface: */ - return snd_card_register(line6->card); + if (snd_card_register(line6->card)) + dev_err(line6->ifcdev, "Failed to register POD HD card.\n"); } static void podhd_disconnect(struct usb_line6 *line6) { - struct usb_line6_podhd *pod = (struct usb_line6_podhd *)line6; + struct usb_line6_podhd *pod = line6_to_podhd(line6); if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO) { struct usb_interface *intf; - del_timer_sync(&pod->startup_timer); - cancel_work_sync(&pod->startup_work); - intf = usb_ifnum_to_if(line6->usbdev, pod->line6.properties->ctrl_if); if (intf) @@ -307,6 +250,116 @@ static void podhd_disconnect(struct usb_line6 *line6) } } +static const unsigned int float_zero_to_one_lookup[] = { +0x00000000, 0x3c23d70a, 0x3ca3d70a, 0x3cf5c28f, 0x3d23d70a, 0x3d4ccccd, +0x3d75c28f, 0x3d8f5c29, 0x3da3d70a, 0x3db851ec, 0x3dcccccd, 0x3de147ae, +0x3df5c28f, 0x3e051eb8, 0x3e0f5c29, 0x3e19999a, 0x3e23d70a, 0x3e2e147b, +0x3e3851ec, 0x3e428f5c, 0x3e4ccccd, 0x3e570a3d, 0x3e6147ae, 0x3e6b851f, +0x3e75c28f, 0x3e800000, 0x3e851eb8, 0x3e8a3d71, 0x3e8f5c29, 0x3e947ae1, +0x3e99999a, 0x3e9eb852, 0x3ea3d70a, 0x3ea8f5c3, 0x3eae147b, 0x3eb33333, +0x3eb851ec, 0x3ebd70a4, 0x3ec28f5c, 0x3ec7ae14, 0x3ecccccd, 0x3ed1eb85, +0x3ed70a3d, 0x3edc28f6, 0x3ee147ae, 0x3ee66666, 0x3eeb851f, 0x3ef0a3d7, +0x3ef5c28f, 0x3efae148, 0x3f000000, 0x3f028f5c, 0x3f051eb8, 0x3f07ae14, +0x3f0a3d71, 0x3f0ccccd, 0x3f0f5c29, 0x3f11eb85, 0x3f147ae1, 0x3f170a3d, +0x3f19999a, 0x3f1c28f6, 0x3f1eb852, 0x3f2147ae, 0x3f23d70a, 0x3f266666, +0x3f28f5c3, 0x3f2b851f, 0x3f2e147b, 0x3f30a3d7, 0x3f333333, 0x3f35c28f, +0x3f3851ec, 0x3f3ae148, 0x3f3d70a4, 0x3f400000, 0x3f428f5c, 0x3f451eb8, +0x3f47ae14, 0x3f4a3d71, 0x3f4ccccd, 0x3f4f5c29, 0x3f51eb85, 0x3f547ae1, +0x3f570a3d, 0x3f59999a, 0x3f5c28f6, 0x3f5eb852, 0x3f6147ae, 0x3f63d70a, +0x3f666666, 0x3f68f5c3, 0x3f6b851f, 0x3f6e147b, 0x3f70a3d7, 0x3f733333, +0x3f75c28f, 0x3f7851ec, 0x3f7ae148, 0x3f7d70a4, 0x3f800000 +}; + +static void podhd_set_monitor_level(struct usb_line6_podhd *podhd, int value) +{ + unsigned int fl; + static const unsigned char msg[16] = { + /* Chunk is 0xc bytes (without first word) */ + 0x0c, 0x00, + /* First chunk in the message */ + 0x01, 0x00, + /* Message size is 2 4-byte words */ + 0x02, 0x00, + /* Unknown */ + 0x04, 0x41, + /* Unknown */ + 0x04, 0x00, 0x13, 0x00, + /* Volume, LE float32, 0.0 - 1.0 */ + 0x00, 0x00, 0x00, 0x00 + }; + unsigned char *buf; + + buf = kmemdup(msg, sizeof(msg), GFP_KERNEL); + if (!buf) + return; + + if (value < 0) + value = 0; + + if (value >= ARRAY_SIZE(float_zero_to_one_lookup)) + value = ARRAY_SIZE(float_zero_to_one_lookup) - 1; + + fl = float_zero_to_one_lookup[value]; + + buf[12] = (fl >> 0) & 0xff; + buf[13] = (fl >> 8) & 0xff; + buf[14] = (fl >> 16) & 0xff; + buf[15] = (fl >> 24) & 0xff; + + line6_send_raw_message(&podhd->line6, buf, sizeof(msg)); + kfree(buf); + + podhd->monitor_level = value; +} + +/* control info callback */ +static int snd_podhd_control_monitor_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + uinfo->value.integer.step = 1; + return 0; +} + +/* control get callback */ +static int snd_podhd_control_monitor_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + struct usb_line6_podhd *podhd = line6_to_podhd(line6pcm->line6); + + ucontrol->value.integer.value[0] = podhd->monitor_level; + return 0; +} + +/* control put callback */ +static int snd_podhd_control_monitor_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + struct usb_line6_podhd *podhd = line6_to_podhd(line6pcm->line6); + + if (ucontrol->value.integer.value[0] == podhd->monitor_level) + return 0; + + podhd_set_monitor_level(podhd, ucontrol->value.integer.value[0]); + return 1; +} + +/* control definition */ +static const struct snd_kcontrol_new podhd_control_monitor = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitor Playback Volume", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_podhd_control_monitor_info, + .get = snd_podhd_control_monitor_get, + .put = snd_podhd_control_monitor_put +}; + /* Try to init POD HD device. */ @@ -314,13 +367,11 @@ static int podhd_init(struct usb_line6 *line6, const struct usb_device_id *id) { int err; - struct usb_line6_podhd *pod = (struct usb_line6_podhd *) line6; + struct usb_line6_podhd *pod = line6_to_podhd(line6); struct usb_interface *intf; line6->disconnect = podhd_disconnect; - - timer_setup(&pod->startup_timer, NULL, 0); - INIT_WORK(&pod->startup_work, podhd_startup_workqueue); + line6->startup = podhd_startup; if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { /* claim the data interface */ @@ -357,13 +408,23 @@ static int podhd_init(struct usb_line6 *line6, return err; } + if (pod->line6.properties->capabilities & LINE6_CAP_HWMON_CTL) { + podhd_set_monitor_level(pod, 100); + err = snd_ctl_add(line6->card, + snd_ctl_new1(&podhd_control_monitor, + line6->line6pcm)); + if (err < 0) + return err; + } + if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO)) { /* register USB audio system directly */ - return podhd_startup_finalize(pod); + return snd_card_register(line6->card); } /* init device and delay registering */ - podhd_startup(pod); + schedule_delayed_work(&line6->startup_work, + msecs_to_jiffies(PODHD_STARTUP_DELAY)); return 0; } @@ -375,12 +436,12 @@ static const struct usb_device_id podhd_id_table[] = { /* TODO: no need to alloc data interfaces when only audio is used */ { LINE6_DEVICE(0x5057), .driver_info = LINE6_PODHD300 }, { LINE6_DEVICE(0x5058), .driver_info = LINE6_PODHD400 }, - { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500_0 }, - { LINE6_IF_NUM(0x414D, 1), .driver_info = LINE6_PODHD500_1 }, + { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500 }, { LINE6_IF_NUM(0x414A, 0), .driver_info = LINE6_PODX3 }, { LINE6_IF_NUM(0x414B, 0), .driver_info = LINE6_PODX3LIVE }, { LINE6_IF_NUM(0x4159, 0), .driver_info = LINE6_PODHD500X }, { LINE6_IF_NUM(0x4156, 0), .driver_info = LINE6_PODHDDESKTOP }, + { LINE6_IF_NUM(0x415A, 0), .driver_info = LINE6_PODHDPROX }, {} }; @@ -409,23 +470,13 @@ static const struct line6_properties podhd_properties_table[] = { .ep_audio_r = 0x82, .ep_audio_w = 0x01, }, - [LINE6_PODHD500_0] = { + [LINE6_PODHD500] = { .id = "PODHD500", .name = "POD HD500", - .capabilities = LINE6_CAP_PCM - | LINE6_CAP_HWMON, - .altsetting = 1, - .ep_ctrl_r = 0x81, - .ep_ctrl_w = 0x01, - .ep_audio_r = 0x86, - .ep_audio_w = 0x02, - }, - [LINE6_PODHD500_1] = { - .id = "PODHD500", - .name = "POD HD500", - .capabilities = LINE6_CAP_PCM - | LINE6_CAP_HWMON, + .capabilities = LINE6_CAP_PCM | LINE6_CAP_CONTROL + | LINE6_CAP_HWMON | LINE6_CAP_HWMON_CTL, .altsetting = 1, + .ctrl_if = 1, .ep_ctrl_r = 0x81, .ep_ctrl_w = 0x01, .ep_audio_r = 0x86, @@ -458,7 +509,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD500X] = { .id = "PODHD500X", .name = "POD HD500X", - .capabilities = LINE6_CAP_CONTROL + .capabilities = LINE6_CAP_CONTROL | LINE6_CAP_HWMON_CTL | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, .ep_ctrl_r = 0x81, @@ -479,6 +530,18 @@ static const struct line6_properties podhd_properties_table[] = { .ep_audio_r = 0x86, .ep_audio_w = 0x02, }, + [LINE6_PODHDPROX] = { + .id = "PODHDPROX", + .name = "POD HD Pro X", + .capabilities = LINE6_CAP_CONTROL | LINE6_CAP_CONTROL_INFO + | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT, + .altsetting = 1, + .ctrl_if = 1, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, }; /* diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c index f47ba94e6f4a..68cda7bf330c 100644 --- a/sound/usb/line6/toneport.c +++ b/sound/usb/line6/toneport.c @@ -1,13 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) * Emil Myhrman (emil.myhrman@gmail.com) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * */ #include <linux/wait.h> @@ -54,9 +50,6 @@ struct usb_line6_toneport { /* Firmware version (x 100) */ u8 firmware_version; - /* Timer for delayed PCM startup */ - struct timer_list timer; - /* Device type */ enum line6_device_type type; @@ -64,11 +57,13 @@ struct usb_line6_toneport { struct toneport_led leds[2]; }; +#define line6_to_toneport(x) container_of(x, struct usb_line6_toneport, line6) + static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2); #define TONEPORT_PCM_DELAY 1 -static struct snd_ratden toneport_ratden = { +static const struct snd_ratden toneport_ratden = { .num_min = 44100, .num_max = 44100, .num_step = 1, @@ -131,11 +126,12 @@ static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2) { int ret; - ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, - cmd1, cmd2, NULL, 0, LINE6_TIMEOUT * HZ); + ret = usb_control_msg_send(usbdev, 0, 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + cmd1, cmd2, NULL, 0, LINE6_TIMEOUT, + GFP_KERNEL); - if (ret < 0) { + if (ret) { dev_err(&usbdev->dev, "send failed (error %d)\n", ret); return ret; } @@ -203,7 +199,7 @@ static int snd_toneport_source_info(struct snd_kcontrol *kcontrol, if (uinfo->value.enumerated.item >= size) uinfo->value.enumerated.item = size - 1; - strcpy(uinfo->value.enumerated.name, + strscpy(uinfo->value.enumerated.name, toneport_source_info[uinfo->value.enumerated.item].name); return 0; @@ -214,8 +210,8 @@ static int snd_toneport_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); - struct usb_line6_toneport *toneport = - (struct usb_line6_toneport *)line6pcm->line6; + struct usb_line6_toneport *toneport = line6_to_toneport(line6pcm->line6); + ucontrol->value.enumerated.item[0] = toneport->source; return 0; } @@ -225,8 +221,7 @@ static int snd_toneport_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); - struct usb_line6_toneport *toneport = - (struct usb_line6_toneport *)line6pcm->line6; + struct usb_line6_toneport *toneport = line6_to_toneport(line6pcm->line6); unsigned int source; source = ucontrol->value.enumerated.item[0]; @@ -241,11 +236,8 @@ static int snd_toneport_source_put(struct snd_kcontrol *kcontrol, return 1; } -static void toneport_start_pcm(struct timer_list *t) +static void toneport_startup(struct usb_line6 *line6) { - struct usb_line6_toneport *toneport = from_timer(toneport, t, timer); - struct usb_line6 *line6 = &toneport->line6; - line6_pcm_acquire(line6->line6pcm, LINE6_STREAM_MONITOR, true); } @@ -291,8 +283,8 @@ static bool toneport_has_led(struct usb_line6_toneport *toneport) } } -static const char * const led_colors[2] = { "red", "green" }; -static const int led_init_vals[2] = { 0x00, 0x26 }; +static const char * const toneport_led_colors[2] = { "red", "green" }; +static const int toneport_led_init_vals[2] = { 0x00, 0x26 }; static void toneport_update_led(struct usb_line6_toneport *toneport) { @@ -320,9 +312,9 @@ static int toneport_init_leds(struct usb_line6_toneport *toneport) led->toneport = toneport; snprintf(led->name, sizeof(led->name), "%s::%s", - dev_name(dev), led_colors[i]); + dev_name(dev), toneport_led_colors[i]); leddev->name = led->name; - leddev->brightness = led_init_vals[i]; + leddev->brightness = toneport_led_init_vals[i]; leddev->max_brightness = 0x26; leddev->brightness_set = toneport_led_brightness_set; err = led_classdev_register(dev, leddev); @@ -365,16 +357,21 @@ static bool toneport_has_source_select(struct usb_line6_toneport *toneport) /* Setup Toneport device. */ -static void toneport_setup(struct usb_line6_toneport *toneport) +static int toneport_setup(struct usb_line6_toneport *toneport) { - u32 ticks; + u32 *ticks; struct usb_line6 *line6 = &toneport->line6; struct usb_device *usbdev = line6->usbdev; + ticks = kmalloc(sizeof(*ticks), GFP_KERNEL); + if (!ticks) + return -ENOMEM; + /* sync time on device with host: */ /* note: 32-bit timestamps overflow in year 2106 */ - ticks = (u32)ktime_get_real_seconds(); - line6_write_data(line6, 0x80c6, &ticks, 4); + *ticks = (u32)ktime_get_real_seconds(); + line6_write_data(line6, 0x80c6, ticks, 4); + kfree(ticks); /* enable device: */ toneport_send_cmd(usbdev, 0x0301, 0x0000); @@ -388,7 +385,9 @@ static void toneport_setup(struct usb_line6_toneport *toneport) if (toneport_has_led(toneport)) toneport_update_led(toneport); - mod_timer(&toneport->timer, jiffies + TONEPORT_PCM_DELAY * HZ); + schedule_delayed_work(&toneport->line6.startup_work, + secs_to_jiffies(TONEPORT_PCM_DELAY)); + return 0; } /* @@ -396,10 +395,7 @@ static void toneport_setup(struct usb_line6_toneport *toneport) */ static void line6_toneport_disconnect(struct usb_line6 *line6) { - struct usb_line6_toneport *toneport = - (struct usb_line6_toneport *)line6; - - del_timer_sync(&toneport->timer); + struct usb_line6_toneport *toneport = line6_to_toneport(line6); if (toneport_has_led(toneport)) toneport_remove_leds(toneport); @@ -413,12 +409,12 @@ static int toneport_init(struct usb_line6 *line6, const struct usb_device_id *id) { int err; - struct usb_line6_toneport *toneport = (struct usb_line6_toneport *) line6; + struct usb_line6_toneport *toneport = line6_to_toneport(line6); toneport->type = id->driver_info; - timer_setup(&toneport->timer, toneport_start_pcm, 0); line6->disconnect = line6_toneport_disconnect; + line6->startup = toneport_startup; /* initialize PCM subsystem: */ err = line6_init_pcm(line6, &toneport_pcm_properties); @@ -451,7 +447,9 @@ static int toneport_init(struct usb_line6 *line6, return err; } - toneport_setup(toneport); + err = toneport_setup(toneport); + if (err) + return err; /* register audio system: */ return snd_card_register(line6->card); @@ -463,7 +461,11 @@ static int toneport_init(struct usb_line6 *line6, */ static int toneport_reset_resume(struct usb_interface *interface) { - toneport_setup(usb_get_intfdata(interface)); + int err; + + err = toneport_setup(usb_get_intfdata(interface)); + if (err) + return err; return line6_resume(interface); } #endif diff --git a/sound/usb/line6/variax.c b/sound/usb/line6/variax.c index e8c852b2ce35..b2f6637c84b2 100644 --- a/sound/usb/line6/variax.c +++ b/sound/usb/line6/variax.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Line 6 Linux USB driver * - * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * + * Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.at) */ #include <linux/slab.h> @@ -26,13 +22,9 @@ Stages of Variax startup procedure */ enum { - VARIAX_STARTUP_INIT = 1, VARIAX_STARTUP_VERSIONREQ, - VARIAX_STARTUP_WAIT, VARIAX_STARTUP_ACTIVATE, - VARIAX_STARTUP_WORKQUEUE, VARIAX_STARTUP_SETUP, - VARIAX_STARTUP_LAST = VARIAX_STARTUP_SETUP - 1 }; enum { @@ -47,17 +39,12 @@ struct usb_line6_variax { /* Buffer for activation code */ unsigned char *buffer_activate; - /* Handler for device initialization */ - struct work_struct startup_work; - - /* Timers for device initialization */ - struct timer_list startup_timer1; - struct timer_list startup_timer2; - /* Current progress in startup procedure */ int startup_progress; }; +#define line6_to_variax(x) container_of(x, struct usb_line6_variax, line6) + #define VARIAX_OFFSET_ACTIVATE 7 /* @@ -81,11 +68,6 @@ static const char variax_activate[] = { 0xf7 }; -/* forward declarations: */ -static void variax_startup2(struct timer_list *t); -static void variax_startup4(struct timer_list *t); -static void variax_startup5(struct timer_list *t); - static void variax_activate_async(struct usb_line6_variax *variax, int a) { variax->buffer_activate[VARIAX_OFFSET_ACTIVATE] = a; @@ -100,74 +82,30 @@ static void variax_activate_async(struct usb_line6_variax *variax, int a) context). After the last one has finished, the device is ready to use. */ -static void variax_startup1(struct usb_line6_variax *variax) -{ - CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_INIT); - - /* delay startup procedure: */ - line6_start_timer(&variax->startup_timer1, VARIAX_STARTUP_DELAY1, - variax_startup2); -} - -static void variax_startup2(struct timer_list *t) -{ - struct usb_line6_variax *variax = from_timer(variax, t, startup_timer1); - struct usb_line6 *line6 = &variax->line6; - - /* schedule another startup procedure until startup is complete: */ - if (variax->startup_progress >= VARIAX_STARTUP_LAST) - return; - - variax->startup_progress = VARIAX_STARTUP_VERSIONREQ; - line6_start_timer(&variax->startup_timer1, VARIAX_STARTUP_DELAY1, - variax_startup2); - - /* request firmware version: */ - line6_version_request_async(line6); -} - -static void variax_startup3(struct usb_line6_variax *variax) -{ - CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_WAIT); - - /* delay startup procedure: */ - line6_start_timer(&variax->startup_timer2, VARIAX_STARTUP_DELAY3, - variax_startup4); -} - -static void variax_startup4(struct timer_list *t) +static void variax_startup(struct usb_line6 *line6) { - struct usb_line6_variax *variax = from_timer(variax, t, startup_timer2); - - CHECK_STARTUP_PROGRESS(variax->startup_progress, - VARIAX_STARTUP_ACTIVATE); - - /* activate device: */ - variax_activate_async(variax, 1); - line6_start_timer(&variax->startup_timer2, VARIAX_STARTUP_DELAY4, - variax_startup5); -} - -static void variax_startup5(struct timer_list *t) -{ - struct usb_line6_variax *variax = from_timer(variax, t, startup_timer2); - - CHECK_STARTUP_PROGRESS(variax->startup_progress, - VARIAX_STARTUP_WORKQUEUE); - - /* schedule work for global work queue: */ - schedule_work(&variax->startup_work); -} - -static void variax_startup6(struct work_struct *work) -{ - struct usb_line6_variax *variax = - container_of(work, struct usb_line6_variax, startup_work); - - CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_SETUP); - - /* ALSA audio interface: */ - snd_card_register(variax->line6.card); + struct usb_line6_variax *variax = line6_to_variax(line6); + + switch (variax->startup_progress) { + case VARIAX_STARTUP_VERSIONREQ: + /* repeat request until getting the response */ + schedule_delayed_work(&line6->startup_work, + msecs_to_jiffies(VARIAX_STARTUP_DELAY1)); + /* request firmware version: */ + line6_version_request_async(line6); + break; + case VARIAX_STARTUP_ACTIVATE: + /* activate device: */ + variax_activate_async(variax, 1); + variax->startup_progress = VARIAX_STARTUP_SETUP; + schedule_delayed_work(&line6->startup_work, + msecs_to_jiffies(VARIAX_STARTUP_DELAY4)); + break; + case VARIAX_STARTUP_SETUP: + /* ALSA audio interface: */ + snd_card_register(variax->line6.card); + break; + } } /* @@ -175,7 +113,7 @@ static void variax_startup6(struct work_struct *work) */ static void line6_variax_process_message(struct usb_line6 *line6) { - struct usb_line6_variax *variax = (struct usb_line6_variax *) line6; + struct usb_line6_variax *variax = line6_to_variax(line6); const unsigned char *buf = variax->line6.buffer_message; switch (buf[0]) { @@ -186,11 +124,19 @@ static void line6_variax_process_message(struct usb_line6 *line6) case LINE6_SYSEX_BEGIN: if (memcmp(buf + 1, variax_init_version + 1, sizeof(variax_init_version) - 1) == 0) { - variax_startup3(variax); + if (variax->startup_progress >= VARIAX_STARTUP_ACTIVATE) + break; + variax->startup_progress = VARIAX_STARTUP_ACTIVATE; + cancel_delayed_work(&line6->startup_work); + schedule_delayed_work(&line6->startup_work, + msecs_to_jiffies(VARIAX_STARTUP_DELAY3)); } else if (memcmp(buf + 1, variax_init_done + 1, sizeof(variax_init_done) - 1) == 0) { /* notify of complete initialization: */ - variax_startup4(&variax->startup_timer2); + if (variax->startup_progress >= VARIAX_STARTUP_SETUP) + break; + cancel_delayed_work(&line6->startup_work); + schedule_delayed_work(&line6->startup_work, 0); } break; } @@ -201,11 +147,7 @@ static void line6_variax_process_message(struct usb_line6 *line6) */ static void line6_variax_disconnect(struct usb_line6 *line6) { - struct usb_line6_variax *variax = (struct usb_line6_variax *)line6; - - del_timer(&variax->startup_timer1); - del_timer(&variax->startup_timer2); - cancel_work_sync(&variax->startup_work); + struct usb_line6_variax *variax = line6_to_variax(line6); kfree(variax->buffer_activate); } @@ -216,15 +158,11 @@ static void line6_variax_disconnect(struct usb_line6 *line6) static int variax_init(struct usb_line6 *line6, const struct usb_device_id *id) { - struct usb_line6_variax *variax = (struct usb_line6_variax *) line6; - int err; + struct usb_line6_variax *variax = line6_to_variax(line6); line6->process_message = line6_variax_process_message; line6->disconnect = line6_variax_disconnect; - - timer_setup(&variax->startup_timer1, NULL, 0); - timer_setup(&variax->startup_timer2, NULL, 0); - INIT_WORK(&variax->startup_work, variax_startup6); + line6->startup = variax_startup; /* initialize USB buffers: */ variax->buffer_activate = kmemdup(variax_activate, @@ -233,13 +171,9 @@ static int variax_init(struct usb_line6 *line6, if (variax->buffer_activate == NULL) return -ENOMEM; - /* initialize MIDI subsystem: */ - err = line6_init_midi(&variax->line6); - if (err < 0) - return err; - /* initiate startup procedure: */ - variax_startup1(variax); + schedule_delayed_work(&line6->startup_work, + msecs_to_jiffies(VARIAX_STARTUP_DELAY1)); return 0; } @@ -304,5 +238,5 @@ static struct usb_driver variax_driver = { module_usb_driver(variax_driver); -MODULE_DESCRIPTION("Vairax Workbench USB driver"); +MODULE_DESCRIPTION("Variax Workbench USB driver"); MODULE_LICENSE("GPL"); diff --git a/sound/usb/media.c b/sound/usb/media.c new file mode 100644 index 000000000000..0064f8d12422 --- /dev/null +++ b/sound/usb/media.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * media.c - Media Controller specific ALSA driver code + * + * Copyright (c) 2019 Shuah Khan <shuah@kernel.org> + * + */ + +/* + * This file adds Media Controller support to the ALSA driver + * to use the Media Controller API to share the tuner with DVB + * and V4L2 drivers that control the media device. + * + * The media device is created based on the existing quirks framework. + * Using this approach, the media controller API usage can be added for + * a specific device. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/usb.h> + +#include <sound/pcm.h> +#include <sound/core.h> + +#include "usbaudio.h" +#include "card.h" +#include "mixer.h" +#include "media.h" + +int snd_media_stream_init(struct snd_usb_substream *subs, struct snd_pcm *pcm, + int stream) +{ + struct media_device *mdev; + struct media_ctl *mctl; + struct device *pcm_dev = pcm->streams[stream].dev; + u32 intf_type; + int ret = 0; + u16 mixer_pad; + struct media_entity *entity; + + mdev = subs->stream->chip->media_dev; + if (!mdev) + return 0; + + if (subs->media_ctl) + return 0; + + /* allocate media_ctl */ + mctl = kzalloc(sizeof(*mctl), GFP_KERNEL); + if (!mctl) + return -ENOMEM; + + mctl->media_dev = mdev; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + intf_type = MEDIA_INTF_T_ALSA_PCM_PLAYBACK; + mctl->media_entity.function = MEDIA_ENT_F_AUDIO_PLAYBACK; + mctl->media_pad.flags = MEDIA_PAD_FL_SOURCE; + mixer_pad = 1; + } else { + intf_type = MEDIA_INTF_T_ALSA_PCM_CAPTURE; + mctl->media_entity.function = MEDIA_ENT_F_AUDIO_CAPTURE; + mctl->media_pad.flags = MEDIA_PAD_FL_SINK; + mixer_pad = 2; + } + mctl->media_entity.name = pcm->name; + media_entity_pads_init(&mctl->media_entity, 1, &mctl->media_pad); + ret = media_device_register_entity(mctl->media_dev, + &mctl->media_entity); + if (ret) + goto free_mctl; + + mctl->intf_devnode = media_devnode_create(mdev, intf_type, 0, + MAJOR(pcm_dev->devt), + MINOR(pcm_dev->devt)); + if (!mctl->intf_devnode) { + ret = -ENOMEM; + goto unregister_entity; + } + mctl->intf_link = media_create_intf_link(&mctl->media_entity, + &mctl->intf_devnode->intf, + MEDIA_LNK_FL_ENABLED); + if (!mctl->intf_link) { + ret = -ENOMEM; + goto devnode_remove; + } + + /* create link between mixer and audio */ + media_device_for_each_entity(entity, mdev) { + switch (entity->function) { + case MEDIA_ENT_F_AUDIO_MIXER: + ret = media_create_pad_link(entity, mixer_pad, + &mctl->media_entity, 0, + MEDIA_LNK_FL_ENABLED); + if (ret) + goto remove_intf_link; + break; + } + } + + subs->media_ctl = mctl; + return 0; + +remove_intf_link: + media_remove_intf_link(mctl->intf_link); +devnode_remove: + media_devnode_remove(mctl->intf_devnode); +unregister_entity: + media_device_unregister_entity(&mctl->media_entity); +free_mctl: + kfree(mctl); + return ret; +} + +void snd_media_stream_delete(struct snd_usb_substream *subs) +{ + struct media_ctl *mctl = subs->media_ctl; + + if (mctl) { + struct media_device *mdev; + + mdev = mctl->media_dev; + if (mdev && media_devnode_is_registered(mdev->devnode)) { + media_devnode_remove(mctl->intf_devnode); + media_device_unregister_entity(&mctl->media_entity); + media_entity_cleanup(&mctl->media_entity); + } + kfree(mctl); + subs->media_ctl = NULL; + } +} + +int snd_media_start_pipeline(struct snd_usb_substream *subs) +{ + struct media_ctl *mctl = subs->media_ctl; + int ret = 0; + + if (!mctl) + return 0; + + guard(mutex)(&mctl->media_dev->graph_mutex); + if (mctl->media_dev->enable_source) + ret = mctl->media_dev->enable_source(&mctl->media_entity, + &mctl->media_pipe); + return ret; +} + +void snd_media_stop_pipeline(struct snd_usb_substream *subs) +{ + struct media_ctl *mctl = subs->media_ctl; + + if (!mctl) + return; + + guard(mutex)(&mctl->media_dev->graph_mutex); + if (mctl->media_dev->disable_source) + mctl->media_dev->disable_source(&mctl->media_entity); +} + +static int snd_media_mixer_init(struct snd_usb_audio *chip) +{ + struct device *ctl_dev = chip->card->ctl_dev; + struct media_intf_devnode *ctl_intf; + struct usb_mixer_interface *mixer; + struct media_device *mdev = chip->media_dev; + struct media_mixer_ctl *mctl; + u32 intf_type = MEDIA_INTF_T_ALSA_CONTROL; + int ret; + + if (!mdev) + return -ENODEV; + + ctl_intf = chip->ctl_intf_media_devnode; + if (!ctl_intf) { + ctl_intf = media_devnode_create(mdev, intf_type, 0, + MAJOR(ctl_dev->devt), + MINOR(ctl_dev->devt)); + if (!ctl_intf) + return -ENOMEM; + chip->ctl_intf_media_devnode = ctl_intf; + } + + list_for_each_entry(mixer, &chip->mixer_list, list) { + + if (mixer->media_mixer_ctl) + continue; + + /* allocate media_mixer_ctl */ + mctl = kzalloc(sizeof(*mctl), GFP_KERNEL); + if (!mctl) + return -ENOMEM; + + mctl->media_dev = mdev; + mctl->media_entity.function = MEDIA_ENT_F_AUDIO_MIXER; + mctl->media_entity.name = chip->card->mixername; + mctl->media_pad[0].flags = MEDIA_PAD_FL_SINK; + mctl->media_pad[1].flags = MEDIA_PAD_FL_SOURCE; + mctl->media_pad[2].flags = MEDIA_PAD_FL_SOURCE; + media_entity_pads_init(&mctl->media_entity, MEDIA_MIXER_PAD_MAX, + mctl->media_pad); + ret = media_device_register_entity(mctl->media_dev, + &mctl->media_entity); + if (ret) { + kfree(mctl); + return ret; + } + + mctl->intf_link = media_create_intf_link(&mctl->media_entity, + &ctl_intf->intf, + MEDIA_LNK_FL_ENABLED); + if (!mctl->intf_link) { + media_device_unregister_entity(&mctl->media_entity); + media_entity_cleanup(&mctl->media_entity); + kfree(mctl); + return -ENOMEM; + } + mctl->intf_devnode = ctl_intf; + mixer->media_mixer_ctl = mctl; + } + return 0; +} + +static void snd_media_mixer_delete(struct snd_usb_audio *chip) +{ + struct usb_mixer_interface *mixer; + struct media_device *mdev = chip->media_dev; + + if (!mdev) + return; + + list_for_each_entry(mixer, &chip->mixer_list, list) { + struct media_mixer_ctl *mctl; + + mctl = mixer->media_mixer_ctl; + if (!mixer->media_mixer_ctl) + continue; + + if (media_devnode_is_registered(mdev->devnode)) { + media_device_unregister_entity(&mctl->media_entity); + media_entity_cleanup(&mctl->media_entity); + } + kfree(mctl); + mixer->media_mixer_ctl = NULL; + } + if (media_devnode_is_registered(mdev->devnode)) + media_devnode_remove(chip->ctl_intf_media_devnode); + chip->ctl_intf_media_devnode = NULL; +} + +int snd_media_device_create(struct snd_usb_audio *chip, + struct usb_interface *iface) +{ + struct media_device *mdev; + struct usb_device *usbdev = interface_to_usbdev(iface); + int ret = 0; + + /* usb-audio driver is probed for each usb interface, and + * there are multiple interfaces per device. Avoid calling + * media_device_usb_allocate() each time usb_audio_probe() + * is called. Do it only once. + */ + if (chip->media_dev) { + mdev = chip->media_dev; + goto snd_mixer_init; + } + + mdev = media_device_usb_allocate(usbdev, KBUILD_MODNAME, THIS_MODULE); + if (IS_ERR(mdev)) + return -ENOMEM; + + /* save media device - avoid lookups */ + chip->media_dev = mdev; + +snd_mixer_init: + /* Create media entities for mixer and control dev */ + ret = snd_media_mixer_init(chip); + /* media_device might be registered, print error and continue */ + if (ret) + dev_err(&usbdev->dev, + "Couldn't create media mixer entities. Error: %d\n", + ret); + + if (!media_devnode_is_registered(mdev->devnode)) { + /* don't register if snd_media_mixer_init() failed */ + if (ret) + goto create_fail; + + /* register media_device */ + ret = media_device_register(mdev); +create_fail: + if (ret) { + snd_media_mixer_delete(chip); + media_device_delete(mdev, KBUILD_MODNAME, THIS_MODULE); + /* clear saved media_dev */ + chip->media_dev = NULL; + dev_err(&usbdev->dev, + "Couldn't register media device. Error: %d\n", + ret); + return ret; + } + } + + return ret; +} + +void snd_media_device_delete(struct snd_usb_audio *chip) +{ + struct media_device *mdev = chip->media_dev; + struct snd_usb_stream *stream; + + /* release resources */ + list_for_each_entry(stream, &chip->pcm_list, list) { + snd_media_stream_delete(&stream->substream[0]); + snd_media_stream_delete(&stream->substream[1]); + } + + snd_media_mixer_delete(chip); + + if (mdev) { + media_device_delete(mdev, KBUILD_MODNAME, THIS_MODULE); + chip->media_dev = NULL; + } +} diff --git a/sound/usb/media.h b/sound/usb/media.h new file mode 100644 index 000000000000..f5bdec1d602f --- /dev/null +++ b/sound/usb/media.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * media.h - Media Controller specific ALSA driver code + * + * Copyright (c) 2019 Shuah Khan <shuah@kernel.org> + * + */ + +/* + * This file adds Media Controller support to the ALSA driver + * to use the Media Controller API to share the tuner with DVB + * and V4L2 drivers that control the media device. + * + * The media device is created based on the existing quirks framework. + * Using this approach, the media controller API usage can be added for + * a specific device. + */ +#ifndef __MEDIA_H + +#ifdef CONFIG_SND_USB_AUDIO_USE_MEDIA_CONTROLLER + +#include <linux/media.h> +#include <media/media-device.h> +#include <media/media-entity.h> +#include <media/media-dev-allocator.h> +#include <sound/asound.h> + +struct media_ctl { + struct media_device *media_dev; + struct media_entity media_entity; + struct media_intf_devnode *intf_devnode; + struct media_link *intf_link; + struct media_pad media_pad; + struct media_pipeline media_pipe; +}; + +/* + * One source pad each for SNDRV_PCM_STREAM_CAPTURE and + * SNDRV_PCM_STREAM_PLAYBACK. One for sink pad to link + * to AUDIO Source + */ +#define MEDIA_MIXER_PAD_MAX (SNDRV_PCM_STREAM_LAST + 2) + +struct media_mixer_ctl { + struct media_device *media_dev; + struct media_entity media_entity; + struct media_intf_devnode *intf_devnode; + struct media_link *intf_link; + struct media_pad media_pad[MEDIA_MIXER_PAD_MAX]; + struct media_pipeline media_pipe; +}; + +int snd_media_device_create(struct snd_usb_audio *chip, + struct usb_interface *iface); +void snd_media_device_delete(struct snd_usb_audio *chip); +int snd_media_stream_init(struct snd_usb_substream *subs, struct snd_pcm *pcm, + int stream); +void snd_media_stream_delete(struct snd_usb_substream *subs); +int snd_media_start_pipeline(struct snd_usb_substream *subs); +void snd_media_stop_pipeline(struct snd_usb_substream *subs); +#else +static inline int snd_media_device_create(struct snd_usb_audio *chip, + struct usb_interface *iface) + { return 0; } +static inline void snd_media_device_delete(struct snd_usb_audio *chip) { } +static inline int snd_media_stream_init(struct snd_usb_substream *subs, + struct snd_pcm *pcm, int stream) + { return 0; } +static inline void snd_media_stream_delete(struct snd_usb_substream *subs) { } +static inline int snd_media_start_pipeline(struct snd_usb_substream *subs) + { return 0; } +static inline void snd_media_stop_pipeline(struct snd_usb_substream *subs) { } +#endif +#endif /* __MEDIA_H */ diff --git a/sound/usb/midi.c b/sound/usb/midi.c index b737f0ec77d0..dd8249e75970 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -47,6 +47,7 @@ #include <linux/usb.h> #include <linux/wait.h> #include <linux/usb/audio.h> +#include <linux/usb/midi.h> #include <linux/module.h> #include <sound/core.h> @@ -77,23 +78,6 @@ MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); MODULE_DESCRIPTION("USB Audio/MIDI helper module"); MODULE_LICENSE("Dual BSD/GPL"); - -struct usb_ms_header_descriptor { - __u8 bLength; - __u8 bDescriptorType; - __u8 bDescriptorSubtype; - __u8 bcdMSC[2]; - __le16 wTotalLength; -} __attribute__ ((packed)); - -struct usb_ms_endpoint_descriptor { - __u8 bLength; - __u8 bDescriptorType; - __u8 bDescriptorSubtype; - __u8 bNumEmbMIDIJack; - __u8 baAssocJackID[0]; -} __attribute__ ((packed)); - struct snd_usb_midi_in_endpoint; struct snd_usb_midi_out_endpoint; struct snd_usb_midi_endpoint; @@ -142,7 +126,7 @@ struct snd_usb_midi_out_endpoint { unsigned int active_urbs; unsigned int drain_urbs; int max_transfer; /* size of urb buffer */ - struct tasklet_struct tasklet; + struct work_struct work; unsigned int next_urb; spinlock_t buffer_lock; @@ -240,10 +224,10 @@ static void snd_usbmidi_input_data(struct snd_usb_midi_in_endpoint *ep, #ifdef DUMP_PACKETS static void dump_urb(const char *type, const u8 *data, int length) { - snd_printk(KERN_DEBUG "%s packet: [", type); + pr_debug("%s packet: [", type); for (; length > 0; ++data, --length) - printk(KERN_CONT " %02x", *data); - printk(KERN_CONT " ]\n"); + pr_cont(" %02x", *data); + pr_cont(" ]\n"); } #else #define dump_urb(type, data, length) /* nothing */ @@ -281,16 +265,15 @@ static void snd_usbmidi_out_urb_complete(struct urb *urb) struct out_urb_context *context = urb->context; struct snd_usb_midi_out_endpoint *ep = context->ep; unsigned int urb_index; - unsigned long flags; - - spin_lock_irqsave(&ep->buffer_lock, flags); - urb_index = context - ep->urbs; - ep->active_urbs &= ~(1 << urb_index); - if (unlikely(ep->drain_urbs)) { - ep->drain_urbs &= ~(1 << urb_index); - wake_up(&ep->drain_wait); + + scoped_guard(spinlock_irqsave, &ep->buffer_lock) { + urb_index = context - ep->urbs; + ep->active_urbs &= ~(1 << urb_index); + if (unlikely(ep->drain_urbs)) { + ep->drain_urbs &= ~(1 << urb_index); + wake_up(&ep->drain_wait); + } } - spin_unlock_irqrestore(&ep->buffer_lock, flags); if (urb->status < 0) { int err = snd_usbmidi_urb_error(urb); if (err < 0) { @@ -311,13 +294,10 @@ static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint *ep) { unsigned int urb_index; struct urb *urb; - unsigned long flags; - spin_lock_irqsave(&ep->buffer_lock, flags); - if (ep->umidi->disconnected) { - spin_unlock_irqrestore(&ep->buffer_lock, flags); + guard(spinlock_irqsave)(&ep->buffer_lock); + if (ep->umidi->disconnected) return; - } urb_index = ep->next_urb; for (;;) { @@ -341,13 +321,12 @@ static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint *ep) break; } ep->next_urb = urb_index; - spin_unlock_irqrestore(&ep->buffer_lock, flags); } -static void snd_usbmidi_out_tasklet(unsigned long data) +static void snd_usbmidi_out_work(struct work_struct *work) { struct snd_usb_midi_out_endpoint *ep = - (struct snd_usb_midi_out_endpoint *) data; + container_of(work, struct snd_usb_midi_out_endpoint, work); snd_usbmidi_do_output(ep); } @@ -355,12 +334,11 @@ static void snd_usbmidi_out_tasklet(unsigned long data) /* called after transfers had been interrupted due to some USB error */ static void snd_usbmidi_error_timer(struct timer_list *t) { - struct snd_usb_midi *umidi = from_timer(umidi, t, error_timer); + struct snd_usb_midi *umidi = timer_container_of(umidi, t, error_timer); unsigned int i, j; - spin_lock(&umidi->disc_lock); + guard(spinlock)(&umidi->disc_lock); if (umidi->disconnected) { - spin_unlock(&umidi->disc_lock); return; } for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { @@ -377,7 +355,6 @@ static void snd_usbmidi_error_timer(struct timer_list *t) if (umidi->endpoints[i].out) snd_usbmidi_do_output(umidi->endpoints[i].out); } - spin_unlock(&umidi->disc_lock); } /* helper function to send static data that may not DMA-able */ @@ -505,16 +482,84 @@ static void ch345_broken_sysex_input(struct snd_usb_midi_in_endpoint *ep, /* * CME protocol: like the standard protocol, but SysEx commands are sent as a - * single USB packet preceded by a 0x0F byte. + * single USB packet preceded by a 0x0F byte, as are system realtime + * messages and MIDI Active Sensing. + * Also, multiple messages can be sent in the same packet. */ static void snd_usbmidi_cme_input(struct snd_usb_midi_in_endpoint *ep, uint8_t *buffer, int buffer_length) { - if (buffer_length < 2 || (buffer[0] & 0x0f) != 0x0f) - snd_usbmidi_standard_input(ep, buffer, buffer_length); - else - snd_usbmidi_input_data(ep, buffer[0] >> 4, - &buffer[1], buffer_length - 1); + int remaining = buffer_length; + + /* + * CME send sysex, song position pointer, system realtime + * and active sensing using CIN 0x0f, which in the standard + * is only intended for single byte unparsed data. + * So we need to interpret these here before sending them on. + * By default, we assume single byte data, which is true + * for system realtime (midi clock, start, stop and continue) + * and active sensing, and handle the other (known) cases + * separately. + * In contrast to the standard, CME does not split sysex + * into multiple 4-byte packets, but lumps everything together + * into one. In addition, CME can string multiple messages + * together in the same packet; pressing the Record button + * on an UF6 sends a sysex message directly followed + * by a song position pointer in the same packet. + * For it to have any reasonable meaning, a sysex message + * needs to be at least 3 bytes in length (0xf0, id, 0xf7), + * corresponding to a packet size of 4 bytes, and the ones sent + * by CME devices are 6 or 7 bytes, making the packet fragments + * 7 or 8 bytes long (six or seven bytes plus preceding CN+CIN byte). + * For the other types, the packet size is always 4 bytes, + * as per the standard, with the data size being 3 for SPP + * and 1 for the others. + * Thus all packet fragments are at least 4 bytes long, so we can + * skip anything that is shorter; this also conveniantly skips + * packets with size 0, which CME devices continuously send when + * they have nothing better to do. + * Another quirk is that sometimes multiple messages are sent + * in the same packet. This has been observed for midi clock + * and active sensing i.e. 0x0f 0xf8 0x00 0x00 0x0f 0xfe 0x00 0x00, + * but also multiple note ons/offs, and control change together + * with MIDI clock. Similarly, some sysex messages are followed by + * the song position pointer in the same packet, and occasionally + * additionally by a midi clock or active sensing. + * We handle this by looping over all data and parsing it along the way. + */ + while (remaining >= 4) { + int source_length = 4; /* default */ + + if ((buffer[0] & 0x0f) == 0x0f) { + int data_length = 1; /* default */ + + if (buffer[1] == 0xf0) { + /* Sysex: Find EOX and send on whole message. */ + /* To kick off the search, skip the first + * two bytes (CN+CIN and SYSEX (0xf0). + */ + uint8_t *tmp_buf = buffer + 2; + int tmp_length = remaining - 2; + + while (tmp_length > 1 && *tmp_buf != 0xf7) { + tmp_buf++; + tmp_length--; + } + data_length = tmp_buf - buffer; + source_length = data_length + 1; + } else if (buffer[1] == 0xf2) { + /* Three byte song position pointer */ + data_length = 3; + } + snd_usbmidi_input_data(ep, buffer[0] >> 4, + &buffer[1], data_length); + } else { + /* normal channel events */ + snd_usbmidi_standard_input(ep, buffer, source_length); + } + buffer += source_length; + remaining -= source_length; + } } /* @@ -1096,13 +1141,11 @@ static int substream_open(struct snd_rawmidi_substream *substream, int dir, struct snd_usb_midi *umidi = substream->rmidi->private_data; struct snd_kcontrol *ctl; - down_read(&umidi->disc_rwsem); - if (umidi->disconnected) { - up_read(&umidi->disc_rwsem); + guard(rwsem_read)(&umidi->disc_rwsem); + if (umidi->disconnected) return open ? -ENODEV : 0; - } - mutex_lock(&umidi->mutex); + guard(mutex)(&umidi->mutex); if (open) { if (!umidi->opened[0] && !umidi->opened[1]) { if (umidi->roland_load_ctl) { @@ -1131,8 +1174,6 @@ static int substream_open(struct snd_rawmidi_substream *substream, int dir, } } } - mutex_unlock(&umidi->mutex); - up_read(&umidi->disc_rwsem); return 0; } @@ -1149,10 +1190,8 @@ static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream) port = &umidi->endpoints[i].out->ports[j]; break; } - if (!port) { - snd_BUG(); + if (!port) return -ENXIO; - } substream->runtime->private_data = port; port->state = STATE_UNKNOWN; @@ -1161,6 +1200,9 @@ static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream) static int snd_usbmidi_output_close(struct snd_rawmidi_substream *substream) { + struct usbmidi_out_port *port = substream->runtime->private_data; + + flush_work(&port->ep->work); return substream_open(substream, 0, 0); } @@ -1178,7 +1220,7 @@ static void snd_usbmidi_output_trigger(struct snd_rawmidi_substream *substream, snd_rawmidi_proceed(substream); return; } - tasklet_schedule(&port->ep->tasklet); + queue_work(system_highpri_wq, &port->ep->work); } } @@ -1210,6 +1252,7 @@ static void snd_usbmidi_output_drain(struct snd_rawmidi_substream *substream) } while (drain_urbs && timeout); finish_wait(&ep->drain_wait, &wait); } + port->active = 0; spin_unlock_irq(&ep->buffer_lock); } @@ -1301,7 +1344,7 @@ static int snd_usbmidi_in_endpoint_create(struct snd_usb_midi *umidi, pipe = usb_rcvintpipe(umidi->dev, ep_info->in_ep); else pipe = usb_rcvbulkpipe(umidi->dev, ep_info->in_ep); - length = usb_maxpacket(umidi->dev, pipe, 0); + length = usb_maxpacket(umidi->dev, pipe); for (i = 0; i < INPUT_URBS; ++i) { buffer = usb_alloc_coherent(umidi->dev, length, GFP_KERNEL, &ep->urbs[i]->transfer_dma); @@ -1332,7 +1375,7 @@ static int snd_usbmidi_in_endpoint_create(struct snd_usb_midi *umidi, error: snd_usbmidi_in_endpoint_delete(ep); - return -ENOMEM; + return err; } /* @@ -1390,7 +1433,7 @@ static int snd_usbmidi_out_endpoint_create(struct snd_usb_midi *umidi, pipe = usb_sndbulkpipe(umidi->dev, ep_info->out_ep); switch (umidi->usb_id) { default: - ep->max_transfer = usb_maxpacket(umidi->dev, pipe, 1); + ep->max_transfer = usb_maxpacket(umidi->dev, pipe); break; /* * Various chips declare a packet size larger than 4 bytes, but @@ -1408,8 +1451,8 @@ static int snd_usbmidi_out_endpoint_create(struct snd_usb_midi *umidi, /* * Some devices only work with 9 bytes packet size: */ - case USB_ID(0x0644, 0x800E): /* Tascam US-122L */ - case USB_ID(0x0644, 0x800F): /* Tascam US-144 */ + case USB_ID(0x0644, 0x800e): /* Tascam US-122L */ + case USB_ID(0x0644, 0x800f): /* Tascam US-144 */ ep->max_transfer = 9; break; } @@ -1441,7 +1484,7 @@ static int snd_usbmidi_out_endpoint_create(struct snd_usb_midi *umidi, } spin_lock_init(&ep->buffer_lock); - tasklet_init(&ep->tasklet, snd_usbmidi_out_tasklet, (unsigned long)ep); + INIT_WORK(&ep->work, snd_usbmidi_out_work); init_waitqueue_head(&ep->drain_wait); for (i = 0; i < 0x10; ++i) @@ -1468,12 +1511,12 @@ static void snd_usbmidi_free(struct snd_usb_midi *umidi) { int i; + if (!umidi->disconnected) + snd_usbmidi_disconnect(&umidi->list); + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { struct snd_usb_midi_endpoint *ep = &umidi->endpoints[i]; - if (ep->out) - snd_usbmidi_out_endpoint_delete(ep->out); - if (ep->in) - snd_usbmidi_in_endpoint_delete(ep->in); + kfree(ep->out); } mutex_destroy(&umidi->mutex); kfree(umidi); @@ -1493,16 +1536,17 @@ void snd_usbmidi_disconnect(struct list_head *p) * a timer may submit an URB. To reliably break the cycle * a flag under lock must be used */ - down_write(&umidi->disc_rwsem); - spin_lock_irq(&umidi->disc_lock); - umidi->disconnected = 1; - spin_unlock_irq(&umidi->disc_lock); - up_write(&umidi->disc_rwsem); + scoped_guard(rwsem_write, &umidi->disc_rwsem) { + guard(spinlock_irq)(&umidi->disc_lock); + umidi->disconnected = 1; + } + + timer_shutdown_sync(&umidi->error_timer); for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { struct snd_usb_midi_endpoint *ep = &umidi->endpoints[i]; if (ep->out) - tasklet_kill(&ep->out->tasklet); + cancel_work_sync(&ep->out->work); if (ep->out) { for (j = 0; j < OUTPUT_URBS; ++j) usb_kill_urb(ep->out->urbs[j].urb); @@ -1525,7 +1569,6 @@ void snd_usbmidi_disconnect(struct list_head *p) ep->in = NULL; } } - del_timer_sync(&umidi->error_timer); } EXPORT_SYMBOL(snd_usbmidi_disconnect); @@ -1755,12 +1798,59 @@ static void snd_usbmidi_get_port_info(struct snd_rawmidi *rmidi, int number, } } +/* return iJack for the corresponding jackID */ +static int find_usb_ijack(struct usb_host_interface *hostif, uint8_t jack_id) +{ + unsigned char *extra = hostif->extra; + int extralen = hostif->extralen; + struct usb_descriptor_header *h; + struct usb_midi_out_jack_descriptor *outjd; + struct usb_midi_in_jack_descriptor *injd; + size_t sz; + + while (extralen > 4) { + h = (struct usb_descriptor_header *)extra; + if (h->bDescriptorType != USB_DT_CS_INTERFACE) + goto next; + + outjd = (struct usb_midi_out_jack_descriptor *)h; + if (h->bLength >= sizeof(*outjd) && + outjd->bDescriptorSubtype == UAC_MIDI_OUT_JACK && + outjd->bJackID == jack_id) { + sz = USB_DT_MIDI_OUT_SIZE(outjd->bNrInputPins); + if (outjd->bLength < sz) + goto next; + return *(extra + sz - 1); + } + + injd = (struct usb_midi_in_jack_descriptor *)h; + if (injd->bLength >= sizeof(*injd) && + injd->bDescriptorSubtype == UAC_MIDI_IN_JACK && + injd->bJackID == jack_id) + return injd->iJack; + +next: + if (!extra[0]) + break; + extralen -= extra[0]; + extra += extra[0]; + } + return 0; +} + static void snd_usbmidi_init_substream(struct snd_usb_midi *umidi, - int stream, int number, + int stream, int number, int jack_id, struct snd_rawmidi_substream **rsubstream) { struct port_info *port_info; const char *name_format; + struct usb_interface *intf; + struct usb_host_interface *hostif; + uint8_t jack_name_buf[32]; + uint8_t *default_jack_name = "MIDI"; + uint8_t *jack_name = default_jack_name; + uint8_t iJack; + int res; struct snd_rawmidi_substream *substream = snd_usbmidi_find_substream(umidi, stream, number); @@ -1770,11 +1860,31 @@ static void snd_usbmidi_init_substream(struct snd_usb_midi *umidi, return; } - /* TODO: read port name from jack descriptor */ + intf = umidi->iface; + if (intf && jack_id >= 0) { + hostif = intf->cur_altsetting; + iJack = find_usb_ijack(hostif, jack_id); + if (iJack != 0) { + res = usb_string(umidi->dev, iJack, jack_name_buf, + ARRAY_SIZE(jack_name_buf)); + if (res) + jack_name = jack_name_buf; + } + } + port_info = find_port_info(umidi, number); - name_format = port_info ? port_info->name : "%s MIDI %d"; - snprintf(substream->name, sizeof(substream->name), - name_format, umidi->card->shortname, number + 1); + if (port_info || jack_name == default_jack_name || + strncmp(umidi->card->shortname, jack_name, strlen(umidi->card->shortname)) != 0) { + name_format = port_info ? port_info->name : + (jack_name != default_jack_name ? "%s %s" : "%s %s %d"); + snprintf(substream->name, sizeof(substream->name), + name_format, umidi->card->shortname, jack_name, number + 1); + } else { + /* The manufacturer included the iProduct name in the jack + * name, do not use both + */ + strscpy(substream->name, jack_name); + } *rsubstream = substream; } @@ -1809,6 +1919,7 @@ static int snd_usbmidi_create_endpoints(struct snd_usb_midi *umidi, snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_OUTPUT, out_ports, + endpoints[i].assoc_out_jacks[j], &umidi->endpoints[i].out->ports[j].substream); ++out_ports; } @@ -1816,6 +1927,7 @@ static int snd_usbmidi_create_endpoints(struct snd_usb_midi *umidi, snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_INPUT, in_ports, + endpoints[i].assoc_in_jacks[j], &umidi->endpoints[i].in->ports[j].substream); ++in_ports; } @@ -1826,6 +1938,28 @@ static int snd_usbmidi_create_endpoints(struct snd_usb_midi *umidi, return 0; } +static struct usb_ms_endpoint_descriptor *find_usb_ms_endpoint_descriptor( + struct usb_host_endpoint *hostep) +{ + unsigned char *extra = hostep->extra; + int extralen = hostep->extralen; + + while (extralen > 3) { + struct usb_ms_endpoint_descriptor *ms_ep = + (struct usb_ms_endpoint_descriptor *)extra; + + if (ms_ep->bLength > 3 && + ms_ep->bDescriptorType == USB_DT_CS_ENDPOINT && + ms_ep->bDescriptorSubtype == UAC_MS_GENERAL) + return ms_ep; + if (!extra[0]) + break; + extralen -= extra[0]; + extra += extra[0]; + } + return NULL; +} + /* * Returns MIDIStreaming device capabilities. */ @@ -1839,7 +1973,7 @@ static int snd_usbmidi_get_ms_info(struct snd_usb_midi *umidi, struct usb_host_endpoint *hostep; struct usb_endpoint_descriptor *ep; struct usb_ms_endpoint_descriptor *ms_ep; - int i, epidx; + int i, j, epidx; intf = umidi->iface; if (!intf) @@ -1852,7 +1986,7 @@ static int snd_usbmidi_get_ms_info(struct snd_usb_midi *umidi, ms_header->bDescriptorType == USB_DT_CS_INTERFACE && ms_header->bDescriptorSubtype == UAC_HEADER) dev_dbg(&umidi->dev->dev, "MIDIStreaming version %02x.%02x\n", - ms_header->bcdMSC[1], ms_header->bcdMSC[0]); + ((uint8_t *)&ms_header->bcdMSC)[1], ((uint8_t *)&ms_header->bcdMSC)[0]); else dev_warn(&umidi->dev->dev, "MIDIStreaming interface descriptor not found\n"); @@ -1863,11 +1997,14 @@ static int snd_usbmidi_get_ms_info(struct snd_usb_midi *umidi, ep = get_ep_desc(hostep); if (!usb_endpoint_xfer_bulk(ep) && !usb_endpoint_xfer_int(ep)) continue; - ms_ep = (struct usb_ms_endpoint_descriptor *)hostep->extra; - if (hostep->extralen < 4 || - ms_ep->bLength < 4 || - ms_ep->bDescriptorType != USB_DT_CS_ENDPOINT || - ms_ep->bDescriptorSubtype != UAC_MS_GENERAL) + ms_ep = find_usb_ms_endpoint_descriptor(hostep); + if (!ms_ep) + continue; + if (ms_ep->bLength <= sizeof(*ms_ep)) + continue; + if (ms_ep->bNumEmbMIDIJack > 0x10) + continue; + if (ms_ep->bLength < sizeof(*ms_ep) + ms_ep->bNumEmbMIDIJack) continue; if (usb_endpoint_dir_out(ep)) { if (endpoints[epidx].out_ep) { @@ -1889,6 +2026,10 @@ static int snd_usbmidi_get_ms_info(struct snd_usb_midi *umidi, endpoints[epidx].out_interval = 1; endpoints[epidx].out_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1; + for (j = 0; j < ms_ep->bNumEmbMIDIJack; ++j) + endpoints[epidx].assoc_out_jacks[j] = ms_ep->baAssocJackID[j]; + for (; j < ARRAY_SIZE(endpoints[epidx].assoc_out_jacks); ++j) + endpoints[epidx].assoc_out_jacks[j] = -1; dev_dbg(&umidi->dev->dev, "EP %02X: %d jack(s)\n", ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack); } else { @@ -1906,6 +2047,10 @@ static int snd_usbmidi_get_ms_info(struct snd_usb_midi *umidi, endpoints[epidx].in_interval = 1; endpoints[epidx].in_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1; + for (j = 0; j < ms_ep->bNumEmbMIDIJack; ++j) + endpoints[epidx].assoc_in_jacks[j] = ms_ep->baAssocJackID[j]; + for (; j < ARRAY_SIZE(endpoints[epidx].assoc_in_jacks); ++j) + endpoints[epidx].assoc_in_jacks[j] = -1; dev_dbg(&umidi->dev->dev, "EP %02X: %d jack(s)\n", ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack); } @@ -1931,16 +2076,15 @@ static int roland_load_get(struct snd_kcontrol *kcontrol, static int roland_load_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *value) { - struct snd_usb_midi *umidi = kcontrol->private_data; + struct snd_usb_midi *umidi = snd_kcontrol_chip(kcontrol); int changed; if (value->value.enumerated.item[0] > 1) return -EINVAL; - mutex_lock(&umidi->mutex); + guard(mutex)(&umidi->mutex); changed = value->value.enumerated.item[0] != kcontrol->private_value; if (changed) kcontrol->private_value = value->value.enumerated.item[0]; - mutex_unlock(&umidi->mutex); return changed; } @@ -2121,6 +2265,8 @@ static int snd_usbmidi_detect_roland(struct snd_usb_midi *umidi, cs_desc[1] == USB_DT_CS_INTERFACE && cs_desc[2] == 0xf1 && cs_desc[3] == 0x02) { + if (cs_desc[4] > 0x10 || cs_desc[5] > 0x10) + continue; endpoint->in_cables = (1 << cs_desc[4]) - 1; endpoint->out_cables = (1 << cs_desc[5]) - 1; return snd_usbmidi_detect_endpoints(umidi, endpoint, 1); @@ -2220,11 +2366,13 @@ static int snd_usbmidi_create_endpoints_midiman(struct snd_usb_midi *umidi, snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_OUTPUT, cable, + -1 /* prevent trying to find jack */, &umidi->endpoints[cable & 1].out->ports[cable].substream); if (endpoint->in_cables & (1 << cable)) snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_INPUT, cable, + -1 /* prevent trying to find jack */, &umidi->endpoints[0].in->ports[cable].substream); } return 0; @@ -2245,7 +2393,7 @@ static int snd_usbmidi_create_rawmidi(struct snd_usb_midi *umidi, out_ports, in_ports, &rmidi); if (err < 0) return err; - strcpy(rmidi->name, umidi->card->shortname); + strscpy(rmidi->name, umidi->card->shortname); rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; @@ -2282,7 +2430,8 @@ void snd_usbmidi_input_stop(struct list_head *p) } EXPORT_SYMBOL(snd_usbmidi_input_stop); -static void snd_usbmidi_input_start_ep(struct snd_usb_midi_in_endpoint *ep) +static void snd_usbmidi_input_start_ep(struct snd_usb_midi *umidi, + struct snd_usb_midi_in_endpoint *ep) { unsigned int i; @@ -2290,8 +2439,12 @@ static void snd_usbmidi_input_start_ep(struct snd_usb_midi_in_endpoint *ep) return; for (i = 0; i < INPUT_URBS; ++i) { struct urb *urb = ep->urbs[i]; - urb->dev = ep->umidi->dev; - snd_usbmidi_submit_urb(urb, GFP_KERNEL); + scoped_guard(spinlock_irqsave, &umidi->disc_lock) { + if (!atomic_read(&urb->use_count)) { + urb->dev = ep->umidi->dev; + snd_usbmidi_submit_urb(urb, GFP_ATOMIC); + } + } } } @@ -2307,7 +2460,7 @@ void snd_usbmidi_input_start(struct list_head *p) if (umidi->input_running || !umidi->opened[1]) return; for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) - snd_usbmidi_input_start_ep(umidi->endpoints[i].in); + snd_usbmidi_input_start_ep(umidi, umidi->endpoints[i].in); umidi->input_running = 1; } EXPORT_SYMBOL(snd_usbmidi_input_start); @@ -2320,9 +2473,8 @@ void snd_usbmidi_suspend(struct list_head *p) struct snd_usb_midi *umidi; umidi = list_entry(p, struct snd_usb_midi, list); - mutex_lock(&umidi->mutex); + guard(mutex)(&umidi->mutex); snd_usbmidi_input_stop(p); - mutex_unlock(&umidi->mutex); } EXPORT_SYMBOL(snd_usbmidi_suspend); @@ -2334,9 +2486,8 @@ void snd_usbmidi_resume(struct list_head *p) struct snd_usb_midi *umidi; umidi = list_entry(p, struct snd_usb_midi, list); - mutex_lock(&umidi->mutex); + guard(mutex)(&umidi->mutex); snd_usbmidi_input_start(p); - mutex_unlock(&umidi->mutex); } EXPORT_SYMBOL(snd_usbmidi_resume); @@ -2347,7 +2498,8 @@ int __snd_usbmidi_create(struct snd_card *card, struct usb_interface *iface, struct list_head *midi_list, const struct snd_usb_audio_quirk *quirk, - unsigned int usb_id) + unsigned int usb_id, + unsigned int *num_rawmidis) { struct snd_usb_midi *umidi; struct snd_usb_midi_endpoint_info endpoints[MIDI_MAX_ENDPOINTS]; @@ -2362,6 +2514,8 @@ int __snd_usbmidi_create(struct snd_card *card, umidi->iface = iface; umidi->quirk = quirk; umidi->usb_protocol_ops = &snd_usbmidi_standard_ops; + if (num_rawmidis) + umidi->next_midi_device = *num_rawmidis; spin_lock_init(&umidi->disc_lock); init_rwsem(&umidi->disc_rwsem); mutex_init(&umidi->mutex); @@ -2382,7 +2536,7 @@ int __snd_usbmidi_create(struct snd_card *card, break; case QUIRK_MIDI_US122L: umidi->usb_protocol_ops = &snd_usbmidi_122l_ops; - /* fall through */ + fallthrough; case QUIRK_MIDI_FIXED_ENDPOINT: memcpy(&endpoints[0], quirk->data, sizeof(struct snd_usb_midi_endpoint_info)); @@ -2481,6 +2635,8 @@ int __snd_usbmidi_create(struct snd_card *card, usb_autopm_get_interface_no_resume(umidi->iface); list_add_tail(&umidi->list, midi_list); + if (num_rawmidis) + *num_rawmidis = umidi->next_midi_device; return 0; free_midi: diff --git a/sound/usb/midi.h b/sound/usb/midi.h index 8c38aec22999..2100f1486b03 100644 --- a/sound/usb/midi.h +++ b/sound/usb/midi.h @@ -13,6 +13,8 @@ struct snd_usb_midi_endpoint_info { uint8_t in_interval; uint16_t out_cables; /* bitmask */ uint16_t in_cables; /* bitmask */ + int16_t assoc_in_jacks[16]; + int16_t assoc_out_jacks[16]; }; /* for QUIRK_MIDI_YAMAHA, data is NULL */ @@ -44,14 +46,15 @@ int __snd_usbmidi_create(struct snd_card *card, struct usb_interface *iface, struct list_head *midi_list, const struct snd_usb_audio_quirk *quirk, - unsigned int usb_id); + unsigned int usb_id, + unsigned int *num_rawmidis); static inline int snd_usbmidi_create(struct snd_card *card, struct usb_interface *iface, struct list_head *midi_list, const struct snd_usb_audio_quirk *quirk) { - return __snd_usbmidi_create(card, iface, midi_list, quirk, 0); + return __snd_usbmidi_create(card, iface, midi_list, quirk, 0, NULL); } void snd_usbmidi_input_stop(struct list_head *p); diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c new file mode 100644 index 000000000000..e6793f3bdfc3 --- /dev/null +++ b/sound/usb/midi2.c @@ -0,0 +1,1233 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MIDI 2.0 support + */ + +#include <linux/bitops.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/wait.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/usb/audio.h> +#include <linux/usb/midi.h> +#include <linux/usb/midi-v2.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/ump.h> +#include "usbaudio.h" +#include "midi.h" +#include "midi2.h" +#include "helper.h" + +static bool midi2_enable = true; +module_param(midi2_enable, bool, 0444); +MODULE_PARM_DESC(midi2_enable, "Enable MIDI 2.0 support."); + +static bool midi2_ump_probe = true; +module_param(midi2_ump_probe, bool, 0444); +MODULE_PARM_DESC(midi2_ump_probe, "Probe UMP v1.1 support at first."); + +/* stream direction; just shorter names */ +enum { + STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT, + STR_IN = SNDRV_RAWMIDI_STREAM_INPUT +}; + +#define NUM_URBS 8 + +struct snd_usb_midi2_urb; +struct snd_usb_midi2_endpoint; +struct snd_usb_midi2_ump; +struct snd_usb_midi2_interface; + +/* URB context */ +struct snd_usb_midi2_urb { + struct urb *urb; + struct snd_usb_midi2_endpoint *ep; + unsigned int index; /* array index */ +}; + +/* A USB MIDI input/output endpoint */ +struct snd_usb_midi2_endpoint { + struct usb_device *dev; + const struct usb_ms20_endpoint_descriptor *ms_ep; /* reference to EP descriptor */ + struct snd_usb_midi2_endpoint *pair; /* bidirectional pair EP */ + struct snd_usb_midi2_ump *rmidi; /* assigned UMP EP pair */ + struct snd_ump_endpoint *ump; /* assigned UMP EP */ + int direction; /* direction (STR_IN/OUT) */ + unsigned int endpoint; /* EP number */ + unsigned int pipe; /* URB pipe */ + unsigned int packets; /* packet buffer size in bytes */ + unsigned int interval; /* interval for INT EP */ + wait_queue_head_t wait; /* URB waiter */ + spinlock_t lock; /* URB locking */ + struct snd_rawmidi_substream *substream; /* NULL when closed */ + unsigned int num_urbs; /* number of allocated URBs */ + unsigned long urb_free; /* bitmap for free URBs */ + unsigned long urb_free_mask; /* bitmask for free URBs */ + atomic_t running; /* running status */ + atomic_t suspended; /* saved running status for suspend */ + bool disconnected; /* shadow of umidi->disconnected */ + struct list_head list; /* list to umidi->ep_list */ + struct snd_usb_midi2_urb urbs[NUM_URBS]; +}; + +/* A UMP endpoint - one or two USB MIDI endpoints are assigned */ +struct snd_usb_midi2_ump { + struct usb_device *dev; + struct snd_usb_midi2_interface *umidi; /* reference to MIDI iface */ + struct snd_ump_endpoint *ump; /* assigned UMP EP object */ + struct snd_usb_midi2_endpoint *eps[2]; /* USB MIDI endpoints */ + int index; /* rawmidi device index */ + unsigned char usb_block_id; /* USB GTB id used for finding a pair */ + bool ump_parsed; /* Parsed UMP 1.1 EP/FB info*/ + struct list_head list; /* list to umidi->rawmidi_list */ +}; + +/* top-level instance per USB MIDI interface */ +struct snd_usb_midi2_interface { + struct snd_usb_audio *chip; /* assigned USB-audio card */ + struct usb_interface *iface; /* assigned USB interface */ + struct usb_host_interface *hostif; + const char *blk_descs; /* group terminal block descriptors */ + unsigned int blk_desc_size; /* size of GTB descriptors */ + bool disconnected; + struct list_head ep_list; /* list of endpoints */ + struct list_head rawmidi_list; /* list of UMP rawmidis */ + struct list_head list; /* list to chip->midi_v2_list */ +}; + +/* submit URBs as much as possible; used for both input and output */ +static void do_submit_urbs_locked(struct snd_usb_midi2_endpoint *ep, + int (*prepare)(struct snd_usb_midi2_endpoint *, + struct urb *)) +{ + struct snd_usb_midi2_urb *ctx; + int index, err = 0; + + if (ep->disconnected) + return; + + while (ep->urb_free) { + index = find_first_bit(&ep->urb_free, ep->num_urbs); + if (index >= ep->num_urbs) + return; + ctx = &ep->urbs[index]; + err = prepare(ep, ctx->urb); + if (err < 0) + return; + if (!ctx->urb->transfer_buffer_length) + return; + ctx->urb->dev = ep->dev; + err = usb_submit_urb(ctx->urb, GFP_ATOMIC); + if (err < 0) { + dev_dbg(&ep->dev->dev, + "usb_submit_urb error %d\n", err); + return; + } + clear_bit(index, &ep->urb_free); + } +} + +/* prepare for output submission: copy from rawmidi buffer to urb packet */ +static int prepare_output_urb(struct snd_usb_midi2_endpoint *ep, + struct urb *urb) +{ + int count; + + count = snd_ump_transmit(ep->ump, urb->transfer_buffer, + ep->packets); + if (count < 0) { + dev_dbg(&ep->dev->dev, "rawmidi transmit error %d\n", count); + return count; + } + cpu_to_le32_array((u32 *)urb->transfer_buffer, count >> 2); + urb->transfer_buffer_length = count; + return 0; +} + +static void submit_output_urbs_locked(struct snd_usb_midi2_endpoint *ep) +{ + do_submit_urbs_locked(ep, prepare_output_urb); +} + +/* URB completion for output; re-filling and re-submit */ +static void output_urb_complete(struct urb *urb) +{ + struct snd_usb_midi2_urb *ctx = urb->context; + struct snd_usb_midi2_endpoint *ep = ctx->ep; + + guard(spinlock_irqsave)(&ep->lock); + set_bit(ctx->index, &ep->urb_free); + if (urb->status >= 0 && atomic_read(&ep->running)) + submit_output_urbs_locked(ep); + if (ep->urb_free == ep->urb_free_mask) + wake_up(&ep->wait); +} + +/* prepare for input submission: just set the buffer length */ +static int prepare_input_urb(struct snd_usb_midi2_endpoint *ep, + struct urb *urb) +{ + urb->transfer_buffer_length = ep->packets; + return 0; +} + +static void submit_input_urbs_locked(struct snd_usb_midi2_endpoint *ep) +{ + do_submit_urbs_locked(ep, prepare_input_urb); +} + +/* URB completion for input; copy into rawmidi buffer and resubmit */ +static void input_urb_complete(struct urb *urb) +{ + struct snd_usb_midi2_urb *ctx = urb->context; + struct snd_usb_midi2_endpoint *ep = ctx->ep; + int len; + + guard(spinlock_irqsave)(&ep->lock); + if (ep->disconnected || urb->status < 0) + goto dequeue; + len = urb->actual_length; + len &= ~3; /* align UMP */ + if (len > ep->packets) + len = ep->packets; + if (len > 0) { + le32_to_cpu_array((u32 *)urb->transfer_buffer, len >> 2); + snd_ump_receive(ep->ump, (u32 *)urb->transfer_buffer, len); + } + dequeue: + set_bit(ctx->index, &ep->urb_free); + submit_input_urbs_locked(ep); + if (ep->urb_free == ep->urb_free_mask) + wake_up(&ep->wait); +} + +/* URB submission helper; for both direction */ +static void submit_io_urbs(struct snd_usb_midi2_endpoint *ep) +{ + if (!ep) + return; + guard(spinlock_irqsave)(&ep->lock); + if (ep->direction == STR_IN) + submit_input_urbs_locked(ep); + else + submit_output_urbs_locked(ep); +} + +/* kill URBs for close, suspend and disconnect */ +static void kill_midi_urbs(struct snd_usb_midi2_endpoint *ep, bool suspending) +{ + int i; + + if (!ep) + return; + if (suspending) + ep->suspended = ep->running; + atomic_set(&ep->running, 0); + for (i = 0; i < ep->num_urbs; i++) { + if (!ep->urbs[i].urb) + break; + usb_kill_urb(ep->urbs[i].urb); + } +} + +/* wait until all URBs get freed */ +static void drain_urb_queue(struct snd_usb_midi2_endpoint *ep) +{ + if (!ep) + return; + guard(spinlock_irq)(&ep->lock); + atomic_set(&ep->running, 0); + wait_event_lock_irq_timeout(ep->wait, + ep->disconnected || + ep->urb_free == ep->urb_free_mask, + ep->lock, msecs_to_jiffies(500)); +} + +/* release URBs for an EP */ +static void free_midi_urbs(struct snd_usb_midi2_endpoint *ep) +{ + struct snd_usb_midi2_urb *ctx; + int i; + + if (!ep) + return; + for (i = 0; i < NUM_URBS; ++i) { + ctx = &ep->urbs[i]; + if (!ctx->urb) + break; + usb_free_coherent(ep->dev, ep->packets, + ctx->urb->transfer_buffer, + ctx->urb->transfer_dma); + usb_free_urb(ctx->urb); + ctx->urb = NULL; + } + ep->num_urbs = 0; +} + +/* allocate URBs for an EP */ +/* the callers should handle allocation errors via free_midi_urbs() */ +static int alloc_midi_urbs(struct snd_usb_midi2_endpoint *ep) +{ + struct snd_usb_midi2_urb *ctx; + void (*comp)(struct urb *urb); + void *buffer; + int i, err; + int endpoint, len; + + endpoint = ep->endpoint; + len = ep->packets; + if (ep->direction == STR_IN) + comp = input_urb_complete; + else + comp = output_urb_complete; + + ep->num_urbs = 0; + ep->urb_free = ep->urb_free_mask = 0; + for (i = 0; i < NUM_URBS; i++) { + ctx = &ep->urbs[i]; + ctx->index = i; + ctx->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!ctx->urb) { + dev_err(&ep->dev->dev, "URB alloc failed\n"); + return -ENOMEM; + } + ctx->ep = ep; + buffer = usb_alloc_coherent(ep->dev, len, GFP_KERNEL, + &ctx->urb->transfer_dma); + if (!buffer) { + dev_err(&ep->dev->dev, + "URB buffer alloc failed (size %d)\n", len); + return -ENOMEM; + } + if (ep->interval) + usb_fill_int_urb(ctx->urb, ep->dev, ep->pipe, + buffer, len, comp, ctx, ep->interval); + else + usb_fill_bulk_urb(ctx->urb, ep->dev, ep->pipe, + buffer, len, comp, ctx); + err = usb_urb_ep_type_check(ctx->urb); + if (err < 0) { + dev_err(&ep->dev->dev, "invalid MIDI EP %x\n", + endpoint); + return err; + } + ctx->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + ep->num_urbs++; + } + ep->urb_free = ep->urb_free_mask = GENMASK(ep->num_urbs - 1, 0); + return 0; +} + +static struct snd_usb_midi2_endpoint * +ump_to_endpoint(struct snd_ump_endpoint *ump, int dir) +{ + struct snd_usb_midi2_ump *rmidi = ump->private_data; + + return rmidi->eps[dir]; +} + +/* ump open callback */ +static int snd_usb_midi_v2_open(struct snd_ump_endpoint *ump, int dir) +{ + struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir); + int err = 0; + + if (!ep || !ep->endpoint) + return -ENODEV; + if (ep->disconnected) + return -EIO; + if (ep->direction == STR_OUT) { + err = alloc_midi_urbs(ep); + if (err) { + free_midi_urbs(ep); + return err; + } + } + return 0; +} + +/* ump close callback */ +static void snd_usb_midi_v2_close(struct snd_ump_endpoint *ump, int dir) +{ + struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir); + + if (ep->direction == STR_OUT) { + kill_midi_urbs(ep, false); + drain_urb_queue(ep); + free_midi_urbs(ep); + } +} + +/* ump trigger callback */ +static void snd_usb_midi_v2_trigger(struct snd_ump_endpoint *ump, int dir, + int up) +{ + struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir); + + atomic_set(&ep->running, up); + if (up && ep->direction == STR_OUT && !ep->disconnected) + submit_io_urbs(ep); +} + +/* ump drain callback */ +static void snd_usb_midi_v2_drain(struct snd_ump_endpoint *ump, int dir) +{ + struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir); + + drain_urb_queue(ep); +} + +/* allocate and start all input streams */ +static int start_input_streams(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_endpoint *ep; + int err; + + list_for_each_entry(ep, &umidi->ep_list, list) { + if (ep->direction == STR_IN) { + err = alloc_midi_urbs(ep); + if (err < 0) + goto error; + } + } + + list_for_each_entry(ep, &umidi->ep_list, list) { + if (ep->direction == STR_IN) + submit_io_urbs(ep); + } + + return 0; + + error: + list_for_each_entry(ep, &umidi->ep_list, list) { + if (ep->direction == STR_IN) + free_midi_urbs(ep); + } + + return err; +} + +static const struct snd_ump_ops snd_usb_midi_v2_ump_ops = { + .open = snd_usb_midi_v2_open, + .close = snd_usb_midi_v2_close, + .trigger = snd_usb_midi_v2_trigger, + .drain = snd_usb_midi_v2_drain, +}; + +/* create a USB MIDI 2.0 endpoint object */ +static int create_midi2_endpoint(struct snd_usb_midi2_interface *umidi, + struct usb_host_endpoint *hostep, + const struct usb_ms20_endpoint_descriptor *ms_ep) +{ + struct snd_usb_midi2_endpoint *ep; + int endpoint, dir; + + usb_audio_dbg(umidi->chip, "Creating an EP 0x%02x, #GTB=%d\n", + hostep->desc.bEndpointAddress, + ms_ep->bNumGrpTrmBlock); + + ep = kzalloc(sizeof(*ep), GFP_KERNEL); + if (!ep) + return -ENOMEM; + + spin_lock_init(&ep->lock); + init_waitqueue_head(&ep->wait); + ep->dev = umidi->chip->dev; + endpoint = hostep->desc.bEndpointAddress; + dir = (endpoint & USB_DIR_IN) ? STR_IN : STR_OUT; + + ep->endpoint = endpoint; + ep->direction = dir; + ep->ms_ep = ms_ep; + if (usb_endpoint_xfer_int(&hostep->desc)) + ep->interval = hostep->desc.bInterval; + else + ep->interval = 0; + if (dir == STR_IN) { + if (ep->interval) + ep->pipe = usb_rcvintpipe(ep->dev, endpoint); + else + ep->pipe = usb_rcvbulkpipe(ep->dev, endpoint); + } else { + if (ep->interval) + ep->pipe = usb_sndintpipe(ep->dev, endpoint); + else + ep->pipe = usb_sndbulkpipe(ep->dev, endpoint); + } + ep->packets = usb_maxpacket(ep->dev, ep->pipe); + list_add_tail(&ep->list, &umidi->ep_list); + + return 0; +} + +/* destructor for endpoint; from snd_usb_midi_v2_free() */ +static void free_midi2_endpoint(struct snd_usb_midi2_endpoint *ep) +{ + list_del(&ep->list); + free_midi_urbs(ep); + kfree(ep); +} + +/* call all endpoint destructors */ +static void free_all_midi2_endpoints(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_endpoint *ep; + + while (!list_empty(&umidi->ep_list)) { + ep = list_first_entry(&umidi->ep_list, + struct snd_usb_midi2_endpoint, list); + free_midi2_endpoint(ep); + } +} + +/* find a MIDI STREAMING descriptor with a given subtype */ +static void *find_usb_ms_endpoint_descriptor(struct usb_host_endpoint *hostep, + unsigned char subtype) +{ + unsigned char *extra = hostep->extra; + int extralen = hostep->extralen; + + while (extralen > 3) { + struct usb_ms_endpoint_descriptor *ms_ep = + (struct usb_ms_endpoint_descriptor *)extra; + + if (ms_ep->bLength > 3 && + ms_ep->bDescriptorType == USB_DT_CS_ENDPOINT && + ms_ep->bDescriptorSubtype == subtype) + return ms_ep; + if (!extra[0]) + break; + extralen -= extra[0]; + extra += extra[0]; + } + return NULL; +} + +/* get the full group terminal block descriptors and return the size */ +static int get_group_terminal_block_descs(struct snd_usb_midi2_interface *umidi) +{ + struct usb_host_interface *hostif = umidi->hostif; + struct usb_device *dev = umidi->chip->dev; + struct usb_ms20_gr_trm_block_header_descriptor header = { 0 }; + unsigned char *data; + int err, size; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_TYPE_STANDARD | USB_DIR_IN, + USB_DT_CS_GR_TRM_BLOCK << 8 | hostif->desc.bAlternateSetting, + hostif->desc.bInterfaceNumber, + &header, sizeof(header)); + if (err < 0) + return err; + size = __le16_to_cpu(header.wTotalLength); + if (!size) { + dev_err(&dev->dev, "Failed to get GTB descriptors for %d:%d\n", + hostif->desc.bInterfaceNumber, hostif->desc.bAlternateSetting); + return -EINVAL; + } + + data = kzalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_TYPE_STANDARD | USB_DIR_IN, + USB_DT_CS_GR_TRM_BLOCK << 8 | hostif->desc.bAlternateSetting, + hostif->desc.bInterfaceNumber, data, size); + if (err < 0) { + kfree(data); + return err; + } + + umidi->blk_descs = data; + umidi->blk_desc_size = size; + return 0; +} + +/* find the corresponding group terminal block descriptor */ +static const struct usb_ms20_gr_trm_block_descriptor * +find_group_terminal_block(struct snd_usb_midi2_interface *umidi, int id) +{ + const unsigned char *data = umidi->blk_descs; + int size = umidi->blk_desc_size; + const struct usb_ms20_gr_trm_block_descriptor *desc; + + size -= sizeof(struct usb_ms20_gr_trm_block_header_descriptor); + data += sizeof(struct usb_ms20_gr_trm_block_header_descriptor); + while (size > 0 && *data && *data <= size) { + desc = (const struct usb_ms20_gr_trm_block_descriptor *)data; + if (desc->bLength >= sizeof(*desc) && + desc->bDescriptorType == USB_DT_CS_GR_TRM_BLOCK && + desc->bDescriptorSubtype == USB_MS_GR_TRM_BLOCK && + desc->bGrpTrmBlkID == id) + return desc; + size -= *data; + data += *data; + } + + return NULL; +} + +/* fill up the information from GTB */ +static int parse_group_terminal_block(struct snd_usb_midi2_ump *rmidi, + const struct usb_ms20_gr_trm_block_descriptor *desc) +{ + struct snd_ump_endpoint *ump = rmidi->ump; + unsigned int protocol, protocol_caps; + + /* set default protocol */ + switch (desc->bMIDIProtocol) { + case USB_MS_MIDI_PROTO_1_0_64: + case USB_MS_MIDI_PROTO_1_0_64_JRTS: + case USB_MS_MIDI_PROTO_1_0_128: + case USB_MS_MIDI_PROTO_1_0_128_JRTS: + protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI1; + break; + case USB_MS_MIDI_PROTO_2_0: + case USB_MS_MIDI_PROTO_2_0_JRTS: + protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI2; + break; + default: + return 0; + } + + if (!ump->info.protocol) + ump->info.protocol = protocol; + + protocol_caps = protocol; + switch (desc->bMIDIProtocol) { + case USB_MS_MIDI_PROTO_1_0_64_JRTS: + case USB_MS_MIDI_PROTO_1_0_128_JRTS: + case USB_MS_MIDI_PROTO_2_0_JRTS: + protocol_caps |= SNDRV_UMP_EP_INFO_PROTO_JRTS_TX | + SNDRV_UMP_EP_INFO_PROTO_JRTS_RX; + break; + } + + ump->info.protocol_caps |= protocol_caps; + return 0; +} + +/* allocate and parse for each assigned group terminal block */ +static int parse_group_terminal_blocks(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_ump *rmidi; + const struct usb_ms20_gr_trm_block_descriptor *desc; + int err; + + err = get_group_terminal_block_descs(umidi); + if (err < 0) + return err; + if (!umidi->blk_descs) + return 0; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + desc = find_group_terminal_block(umidi, rmidi->usb_block_id); + if (!desc) + continue; + err = parse_group_terminal_block(rmidi, desc); + if (err < 0) + return err; + } + + return 0; +} + +/* parse endpoints included in the given interface and create objects */ +static int parse_midi_2_0_endpoints(struct snd_usb_midi2_interface *umidi) +{ + struct usb_host_interface *hostif = umidi->hostif; + struct usb_host_endpoint *hostep; + struct usb_ms20_endpoint_descriptor *ms_ep; + int i, err; + + for (i = 0; i < hostif->desc.bNumEndpoints; i++) { + hostep = &hostif->endpoint[i]; + if (!usb_endpoint_xfer_bulk(&hostep->desc) && + !usb_endpoint_xfer_int(&hostep->desc)) + continue; + ms_ep = find_usb_ms_endpoint_descriptor(hostep, USB_MS_GENERAL_2_0); + if (!ms_ep) + continue; + if (ms_ep->bLength <= sizeof(*ms_ep)) + continue; + if (!ms_ep->bNumGrpTrmBlock) + continue; + if (ms_ep->bLength < sizeof(*ms_ep) + ms_ep->bNumGrpTrmBlock) + continue; + err = create_midi2_endpoint(umidi, hostep, ms_ep); + if (err < 0) + return err; + } + return 0; +} + +static void free_all_midi2_umps(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_ump *rmidi; + + while (!list_empty(&umidi->rawmidi_list)) { + rmidi = list_first_entry(&umidi->rawmidi_list, + struct snd_usb_midi2_ump, list); + list_del(&rmidi->list); + kfree(rmidi); + } +} + +static int create_midi2_ump(struct snd_usb_midi2_interface *umidi, + struct snd_usb_midi2_endpoint *ep_in, + struct snd_usb_midi2_endpoint *ep_out, + int blk_id) +{ + struct snd_usb_midi2_ump *rmidi; + struct snd_ump_endpoint *ump; + int input, output; + char idstr[16]; + int err; + + rmidi = kzalloc(sizeof(*rmidi), GFP_KERNEL); + if (!rmidi) + return -ENOMEM; + INIT_LIST_HEAD(&rmidi->list); + rmidi->dev = umidi->chip->dev; + rmidi->umidi = umidi; + rmidi->usb_block_id = blk_id; + + rmidi->index = umidi->chip->num_rawmidis; + snprintf(idstr, sizeof(idstr), "UMP %d", rmidi->index); + input = ep_in ? 1 : 0; + output = ep_out ? 1 : 0; + err = snd_ump_endpoint_new(umidi->chip->card, idstr, rmidi->index, + output, input, &ump); + if (err < 0) { + usb_audio_dbg(umidi->chip, "Failed to create a UMP object\n"); + kfree(rmidi); + return err; + } + + rmidi->ump = ump; + umidi->chip->num_rawmidis++; + + ump->private_data = rmidi; + ump->ops = &snd_usb_midi_v2_ump_ops; + + rmidi->eps[STR_IN] = ep_in; + rmidi->eps[STR_OUT] = ep_out; + if (ep_in) { + ep_in->pair = ep_out; + ep_in->rmidi = rmidi; + ep_in->ump = ump; + } + if (ep_out) { + ep_out->pair = ep_in; + ep_out->rmidi = rmidi; + ep_out->ump = ump; + } + + list_add_tail(&rmidi->list, &umidi->rawmidi_list); + return 0; +} + +/* find the UMP EP with the given USB block id */ +static struct snd_usb_midi2_ump * +find_midi2_ump(struct snd_usb_midi2_interface *umidi, int blk_id) +{ + struct snd_usb_midi2_ump *rmidi; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + if (rmidi->usb_block_id == blk_id) + return rmidi; + } + return NULL; +} + +/* look for the matching output endpoint and create UMP object if found */ +static int find_matching_ep_partner(struct snd_usb_midi2_interface *umidi, + struct snd_usb_midi2_endpoint *ep, + int blk_id) +{ + struct snd_usb_midi2_endpoint *pair_ep; + int blk; + + usb_audio_dbg(umidi->chip, "Looking for a pair for EP-in 0x%02x\n", + ep->endpoint); + list_for_each_entry(pair_ep, &umidi->ep_list, list) { + if (pair_ep->direction != STR_OUT) + continue; + if (pair_ep->pair) + continue; /* already paired */ + for (blk = 0; blk < pair_ep->ms_ep->bNumGrpTrmBlock; blk++) { + if (pair_ep->ms_ep->baAssoGrpTrmBlkID[blk] == blk_id) { + usb_audio_dbg(umidi->chip, + "Found a match with EP-out 0x%02x blk %d\n", + pair_ep->endpoint, blk); + return create_midi2_ump(umidi, ep, pair_ep, blk_id); + } + } + } + return 0; +} + +/* Call UMP helper to parse UMP endpoints; + * this needs to be called after starting the input streams for bi-directional + * communications + */ +static int parse_ump_endpoints(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_ump *rmidi; + int err; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + if (!rmidi->ump || + !(rmidi->ump->core.info_flags & SNDRV_RAWMIDI_INFO_DUPLEX)) + continue; + err = snd_ump_parse_endpoint(rmidi->ump); + if (!err) { + rmidi->ump_parsed = true; + } else { + if (err == -ENOMEM) + return err; + /* fall back to GTB later */ + } + } + return 0; +} + +/* create a UMP block from a GTB entry */ +static int create_gtb_block(struct snd_usb_midi2_ump *rmidi, int dir, int blk) +{ + struct snd_usb_midi2_interface *umidi = rmidi->umidi; + const struct usb_ms20_gr_trm_block_descriptor *desc; + struct snd_ump_block *fb; + int type, err; + + desc = find_group_terminal_block(umidi, blk); + if (!desc) + return 0; + + usb_audio_dbg(umidi->chip, + "GTB %d: type=%d, group=%d/%d, protocol=%d, in bw=%d, out bw=%d\n", + blk, desc->bGrpTrmBlkType, desc->nGroupTrm, + desc->nNumGroupTrm, desc->bMIDIProtocol, + __le16_to_cpu(desc->wMaxInputBandwidth), + __le16_to_cpu(desc->wMaxOutputBandwidth)); + + /* assign the direction */ + switch (desc->bGrpTrmBlkType) { + case USB_MS_GR_TRM_BLOCK_TYPE_BIDIRECTIONAL: + type = SNDRV_UMP_DIR_BIDIRECTION; + break; + case USB_MS_GR_TRM_BLOCK_TYPE_INPUT_ONLY: + type = SNDRV_UMP_DIR_INPUT; + break; + case USB_MS_GR_TRM_BLOCK_TYPE_OUTPUT_ONLY: + type = SNDRV_UMP_DIR_OUTPUT; + break; + default: + usb_audio_dbg(umidi->chip, "Unsupported GTB type %d\n", + desc->bGrpTrmBlkType); + return 0; /* unsupported */ + } + + /* guess work: set blk-1 as the (0-based) block ID */ + err = snd_ump_block_new(rmidi->ump, blk - 1, type, + desc->nGroupTrm, desc->nNumGroupTrm, + &fb); + if (err == -EBUSY) + return 0; /* already present */ + else if (err) + return err; + + if (desc->iBlockItem) + usb_string(rmidi->dev, desc->iBlockItem, + fb->info.name, sizeof(fb->info.name)); + + if (__le16_to_cpu(desc->wMaxInputBandwidth) == 1 || + __le16_to_cpu(desc->wMaxOutputBandwidth) == 1) + fb->info.flags |= SNDRV_UMP_BLOCK_IS_MIDI1 | + SNDRV_UMP_BLOCK_IS_LOWSPEED; + + /* if MIDI 2.0 protocol is supported and yet the GTB shows MIDI 1.0, + * treat it as a MIDI 1.0-specific block + */ + if (rmidi->ump->info.protocol_caps & SNDRV_UMP_EP_INFO_PROTO_MIDI2) { + switch (desc->bMIDIProtocol) { + case USB_MS_MIDI_PROTO_1_0_64: + case USB_MS_MIDI_PROTO_1_0_64_JRTS: + case USB_MS_MIDI_PROTO_1_0_128: + case USB_MS_MIDI_PROTO_1_0_128_JRTS: + fb->info.flags |= SNDRV_UMP_BLOCK_IS_MIDI1; + break; + } + } + + snd_ump_update_group_attrs(rmidi->ump); + + usb_audio_dbg(umidi->chip, + "Created a UMP block %d from GTB, name=%s, flags=0x%x\n", + blk, fb->info.name, fb->info.flags); + return 0; +} + +/* Create UMP blocks for each UMP EP */ +static int create_blocks_from_gtb(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_ump *rmidi; + int i, blk, err, dir; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + if (!rmidi->ump) + continue; + /* Blocks have been already created? */ + if (rmidi->ump_parsed || rmidi->ump->info.num_blocks) + continue; + /* GTB is static-only */ + rmidi->ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS; + /* loop over GTBs */ + for (dir = 0; dir < 2; dir++) { + if (!rmidi->eps[dir]) + continue; + for (i = 0; i < rmidi->eps[dir]->ms_ep->bNumGrpTrmBlock; i++) { + blk = rmidi->eps[dir]->ms_ep->baAssoGrpTrmBlkID[i]; + err = create_gtb_block(rmidi, dir, blk); + if (err < 0) + return err; + } + } + } + + return 0; +} + +/* attach legacy rawmidis */ +static int attach_legacy_rawmidi(struct snd_usb_midi2_interface *umidi) +{ +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + struct snd_usb_midi2_ump *rmidi; + int err; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + err = snd_ump_attach_legacy_rawmidi(rmidi->ump, + "Legacy MIDI", + umidi->chip->num_rawmidis); + if (err < 0) + return err; + umidi->chip->num_rawmidis++; + } +#endif + return 0; +} + +static void snd_usb_midi_v2_free(struct snd_usb_midi2_interface *umidi) +{ + free_all_midi2_endpoints(umidi); + free_all_midi2_umps(umidi); + list_del(&umidi->list); + kfree(umidi->blk_descs); + kfree(umidi); +} + +/* parse the interface for MIDI 2.0 */ +static int parse_midi_2_0(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_endpoint *ep; + int blk, id, err; + + /* First, create an object for each USB MIDI Endpoint */ + err = parse_midi_2_0_endpoints(umidi); + if (err < 0) + return err; + if (list_empty(&umidi->ep_list)) { + usb_audio_warn(umidi->chip, "No MIDI endpoints found\n"); + return -ENODEV; + } + + /* + * Next, look for EP I/O pairs that are found in group terminal blocks + * A UMP object is created for each EP I/O pair as bidirecitonal + * UMP EP + */ + list_for_each_entry(ep, &umidi->ep_list, list) { + /* only input in this loop; output is matched in find_midi_ump() */ + if (ep->direction != STR_IN) + continue; + for (blk = 0; blk < ep->ms_ep->bNumGrpTrmBlock; blk++) { + id = ep->ms_ep->baAssoGrpTrmBlkID[blk]; + err = find_matching_ep_partner(umidi, ep, id); + if (err < 0) + return err; + } + } + + /* + * For the remaining EPs, treat as singles, create a UMP object with + * unidirectional EP + */ + list_for_each_entry(ep, &umidi->ep_list, list) { + if (ep->rmidi) + continue; /* already paired */ + for (blk = 0; blk < ep->ms_ep->bNumGrpTrmBlock; blk++) { + id = ep->ms_ep->baAssoGrpTrmBlkID[blk]; + if (find_midi2_ump(umidi, id)) + continue; + usb_audio_dbg(umidi->chip, + "Creating a unidirection UMP for EP=0x%02x, blk=%d\n", + ep->endpoint, id); + if (ep->direction == STR_IN) + err = create_midi2_ump(umidi, ep, NULL, id); + else + err = create_midi2_ump(umidi, NULL, ep, id); + if (err < 0) + return err; + break; + } + } + + return 0; +} + +/* is the given interface for MIDI 2.0? */ +static bool is_midi2_altset(struct usb_host_interface *hostif) +{ + struct usb_ms_header_descriptor *ms_header = + (struct usb_ms_header_descriptor *)hostif->extra; + + if (hostif->extralen < 7 || + ms_header->bLength < 7 || + ms_header->bDescriptorType != USB_DT_CS_INTERFACE || + ms_header->bDescriptorSubtype != UAC_HEADER) + return false; + + return le16_to_cpu(ms_header->bcdMSC) == USB_MS_REV_MIDI_2_0; +} + +/* change the altsetting */ +static int set_altset(struct snd_usb_midi2_interface *umidi) +{ + usb_audio_dbg(umidi->chip, "Setting host iface %d:%d\n", + umidi->hostif->desc.bInterfaceNumber, + umidi->hostif->desc.bAlternateSetting); + return usb_set_interface(umidi->chip->dev, + umidi->hostif->desc.bInterfaceNumber, + umidi->hostif->desc.bAlternateSetting); +} + +/* fill UMP Endpoint name string from USB descriptor */ +static void fill_ump_ep_name(struct snd_ump_endpoint *ump, + struct usb_device *dev, int id) +{ + int len; + + usb_string(dev, id, ump->info.name, sizeof(ump->info.name)); + + /* trim superfluous "MIDI" suffix */ + len = strlen(ump->info.name); + if (len > 5 && !strcmp(ump->info.name + len - 5, " MIDI")) + ump->info.name[len - 5] = 0; +} + +/* fill the fallback name string for each rawmidi instance */ +static void set_fallback_rawmidi_names(struct snd_usb_midi2_interface *umidi) +{ + struct usb_device *dev = umidi->chip->dev; + struct snd_usb_midi2_ump *rmidi; + struct snd_ump_endpoint *ump; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + ump = rmidi->ump; + /* fill UMP EP name from USB descriptors */ + if (!*ump->info.name && umidi->hostif->desc.iInterface) + fill_ump_ep_name(ump, dev, umidi->hostif->desc.iInterface); + else if (!*ump->info.name && dev->descriptor.iProduct) + fill_ump_ep_name(ump, dev, dev->descriptor.iProduct); + /* fill fallback name */ + if (!*ump->info.name) + scnprintf(ump->info.name, sizeof(ump->info.name), + "USB MIDI %d", rmidi->index); + /* copy as rawmidi name if not set */ + if (!*ump->core.name) + strscpy(ump->core.name, ump->info.name, + sizeof(ump->core.name)); + /* use serial number string as unique UMP product id */ + if (!*ump->info.product_id && dev->descriptor.iSerialNumber) + usb_string(dev, dev->descriptor.iSerialNumber, + ump->info.product_id, + sizeof(ump->info.product_id)); + } +} + +/* create MIDI interface; fallback to MIDI 1.0 if needed */ +int snd_usb_midi_v2_create(struct snd_usb_audio *chip, + struct usb_interface *iface, + const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id) +{ + struct snd_usb_midi2_interface *umidi; + struct usb_host_interface *hostif; + int err; + + usb_audio_dbg(chip, "Parsing interface %d...\n", + iface->altsetting[0].desc.bInterfaceNumber); + + /* fallback to MIDI 1.0? */ + if (!midi2_enable) { + usb_audio_info(chip, "Falling back to MIDI 1.0 by module option\n"); + goto fallback_to_midi1; + } + if ((quirk && quirk->type != QUIRK_MIDI_STANDARD_INTERFACE) || + iface->num_altsetting < 2) { + usb_audio_info(chip, "Quirk or no altset; falling back to MIDI 1.0\n"); + goto fallback_to_midi1; + } + hostif = &iface->altsetting[1]; + if (!is_midi2_altset(hostif)) { + usb_audio_info(chip, "No MIDI 2.0 at altset 1, falling back to MIDI 1.0\n"); + goto fallback_to_midi1; + } + if (!hostif->desc.bNumEndpoints) { + usb_audio_info(chip, "No endpoint at altset 1, falling back to MIDI 1.0\n"); + goto fallback_to_midi1; + } + + usb_audio_dbg(chip, "Creating a MIDI 2.0 instance for %d:%d\n", + hostif->desc.bInterfaceNumber, + hostif->desc.bAlternateSetting); + + umidi = kzalloc(sizeof(*umidi), GFP_KERNEL); + if (!umidi) + return -ENOMEM; + umidi->chip = chip; + umidi->iface = iface; + umidi->hostif = hostif; + INIT_LIST_HEAD(&umidi->rawmidi_list); + INIT_LIST_HEAD(&umidi->ep_list); + + list_add_tail(&umidi->list, &chip->midi_v2_list); + + err = set_altset(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to set altset\n"); + goto error; + } + + /* assume only altset 1 corresponding to MIDI 2.0 interface */ + err = parse_midi_2_0(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to parse MIDI 2.0 interface\n"); + goto error; + } + + /* parse USB group terminal blocks */ + err = parse_group_terminal_blocks(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to parse GTB\n"); + goto error; + } + + err = start_input_streams(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to start input streams\n"); + goto error; + } + + if (midi2_ump_probe) { + err = parse_ump_endpoints(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to parse UMP endpoint\n"); + goto error; + } + } + + err = create_blocks_from_gtb(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to create GTB blocks\n"); + goto error; + } + + set_fallback_rawmidi_names(umidi); + + err = attach_legacy_rawmidi(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to create legacy rawmidi\n"); + goto error; + } + + return 0; + + error: + snd_usb_midi_v2_free(umidi); + return err; + + fallback_to_midi1: + return __snd_usbmidi_create(chip->card, iface, &chip->midi_list, + quirk, usb_id, &chip->num_rawmidis); +} + +static void suspend_midi2_endpoint(struct snd_usb_midi2_endpoint *ep) +{ + kill_midi_urbs(ep, true); + drain_urb_queue(ep); +} + +void snd_usb_midi_v2_suspend_all(struct snd_usb_audio *chip) +{ + struct snd_usb_midi2_interface *umidi; + struct snd_usb_midi2_endpoint *ep; + + list_for_each_entry(umidi, &chip->midi_v2_list, list) { + list_for_each_entry(ep, &umidi->ep_list, list) + suspend_midi2_endpoint(ep); + } +} + +static void resume_midi2_endpoint(struct snd_usb_midi2_endpoint *ep) +{ + ep->running = ep->suspended; + if (ep->direction == STR_IN) + submit_io_urbs(ep); + /* FIXME: does it all? */ +} + +void snd_usb_midi_v2_resume_all(struct snd_usb_audio *chip) +{ + struct snd_usb_midi2_interface *umidi; + struct snd_usb_midi2_endpoint *ep; + + list_for_each_entry(umidi, &chip->midi_v2_list, list) { + set_altset(umidi); + list_for_each_entry(ep, &umidi->ep_list, list) + resume_midi2_endpoint(ep); + } +} + +void snd_usb_midi_v2_disconnect_all(struct snd_usb_audio *chip) +{ + struct snd_usb_midi2_interface *umidi; + struct snd_usb_midi2_endpoint *ep; + + list_for_each_entry(umidi, &chip->midi_v2_list, list) { + umidi->disconnected = 1; + list_for_each_entry(ep, &umidi->ep_list, list) { + ep->disconnected = 1; + kill_midi_urbs(ep, false); + drain_urb_queue(ep); + } + } +} + +/* release the MIDI instance */ +void snd_usb_midi_v2_free_all(struct snd_usb_audio *chip) +{ + struct snd_usb_midi2_interface *umidi, *next; + + list_for_each_entry_safe(umidi, next, &chip->midi_v2_list, list) + snd_usb_midi_v2_free(umidi); +} diff --git a/sound/usb/midi2.h b/sound/usb/midi2.h new file mode 100644 index 000000000000..94a65fcbd58b --- /dev/null +++ b/sound/usb/midi2.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __USB_AUDIO_MIDI2_H +#define __USB_AUDIO_MIDI2_H + +#include "midi.h" + +#if IS_ENABLED(CONFIG_SND_USB_AUDIO_MIDI_V2) +int snd_usb_midi_v2_create(struct snd_usb_audio *chip, + struct usb_interface *iface, + const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id); +void snd_usb_midi_v2_suspend_all(struct snd_usb_audio *chip); +void snd_usb_midi_v2_resume_all(struct snd_usb_audio *chip); +void snd_usb_midi_v2_disconnect_all(struct snd_usb_audio *chip); +void snd_usb_midi_v2_free_all(struct snd_usb_audio *chip); +#else /* CONFIG_SND_USB_AUDIO_MIDI_V2 */ +/* fallback to MIDI 1.0 creation */ +static inline int snd_usb_midi_v2_create(struct snd_usb_audio *chip, + struct usb_interface *iface, + const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id) +{ + return __snd_usbmidi_create(chip->card, iface, &chip->midi_list, + quirk, usb_id, &chip->num_rawmidis); +} + +static inline void snd_usb_midi_v2_suspend_all(struct snd_usb_audio *chip) {} +static inline void snd_usb_midi_v2_resume_all(struct snd_usb_audio *chip) {} +static inline void snd_usb_midi_v2_disconnect_all(struct snd_usb_audio *chip) {} +static inline void snd_usb_midi_v2_free_all(struct snd_usb_audio *chip) {} +#endif /* CONFIG_SND_USB_AUDIO_MIDI_V2 */ + +#endif /* __USB_AUDIO_MIDI2_H */ diff --git a/sound/usb/misc/Makefile b/sound/usb/misc/Makefile index ccefd8158936..3e9f4adc28de 100644 --- a/sound/usb/misc/Makefile +++ b/sound/usb/misc/Makefile @@ -1,2 +1,3 @@ -snd-ua101-objs := ua101.o +# SPDX-License-Identifier: GPL-2.0-only +snd-ua101-y := ua101.o obj-$(CONFIG_SND_USB_UA101) += snd-ua101.o diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c index a0b6d039017f..76b6eb55dcc2 100644 --- a/sound/usb/misc/ua101.c +++ b/sound/usb/misc/ua101.c @@ -1,17 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Edirol UA-101/UA-1000 driver * Copyright (c) Clemens Ladisch <clemens@ladisch.de> - * - * This driver is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2. - * - * This driver 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. - * - * You should have received a copy of the GNU General Public License - * along with this driver. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/init.h> @@ -29,7 +19,6 @@ MODULE_DESCRIPTION("Edirol UA-101/1000 driver"); MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); MODULE_LICENSE("GPL v2"); -MODULE_SUPPORTED_DEVICE("{{Edirol,UA-101},{Edirol,UA-1000}}"); /* * Should not be lower than the minimum scheduling delay of the host @@ -106,7 +95,7 @@ struct ua101 { u8 rate_feedback[MAX_QUEUE_LENGTH]; struct list_head ready_playback_urbs; - struct tasklet_struct playback_tasklet; + struct work_struct playback_work; wait_queue_head_t alsa_capture_wait; wait_queue_head_t rate_feedback_wait; wait_queue_head_t alsa_playback_wait; @@ -182,7 +171,6 @@ static void playback_urb_complete(struct urb *usb_urb) { struct ua101_urb *urb = (struct ua101_urb *)usb_urb; struct ua101 *ua = urb->urb.context; - unsigned long flags; if (unlikely(urb->urb.status == -ENOENT || /* unlinked */ urb->urb.status == -ENODEV || /* device removed */ @@ -195,14 +183,13 @@ static void playback_urb_complete(struct urb *usb_urb) if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) { /* append URB to FIFO */ - spin_lock_irqsave(&ua->lock, flags); + guard(spinlock_irqsave)(&ua->lock); list_add_tail(&urb->ready_list, &ua->ready_playback_urbs); if (ua->rate_feedback_count > 0) - tasklet_schedule(&ua->playback_tasklet); + queue_work(system_highpri_wq, &ua->playback_work); ua->playback.substream->runtime->delay -= urb->urb.iso_frame_desc[0].length / ua->playback.frame_bytes; - spin_unlock_irqrestore(&ua->lock, flags); } } @@ -257,10 +244,9 @@ static inline void add_with_wraparound(struct ua101 *ua, *value -= ua->playback.queue_length; } -static void playback_tasklet(unsigned long data) +static void playback_work(struct work_struct *work) { - struct ua101 *ua = (void *)data; - unsigned long flags; + struct ua101 *ua = container_of(work, struct ua101, playback_work); unsigned int frames; struct ua101_urb *urb; bool do_period_elapsed = false; @@ -280,43 +266,43 @@ static void playback_tasklet(unsigned long data) * submitting playback URBs is possible as long as both FIFOs are * nonempty. */ - spin_lock_irqsave(&ua->lock, flags); - while (ua->rate_feedback_count > 0 && - !list_empty(&ua->ready_playback_urbs)) { - /* take packet size out of FIFO */ - frames = ua->rate_feedback[ua->rate_feedback_start]; - add_with_wraparound(ua, &ua->rate_feedback_start, 1); - ua->rate_feedback_count--; - - /* take URB out of FIFO */ - urb = list_first_entry(&ua->ready_playback_urbs, - struct ua101_urb, ready_list); - list_del(&urb->ready_list); - - /* fill packet with data or silence */ - urb->urb.iso_frame_desc[0].length = - frames * ua->playback.frame_bytes; - if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) - do_period_elapsed |= copy_playback_data(&ua->playback, - &urb->urb, - frames); - else - memset(urb->urb.transfer_buffer, 0, - urb->urb.iso_frame_desc[0].length); - - /* and off you go ... */ - err = usb_submit_urb(&urb->urb, GFP_ATOMIC); - if (unlikely(err < 0)) { - spin_unlock_irqrestore(&ua->lock, flags); - abort_usb_playback(ua); - abort_alsa_playback(ua); - dev_err(&ua->dev->dev, "USB request error %d: %s\n", - err, usb_error_string(err)); - return; + scoped_guard(spinlock_irqsave, &ua->lock) { + while (ua->rate_feedback_count > 0 && + !list_empty(&ua->ready_playback_urbs)) { + /* take packet size out of FIFO */ + frames = ua->rate_feedback[ua->rate_feedback_start]; + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + ua->rate_feedback_count--; + + /* take URB out of FIFO */ + urb = list_first_entry(&ua->ready_playback_urbs, + struct ua101_urb, ready_list); + list_del(&urb->ready_list); + + /* fill packet with data or silence */ + urb->urb.iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) + do_period_elapsed |= copy_playback_data(&ua->playback, + &urb->urb, + frames); + else + memset(urb->urb.transfer_buffer, 0, + urb->urb.iso_frame_desc[0].length); + + /* and off you go ... */ + err = usb_submit_urb(&urb->urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + abort_usb_playback(ua); + abort_alsa_playback(ua); + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); + return; + } + ua->playback.substream->runtime->delay += frames; } - ua->playback.substream->runtime->delay += frames; } - spin_unlock_irqrestore(&ua->lock, flags); + if (do_period_elapsed) snd_pcm_period_elapsed(ua->playback.substream); } @@ -358,7 +344,6 @@ static void capture_urb_complete(struct urb *urb) { struct ua101 *ua = urb->context; struct ua101_stream *stream = &ua->capture; - unsigned long flags; unsigned int frames, write_ptr; bool do_period_elapsed; int err; @@ -375,47 +360,45 @@ static void capture_urb_complete(struct urb *urb) else frames = 0; - spin_lock_irqsave(&ua->lock, flags); + scoped_guard(spinlock_irqsave, &ua->lock) { - if (frames > 0 && test_bit(ALSA_CAPTURE_RUNNING, &ua->states)) - do_period_elapsed = copy_capture_data(stream, urb, frames); - else - do_period_elapsed = false; - - if (test_bit(USB_CAPTURE_RUNNING, &ua->states)) { - err = usb_submit_urb(urb, GFP_ATOMIC); - if (unlikely(err < 0)) { - spin_unlock_irqrestore(&ua->lock, flags); - dev_err(&ua->dev->dev, "USB request error %d: %s\n", - err, usb_error_string(err)); - goto stream_stopped; - } + if (frames > 0 && test_bit(ALSA_CAPTURE_RUNNING, &ua->states)) + do_period_elapsed = copy_capture_data(stream, urb, frames); + else + do_period_elapsed = false; + + if (test_bit(USB_CAPTURE_RUNNING, &ua->states)) { + err = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); + goto stream_stopped; + } - /* append packet size to FIFO */ - write_ptr = ua->rate_feedback_start; - add_with_wraparound(ua, &write_ptr, ua->rate_feedback_count); - ua->rate_feedback[write_ptr] = frames; - if (ua->rate_feedback_count < ua->playback.queue_length) { - ua->rate_feedback_count++; - if (ua->rate_feedback_count == - ua->playback.queue_length) - wake_up(&ua->rate_feedback_wait); - } else { - /* - * Ring buffer overflow; this happens when the playback - * stream is not running. Throw away the oldest entry, - * so that the playback stream, when it starts, sees - * the most recent packet sizes. - */ - add_with_wraparound(ua, &ua->rate_feedback_start, 1); + /* append packet size to FIFO */ + write_ptr = ua->rate_feedback_start; + add_with_wraparound(ua, &write_ptr, ua->rate_feedback_count); + ua->rate_feedback[write_ptr] = frames; + if (ua->rate_feedback_count < ua->playback.queue_length) { + ua->rate_feedback_count++; + if (ua->rate_feedback_count == + ua->playback.queue_length) + wake_up(&ua->rate_feedback_wait); + } else { + /* + * Ring buffer overflow; this happens when the playback + * stream is not running. Throw away the oldest entry, + * so that the playback stream, when it starts, sees + * the most recent packet sizes. + */ + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + } + if (test_bit(USB_PLAYBACK_RUNNING, &ua->states) && + !list_empty(&ua->ready_playback_urbs)) + queue_work(system_highpri_wq, &ua->playback_work); } - if (test_bit(USB_PLAYBACK_RUNNING, &ua->states) && - !list_empty(&ua->ready_playback_urbs)) - tasklet_schedule(&ua->playback_tasklet); } - spin_unlock_irqrestore(&ua->lock, flags); - if (do_period_elapsed) snd_pcm_period_elapsed(stream->substream); @@ -542,7 +525,7 @@ static void stop_usb_playback(struct ua101 *ua) kill_stream_urbs(&ua->playback); - tasklet_kill(&ua->playback_tasklet); + cancel_work_sync(&ua->playback_work); disable_iso_interface(ua, INTF_PLAYBACK); } @@ -560,7 +543,7 @@ static int start_usb_playback(struct ua101 *ua) return 0; kill_stream_urbs(&ua->playback); - tasklet_kill(&ua->playback_tasklet); + cancel_work_sync(&ua->playback_work); err = enable_iso_interface(ua, INTF_PLAYBACK); if (err < 0) @@ -569,9 +552,9 @@ static int start_usb_playback(struct ua101 *ua) clear_bit(PLAYBACK_URB_COMPLETED, &ua->states); ua->playback.urbs[0]->urb.complete = first_playback_urb_complete; - spin_lock_irq(&ua->lock); - INIT_LIST_HEAD(&ua->ready_playback_urbs); - spin_unlock_irq(&ua->lock); + scoped_guard(spinlock_irq, &ua->lock) { + INIT_LIST_HEAD(&ua->ready_playback_urbs); + } /* * We submit the initial URBs all at once, so we have to wait for the @@ -592,11 +575,11 @@ static int start_usb_playback(struct ua101 *ua) for (i = 0; i < ua->playback.queue_length; ++i) { /* all initial URBs contain silence */ - spin_lock_irq(&ua->lock); - frames = ua->rate_feedback[ua->rate_feedback_start]; - add_with_wraparound(ua, &ua->rate_feedback_start, 1); - ua->rate_feedback_count--; - spin_unlock_irq(&ua->lock); + scoped_guard(spinlock_irq, &ua->lock) { + frames = ua->rate_feedback[ua->rate_feedback_start]; + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + ua->rate_feedback_count--; + } urb = &ua->playback.urbs[i]->urb; urb->iso_frame_desc[0].length = frames * ua->playback.frame_bytes; @@ -669,11 +652,10 @@ static int capture_pcm_open(struct snd_pcm_substream *substream) DIV_ROUND_CLOSEST(ua->rate, ua->packets_per_second); substream->runtime->delay = substream->runtime->hw.fifo_size; - mutex_lock(&ua->mutex); + guard(mutex)(&ua->mutex); err = start_usb_capture(ua); if (err >= 0) set_bit(ALSA_CAPTURE_OPEN, &ua->states); - mutex_unlock(&ua->mutex); return err; } @@ -690,31 +672,28 @@ static int playback_pcm_open(struct snd_pcm_substream *substream) DIV_ROUND_CLOSEST(ua->rate * ua->playback.queue_length, ua->packets_per_second); - mutex_lock(&ua->mutex); + guard(mutex)(&ua->mutex); err = start_usb_capture(ua); if (err < 0) - goto error; + return err; err = start_usb_playback(ua); if (err < 0) { if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states)) stop_usb_capture(ua); - goto error; + return err; } set_bit(ALSA_PLAYBACK_OPEN, &ua->states); -error: - mutex_unlock(&ua->mutex); - return err; + return 0; } static int capture_pcm_close(struct snd_pcm_substream *substream) { struct ua101 *ua = substream->private_data; - mutex_lock(&ua->mutex); + guard(mutex)(&ua->mutex); clear_bit(ALSA_CAPTURE_OPEN, &ua->states); if (!test_bit(ALSA_PLAYBACK_OPEN, &ua->states)) stop_usb_capture(ua); - mutex_unlock(&ua->mutex); return 0; } @@ -722,12 +701,11 @@ static int playback_pcm_close(struct snd_pcm_substream *substream) { struct ua101 *ua = substream->private_data; - mutex_lock(&ua->mutex); + guard(mutex)(&ua->mutex); stop_usb_playback(ua); clear_bit(ALSA_PLAYBACK_OPEN, &ua->states); if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states)) stop_usb_capture(ua); - mutex_unlock(&ua->mutex); return 0; } @@ -735,16 +713,9 @@ static int capture_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { struct ua101 *ua = substream->private_data; - int err; - - mutex_lock(&ua->mutex); - err = start_usb_capture(ua); - mutex_unlock(&ua->mutex); - if (err < 0) - return err; - return snd_pcm_lib_alloc_vmalloc_buffer(substream, - params_buffer_bytes(hw_params)); + guard(mutex)(&ua->mutex); + return start_usb_capture(ua); } static int playback_pcm_hw_params(struct snd_pcm_substream *substream, @@ -753,21 +724,11 @@ static int playback_pcm_hw_params(struct snd_pcm_substream *substream, struct ua101 *ua = substream->private_data; int err; - mutex_lock(&ua->mutex); + guard(mutex)(&ua->mutex); err = start_usb_capture(ua); if (err >= 0) err = start_usb_playback(ua); - mutex_unlock(&ua->mutex); - if (err < 0) - return err; - - return snd_pcm_lib_alloc_vmalloc_buffer(substream, - params_buffer_bytes(hw_params)); -} - -static int ua101_pcm_hw_free(struct snd_pcm_substream *substream) -{ - return snd_pcm_lib_free_vmalloc_buffer(substream); + return err; } static int capture_pcm_prepare(struct snd_pcm_substream *substream) @@ -775,9 +736,9 @@ static int capture_pcm_prepare(struct snd_pcm_substream *substream) struct ua101 *ua = substream->private_data; int err; - mutex_lock(&ua->mutex); - err = start_usb_capture(ua); - mutex_unlock(&ua->mutex); + scoped_guard(mutex, &ua->mutex) { + err = start_usb_capture(ua); + } if (err < 0) return err; @@ -805,11 +766,11 @@ static int playback_pcm_prepare(struct snd_pcm_substream *substream) struct ua101 *ua = substream->private_data; int err; - mutex_lock(&ua->mutex); - err = start_usb_capture(ua); - if (err >= 0) - err = start_usb_playback(ua); - mutex_unlock(&ua->mutex); + scoped_guard(mutex, &ua->mutex) { + err = start_usb_capture(ua); + if (err >= 0) + err = start_usb_playback(ua); + } if (err < 0) return err; @@ -867,13 +828,8 @@ static int playback_pcm_trigger(struct snd_pcm_substream *substream, int cmd) static inline snd_pcm_uframes_t ua101_pcm_pointer(struct ua101 *ua, struct ua101_stream *stream) { - unsigned long flags; - unsigned int pos; - - spin_lock_irqsave(&ua->lock, flags); - pos = stream->buffer_pos; - spin_unlock_irqrestore(&ua->lock, flags); - return pos; + guard(spinlock_irqsave)(&ua->lock); + return stream->buffer_pos; } static snd_pcm_uframes_t capture_pcm_pointer(struct snd_pcm_substream *subs) @@ -893,25 +849,19 @@ static snd_pcm_uframes_t playback_pcm_pointer(struct snd_pcm_substream *subs) static const struct snd_pcm_ops capture_pcm_ops = { .open = capture_pcm_open, .close = capture_pcm_close, - .ioctl = snd_pcm_lib_ioctl, .hw_params = capture_pcm_hw_params, - .hw_free = ua101_pcm_hw_free, .prepare = capture_pcm_prepare, .trigger = capture_pcm_trigger, .pointer = capture_pcm_pointer, - .page = snd_pcm_lib_get_vmalloc_page, }; static const struct snd_pcm_ops playback_pcm_ops = { .open = playback_pcm_open, .close = playback_pcm_close, - .ioctl = snd_pcm_lib_ioctl, .hw_params = playback_pcm_hw_params, - .hw_free = ua101_pcm_hw_free, .prepare = playback_pcm_prepare, .trigger = playback_pcm_trigger, .pointer = playback_pcm_pointer, - .page = snd_pcm_lib_get_vmalloc_page, }; static const struct uac_format_type_i_discrete_descriptor * @@ -1030,7 +980,7 @@ static int detect_usb_format(struct ua101 *ua) fmt_playback->bSubframeSize * ua->playback.channels; epd = &ua->intf[INTF_CAPTURE]->altsetting[1].endpoint[0].desc; - if (!usb_endpoint_is_isoc_in(epd)) { + if (!usb_endpoint_is_isoc_in(epd) || usb_endpoint_maxp(epd) == 0) { dev_err(&ua->dev->dev, "invalid capture endpoint\n"); return -ENXIO; } @@ -1038,7 +988,7 @@ static int detect_usb_format(struct ua101 *ua) ua->capture.max_packet_bytes = usb_endpoint_maxp(epd); epd = &ua->intf[INTF_PLAYBACK]->altsetting[1].endpoint[0].desc; - if (!usb_endpoint_is_isoc_out(epd)) { + if (!usb_endpoint_is_isoc_out(epd) || usb_endpoint_maxp(epd) == 0) { dev_err(&ua->dev->dev, "invalid playback endpoint\n"); return -ENXIO; } @@ -1157,18 +1107,18 @@ static void free_usb_related_resources(struct ua101 *ua, unsigned int i; struct usb_interface *intf; - mutex_lock(&ua->mutex); - free_stream_urbs(&ua->capture); - free_stream_urbs(&ua->playback); - mutex_unlock(&ua->mutex); + scoped_guard(mutex, &ua->mutex) { + free_stream_urbs(&ua->capture); + free_stream_urbs(&ua->playback); + } free_stream_buffers(ua, &ua->capture); free_stream_buffers(ua, &ua->playback); for (i = 0; i < ARRAY_SIZE(ua->intf); ++i) { - mutex_lock(&ua->mutex); - intf = ua->intf[i]; - ua->intf[i] = NULL; - mutex_unlock(&ua->mutex); + scoped_guard(mutex, &ua->mutex) { + intf = ua->intf[i]; + ua->intf[i] = NULL; + } if (intf) { usb_set_intfdata(intf, NULL); if (intf != interface) @@ -1222,22 +1172,18 @@ static int ua101_probe(struct usb_interface *interface, intf_numbers[is_ua1000][0]) return -ENODEV; - mutex_lock(&devices_mutex); + guard(mutex)(&devices_mutex); for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) if (enable[card_index] && !(devices_used & (1 << card_index))) break; - if (card_index >= SNDRV_CARDS) { - mutex_unlock(&devices_mutex); + if (card_index >= SNDRV_CARDS) return -ENOENT; - } err = snd_card_new(&interface->dev, index[card_index], id[card_index], THIS_MODULE, sizeof(*ua), &card); - if (err < 0) { - mutex_unlock(&devices_mutex); + if (err < 0) return err; - } card->private_free = ua101_card_free; ua = card->private_data; ua->dev = interface_to_usbdev(interface); @@ -1247,8 +1193,7 @@ static int ua101_probe(struct usb_interface *interface, spin_lock_init(&ua->lock); mutex_init(&ua->mutex); INIT_LIST_HEAD(&ua->ready_playback_urbs); - tasklet_init(&ua->playback_tasklet, - playback_tasklet, (unsigned long)ua); + INIT_WORK(&ua->playback_work, playback_work); init_waitqueue_head(&ua->alsa_capture_wait); init_waitqueue_head(&ua->rate_feedback_wait); init_waitqueue_head(&ua->alsa_playback_wait); @@ -1277,8 +1222,8 @@ static int ua101_probe(struct usb_interface *interface, goto probe_error; name = usb_id->idProduct == 0x0044 ? "UA-1000" : "UA-101"; - strcpy(card->driver, "UA-101"); - strcpy(card->shortname, name); + strscpy(card->driver, "UA-101"); + strscpy(card->shortname, name); usb_make_path(ua->dev, usb_path, sizeof(usb_path)); snprintf(ua->card->longname, sizeof(ua->card->longname), "EDIROL %s (serial %s), %u Hz at %s, %s speed", name, @@ -1303,9 +1248,11 @@ static int ua101_probe(struct usb_interface *interface, if (err < 0) goto probe_error; ua->pcm->private_data = ua; - strcpy(ua->pcm->name, name); + strscpy(ua->pcm->name, name); snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_pcm_ops); snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_pcm_ops); + snd_pcm_set_managed_buffer_all(ua->pcm, SNDRV_DMA_TYPE_VMALLOC, + NULL, 0, 0); err = snd_usbmidi_create(card, ua->intf[INTF_MIDI], &ua->midi_list, &midi_quirk); @@ -1319,13 +1266,11 @@ static int ua101_probe(struct usb_interface *interface, usb_set_intfdata(interface, ua); devices_used |= 1 << card_index; - mutex_unlock(&devices_mutex); return 0; probe_error: free_usb_related_resources(ua, interface); snd_card_free(card); - mutex_unlock(&devices_mutex); return err; } @@ -1337,7 +1282,7 @@ static void ua101_disconnect(struct usb_interface *interface) if (!ua) return; - mutex_lock(&devices_mutex); + guard(mutex)(&devices_mutex); set_bit(DISCONNECTED, &ua->states); wake_up(&ua->rate_feedback_wait); @@ -1350,18 +1295,16 @@ static void ua101_disconnect(struct usb_interface *interface) snd_usbmidi_disconnect(midi); abort_alsa_playback(ua); abort_alsa_capture(ua); - mutex_lock(&ua->mutex); - stop_usb_playback(ua); - stop_usb_capture(ua); - mutex_unlock(&ua->mutex); + scoped_guard(mutex, &ua->mutex) { + stop_usb_playback(ua); + stop_usb_capture(ua); + } free_usb_related_resources(ua, interface); devices_used &= ~(1 << ua->card_index); snd_card_free_when_closed(ua->card); - - mutex_unlock(&devices_mutex); } static const struct usb_device_id ua101_ids[] = { diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index e7d441d0e839..3af71d42b9b9 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * (Tentative) USB Audio Driver for ALSA * @@ -8,22 +9,6 @@ * Many codes borrowed from audio.c by * Alan Cox (alan@lxorguk.ukuu.org.uk) * Thomas Sailer (sailer@ife.ee.ethz.ch) - * - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * */ /* @@ -83,6 +68,7 @@ struct mixer_build { unsigned char *buffer; unsigned int buflen; DECLARE_BITMAP(unitbitmap, MAX_ID_ELEMS); + DECLARE_BITMAP(termbitmap, MAX_ID_ELEMS); struct usb_audio_term oterm; const struct usbmix_name_map *map; const struct usbmix_selector_map *selector_map; @@ -129,11 +115,14 @@ find_map(const struct usbmix_name_map *p, int unitid, int control) static int check_mapped_name(const struct usbmix_name_map *p, char *buf, int buflen) { + int len; + if (!p || !p->name) return 0; buflen--; - return strlcpy(buf, p->name, buflen); + len = strscpy(buf, p->name, buflen); + return len < 0 ? buflen : len; } /* ignore the error value if ignore_ctl_error flag is set */ @@ -156,6 +145,7 @@ static inline void check_mapped_dB(const struct usbmix_name_map *p, if (p && p->dB) { cval->dBmin = p->dB->min; cval->dBmax = p->dB->max; + cval->min_mute = p->dB->min_mute; cval->initialized = 1; } } @@ -165,12 +155,15 @@ static int check_mapped_selector_name(struct mixer_build *state, int unitid, int index, char *buf, int buflen) { const struct usbmix_selector_map *p; + int len; if (!state->selector_map) return 0; for (p = state->selector_map; p->id; p++) { - if (p->id == unitid && index < p->count) - return strlcpy(buf, p->names[index], buflen); + if (p->id == unitid && index < p->count) { + len = strscpy(buf, p->names[index], buflen); + return len < 0 ? buflen : len; + } } return 0; } @@ -268,7 +261,7 @@ static int get_relative_value(struct usb_mixer_elem_info *cval, int val) if (val < cval->min) return 0; else if (val >= cval->max) - return (cval->max - cval->min + cval->res - 1) / cval->res; + return DIV_ROUND_UP(cval->max - cval->min, cval->res); else return (val - cval->min) / cval->res; } @@ -306,6 +299,11 @@ static int uac2_ctl_value_size(int val_type) * retrieve a mixer value */ +static inline int mixer_ctrl_intf(struct usb_mixer_interface *mixer) +{ + return get_iface_desc(mixer->hostif)->bInterfaceNumber; +} + static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret) { @@ -315,31 +313,26 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, int timeout = 10; int idx = 0, err; - err = snd_usb_lock_shutdown(chip); - if (err < 0) + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) return -EIO; while (timeout-- > 0) { - idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8); + idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), request, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, validx, idx, buf, val_len); if (err >= val_len) { *value_ret = convert_signed_value(cval, snd_usb_combine_bytes(buf, val_len)); - err = 0; - goto out; + return 0; } else if (err == -ETIMEDOUT) { - goto out; + return err; } } usb_audio_dbg(chip, "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", request, validx, idx, cval->val_type); - err = -EINVAL; - - out: - snd_usb_unlock_shutdown(chip); - return err; + return -EINVAL; } static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, @@ -364,19 +357,19 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, memset(buf, 0, sizeof(buf)); - ret = snd_usb_lock_shutdown(chip) ? -EIO : 0; - if (ret) - goto error; + { + CLASS(snd_usb_lock, pm)(chip); + if (pm.err) + return -EIO; - idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8); - ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, - validx, idx, buf, size); - snd_usb_unlock_shutdown(chip); + idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); + ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx, idx, buf, size); + } if (ret < 0) { -error: - usb_audio_err(chip, + usb_audio_dbg(chip, "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", request, validx, idx, cval->val_type); return ret; @@ -437,7 +430,7 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, { int err; - if (cval->cached & (1 << channel)) { + if (cval->cached & BIT(channel)) { *value = cval->cache_val[index]; return 0; } @@ -449,7 +442,7 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, cval->control, channel, err); return err; } - cval->cached |= 1 << channel; + cval->cached |= BIT(channel); cval->cache_val[index] = *value; return 0; } @@ -488,30 +481,24 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, buf[2] = (value_set >> 16) & 0xff; buf[3] = (value_set >> 24) & 0xff; - err = snd_usb_lock_shutdown(chip); - if (err < 0) + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) return -EIO; while (timeout-- > 0) { - idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8); + idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), request, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, validx, idx, buf, val_len); - if (err >= 0) { - err = 0; - goto out; - } else if (err == -ETIMEDOUT) { - goto out; - } + if (err >= 0) + return 0; + else if (err == -ETIMEDOUT) + return err; } usb_audio_dbg(chip, "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d, data = %#x/%#x\n", request, validx, idx, cval->val_type, buf[0], buf[1]); - err = -EINVAL; - - out: - snd_usb_unlock_shutdown(chip); - return err; + return -EINVAL; } static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, @@ -526,7 +513,7 @@ int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int err; unsigned int read_only = (channel == 0) ? cval->master_readonly : - cval->ch_readonly & (1 << (channel - 1)); + cval->ch_readonly & BIT(channel - 1); if (read_only) { usb_audio_dbg(cval->head.mixer->chip, @@ -540,7 +527,7 @@ int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, value); if (err < 0) return err; - cval->cached |= 1 << channel; + cval->cached |= BIT(channel); cval->cache_val[index] = value; return 0; } @@ -551,7 +538,7 @@ int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int snd_usb_mixer_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag, unsigned int size, unsigned int __user *_tlv) { - struct usb_mixer_elem_info *cval = kcontrol->private_data; + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); DECLARE_TLV_DB_MINMAX(scale, 0, 0); if (size < sizeof(scale)) @@ -590,8 +577,9 @@ static int check_matrix_bitmap(unsigned char *bmap, * if failed, give up and free the control instance. */ -int snd_usb_mixer_add_control(struct usb_mixer_elem_list *list, - struct snd_kcontrol *kctl) +int snd_usb_mixer_add_list(struct usb_mixer_elem_list *list, + struct snd_kcontrol *kctl, + bool is_std_info) { struct usb_mixer_interface *mixer = list->mixer; int err; @@ -605,6 +593,7 @@ int snd_usb_mixer_add_control(struct usb_mixer_elem_list *list, return err; } list->kctl = kctl; + list->is_std_info = is_std_info; list->next_id_elem = mixer->id_elems[list->id]; mixer->id_elems[list->id] = list; return 0; @@ -676,40 +665,40 @@ static int get_term_name(struct snd_usb_audio *chip, struct usb_audio_term *iter return 0; switch (iterm->type >> 16) { case UAC3_SELECTOR_UNIT: - strcpy(name, "Selector"); + strscpy(name, "Selector", maxlen); return 8; case UAC3_PROCESSING_UNIT: - strcpy(name, "Process Unit"); + strscpy(name, "Process Unit", maxlen); return 12; case UAC3_EXTENSION_UNIT: - strcpy(name, "Ext Unit"); + strscpy(name, "Ext Unit", maxlen); return 8; case UAC3_MIXER_UNIT: - strcpy(name, "Mixer"); + strscpy(name, "Mixer", maxlen); return 5; default: - return sprintf(name, "Unit %d", iterm->id); + return scnprintf(name, maxlen, "Unit %d", iterm->id); } } switch (iterm->type & 0xff00) { case 0x0100: - strcpy(name, "PCM"); + strscpy(name, "PCM", maxlen); return 3; case 0x0200: - strcpy(name, "Mic"); + strscpy(name, "Mic", maxlen); return 3; case 0x0400: - strcpy(name, "Headset"); + strscpy(name, "Headset", maxlen); return 7; case 0x0500: - strcpy(name, "Phone"); + strscpy(name, "Phone", maxlen); return 5; } for (names = iterm_names; names->type; names++) { if (names->type == iterm->type) { - strcpy(name, names->name); + strscpy(name, names->name, maxlen); return strlen(names->name); } } @@ -730,7 +719,7 @@ static int get_cluster_channels_v3(struct mixer_build *state, unsigned int clust UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, cluster_id, - snd_usb_ctrl_intf(state->chip), + snd_usb_ctrl_intf(state->mixer->hostif), &c_header, sizeof(c_header)); if (err < 0) goto error; @@ -753,12 +742,6 @@ static int uac_mixer_unit_get_channels(struct mixer_build *state, struct uac_mixer_unit_descriptor *desc) { int mu_channels; - void *c; - - if (desc->bLength < sizeof(*desc)) - return -EINVAL; - if (!desc->bNrInPins) - return -EINVAL; switch (state->mixer->protocol) { case UAC_VERSION_1: @@ -774,229 +757,274 @@ static int uac_mixer_unit_get_channels(struct mixer_build *state, break; } - if (!mu_channels) - return 0; - - c = uac_mixer_unit_bmControls(desc, state->mixer->protocol); - if (c - (void *)desc + (mu_channels - 1) / 8 >= desc->bLength) - return 0; /* no bmControls -> skip */ - return mu_channels; } /* - * parse the source unit recursively until it reaches to a terminal - * or a branched unit. + * Parse Input Terminal Unit */ -static int check_input_term(struct mixer_build *state, int id, - struct usb_audio_term *term) +static int __check_input_term(struct mixer_build *state, int id, + struct usb_audio_term *term); + +static int parse_term_uac1_iterm_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac_input_terminal_descriptor *d = p1; + + term->type = le16_to_cpu(d->wTerminalType); + term->channels = d->bNrChannels; + term->chconfig = le16_to_cpu(d->wChannelConfig); + term->name = d->iTerminal; + return 0; +} + +static int parse_term_uac2_iterm_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) { + struct uac2_input_terminal_descriptor *d = p1; + int err; + + /* call recursively to verify the referenced clock entity */ + err = __check_input_term(state, d->bCSourceID, term); + if (err < 0) + return err; + + /* save input term properties after recursion, + * to ensure they are not overriden by the recursion calls + */ + term->id = id; + term->type = le16_to_cpu(d->wTerminalType); + term->channels = d->bNrChannels; + term->chconfig = le32_to_cpu(d->bmChannelConfig); + term->name = d->iTerminal; + return 0; +} + +static int parse_term_uac3_iterm_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac3_input_terminal_descriptor *d = p1; + int err; + + /* call recursively to verify the referenced clock entity */ + err = __check_input_term(state, d->bCSourceID, term); + if (err < 0) + return err; + + /* save input term properties after recursion, + * to ensure they are not overriden by the recursion calls + */ + term->id = id; + term->type = le16_to_cpu(d->wTerminalType); + + err = get_cluster_channels_v3(state, le16_to_cpu(d->wClusterDescrID)); + if (err < 0) + return err; + term->channels = err; + + /* REVISIT: UAC3 IT doesn't have channels cfg */ + term->chconfig = 0; + + term->name = le16_to_cpu(d->wTerminalDescrStr); + return 0; +} + +static int parse_term_mixer_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac_mixer_unit_descriptor *d = p1; int protocol = state->mixer->protocol; int err; - void *p1; - memset(term, 0, sizeof(*term)); - while ((p1 = find_audio_control_unit(state, id)) != NULL) { - unsigned char *hdr = p1; - term->id = id; + err = uac_mixer_unit_get_channels(state, d); + if (err <= 0) + return err; - if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) { - switch (hdr[2]) { - case UAC_INPUT_TERMINAL: - if (protocol == UAC_VERSION_1) { - struct uac_input_terminal_descriptor *d = p1; - - term->type = le16_to_cpu(d->wTerminalType); - term->channels = d->bNrChannels; - term->chconfig = le16_to_cpu(d->wChannelConfig); - term->name = d->iTerminal; - } else { /* UAC_VERSION_2 */ - struct uac2_input_terminal_descriptor *d = p1; - - /* call recursively to verify that the - * referenced clock entity is valid */ - err = check_input_term(state, d->bCSourceID, term); - if (err < 0) - return err; - - /* save input term properties after recursion, - * to ensure they are not overriden by the - * recursion calls */ - term->id = id; - term->type = le16_to_cpu(d->wTerminalType); - term->channels = d->bNrChannels; - term->chconfig = le32_to_cpu(d->bmChannelConfig); - term->name = d->iTerminal; - } - return 0; - case UAC_FEATURE_UNIT: { - /* the header is the same for v1 and v2 */ - struct uac_feature_unit_descriptor *d = p1; + term->type = UAC3_MIXER_UNIT << 16; /* virtual type */ + term->channels = err; + if (protocol != UAC_VERSION_3) { + term->chconfig = uac_mixer_unit_wChannelConfig(d, protocol); + term->name = uac_mixer_unit_iMixer(d); + } + return 0; +} - id = d->bSourceID; - break; /* continue to parse */ - } - case UAC_MIXER_UNIT: { - struct uac_mixer_unit_descriptor *d = p1; - - term->type = UAC3_MIXER_UNIT << 16; /* virtual type */ - term->channels = uac_mixer_unit_bNrChannels(d); - term->chconfig = uac_mixer_unit_wChannelConfig(d, protocol); - term->name = uac_mixer_unit_iMixer(d); - 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 = UAC3_SELECTOR_UNIT << 16; /* virtual type */ - term->id = id; - term->name = uac_selector_unit_iSelector(d); - return 0; - } - case UAC1_PROCESSING_UNIT: - /* UAC2_EFFECT_UNIT */ - if (protocol == UAC_VERSION_1) - term->type = UAC3_PROCESSING_UNIT << 16; /* virtual type */ - else /* UAC_VERSION_2 */ - term->type = UAC3_EFFECT_UNIT << 16; /* virtual type */ - /* fall through */ - case UAC1_EXTENSION_UNIT: - /* UAC2_PROCESSING_UNIT_V2 */ - if (protocol == UAC_VERSION_1 && !term->type) - term->type = UAC3_EXTENSION_UNIT << 16; /* virtual type */ - else if (protocol == UAC_VERSION_2 && !term->type) - term->type = UAC3_PROCESSING_UNIT << 16; /* virtual type */ - /* fall through */ - case UAC2_EXTENSION_UNIT_V2: { - struct uac_processing_unit_descriptor *d = p1; - - if (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; - } +static int parse_term_selector_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac_selector_unit_descriptor *d = p1; + int err; - if (d->bNrInPins) { - id = d->baSourceID[0]; - break; /* continue to parse */ - } - if (!term->type) - term->type = UAC3_EXTENSION_UNIT << 16; /* virtual type */ + /* call recursively to retrieve the channel info */ + err = __check_input_term(state, d->baSourceID[0], term); + if (err < 0) + return err; + term->type = UAC3_SELECTOR_UNIT << 16; /* virtual type */ + term->id = id; + if (state->mixer->protocol != UAC_VERSION_3) + term->name = uac_selector_unit_iSelector(d); + return 0; +} - term->channels = uac_processing_unit_bNrChannels(d); - term->chconfig = uac_processing_unit_wChannelConfig(d, protocol); - term->name = uac_processing_unit_iProcessing(d, protocol); - return 0; - } - case UAC2_CLOCK_SOURCE: { - struct uac_clock_source_descriptor *d = p1; +static int parse_term_proc_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id, int vtype) +{ + struct uac_processing_unit_descriptor *d = p1; + int protocol = state->mixer->protocol; + int err; - term->type = UAC3_CLOCK_SOURCE << 16; /* virtual type */ - term->id = id; - term->name = d->iClockSource; - return 0; - } - default: - return -ENODEV; - } - } else { /* UAC_VERSION_3 */ - switch (hdr[2]) { - case UAC_INPUT_TERMINAL: { - struct uac3_input_terminal_descriptor *d = p1; - - /* call recursively to verify that the - * referenced clock entity is valid */ - err = check_input_term(state, d->bCSourceID, term); - if (err < 0) - return err; + if (d->bNrInPins) { + /* call recursively to retrieve the channel info */ + err = __check_input_term(state, d->baSourceID[0], term); + if (err < 0) + return err; + } - /* save input term properties after recursion, - * to ensure they are not overriden by the - * recursion calls */ - term->id = id; - term->type = le16_to_cpu(d->wTerminalType); + term->type = vtype << 16; /* virtual type */ + term->id = id; - err = get_cluster_channels_v3(state, le16_to_cpu(d->wClusterDescrID)); - if (err < 0) - return err; - term->channels = err; + if (protocol == UAC_VERSION_3) + return 0; - /* REVISIT: UAC3 IT doesn't have channels cfg */ - term->chconfig = 0; + if (!term->channels) { + term->channels = uac_processing_unit_bNrChannels(d); + term->chconfig = uac_processing_unit_wChannelConfig(d, protocol); + } + term->name = uac_processing_unit_iProcessing(d, protocol); + return 0; +} - term->name = le16_to_cpu(d->wTerminalDescrStr); - return 0; - } - case UAC3_FEATURE_UNIT: { - struct uac3_feature_unit_descriptor *d = p1; +static int parse_term_effect_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac2_effect_unit_descriptor *d = p1; + int err; - id = d->bSourceID; - break; /* continue to parse */ - } - case UAC3_CLOCK_SOURCE: { - struct uac3_clock_source_descriptor *d = p1; + err = __check_input_term(state, d->bSourceID, term); + if (err < 0) + return err; + term->type = UAC3_EFFECT_UNIT << 16; /* virtual type */ + term->id = id; + return 0; +} - term->type = UAC3_CLOCK_SOURCE << 16; /* virtual type */ - term->id = id; - term->name = le16_to_cpu(d->wClockSourceStr); - return 0; - } - case UAC3_MIXER_UNIT: { - struct uac_mixer_unit_descriptor *d = p1; +static int parse_term_uac2_clock_source(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac_clock_source_descriptor *d = p1; - err = uac_mixer_unit_get_channels(state, d); - if (err <= 0) - return err; + term->type = UAC2_CLOCK_SOURCE << 16; /* virtual type */ + term->id = id; + term->name = d->iClockSource; + return 0; +} - term->channels = err; - term->type = UAC3_MIXER_UNIT << 16; /* virtual type */ +static int parse_term_uac3_clock_source(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac3_clock_source_descriptor *d = p1; - return 0; - } - case UAC3_SELECTOR_UNIT: - case UAC3_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 = UAC3_SELECTOR_UNIT << 16; /* virtual type */ - term->id = id; - term->name = 0; /* TODO: UAC3 Class-specific strings */ + term->type = UAC3_CLOCK_SOURCE << 16; /* virtual type */ + term->id = id; + term->name = le16_to_cpu(d->wClockSourceStr); + return 0; +} - return 0; - } - case UAC3_PROCESSING_UNIT: { - struct uac_processing_unit_descriptor *d = p1; +#define PTYPE(a, b) ((a) << 8 | (b)) - if (!d->bNrInPins) - return -EINVAL; +/* + * parse the source unit recursively until it reaches to a terminal + * or a branched unit. + */ +static int __check_input_term(struct mixer_build *state, int id, + struct usb_audio_term *term) +{ + int protocol = state->mixer->protocol; + void *p1; + unsigned char *hdr; - /* call recursively to retrieve the channel info */ - err = check_input_term(state, d->baSourceID[0], term); - if (err < 0) - return err; + for (;;) { + /* a loop in the terminal chain? */ + if (test_and_set_bit(id, state->termbitmap)) + return -EINVAL; - term->type = UAC3_PROCESSING_UNIT << 16; /* virtual type */ - term->id = id; - term->name = 0; /* TODO: UAC3 Class-specific strings */ + p1 = find_audio_control_unit(state, id); + if (!p1) + break; + if (!snd_usb_validate_audio_desc(p1, protocol)) + break; /* bad descriptor */ - return 0; - } - default: - return -ENODEV; - } + hdr = p1; + term->id = id; + + switch (PTYPE(protocol, hdr[2])) { + case PTYPE(UAC_VERSION_1, UAC_FEATURE_UNIT): + case PTYPE(UAC_VERSION_2, UAC_FEATURE_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_FEATURE_UNIT): { + /* the header is the same for all versions */ + struct uac_feature_unit_descriptor *d = p1; + + id = d->bSourceID; + break; /* continue to parse */ + } + case PTYPE(UAC_VERSION_1, UAC_INPUT_TERMINAL): + return parse_term_uac1_iterm_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_2, UAC_INPUT_TERMINAL): + return parse_term_uac2_iterm_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_3, UAC_INPUT_TERMINAL): + return parse_term_uac3_iterm_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_1, UAC_MIXER_UNIT): + case PTYPE(UAC_VERSION_2, UAC_MIXER_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_MIXER_UNIT): + return parse_term_mixer_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_1, UAC_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_2, UAC_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SELECTOR): + case PTYPE(UAC_VERSION_3, UAC3_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SELECTOR): + return parse_term_selector_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_1, UAC1_PROCESSING_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_PROCESSING_UNIT_V2): + case PTYPE(UAC_VERSION_3, UAC3_PROCESSING_UNIT): + return parse_term_proc_unit(state, term, p1, id, + UAC3_PROCESSING_UNIT); + case PTYPE(UAC_VERSION_2, UAC2_EFFECT_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_EFFECT_UNIT): + return parse_term_effect_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_1, UAC1_EXTENSION_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_EXTENSION_UNIT_V2): + case PTYPE(UAC_VERSION_3, UAC3_EXTENSION_UNIT): + return parse_term_proc_unit(state, term, p1, id, + UAC3_EXTENSION_UNIT); + case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SOURCE): + return parse_term_uac2_clock_source(state, term, p1, id); + case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SOURCE): + return parse_term_uac3_clock_source(state, term, p1, id); + default: + return -ENODEV; } } return -ENODEV; } + +static int check_input_term(struct mixer_build *state, int id, + struct usb_audio_term *term) +{ + memset(term, 0, sizeof(*term)); + memset(state->termbitmap, 0, sizeof(state->termbitmap)); + return __check_input_term(state, id, term); +} + /* * Feature Unit */ @@ -1009,7 +1037,7 @@ struct usb_feature_control_info { int type_uac2; /* data type for uac2 if different from uac1, else -1 */ }; -static struct usb_feature_control_info audio_feature_info[] = { +static const struct usb_feature_control_info audio_feature_info[] = { { UAC_FU_MUTE, "Mute", USB_MIXER_INV_BOOLEAN, -1 }, { UAC_FU_VOLUME, "Volume", USB_MIXER_S16, -1 }, { UAC_FU_BASS, "Tone Control - Bass", USB_MIXER_S8, -1 }, @@ -1026,10 +1054,15 @@ static struct usb_feature_control_info audio_feature_info[] = { { UAC2_FU_PHASE_INVERTER, "Phase Inverter Control", USB_MIXER_BOOLEAN, -1 }, }; +static void usb_mixer_elem_info_free(struct usb_mixer_elem_info *cval) +{ + kfree(cval); +} + /* private_free callback */ void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl) { - kfree(kctl->private_data); + usb_mixer_elem_info_free(kctl->private_data); kctl->private_data = NULL; } @@ -1042,6 +1075,21 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval, struct snd_kcontrol *kctl) { struct snd_usb_audio *chip = cval->head.mixer->chip; + + if (chip->quirk_flags & QUIRK_FLAG_MIC_RES_384) { + if (!strcmp(kctl->id.name, "Mic Capture Volume")) { + usb_audio_info(chip, + "set resolution quirk: cval->res = 384\n"); + cval->res = 384; + } + } else if (chip->quirk_flags & QUIRK_FLAG_MIC_RES_16) { + if (!strcmp(kctl->id.name, "Mic Capture Volume")) { + usb_audio_info(chip, + "set resolution quirk: cval->res = 16\n"); + cval->res = 16; + } + } + switch (chip->usb_id) { case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */ case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C600 */ @@ -1099,6 +1147,14 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval, } break; + case USB_ID(0x045e, 0x070f): /* MS LifeChat LX-3000 Headset */ + if (!strcmp(kctl->id.name, "Speaker Playback Volume")) { + usb_audio_info(chip, + "set volume quirk for MS LifeChat LX-3000\n"); + cval->res = 192; + } + break; + case USB_ID(0x0471, 0x0101): case USB_ID(0x0471, 0x0104): case USB_ID(0x0471, 0x0105): @@ -1126,36 +1182,57 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval, } break; - case USB_ID(0x046d, 0x0807): /* Logitech Webcam C500 */ - case USB_ID(0x046d, 0x0808): - case USB_ID(0x046d, 0x0809): - case USB_ID(0x046d, 0x0819): /* Logitech Webcam C210 */ - case USB_ID(0x046d, 0x081b): /* HD Webcam c310 */ - case USB_ID(0x046d, 0x081d): /* HD Webcam c510 */ - case USB_ID(0x046d, 0x0825): /* HD Webcam c270 */ - case USB_ID(0x046d, 0x0826): /* HD Webcam c525 */ - case USB_ID(0x046d, 0x08ca): /* Logitech Quickcam Fusion */ - case USB_ID(0x046d, 0x0991): - case USB_ID(0x046d, 0x09a2): /* QuickCam Communicate Deluxe/S7500 */ - /* Most audio usb devices lie about volume resolution. - * Most Logitech webcams have res = 384. - * Probably there is some logitech magic behind this number --fishor - */ - if (!strcmp(kctl->id.name, "Mic Capture Volume")) { + case USB_ID(0x0495, 0x3042): /* ESS Technology Asus USB DAC */ + if ((strstr(kctl->id.name, "Playback Volume") != NULL) || + strstr(kctl->id.name, "Capture Volume") != NULL) { + cval->min >>= 8; + cval->max = 0; + cval->res = 1; + } + break; + case USB_ID(0x3302, 0x12db): /* MOONDROP Quark2 */ + if (!strcmp(kctl->id.name, "PCM Playback Volume")) { usb_audio_info(chip, - "set resolution quirk: cval->res = 384\n"); - cval->res = 384; + "set volume quirk for MOONDROP Quark2\n"); + cval->min = -14208; /* Mute under it */ + } + break; + case USB_ID(0x12d1, 0x3a07): /* Huawei Technologies Co., Ltd. CM-Q3 */ + if (!strcmp(kctl->id.name, "PCM Playback Volume")) { + usb_audio_info(chip, + "set volume quirk for Huawei Technologies Co., Ltd. CM-Q3\n"); + cval->min = -11264; /* Mute under it */ } break; } } +/* forcibly initialize the current mixer value; if GET_CUR fails, set to + * the minimum as default + */ +static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) +{ + int val, err; + + err = snd_usb_get_cur_mix_value(cval, ch, idx, &val); + if (!err) + return; + if (!cval->head.mixer->ignore_ctl_error) + usb_audio_warn(cval->head.mixer->chip, + "%d:%d: failed to get current value for ch %d (%d)\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + ch, err); + snd_usb_set_cur_mix_value(cval, ch, idx, cval->min); +} + /* * retrieve the minimum and maximum values for the specified control */ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, int default_min, struct snd_kcontrol *kctl) { + int i, idx; + /* for failsafe */ cval->min = default_min; cval->max = cval->min + 1; @@ -1168,9 +1245,8 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, } else { int minchn = 0; if (cval->cmask) { - int i; for (i = 0; i < MAX_CHANNELS; i++) - if (cval->cmask & (1 << i)) { + if (cval->cmask & BIT(i)) { minchn = i + 1; break; } @@ -1179,7 +1255,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) { usb_audio_err(cval->head.mixer->chip, "%d:%d: cannot get min/max values for control %d (id %d)\n", - cval->head.id, snd_usb_ctrl_intf(cval->head.mixer->chip), + cval->head.id, mixer_ctrl_intf(cval->head.mixer), cval->control, cval->head.id); return -EINVAL; } @@ -1187,7 +1263,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, (cval->control << 8) | minchn, &cval->res) < 0) { cval->res = 1; - } else { + } else if (cval->head.mixer->protocol == UAC_VERSION_1) { int last_valid_res = cval->res; while (cval->res > 1) { @@ -1213,7 +1289,8 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, if (cval->min + cval->res < cval->max) { int last_valid_res = cval->res; int saved, test, check; - get_cur_mix_raw(cval, minchn, &saved); + if (get_cur_mix_raw(cval, minchn, &saved) < 0) + goto no_res_check; for (;;) { test = saved; if (test < cval->max) @@ -1233,6 +1310,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, snd_usb_set_cur_mix_value(cval, minchn, 0, saved); } +no_res_check: cval->initialized = 1; } @@ -1254,6 +1332,30 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, /* totally crap, return an error */ return -EINVAL; } + } else { + /* if the max volume is too low, it's likely a bogus range; + * here we use -96dB as the threshold + */ + if (cval->dBmax <= -9600) { + usb_audio_info(cval->head.mixer->chip, + "%d:%d: bogus dB values (%d/%d), disabling dB reporting\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->dBmin, cval->dBmax); + cval->dBmin = cval->dBmax = 0; + } + } + + /* initialize all elements */ + if (!cval->cmask) { + init_cur_mix_raw(cval, 0, 0); + } else { + idx = 0; + for (i = 0; i < MAX_CHANNELS; i++) { + if (cval->cmask & BIT(i)) { + init_cur_mix_raw(cval, i + 1, idx); + idx++; + } + } } return 0; @@ -1261,11 +1363,24 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, #define get_min_max(cval, def) get_min_max_with_quirks(cval, def, NULL) +/* get the max value advertised via control API */ +static int get_max_exposed(struct usb_mixer_elem_info *cval) +{ + if (!cval->max_exposed) { + if (cval->res) + cval->max_exposed = + DIV_ROUND_UP(cval->max - cval->min, cval->res); + else + cval->max_exposed = cval->max - cval->min; + } + return cval->max_exposed; +} + /* get a feature/mixer unit info */ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - struct usb_mixer_elem_info *cval = kcontrol->private_data; + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); if (cval->val_type == USB_MIXER_BOOLEAN || cval->val_type == USB_MIXER_INV_BOOLEAN) @@ -1273,11 +1388,8 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, else uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = cval->channels; - if (cval->val_type == USB_MIXER_BOOLEAN || - cval->val_type == USB_MIXER_INV_BOOLEAN) { - uinfo->value.integer.min = 0; - uinfo->value.integer.max = 1; - } else { + if (cval->val_type != USB_MIXER_BOOLEAN && + cval->val_type != USB_MIXER_INV_BOOLEAN) { if (!cval->initialized) { get_min_max_with_quirks(cval, 0, kcontrol); if (cval->initialized && cval->dBmin >= cval->dBmax) { @@ -1289,10 +1401,10 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, &kcontrol->id); } } - uinfo->value.integer.min = 0; - uinfo->value.integer.max = - (cval->max - cval->min + cval->res - 1) / cval->res; } + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = get_max_exposed(cval); return 0; } @@ -1300,14 +1412,14 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *cval = kcontrol->private_data; + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int c, cnt, val, err; ucontrol->value.integer.value[0] = cval->min; if (cval->cmask) { cnt = 0; for (c = 0; c < MAX_CHANNELS; c++) { - if (!(cval->cmask & (1 << c))) + if (!(cval->cmask & BIT(c))) continue; err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &val); if (err < 0) @@ -1332,19 +1444,22 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *cval = kcontrol->private_data; + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); + int max_val = get_max_exposed(cval); int c, cnt, val, oval, err; int changed = 0; if (cval->cmask) { cnt = 0; for (c = 0; c < MAX_CHANNELS; c++) { - if (!(cval->cmask & (1 << c))) + if (!(cval->cmask & BIT(c))) continue; err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &oval); if (err < 0) return filter_error(cval, err); val = ucontrol->value.integer.value[cnt]; + if (val < 0 || val > max_val) + return -EINVAL; val = get_abs_value(cval, val); if (oval != val) { snd_usb_set_cur_mix_value(cval, c + 1, cnt, val); @@ -1358,6 +1473,8 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, if (err < 0) return filter_error(cval, err); val = ucontrol->value.integer.value[0]; + if (val < 0 || val > max_val) + return -EINVAL; val = get_abs_value(cval, val); if (val != oval) { snd_usb_set_cur_mix_value(cval, 0, 0, val); @@ -1371,7 +1488,7 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, static int mixer_ctl_master_bool_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *cval = kcontrol->private_data; + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, err; err = snd_usb_get_cur_mix_value(cval, 0, 0, &val); @@ -1382,52 +1499,76 @@ static int mixer_ctl_master_bool_get(struct snd_kcontrol *kcontrol, return 0; } -/* get the connectors status and report it as boolean type */ -static int mixer_ctl_connector_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static int get_connector_value(struct usb_mixer_elem_info *cval, + char *name, int *val) { - struct usb_mixer_elem_info *cval = kcontrol->private_data; struct snd_usb_audio *chip = cval->head.mixer->chip; - int idx = 0, validx, ret, val; + int idx = 0, validx, ret; validx = cval->control << 8 | 0; - ret = snd_usb_lock_shutdown(chip) ? -EIO : 0; - if (ret) + CLASS(snd_usb_lock, pm)(chip); + if (pm.err) { + ret = -EIO; goto error; + } - idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8); + idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); if (cval->head.mixer->protocol == UAC_VERSION_2) { struct uac2_connectors_ctl_blk uac2_conn; ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), UAC2_CS_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, validx, idx, &uac2_conn, sizeof(uac2_conn)); - val = !!uac2_conn.bNrChannels; + if (val) + *val = !!uac2_conn.bNrChannels; } else { /* UAC_VERSION_3 */ struct uac3_insertion_ctl_blk uac3_conn; ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), UAC2_CS_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, validx, idx, &uac3_conn, sizeof(uac3_conn)); - val = !!uac3_conn.bmConInserted; + if (val) + *val = !!uac3_conn.bmConInserted; } - snd_usb_unlock_shutdown(chip); - if (ret < 0) { + if (name && strstr(name, "Speaker")) { + if (val) + *val = 1; + return 0; + } error: usb_audio_err(chip, "cannot get connectors status: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", UAC_GET_CUR, validx, idx, cval->val_type); - return ret; + + if (val) + *val = 0; + + return filter_error(cval, ret); } + return ret; +} + +/* get the connectors status and report it as boolean type */ +static int mixer_ctl_connector_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); + int ret, val; + + ret = get_connector_value(cval, kcontrol->id.name, &val); + + if (ret < 0) + return ret; + ucontrol->value.integer.value[0] = val; return 0; } -static struct snd_kcontrol_new usb_feature_unit_ctl = { +static const struct snd_kcontrol_new usb_feature_unit_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", /* will be filled later manually */ .info = mixer_ctl_feature_info, @@ -1448,7 +1589,7 @@ static const struct snd_kcontrol_new usb_feature_unit_ctl_ro = { * A control which shows the boolean value from reading a UAC control on * the master channel. */ -static struct snd_kcontrol_new usb_bool_master_control_ctl_ro = { +static const struct snd_kcontrol_new usb_bool_master_control_ctl_ro = { .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = "", /* will be filled later manually */ .access = SNDRV_CTL_ELEM_ACCESS_READ, @@ -1470,7 +1611,7 @@ static const struct snd_kcontrol_new usb_connector_ctl_ro = { * This symbol is exported in order to allow the mixer quirks to * hook up to the standard feature unit control mechanism */ -struct snd_kcontrol_new *snd_usb_feature_unit_ctl = &usb_feature_unit_ctl; +const struct snd_kcontrol_new *snd_usb_feature_unit_ctl = &usb_feature_unit_ctl; /* * build a feature control @@ -1488,9 +1629,9 @@ static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str) static void check_no_speaker_on_headset(struct snd_kcontrol *kctl, struct snd_card *card) { - const char *names_to_check[] = { + static const char * const names_to_check[] = { "Headset", "headset", "Headphone", "headphone", NULL}; - const char **s; + const char * const *s; bool found = false; if (strcmp("Speaker", kctl->id.name)) @@ -1505,10 +1646,10 @@ static void check_no_speaker_on_headset(struct snd_kcontrol *kctl, if (!found) return; - strlcpy(kctl->id.name, "Headphone", sizeof(kctl->id.name)); + snd_ctl_rename(card, kctl, "Headphone"); } -static struct usb_feature_control_info *get_feature_control_info(int control) +static const struct usb_feature_control_info *get_feature_control_info(int control) { int i; @@ -1526,7 +1667,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, struct usb_audio_term *oterm, int unitid, int nameid, int readonly_mask) { - struct usb_feature_control_info *ctl_info; + const struct usb_feature_control_info *ctl_info; unsigned int len = 0; int mapped_name = 0; struct snd_kcontrol *kctl; @@ -1552,7 +1693,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, ctl_info = get_feature_control_info(control); if (!ctl_info) { - kfree(cval); + usb_mixer_elem_info_free(cval); return; } if (mixer->protocol == UAC_VERSION_1) @@ -1567,7 +1708,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, } else { int i, c = 0; for (i = 0; i < 16; i++) - if (ctl_mask & (1 << i)) + if (ctl_mask & BIT(i)) c++; cval->channels = c; cval->ch_readonly = readonly_mask; @@ -1585,7 +1726,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, if (!kctl) { usb_audio_err(mixer->chip, "cannot malloc kcontrol\n"); - kfree(cval); + usb_mixer_elem_info_free(cval); return; } kctl->private_free = snd_usb_mixer_elem_free; @@ -1640,7 +1781,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, break; default: if (!len) - strlcpy(kctl->id.name, audio_feature_info[control-1].name, + strscpy(kctl->id.name, audio_feature_info[control-1].name, sizeof(kctl->id.name)); break; } @@ -1648,6 +1789,16 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, /* get min/max values */ get_min_max_with_quirks(cval, 0, kctl); + /* skip a bogus volume range */ + if (cval->max <= cval->min) { + usb_audio_dbg(mixer->chip, + "[%d] FU [%s] skipped due to invalid volume\n", + cval->head.id, kctl->id.name); + snd_ctl_free_one(kctl); + return; + } + + if (control == UAC_FU_VOLUME) { check_mapped_dB(map, cval); if (cval->dBmin < cval->dBmax || !cval->initialized) { @@ -1709,7 +1860,7 @@ static void get_connector_control_name(struct usb_mixer_interface *mixer, int name_len = get_term_name(mixer->chip, term, name, name_size, 0); if (name_len == 0) - strlcpy(name, "Unknown", name_size); + strscpy(name, "Unknown", name_size); /* * sound/core/ctljack.c has a convention of naming jack controls @@ -1722,17 +1873,36 @@ static void get_connector_control_name(struct usb_mixer_interface *mixer, strlcat(name, " - Output Jack", name_size); } +/* get connector value to "wake up" the USB audio */ +static int connector_mixer_resume(struct usb_mixer_elem_list *list) +{ + struct usb_mixer_elem_info *cval = mixer_elem_list_to_info(list); + + get_connector_value(cval, NULL, NULL); + return 0; +} + /* Build a mixer control for a UAC connector control (jack-detect) */ static void build_connector_control(struct usb_mixer_interface *mixer, + const struct usbmix_name_map *imap, struct usb_audio_term *term, bool is_input) { struct snd_kcontrol *kctl; struct usb_mixer_elem_info *cval; + const struct usbmix_name_map *map; + + map = find_map(imap, term->id, 0); + if (check_ignored_ctl(map)) + return; cval = kzalloc(sizeof(*cval), GFP_KERNEL); if (!cval) return; snd_usb_mixer_elem_init_std(&cval->head, mixer, term->id); + + /* set up a specific resume callback */ + cval->head.resume = connector_mixer_resume; + /* * UAC2: The first byte from reading the UAC2_TE_CONNECTOR control returns the * number of channels connected. @@ -1755,11 +1925,15 @@ static void build_connector_control(struct usb_mixer_interface *mixer, kctl = snd_ctl_new1(&usb_connector_ctl_ro, cval); if (!kctl) { usb_audio_err(mixer->chip, "cannot malloc kcontrol\n"); - kfree(cval); + usb_mixer_elem_info_free(cval); return; } - get_connector_control_name(mixer, term, is_input, kctl->id.name, - sizeof(kctl->id.name)); + + if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) + strlcat(kctl->id.name, " Jack", sizeof(kctl->id.name)); + else + get_connector_control_name(mixer, term, is_input, kctl->id.name, + sizeof(kctl->id.name)); kctl->private_free = snd_usb_mixer_elem_free; snd_usb_mixer_add_control(&cval->head, kctl); } @@ -1770,19 +1944,11 @@ static int parse_clock_source_unit(struct mixer_build *state, int unitid, struct uac_clock_source_descriptor *hdr = _ftr; struct usb_mixer_elem_info *cval; struct snd_kcontrol *kctl; - char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; int ret; if (state->mixer->protocol != UAC_VERSION_2) return -EINVAL; - if (hdr->bLength != sizeof(*hdr)) { - usb_audio_dbg(state->chip, - "Bogus clock source descriptor length of %d, ignoring.\n", - hdr->bLength); - return 0; - } - /* * The only property of this unit we are interested in is the * clock source validity. If that isn't readable, just bail out. @@ -1808,16 +1974,15 @@ static int parse_clock_source_unit(struct mixer_build *state, int unitid, kctl = snd_ctl_new1(&usb_bool_master_control_ctl_ro, cval); if (!kctl) { - kfree(cval); + usb_mixer_elem_info_free(cval); return -ENOMEM; } kctl->private_free = snd_usb_mixer_elem_free; ret = snd_usb_copy_string_desc(state->chip, hdr->iClockSource, - name, sizeof(name)); + kctl->id.name, sizeof(kctl->id.name)); if (ret > 0) - snprintf(kctl->id.name, sizeof(kctl->id.name), - "%s Validity", name); + append_ctl_name(kctl, " Validity"); else snprintf(kctl->id.name, sizeof(kctl->id.name), "Clock Source %d Validity", hdr->bClockID); @@ -1835,68 +2000,33 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, { int channels, i, j; struct usb_audio_term iterm; - unsigned int master_bits, first_ch_bits; + unsigned int master_bits; int err, csize; struct uac_feature_unit_descriptor *hdr = _ftr; __u8 *bmaControls; if (state->mixer->protocol == UAC_VERSION_1) { - if (hdr->bLength < 7) { - usb_audio_err(state->chip, - "unit %u: invalid UAC_FEATURE_UNIT descriptor\n", - unitid); - return -EINVAL; - } csize = hdr->bControlSize; - if (!csize) { - usb_audio_dbg(state->chip, - "unit %u: invalid bControlSize == 0\n", - unitid); - return -EINVAL; - } channels = (hdr->bLength - 7) / csize - 1; bmaControls = hdr->bmaControls; - if (hdr->bLength < 7 + csize) { - usb_audio_err(state->chip, - "unit %u: invalid UAC_FEATURE_UNIT descriptor\n", - unitid); - return -EINVAL; - } } else if (state->mixer->protocol == UAC_VERSION_2) { struct uac2_feature_unit_descriptor *ftr = _ftr; - if (hdr->bLength < 6) { - usb_audio_err(state->chip, - "unit %u: invalid UAC_FEATURE_UNIT descriptor\n", - unitid); - return -EINVAL; - } csize = 4; channels = (hdr->bLength - 6) / 4 - 1; bmaControls = ftr->bmaControls; - if (hdr->bLength < 6 + csize) { - usb_audio_err(state->chip, - "unit %u: invalid UAC_FEATURE_UNIT descriptor\n", - unitid); - return -EINVAL; - } } else { /* UAC_VERSION_3 */ struct uac3_feature_unit_descriptor *ftr = _ftr; - if (hdr->bLength < 7) { - usb_audio_err(state->chip, - "unit %u: invalid UAC3_FEATURE_UNIT descriptor\n", - unitid); - return -EINVAL; - } csize = 4; channels = (ftr->bLength - 7) / 4 - 1; bmaControls = ftr->bmaControls; - if (hdr->bLength < 7 + csize) { - usb_audio_err(state->chip, - "unit %u: invalid UAC3_FEATURE_UNIT descriptor\n", - unitid); - return -EINVAL; - } + } + + if (channels > 32) { + usb_audio_info(state->chip, + "usbmixer: too many channels (%d) in unit %d\n", + channels, unitid); + return -EINVAL; } /* parse the source unit */ @@ -1926,10 +2056,6 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, break; } - if (channels > 0) - first_ch_bits = snd_usb_combine_bytes(bmaControls + csize, csize); - else - first_ch_bits = 0; if (state->mixer->protocol == UAC_VERSION_1) { /* check all control types */ @@ -1942,8 +2068,8 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize); - if (mask & (1 << i)) - ch_bits |= (1 << j); + if (mask & BIT(i)) + ch_bits |= BIT(j); } /* audio class v1 controls are never read-only */ @@ -1954,7 +2080,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, if (ch_bits & 1) build_feature_ctl(state, _ftr, ch_bits, control, &iterm, unitid, 0); - if (master_bits & (1 << i)) + if (master_bits & BIT(i)) build_feature_ctl(state, _ftr, 0, control, &iterm, unitid, 0); } @@ -1970,9 +2096,9 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize); if (uac_v2v3_control_is_readable(mask, control)) { - ch_bits |= (1 << j); + ch_bits |= BIT(j); if (!uac_v2v3_control_is_writeable(mask, control)) - ch_read_only |= (1 << j); + ch_read_only |= BIT(j); } } @@ -2007,6 +2133,31 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, * Mixer Unit */ +/* check whether the given in/out overflows bmMixerControls matrix */ +static bool mixer_bitmap_overflow(struct uac_mixer_unit_descriptor *desc, + int protocol, int num_ins, int num_outs) +{ + u8 *hdr = (u8 *)desc; + u8 *c = uac_mixer_unit_bmControls(desc, protocol); + size_t rest; /* remaining bytes after bmMixerControls */ + + switch (protocol) { + case UAC_VERSION_1: + default: + rest = 1; /* iMixer */ + break; + case UAC_VERSION_2: + rest = 2; /* bmControls + iMixer */ + break; + case UAC_VERSION_3: + rest = 6; /* bmControls + wMixerDescrStr */ + break; + } + + /* overflow? */ + return c + (num_ins * num_outs + 7) / 8 + rest > hdr + hdr[0]; +} + /* * build a mixer unit control * @@ -2038,7 +2189,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, __u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol); if (check_matrix_bitmap(c, in_ch, i, num_outs)) { - cval->cmask |= (1 << i); + cval->cmask |= BIT(i); cval->channels++; } } @@ -2049,7 +2200,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); if (!kctl) { usb_audio_err(state->chip, "cannot malloc kcontrol\n"); - kfree(cval); + usb_mixer_elem_info_free(cval); return; } kctl->private_free = snd_usb_mixer_elem_free; @@ -2059,7 +2210,8 @@ static void build_mixer_unit_ctl(struct mixer_build *state, len = get_term_name(state->chip, iterm, kctl->id.name, sizeof(kctl->id.name), 0); if (!len) - len = sprintf(kctl->id.name, "Mixer Source %d", in_ch + 1); + snprintf(kctl->id.name, sizeof(kctl->id.name), "Mixer Source %d", in_ch + 1); + append_ctl_name(kctl, " Volume"); usb_audio_dbg(state->chip, "[%d] MU [%s] ch = %d, val = %d/%d\n", @@ -2075,15 +2227,11 @@ static int parse_audio_input_terminal(struct mixer_build *state, int unitid, if (state->mixer->protocol == UAC_VERSION_2) { struct uac2_input_terminal_descriptor *d_v2 = raw_desc; - if (d_v2->bLength < sizeof(*d_v2)) - return -EINVAL; control = UAC2_TE_CONNECTOR; term_id = d_v2->bTerminalID; bmctls = le16_to_cpu(d_v2->bmControls); } else if (state->mixer->protocol == UAC_VERSION_3) { struct uac3_input_terminal_descriptor *d_v3 = raw_desc; - if (d_v3->bLength < sizeof(*d_v3)) - return -EINVAL; control = UAC3_TE_INSERTION; term_id = d_v3->bTerminalID; bmctls = le32_to_cpu(d_v3->bmControls); @@ -2094,8 +2242,9 @@ static int parse_audio_input_terminal(struct mixer_build *state, int unitid, check_input_term(state, term_id, &iterm); /* Check for jack detection. */ - if (uac_v2v3_control_is_readable(bmctls, control)) - build_connector_control(state->mixer, &iterm, true); + if ((iterm.type & 0xff00) != 0x0100 && + uac_v2v3_control_is_readable(bmctls, control)) + build_connector_control(state->mixer, state->map, &iterm, true); return 0; } @@ -2135,6 +2284,9 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, if (err < 0) return err; num_ins += iterm.channels; + if (mixer_bitmap_overflow(desc, state->mixer->protocol, + num_ins, num_outs)) + break; for (; ich < num_ins; ich++) { int och, ich_has_controls = 0; @@ -2163,7 +2315,7 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *cval = kcontrol->private_data; + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int err, val; err = get_cur_ctl_value(cval, cval->control << 8, &val); @@ -2180,13 +2332,15 @@ static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *cval = kcontrol->private_data; + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, oval, err; err = get_cur_ctl_value(cval, cval->control << 8, &oval); if (err < 0) return filter_error(cval, err); val = ucontrol->value.integer.value[0]; + if (val < 0 || val > get_max_exposed(cval)) + return -EINVAL; val = get_abs_value(cval, val); if (val != oval) { set_cur_ctl_value(cval, cval->control << 8, val); @@ -2209,7 +2363,7 @@ static const struct snd_kcontrol_new mixer_procunit_ctl = { */ struct procunit_value_info { int control; - char *suffix; + const char *suffix; int val_type; int min_value; }; @@ -2217,44 +2371,44 @@ struct procunit_value_info { struct procunit_info { int type; char *name; - struct procunit_value_info *values; + const struct procunit_value_info *values; }; -static struct procunit_value_info undefined_proc_info[] = { +static const struct procunit_value_info undefined_proc_info[] = { { 0x00, "Control Undefined", 0 }, { 0 } }; -static struct procunit_value_info updown_proc_info[] = { +static const struct procunit_value_info updown_proc_info[] = { { UAC_UD_ENABLE, "Switch", USB_MIXER_BOOLEAN }, { UAC_UD_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 }, { 0 } }; -static struct procunit_value_info prologic_proc_info[] = { +static const struct procunit_value_info prologic_proc_info[] = { { UAC_DP_ENABLE, "Switch", USB_MIXER_BOOLEAN }, { UAC_DP_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 }, { 0 } }; -static struct procunit_value_info threed_enh_proc_info[] = { +static const struct procunit_value_info threed_enh_proc_info[] = { { UAC_3D_ENABLE, "Switch", USB_MIXER_BOOLEAN }, { UAC_3D_SPACE, "Spaciousness", USB_MIXER_U8 }, { 0 } }; -static struct procunit_value_info reverb_proc_info[] = { +static const struct procunit_value_info reverb_proc_info[] = { { UAC_REVERB_ENABLE, "Switch", USB_MIXER_BOOLEAN }, { UAC_REVERB_LEVEL, "Level", USB_MIXER_U8 }, { UAC_REVERB_TIME, "Time", USB_MIXER_U16 }, { UAC_REVERB_FEEDBACK, "Feedback", USB_MIXER_U8 }, { 0 } }; -static struct procunit_value_info chorus_proc_info[] = { +static const struct procunit_value_info chorus_proc_info[] = { { UAC_CHORUS_ENABLE, "Switch", USB_MIXER_BOOLEAN }, { UAC_CHORUS_LEVEL, "Level", USB_MIXER_U8 }, { UAC_CHORUS_RATE, "Rate", USB_MIXER_U16 }, { UAC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 }, { 0 } }; -static struct procunit_value_info dcr_proc_info[] = { +static const struct procunit_value_info dcr_proc_info[] = { { UAC_DCR_ENABLE, "Switch", USB_MIXER_BOOLEAN }, { UAC_DCR_RATE, "Ratio", USB_MIXER_U16 }, { UAC_DCR_MAXAMPL, "Max Amp", USB_MIXER_S16 }, @@ -2264,7 +2418,7 @@ static struct procunit_value_info dcr_proc_info[] = { { 0 } }; -static struct procunit_info procunits[] = { +static const struct procunit_info procunits[] = { { UAC_PROCESS_UP_DOWNMIX, "Up Down", updown_proc_info }, { UAC_PROCESS_DOLBY_PROLOGIC, "Dolby Prologic", prologic_proc_info }, { UAC_PROCESS_STEREO_EXTENDER, "3D Stereo Extender", threed_enh_proc_info }, @@ -2274,16 +2428,16 @@ static struct procunit_info procunits[] = { { 0 }, }; -static struct procunit_value_info uac3_updown_proc_info[] = { +static const struct procunit_value_info uac3_updown_proc_info[] = { { UAC3_UD_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 }, { 0 } }; -static struct procunit_value_info uac3_stereo_ext_proc_info[] = { +static const struct procunit_value_info uac3_stereo_ext_proc_info[] = { { UAC3_EXT_WIDTH_CONTROL, "Width Control", USB_MIXER_U8 }, { 0 } }; -static struct procunit_info uac3_procunits[] = { +static const struct procunit_info uac3_procunits[] = { { UAC3_PROCESS_UP_DOWNMIX, "Up Down", uac3_updown_proc_info }, { UAC3_PROCESS_STEREO_EXTENDER, "3D Stereo Extender", uac3_stereo_ext_proc_info }, { UAC3_PROCESS_MULTI_FUNCTION, "Multi-Function", undefined_proc_info }, @@ -2293,23 +2447,23 @@ static struct procunit_info uac3_procunits[] = { /* * predefined data for extension units */ -static struct procunit_value_info clock_rate_xu_info[] = { +static const struct procunit_value_info clock_rate_xu_info[] = { { USB_XU_CLOCK_RATE_SELECTOR, "Selector", USB_MIXER_U8, 0 }, { 0 } }; -static struct procunit_value_info clock_source_xu_info[] = { +static const struct procunit_value_info clock_source_xu_info[] = { { USB_XU_CLOCK_SOURCE_SELECTOR, "External", USB_MIXER_BOOLEAN }, { 0 } }; -static struct procunit_value_info spdif_format_xu_info[] = { +static const struct procunit_value_info spdif_format_xu_info[] = { { USB_XU_DIGITAL_FORMAT_SELECTOR, "SPDIF/AC3", USB_MIXER_BOOLEAN }, { 0 } }; -static struct procunit_value_info soft_limit_xu_info[] = { +static const struct procunit_value_info soft_limit_xu_info[] = { { USB_XU_SOFT_LIMIT_SELECTOR, " ", USB_MIXER_BOOLEAN }, { 0 } }; -static struct procunit_info extunits[] = { +static const struct procunit_info extunits[] = { { USB_XU_CLOCK_RATE, "Clock rate", clock_rate_xu_info }, { USB_XU_CLOCK_SOURCE, "DigitalIn CLK source", clock_source_xu_info }, { USB_XU_DIGITAL_IO_STATUS, "DigitalOut format:", spdif_format_xu_info }, @@ -2321,37 +2475,28 @@ static struct procunit_info extunits[] = { * build a processing/extension unit */ static int build_audio_procunit(struct mixer_build *state, int unitid, - void *raw_desc, struct procunit_info *list, - char *name) + void *raw_desc, const struct procunit_info *list, + bool extension_unit) { struct uac_processing_unit_descriptor *desc = raw_desc; int num_ins; struct usb_mixer_elem_info *cval; struct snd_kcontrol *kctl; - int i, err, nameid, type, len; - struct procunit_info *info; - struct procunit_value_info *valinfo; + int i, err, nameid, type, len, val; + const struct procunit_info *info; + const struct procunit_value_info *valinfo; const struct usbmix_name_map *map; - static struct procunit_value_info default_value_info[] = { + static const struct procunit_value_info default_value_info[] = { { 0x01, "Switch", USB_MIXER_BOOLEAN }, { 0 } }; - static struct procunit_info default_info = { + static const struct procunit_info default_info = { 0, NULL, default_value_info }; - - if (desc->bLength < 13) { - usb_audio_err(state->chip, "invalid %s descriptor (id %d)\n", name, unitid); - return -EINVAL; - } + const char *name = extension_unit ? + "Extension Unit" : "Processing Unit"; num_ins = desc->bNrInPins; - if (desc->bLength < 13 + num_ins || - desc->bLength < num_ins + uac_processing_unit_bControlSize(desc, state->mixer->protocol)) { - usb_audio_err(state->chip, "invalid %s descriptor (id %d)\n", name, unitid); - return -EINVAL; - } - for (i = 0; i < num_ins; i++) { err = parse_audio_unit(state, desc->baSourceID[i]); if (err < 0) @@ -2370,7 +2515,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, if (state->mixer->protocol == UAC_VERSION_1) { if (!(controls[valinfo->control / 8] & - (1 << ((valinfo->control % 8) - 1)))) + BIT((valinfo->control % 8) - 1))) continue; } else { /* UAC_VERSION_2/3 */ if (!uac_v2v3_control_is_readable(controls[valinfo->control / 8], @@ -2440,9 +2585,15 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, break; } + err = get_cur_ctl_value(cval, cval->control << 8, &val); + if (err < 0) { + usb_mixer_elem_info_free(cval); + return -EINVAL; + } + kctl = snd_ctl_new1(&mixer_procunit_ctl, cval); if (!kctl) { - kfree(cval); + usb_mixer_elem_info_free(cval); return -ENOMEM; } kctl->private_free = snd_usb_mixer_elem_free; @@ -2450,9 +2601,12 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) { /* nothing */ ; } else if (info->name) { - strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name)); + strscpy(kctl->id.name, info->name, sizeof(kctl->id.name)); } else { - nameid = uac_processing_unit_iProcessing(desc, state->mixer->protocol); + if (extension_unit) + nameid = uac_extension_unit_iExtension(desc, state->mixer->protocol); + else + nameid = uac_processing_unit_iProcessing(desc, state->mixer->protocol); len = 0; if (nameid) len = snd_usb_copy_string_desc(state->chip, @@ -2460,7 +2614,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, kctl->id.name, sizeof(kctl->id.name)); if (!len) - strlcpy(kctl->id.name, name, sizeof(kctl->id.name)); + strscpy(kctl->id.name, name, sizeof(kctl->id.name)); } append_ctl_name(kctl, " "); append_ctl_name(kctl, valinfo->suffix); @@ -2485,10 +2639,10 @@ static int parse_audio_processing_unit(struct mixer_build *state, int unitid, case UAC_VERSION_2: default: return build_audio_procunit(state, unitid, raw_desc, - procunits, "Processing Unit"); + procunits, false); case UAC_VERSION_3: return build_audio_procunit(state, unitid, raw_desc, - uac3_procunits, "Processing Unit"); + uac3_procunits, false); } } @@ -2499,8 +2653,7 @@ static int parse_audio_extension_unit(struct mixer_build *state, int unitid, * Note that we parse extension units with processing unit descriptors. * That's ok as the layout is the same. */ - return build_audio_procunit(state, unitid, raw_desc, - extunits, "Extension Unit"); + return build_audio_procunit(state, unitid, raw_desc, extunits, true); } /* @@ -2514,7 +2667,7 @@ static int parse_audio_extension_unit(struct mixer_build *state, int unitid, static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - struct usb_mixer_elem_info *cval = kcontrol->private_data; + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); const char **itemlist = (const char **)kcontrol->private_value; if (snd_BUG_ON(!itemlist)) @@ -2526,7 +2679,7 @@ static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol, static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *cval = kcontrol->private_data; + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, err; err = get_cur_ctl_value(cval, cval->control << 8, &val); @@ -2543,13 +2696,15 @@ static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *cval = kcontrol->private_data; + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, oval, err; err = get_cur_ctl_value(cval, cval->control << 8, &oval); if (err < 0) return filter_error(cval, err); val = ucontrol->value.enumerated.item[0]; + if (val < 0 || val >= cval->max) /* here cval->max = # elements */ + return -EINVAL; val = get_abs_value(cval, val); if (val != oval) { set_cur_ctl_value(cval, cval->control << 8, val); @@ -2578,7 +2733,7 @@ static void usb_mixer_selector_elem_free(struct snd_kcontrol *kctl) if (kctl->private_data) { struct usb_mixer_elem_info *cval = kctl->private_data; num_ins = cval->max; - kfree(cval); + usb_mixer_elem_info_free(cval); kctl->private_data = NULL; } if (kctl->private_value) { @@ -2604,13 +2759,6 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, const struct usbmix_name_map *map; char **namelist; - if (desc->bLength < 5 || !desc->bNrInPins || - desc->bLength < 5 + desc->bNrInPins) { - usb_audio_err(state->chip, - "invalid SELECTOR UNIT descriptor %d\n", unitid); - return -EINVAL; - } - for (i = 0; i < desc->bNrInPins; i++) { err = parse_audio_unit(state, desc->baSourceID[i]); if (err < 0) @@ -2650,22 +2798,18 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, break; } - namelist = kmalloc_array(desc->bNrInPins, sizeof(char *), GFP_KERNEL); + namelist = kcalloc(desc->bNrInPins, sizeof(char *), GFP_KERNEL); if (!namelist) { - kfree(cval); - return -ENOMEM; + err = -ENOMEM; + goto error_cval; } #define MAX_ITEM_NAME_LEN 64 for (i = 0; i < desc->bNrInPins; i++) { struct usb_audio_term iterm; - len = 0; namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL); if (!namelist[i]) { - while (i--) - kfree(namelist[i]); - kfree(namelist); - kfree(cval); - return -ENOMEM; + err = -ENOMEM; + goto error_name; } len = check_mapped_selector_name(state, unitid, i, namelist[i], MAX_ITEM_NAME_LEN); @@ -2673,15 +2817,14 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, len = get_term_name(state->chip, &iterm, namelist[i], MAX_ITEM_NAME_LEN, 0); if (! len) - sprintf(namelist[i], "Input %u", i); + scnprintf(namelist[i], MAX_ITEM_NAME_LEN, "Input %u", i); } kctl = snd_ctl_new1(&mixer_selectunit_ctl, cval); if (! kctl) { usb_audio_err(state->chip, "cannot malloc kcontrol\n"); - kfree(namelist); - kfree(cval); - return -ENOMEM; + err = -ENOMEM; + goto error_name; } kctl->private_value = (unsigned long)namelist; kctl->private_free = usb_mixer_selector_elem_free; @@ -2712,7 +2855,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, kctl->id.name, sizeof(kctl->id.name), 0); /* ... or use the fixed string "USB" as the last resort */ if (!len) - strlcpy(kctl->id.name, "USB", sizeof(kctl->id.name)); + strscpy(kctl->id.name, "USB", sizeof(kctl->id.name)); /* and add the proper suffix */ if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR || @@ -2727,6 +2870,14 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, usb_audio_dbg(state->chip, "[%d] SU [%s] items = %d\n", cval->head.id, kctl->id.name, desc->bNrInPins); return snd_usb_mixer_add_control(&cval->head, kctl); + + error_name: + for (i = 0; i < desc->bNrInPins; i++) + kfree(namelist[i]); + kfree(namelist); + error_cval: + usb_mixer_elem_info_free(cval); + return err; } /* @@ -2747,62 +2898,49 @@ static int parse_audio_unit(struct mixer_build *state, int unitid) return -EINVAL; } - if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) { - switch (p1[2]) { - case UAC_INPUT_TERMINAL: - return parse_audio_input_terminal(state, unitid, p1); - case UAC_MIXER_UNIT: - return parse_audio_mixer_unit(state, unitid, p1); - case UAC2_CLOCK_SOURCE: - return parse_clock_source_unit(state, unitid, p1); - case UAC_SELECTOR_UNIT: - case UAC2_CLOCK_SELECTOR: - 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 */ - if (protocol == UAC_VERSION_1) - return parse_audio_processing_unit(state, unitid, p1); - else - return 0; /* FIXME - effect units not implemented yet */ - case UAC1_EXTENSION_UNIT: - /* UAC2_PROCESSING_UNIT_V2 has the same value */ - if (protocol == UAC_VERSION_1) - return parse_audio_extension_unit(state, unitid, p1); - else /* UAC_VERSION_2 */ - return parse_audio_processing_unit(state, unitid, p1); - case UAC2_EXTENSION_UNIT_V2: - return parse_audio_extension_unit(state, unitid, p1); - default: - usb_audio_err(state->chip, - "unit %u: unexpected type 0x%02x\n", unitid, p1[2]); - return -EINVAL; - } - } else { /* UAC_VERSION_3 */ - switch (p1[2]) { - case UAC_INPUT_TERMINAL: - return parse_audio_input_terminal(state, unitid, p1); - case UAC3_MIXER_UNIT: - return parse_audio_mixer_unit(state, unitid, p1); - case UAC3_CLOCK_SOURCE: - return parse_clock_source_unit(state, unitid, p1); - case UAC3_SELECTOR_UNIT: - case UAC3_CLOCK_SELECTOR: - return parse_audio_selector_unit(state, unitid, p1); - case UAC3_FEATURE_UNIT: - return parse_audio_feature_unit(state, unitid, p1); - case UAC3_EFFECT_UNIT: - return 0; /* FIXME - effect units not implemented yet */ - case UAC3_PROCESSING_UNIT: - return parse_audio_processing_unit(state, unitid, p1); - case UAC3_EXTENSION_UNIT: - return parse_audio_extension_unit(state, unitid, p1); - default: - usb_audio_err(state->chip, - "unit %u: unexpected type 0x%02x\n", unitid, p1[2]); - return -EINVAL; - } + if (!snd_usb_validate_audio_desc(p1, protocol)) { + usb_audio_dbg(state->chip, "invalid unit %d\n", unitid); + return 0; /* skip invalid unit */ + } + + switch (PTYPE(protocol, p1[2])) { + case PTYPE(UAC_VERSION_1, UAC_INPUT_TERMINAL): + case PTYPE(UAC_VERSION_2, UAC_INPUT_TERMINAL): + case PTYPE(UAC_VERSION_3, UAC_INPUT_TERMINAL): + return parse_audio_input_terminal(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC_MIXER_UNIT): + case PTYPE(UAC_VERSION_2, UAC_MIXER_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_MIXER_UNIT): + return parse_audio_mixer_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SOURCE): + case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SOURCE): + return parse_clock_source_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_2, UAC_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SELECTOR): + case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SELECTOR): + return parse_audio_selector_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC_FEATURE_UNIT): + case PTYPE(UAC_VERSION_2, UAC_FEATURE_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_FEATURE_UNIT): + return parse_audio_feature_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC1_PROCESSING_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_PROCESSING_UNIT_V2): + case PTYPE(UAC_VERSION_3, UAC3_PROCESSING_UNIT): + return parse_audio_processing_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC1_EXTENSION_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_EXTENSION_UNIT_V2): + case PTYPE(UAC_VERSION_3, UAC3_EXTENSION_UNIT): + return parse_audio_extension_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_2, UAC2_EFFECT_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_EFFECT_UNIT): + return 0; /* FIXME - effect units not implemented yet */ + default: + usb_audio_err(state->chip, + "unit %u: unexpected type 0x%02x\n", + unitid, p1[2]); + return -EINVAL; } } @@ -2837,7 +2975,7 @@ struct uac3_badd_profile { int st_chmask; /* side tone mixing channel mask */ }; -static struct uac3_badd_profile uac3_badd_profiles[] = { +static const struct uac3_badd_profile uac3_badd_profiles[] = { { /* * BAIF, BAOF or combination of both @@ -2898,7 +3036,7 @@ static struct uac3_badd_profile uac3_badd_profiles[] = { }; static bool uac3_badd_func_has_valid_channels(struct usb_mixer_interface *mixer, - struct uac3_badd_profile *f, + const struct uac3_badd_profile *f, int c_chmask, int p_chmask) { /* @@ -2942,12 +3080,14 @@ static int snd_usb_mixer_controls_badd(struct usb_mixer_interface *mixer, struct usb_device *dev = mixer->chip->dev; struct usb_interface_assoc_descriptor *assoc; int badd_profile = mixer->chip->badd_profile; - struct uac3_badd_profile *f; + const struct uac3_badd_profile *f; const struct usbmix_ctl_map *map; int p_chmask = 0, c_chmask = 0, st_chmask = 0; int i; assoc = usb_ifnum_to_if(dev, ctrlif)->intf_assoc; + if (!assoc) + return -EINVAL; /* Detect BADD capture/playback channels from AS EP descriptors */ for (i = 0; i < assoc->bInterfaceCount; i++) { @@ -2964,6 +3104,9 @@ static int snd_usb_mixer_controls_badd(struct usb_mixer_interface *mixer, continue; iface = usb_ifnum_to_if(dev, intf); + if (!iface) + continue; + num = iface->num_altsetting; if (num < 2) @@ -3073,13 +3216,13 @@ static int snd_usb_mixer_controls_badd(struct usb_mixer_interface *mixer, memset(&iterm, 0, sizeof(iterm)); iterm.id = UAC3_BADD_IT_ID4; iterm.type = UAC_BIDIR_TERMINAL_HEADSET; - build_connector_control(mixer, &iterm, true); + build_connector_control(mixer, map->map, &iterm, true); /* Output Term - Insertion control */ memset(&oterm, 0, sizeof(oterm)); oterm.id = UAC3_BADD_OT_ID3; oterm.type = UAC_BIDIR_TERMINAL_HEADSET; - build_connector_control(mixer, &oterm, false); + build_connector_control(mixer, map->map, &oterm, false); } return 0; @@ -3108,7 +3251,7 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) if (map->id == state.chip->usb_id) { state.map = map->map; state.selector_map = map->selector_map; - mixer->ignore_ctl_error = map->ignore_ctl_error; + mixer->connector_map = map->connector_map; break; } } @@ -3117,11 +3260,12 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) while ((p = snd_usb_find_csint_desc(mixer->hostif->extra, mixer->hostif->extralen, p, UAC_OUTPUT_TERMINAL)) != NULL) { + if (!snd_usb_validate_audio_desc(p, mixer->protocol)) + continue; /* skip invalid descriptor */ + 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; @@ -3133,8 +3277,6 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) } else if (mixer->protocol == 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; @@ -3152,16 +3294,15 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) if (err < 0 && err != -EINVAL) return err; - if (uac_v2v3_control_is_readable(le16_to_cpu(desc->bmControls), + if ((state.oterm.type & 0xff00) != 0x0100 && + uac_v2v3_control_is_readable(le16_to_cpu(desc->bmControls), UAC2_TE_CONNECTOR)) { - build_connector_control(state.mixer, &state.oterm, - false); + build_connector_control(state.mixer, state.map, + &state.oterm, false); } } else { /* UAC_VERSION_3 */ struct uac3_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; @@ -3179,10 +3320,11 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) if (err < 0 && err != -EINVAL) return err; - if (uac_v2v3_control_is_readable(le32_to_cpu(desc->bmControls), + if ((state.oterm.type & 0xff00) != 0x0100 && + uac_v2v3_control_is_readable(le32_to_cpu(desc->bmControls), UAC3_TE_INSERTION)) { - build_connector_control(state.mixer, &state.oterm, - false); + build_connector_control(state.mixer, state.map, + &state.oterm, false); } } } @@ -3190,13 +3332,38 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) return 0; } +static int delegate_notify(struct usb_mixer_interface *mixer, int unitid, + u8 *control, u8 *channel) +{ + const struct usbmix_connector_map *map = mixer->connector_map; + + if (!map) + return unitid; + + for (; map->id; map++) { + if (map->id == unitid) { + if (control && map->control) + *control = map->control; + if (channel && map->channel) + *channel = map->channel; + return map->delegated_id; + } + } + return unitid; +} + void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid) { struct usb_mixer_elem_list *list; + unitid = delegate_notify(mixer, unitid, NULL, NULL); + for_each_mixer_elem(list, mixer, unitid) { - struct usb_mixer_elem_info *info = - mixer_elem_list_to_info(list); + struct usb_mixer_elem_info *info; + + if (!list->is_std_info) + continue; + info = mixer_elem_list_to_info(list); /* invalidate cache, so the value is read from the device */ info->cached = 0; snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, @@ -3208,8 +3375,17 @@ static void snd_usb_mixer_dump_cval(struct snd_info_buffer *buffer, struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *cval = mixer_elem_list_to_info(list); - static char *val_types[] = {"BOOLEAN", "INV_BOOLEAN", - "S8", "U8", "S16", "U16"}; + static const char * const val_types[] = { + [USB_MIXER_BOOLEAN] = "BOOLEAN", + [USB_MIXER_INV_BOOLEAN] = "INV_BOOLEAN", + [USB_MIXER_S8] = "S8", + [USB_MIXER_U8] = "U8", + [USB_MIXER_S16] = "S16", + [USB_MIXER_U16] = "U16", + [USB_MIXER_S32] = "S32", + [USB_MIXER_U32] = "U32", + [USB_MIXER_BESPOKEN] = "BESPOKEN", + }; snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%x, " "channels=%i, type=\"%s\"\n", cval->head.id, cval->control, cval->cmask, cval->channels, @@ -3229,7 +3405,7 @@ static void snd_usb_mixer_proc_read(struct snd_info_entry *entry, list_for_each_entry(mixer, &chip->mixer_list, list) { snd_iprintf(buffer, "USB Mixer: usb_id=0x%08x, ctrlif=%i, ctlerr=%i\n", - chip->usb_id, snd_usb_ctrl_intf(chip), + chip->usb_id, mixer_ctrl_intf(mixer), mixer->ignore_ctl_error); snd_iprintf(buffer, "Card: %s\n", chip->card->longname); for (unitid = 0; unitid < MAX_ID_ELEMS; unitid++) { @@ -3263,6 +3439,8 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, return; } + unitid = delegate_notify(mixer, unitid, &control, &channel); + for_each_mixer_elem(list, mixer, unitid) count++; @@ -3274,6 +3452,8 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, if (!list->kctl) continue; + if (!list->is_std_info) + continue; info = mixer_elem_list_to_info(list); if (count > 1 && info->control != control) @@ -3283,7 +3463,7 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, case UAC2_CS_CUR: /* invalidate cache, so the value is read from the device */ if (channel) - info->cached &= ~(1 << channel); + info->cached &= ~BIT(channel); else /* master channel */ info->cached = 0; @@ -3396,65 +3576,21 @@ static int snd_usb_mixer_status_create(struct usb_mixer_interface *mixer) return 0; } -static int keep_iface_ctl_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); - - ucontrol->value.integer.value[0] = mixer->chip->keep_iface; - return 0; -} - -static int keep_iface_ctl_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); - bool keep_iface = !!ucontrol->value.integer.value[0]; - - if (mixer->chip->keep_iface == keep_iface) - return 0; - mixer->chip->keep_iface = keep_iface; - return 1; -} - -static const struct snd_kcontrol_new keep_iface_ctl = { - .iface = SNDRV_CTL_ELEM_IFACE_CARD, - .name = "Keep Interface", - .info = snd_ctl_boolean_mono_info, - .get = keep_iface_ctl_get, - .put = keep_iface_ctl_put, -}; - -static int create_keep_iface_ctl(struct usb_mixer_interface *mixer) -{ - struct snd_kcontrol *kctl = snd_ctl_new1(&keep_iface_ctl, mixer); - - /* need only one control per card */ - if (snd_ctl_find_id(mixer->chip->card, &kctl->id)) { - snd_ctl_free_one(kctl); - return 0; - } - - return snd_ctl_add(mixer->chip->card, kctl); -} - -int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, - int ignore_error) +int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif) { - static struct snd_device_ops dev_ops = { + static const struct snd_device_ops dev_ops = { .dev_free = snd_usb_mixer_dev_free }; struct usb_mixer_interface *mixer; - struct snd_info_entry *entry; int err; - strcpy(chip->card->mixername, "USB Mixer"); + strscpy(chip->card->mixername, "USB Mixer"); mixer = kzalloc(sizeof(*mixer), GFP_KERNEL); if (!mixer) return -ENOMEM; mixer->chip = chip; - mixer->ignore_ctl_error = ignore_error; + mixer->ignore_ctl_error = !!(chip->quirk_flags & QUIRK_FLAG_IGNORE_CTL_ERROR); mixer->id_elems = kcalloc(MAX_ID_ELEMS, sizeof(*mixer->id_elems), GFP_KERNEL); if (!mixer->id_elems) { @@ -3491,19 +3627,17 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, if (err < 0) goto _error; - err = create_keep_iface_ctl(mixer); + err = snd_usb_mixer_apply_create_quirk(mixer); if (err < 0) goto _error; - snd_usb_mixer_apply_create_quirk(mixer); - err = snd_device_new(chip->card, SNDRV_DEV_CODEC, mixer, &dev_ops); if (err < 0) goto _error; - if (list_empty(&chip->mixer_list) && - !snd_card_proc_new(chip->card, "usbmixer", &entry)) - snd_info_set_text_ops(entry, chip, snd_usb_mixer_proc_read); + if (list_empty(&chip->mixer_list)) + snd_card_ro_proc_new(chip->card, "usbmixer", chip, + snd_usb_mixer_proc_read); list_add(&mixer->list, &chip->mixer_list); return 0; @@ -3521,10 +3655,11 @@ void snd_usb_mixer_disconnect(struct usb_mixer_interface *mixer) usb_kill_urb(mixer->urb); if (mixer->rc_urb) usb_kill_urb(mixer->rc_urb); + if (mixer->private_free) + mixer->private_free(mixer); mixer->disconnected = true; } -#ifdef CONFIG_PM /* stop any bus activity of a mixer */ static void snd_usb_mixer_inactivate(struct usb_mixer_interface *mixer) { @@ -3548,6 +3683,8 @@ static int snd_usb_mixer_activate(struct usb_mixer_interface *mixer) int snd_usb_mixer_suspend(struct usb_mixer_interface *mixer) { snd_usb_mixer_inactivate(mixer); + if (mixer->private_suspend) + mixer->private_suspend(mixer); return 0; } @@ -3556,45 +3693,43 @@ static int restore_mixer_value(struct usb_mixer_elem_list *list) struct usb_mixer_elem_info *cval = mixer_elem_list_to_info(list); int c, err, idx; + if (cval->val_type == USB_MIXER_BESPOKEN) + return 0; + if (cval->cmask) { idx = 0; for (c = 0; c < MAX_CHANNELS; c++) { - if (!(cval->cmask & (1 << c))) + if (!(cval->cmask & BIT(c))) continue; - if (cval->cached & (1 << (c + 1))) { + if (cval->cached & BIT(c + 1)) { err = snd_usb_set_cur_mix_value(cval, c + 1, idx, cval->cache_val[idx]); if (err < 0) - return err; + break; } idx++; } } else { /* master */ - if (cval->cached) { - err = snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val); - if (err < 0) - return err; - } + if (cval->cached) + snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val); } return 0; } -int snd_usb_mixer_resume(struct usb_mixer_interface *mixer, bool reset_resume) +int snd_usb_mixer_resume(struct usb_mixer_interface *mixer) { struct usb_mixer_elem_list *list; int id, err; - if (reset_resume) { - /* restore cached mixer values */ - for (id = 0; id < MAX_ID_ELEMS; id++) { - for_each_mixer_elem(list, mixer, id) { - if (list->resume) { - err = list->resume(list); - if (err < 0) - return err; - } + /* restore cached mixer values */ + for (id = 0; id < MAX_ID_ELEMS; id++) { + for_each_mixer_elem(list, mixer, id) { + if (list->resume) { + err = list->resume(list); + if (err < 0) + return err; } } } @@ -3603,7 +3738,6 @@ int snd_usb_mixer_resume(struct usb_mixer_interface *mixer, bool reset_resume) return snd_usb_mixer_activate(mixer); } -#endif void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list, struct usb_mixer_interface *mixer, @@ -3612,7 +3746,5 @@ void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list, list->mixer = mixer; list->id = unitid; list->dump = snd_usb_mixer_dump_cval; -#ifdef CONFIG_PM list->resume = restore_mixer_value; -#endif } diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 3d12af8bf191..167fbfcf01ac 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -4,6 +4,15 @@ #include <sound/info.h> +struct media_mixer_ctl; + +struct usbmix_connector_map { + u8 id; + u8 delegated_id; + u8 control; + u8 channel; +}; + struct usb_mixer_interface { struct snd_usb_audio *chip; struct usb_host_interface *hostif; @@ -16,6 +25,9 @@ struct usb_mixer_interface { /* the usb audio specification version this interface complies to */ int protocol; + /* optional connector delegation map */ + const struct usbmix_connector_map *connector_map; + /* Sound Blaster remote control stuff */ const struct rc_config *rc_cfg; u32 rc_code; @@ -23,8 +35,13 @@ struct usb_mixer_interface { struct urb *rc_urb; struct usb_ctrlrequest *rc_setup_packet; u8 rc_buffer[6]; + struct media_mixer_ctl *media_mixer_ctl; bool disconnected; + + void *private_data; + void (*private_free)(struct usb_mixer_interface *mixer); + void (*private_suspend)(struct usb_mixer_interface *mixer); }; #define MAX_CHANNELS 16 /* max logical channels */ @@ -38,6 +55,7 @@ enum { USB_MIXER_U16, USB_MIXER_S32, USB_MIXER_U32, + USB_MIXER_BESPOKEN, /* non-standard type */ }; typedef void (*usb_mixer_elem_dump_func_t)(struct snd_info_buffer *buffer, @@ -49,6 +67,7 @@ struct usb_mixer_elem_list { struct usb_mixer_elem_list *next_id_elem; /* list of controls with same id */ struct snd_kcontrol *kctl; unsigned int id; + bool is_std_info; usb_mixer_elem_dump_func_t dump; usb_mixer_elem_resume_func_t resume; }; @@ -69,6 +88,7 @@ struct usb_mixer_elem_info { int channels; int val_type; int min, max, res; + int max_exposed; /* control API exposes the value in 0..max_exposed */ int dBmin, dBmax; int cached; int cache_val[MAX_CHANNELS]; @@ -77,8 +97,7 @@ struct usb_mixer_elem_info { void *private_data; }; -int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, - int ignore_error); +int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif); void snd_usb_mixer_disconnect(struct usb_mixer_interface *mixer); void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid); @@ -86,8 +105,12 @@ void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid); int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int value_set); -int snd_usb_mixer_add_control(struct usb_mixer_elem_list *list, - struct snd_kcontrol *kctl); +int snd_usb_mixer_add_list(struct usb_mixer_elem_list *list, + struct snd_kcontrol *kctl, + bool is_std_info); + +#define snd_usb_mixer_add_control(list, kctl) \ + snd_usb_mixer_add_list(list, kctl, true) void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list, struct usb_mixer_interface *mixer, @@ -96,10 +119,8 @@ void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list, int snd_usb_mixer_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag, unsigned int size, unsigned int __user *_tlv); -#ifdef CONFIG_PM int snd_usb_mixer_suspend(struct usb_mixer_interface *mixer); -int snd_usb_mixer_resume(struct usb_mixer_interface *mixer, bool reset_resume); -#endif +int snd_usb_mixer_resume(struct usb_mixer_interface *mixer); int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int index, int value); @@ -109,6 +130,6 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, extern void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl); -extern struct snd_kcontrol_new *snd_usb_feature_unit_ctl; +extern const struct snd_kcontrol_new *snd_usb_feature_unit_ctl; #endif /* __USBMIXER_H */ diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index 71069e110897..faac7df1fbcf 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -1,34 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Additional mixer mapping * * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * */ struct usbmix_dB_map { - u32 min; - u32 max; + int min; + int max; + bool min_mute; }; struct usbmix_name_map { int id; const char *name; int control; - struct usbmix_dB_map *dB; + const struct usbmix_dB_map *dB; }; struct usbmix_selector_map { @@ -41,7 +28,7 @@ struct usbmix_ctl_map { u32 id; const struct usbmix_name_map *map; const struct usbmix_selector_map *selector_map; - int ignore_ctl_error; + const struct usbmix_connector_map *connector_map; }; /* @@ -66,7 +53,7 @@ Mic-IN[9] --+->FU[10]----------------------------+ | ++--+->SU[11]-->FU[12] --------------------------------------------------------------------------------------> USB_OUT[13] */ -static struct usbmix_name_map extigy_map[] = { +static const struct usbmix_name_map extigy_map[] = { /* 1: IT pcm */ { 2, "PCM Playback" }, /* FU */ /* 3: IT pcm */ @@ -107,12 +94,12 @@ static struct usbmix_name_map extigy_map[] = { * e.g. no Master and fake PCM volume * Pavel Mihaylov <bin@bash.info> */ -static struct usbmix_dB_map mp3plus_dB_1 = {.min = -4781, .max = 0}; +static const struct usbmix_dB_map mp3plus_dB_1 = {.min = -4781, .max = 0}; /* just guess */ -static struct usbmix_dB_map mp3plus_dB_2 = {.min = -1781, .max = 618}; +static const struct usbmix_dB_map mp3plus_dB_2 = {.min = -1781, .max = 618}; /* just guess */ -static struct usbmix_name_map mp3plus_map[] = { +static const struct usbmix_name_map mp3plus_map[] = { /* 1: IT pcm */ /* 2: IT mic */ /* 3: IT line */ @@ -153,7 +140,7 @@ Lin_IN[7]-+--->FU[8]---+ +->EU[23]->FU[28]------------->Spk_OUT[19] | ^ +->FU[13]--------------------------------------+ */ -static struct usbmix_name_map audigy2nx_map[] = { +static const struct usbmix_name_map audigy2nx_map[] = { /* 1: IT pcm playback */ /* 4: IT digital in */ { 6, "Digital In Playback" }, /* FU */ @@ -181,12 +168,12 @@ static struct usbmix_name_map audigy2nx_map[] = { { 0 } /* terminator */ }; -static struct usbmix_name_map mbox1_map[] = { +static const struct usbmix_name_map mbox1_map[] = { { 1, "Clock" }, { 0 } /* terminator */ }; -static struct usbmix_selector_map c400_selectors[] = { +static const struct usbmix_selector_map c400_selectors[] = { { .id = 0x80, .count = 2, @@ -195,7 +182,7 @@ static struct usbmix_selector_map c400_selectors[] = { { 0 } /* terminator */ }; -static struct usbmix_selector_map audigy2nx_selectors[] = { +static const struct usbmix_selector_map audigy2nx_selectors[] = { { .id = 14, /* Capture Source */ .count = 3, @@ -215,21 +202,21 @@ static struct usbmix_selector_map audigy2nx_selectors[] = { }; /* Creative SoundBlaster Live! 24-bit External */ -static struct usbmix_name_map live24ext_map[] = { +static const struct usbmix_name_map live24ext_map[] = { /* 2: PCM Playback Volume */ { 5, "Mic Capture" }, /* FU, default PCM Capture Volume */ { 0 } /* terminator */ }; /* LineX FM Transmitter entry - needed to bypass controls bug */ -static struct usbmix_name_map linex_map[] = { +static const struct usbmix_name_map linex_map[] = { /* 1: IT pcm */ /* 2: OT Speaker */ { 3, "Master" }, /* FU: master volume - left / right / mute */ { 0 } /* terminator */ }; -static struct usbmix_name_map maya44_map[] = { +static const struct usbmix_name_map maya44_map[] = { /* 1: IT line */ { 2, "Line Playback" }, /* FU */ /* 3: IT line */ @@ -246,13 +233,13 @@ static struct usbmix_name_map maya44_map[] = { }; /* Section "justlink_map" below added by James Courtier-Dutton <James@superbug.demon.co.uk> - * sourced from Maplin Electronics (http://www.maplin.co.uk), part number A56AK + * sourced from Maplin Electronics (https://www.maplin.co.uk), part number A56AK * Part has 2 connectors that act as a single output. (TOSLINK Optical for digital out, and 3.5mm Jack for Analogue out.) * The USB Mixer publishes a Microphone and extra Volume controls for it, but none exist on the device, * so this map removes all unwanted sliders from alsamixer */ -static struct usbmix_name_map justlink_map[] = { +static const struct usbmix_name_map justlink_map[] = { /* 1: IT pcm playback */ /* 2: Not present */ { 3, NULL}, /* IT mic (No mic input on device) */ @@ -269,7 +256,7 @@ static struct usbmix_name_map justlink_map[] = { }; /* TerraTec Aureon 5.1 MkII USB */ -static struct usbmix_name_map aureon_51_2_map[] = { +static const struct usbmix_name_map aureon_51_2_map[] = { /* 1: IT USB */ /* 2: IT Mic */ /* 3: IT Line */ @@ -288,7 +275,7 @@ static struct usbmix_name_map aureon_51_2_map[] = { {} /* terminator */ }; -static struct usbmix_name_map scratch_live_map[] = { +static const struct usbmix_name_map scratch_live_map[] = { /* 1: IT Line 1 (USB streaming) */ /* 2: OT Line 1 (Speaker) */ /* 3: IT Line 1 (Line connector) */ @@ -304,7 +291,7 @@ static struct usbmix_name_map scratch_live_map[] = { { 0 } /* terminator */ }; -static struct usbmix_name_map ebox44_map[] = { +static const struct usbmix_name_map ebox44_map[] = { { 4, NULL }, /* FU */ { 6, NULL }, /* MU */ { 7, NULL }, /* FU */ @@ -319,7 +306,7 @@ static struct usbmix_name_map ebox44_map[] = { * FIXME: or mp3plus_map should use "Capture Source" too, * so this maps can be merget */ -static struct usbmix_name_map hercules_usb51_map[] = { +static const struct usbmix_name_map hercules_usb51_map[] = { { 8, "Capture Source" }, /* SU, default "PCM Capture Source" */ { 9, "Master Playback" }, /* FU, default "Speaker Playback" */ { 10, "Mic Boost", 7 }, /* FU, default "Auto Gain Input" */ @@ -330,7 +317,7 @@ static struct usbmix_name_map hercules_usb51_map[] = { }; /* Plantronics Gamecom 780 has a broken volume control, better to disable it */ -static struct usbmix_name_map gamecom780_map[] = { +static const struct usbmix_name_map gamecom780_map[] = { { 9, NULL }, /* FU, speaker out */ {} }; @@ -344,12 +331,26 @@ static const struct usbmix_name_map scms_usb3318_map[] = { }; /* Bose companion 5, the dB conversion factor is 16 instead of 256 */ -static struct usbmix_dB_map bose_companion5_dB = {-5006, -6}; -static struct usbmix_name_map bose_companion5_map[] = { +static const struct usbmix_dB_map bose_companion5_dB = {-5006, -6}; +static const struct usbmix_name_map bose_companion5_map[] = { { 3, NULL, .dB = &bose_companion5_dB }, { 0 } /* terminator */ }; +/* Bose Revolve+ SoundLink, correction of dB maps */ +static const struct usbmix_dB_map bose_soundlink_dB = {-8283, -0, true}; +static const struct usbmix_name_map bose_soundlink_map[] = { + { 2, NULL, .dB = &bose_soundlink_dB }, + { 0 } /* terminator */ +}; + +/* Sennheiser Communications Headset [PC 8], the dB value is reported as -6 negative maximum */ +static const struct usbmix_dB_map sennheiser_pc8_dB = {-9500, 0}; +static const struct usbmix_name_map sennheiser_pc8_map[] = { + { 9, NULL, .dB = &sennheiser_pc8_dB }, + { 0 } /* terminator */ +}; + /* * Dell usb dock with ALC4020 codec had a firmware problem where it got * screwed up when zero volume is passed; just skip it as a workaround @@ -364,14 +365,144 @@ static const struct usbmix_name_map dell_alc4020_map[] = { }; /* + * Corsair Virtuoso calls everything "Headset" without this, leading to + * applications moving the sidetone control instead of the main one. + */ +static const struct usbmix_name_map corsair_virtuoso_map[] = { + { 3, "Mic Capture" }, + { 6, "Sidetone Playback" }, + { 0 } +}; + +/* Microsoft USB Link headset */ +/* a guess work: raw playback volume values are from 2 to 129 */ +static const struct usbmix_dB_map ms_usb_link_dB = { -3225, 0, true }; +static const struct usbmix_name_map ms_usb_link_map[] = { + { 9, NULL, .dB = &ms_usb_link_dB }, + { 10, NULL }, /* Headset Capture volume; seems non-working, disabled */ + { 0 } /* terminator */ +}; + +/* KTMicro USB */ +static struct usbmix_name_map s31b2_0022_map[] = { + { 23, "Speaker Playback" }, + { 18, "Headphone Playback" }, + { 0 } +}; + +/* ASUS ROG Zenith II with Realtek ALC1220-VB */ +static const struct usbmix_name_map asus_zenith_ii_map[] = { + { 19, NULL, 12 }, /* FU, Input Gain Pad - broken response, disabled */ + { 16, "Speaker" }, /* OT */ + { 22, "Speaker Playback" }, /* FU */ + { 7, "Line" }, /* IT */ + { 19, "Line Capture" }, /* FU */ + { 8, "Mic" }, /* IT */ + { 20, "Mic Capture" }, /* FU */ + { 9, "Front Mic" }, /* IT */ + { 21, "Front Mic Capture" }, /* FU */ + { 17, "IEC958" }, /* OT */ + { 23, "IEC958 Playback" }, /* FU */ + {} +}; + +static const struct usbmix_connector_map asus_zenith_ii_connector_map[] = { + { 10, 16 }, /* (Back) Speaker */ + { 11, 17 }, /* SPDIF */ + { 13, 7 }, /* Line */ + { 14, 8 }, /* Mic */ + { 15, 9 }, /* Front Mic */ + {} +}; + +static const struct usbmix_name_map lenovo_p620_rear_map[] = { + { 19, NULL, 12 }, /* FU, Input Gain Pad */ + {} +}; + +/* TRX40 mobos with Realtek ALC1220-VB */ +static const struct usbmix_name_map trx40_mobo_map[] = { + { 18, NULL }, /* OT, IEC958 - broken response, disabled */ + { 19, NULL, 12 }, /* FU, Input Gain Pad - broken response, disabled */ + { 16, "Speaker" }, /* OT */ + { 22, "Speaker Playback" }, /* FU */ + { 7, "Line" }, /* IT */ + { 19, "Line Capture" }, /* FU */ + { 17, "Front Headphone" }, /* OT */ + { 23, "Front Headphone Playback" }, /* FU */ + { 8, "Mic" }, /* IT */ + { 20, "Mic Capture" }, /* FU */ + { 9, "Front Mic" }, /* IT */ + { 21, "Front Mic Capture" }, /* FU */ + { 24, "IEC958 Playback" }, /* FU */ + {} +}; + +static const struct usbmix_connector_map trx40_mobo_connector_map[] = { + { 10, 16 }, /* (Back) Speaker */ + { 11, 17 }, /* Front Headphone */ + { 13, 7 }, /* Line */ + { 14, 8 }, /* Mic */ + { 15, 9 }, /* Front Mic */ + {} +}; + +/* Rear panel + front mic on Gigabyte TRX40 Aorus Master with ALC1220-VB */ +static const struct usbmix_name_map aorus_master_alc1220vb_map[] = { + { 17, NULL }, /* OT, IEC958?, disabled */ + { 19, NULL, 12 }, /* FU, Input Gain Pad - broken response, disabled */ + { 16, "Line Out" }, /* OT */ + { 22, "Line Out Playback" }, /* FU */ + { 7, "Line" }, /* IT */ + { 19, "Line Capture" }, /* FU */ + { 8, "Mic" }, /* IT */ + { 20, "Mic Capture" }, /* FU */ + { 9, "Front Mic" }, /* IT */ + { 21, "Front Mic Capture" }, /* FU */ + {} +}; + +/* MSI MPG X570S Carbon Max Wifi with ALC4080 */ +static const struct usbmix_name_map msi_mpg_x570s_carbon_max_wifi_alc4080_map[] = { + { 29, "Speaker Playback" }, + { 30, "Front Headphone Playback" }, + { 32, "IEC958 Playback" }, + {} +}; + +/* Gigabyte B450/550 Mobo */ +static const struct usbmix_name_map gigabyte_b450_map[] = { + { 24, NULL }, /* OT, IEC958?, disabled */ + { 21, "Speaker" }, /* OT */ + { 29, "Speaker Playback" }, /* FU */ + { 22, "Headphone" }, /* OT */ + { 30, "Headphone Playback" }, /* FU */ + { 11, "Line" }, /* IT */ + { 27, "Line Capture" }, /* FU */ + { 12, "Mic" }, /* IT */ + { 28, "Mic Capture" }, /* FU */ + { 9, "Front Mic" }, /* IT */ + { 25, "Front Mic Capture" }, /* FU */ + {} +}; + +static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { + { 13, 21 }, /* Speaker */ + { 14, 22 }, /* Headphone */ + { 19, 11 }, /* Line */ + { 20, 12 }, /* Mic */ + { 17, 9 }, /* Front Mic */ + {} +}; + +/* * Control map entries */ -static struct usbmix_ctl_map usbmix_ctl_maps[] = { +static const struct usbmix_ctl_map usbmix_ctl_maps[] = { { .id = USB_ID(0x041e, 0x3000), .map = extigy_map, - .ignore_ctl_error = 1, }, { .id = USB_ID(0x041e, 0x3010), @@ -391,29 +522,11 @@ static struct usbmix_ctl_map usbmix_ctl_maps[] = { .map = audigy2nx_map, .selector_map = audigy2nx_selectors, }, - { /* Logitech, Inc. QuickCam Pro for Notebooks */ - .id = USB_ID(0x046d, 0x0991), - .ignore_ctl_error = 1, - }, - { /* Logitech, Inc. QuickCam E 3500 */ - .id = USB_ID(0x046d, 0x09a4), - .ignore_ctl_error = 1, - }, { /* Plantronics GameCom 780 */ .id = USB_ID(0x047f, 0xc010), .map = gamecom780_map, }, { - /* Hercules DJ Console (Windows Edition) */ - .id = USB_ID(0x06f8, 0xb000), - .ignore_ctl_error = 1, - }, - { - /* Hercules DJ Console (Macintosh Edition) */ - .id = USB_ID(0x06f8, 0xd002), - .ignore_ctl_error = 1, - }, - { /* Hercules Gamesurround Muse Pocket LT * (USB 5.1 Channel Audio Adapter) */ @@ -431,7 +544,6 @@ static struct usbmix_ctl_map usbmix_ctl_maps[] = { { .id = USB_ID(0x08bb, 0x2702), .map = linex_map, - .ignore_ctl_error = 1, }, { .id = USB_ID(0x0a92, 0x0091), @@ -456,7 +568,6 @@ static struct usbmix_ctl_map usbmix_ctl_maps[] = { { .id = USB_ID(0x13e5, 0x0001), .map = scratch_live_map, - .ignore_ctl_error = 1, }, { .id = USB_ID(0x200c, 0x1018), @@ -482,6 +593,117 @@ static struct usbmix_ctl_map usbmix_ctl_maps[] = { .id = USB_ID(0x05a7, 0x1020), .map = bose_companion5_map, }, + { + /* Bose Revolve+ SoundLink */ + .id = USB_ID(0x05a7, 0x40fa), + .map = bose_soundlink_map, + }, + { + /* Corsair Virtuoso SE Latest (wired mode) */ + .id = USB_ID(0x1b1c, 0x0a3f), + .map = corsair_virtuoso_map, + }, + { + /* Corsair Virtuoso SE Latest (wireless mode) */ + .id = USB_ID(0x1b1c, 0x0a40), + .map = corsair_virtuoso_map, + }, + { + /* Corsair Virtuoso SE (wired mode) */ + .id = USB_ID(0x1b1c, 0x0a3d), + .map = corsair_virtuoso_map, + }, + { + /* Corsair Virtuoso SE (wireless mode) */ + .id = USB_ID(0x1b1c, 0x0a3e), + .map = corsair_virtuoso_map, + }, + { + /* Corsair Virtuoso (wired mode) */ + .id = USB_ID(0x1b1c, 0x0a41), + .map = corsair_virtuoso_map, + }, + { + /* Corsair Virtuoso (wireless mode) */ + .id = USB_ID(0x1b1c, 0x0a42), + .map = corsair_virtuoso_map, + }, + { + /* Corsair HS80 RGB Wireless (wired mode) */ + .id = USB_ID(0x1b1c, 0x0a6a), + .map = corsair_virtuoso_map, + }, + { + /* Corsair HS80 RGB Wireless (wireless mode) */ + .id = USB_ID(0x1b1c, 0x0a6b), + .map = corsair_virtuoso_map, + }, + { /* Gigabyte TRX40 Aorus Master (rear panel + front mic) */ + .id = USB_ID(0x0414, 0xa001), + .map = aorus_master_alc1220vb_map, + }, + { /* Gigabyte TRX40 Aorus Pro WiFi */ + .id = USB_ID(0x0414, 0xa002), + .map = trx40_mobo_map, + .connector_map = trx40_mobo_connector_map, + }, + { /* Gigabyte B450/550 Mobo */ + .id = USB_ID(0x0414, 0xa00d), + .map = gigabyte_b450_map, + .connector_map = gigabyte_b450_connector_map, + }, + { /* ASUS ROG Zenith II (main audio) */ + .id = USB_ID(0x0b05, 0x1916), + .map = asus_zenith_ii_map, + .connector_map = asus_zenith_ii_connector_map, + }, + { /* ASUS ROG Strix */ + .id = USB_ID(0x0b05, 0x1917), + .map = trx40_mobo_map, + .connector_map = trx40_mobo_connector_map, + }, + { /* MSI TRX40 Creator */ + .id = USB_ID(0x0db0, 0x0d64), + .map = trx40_mobo_map, + .connector_map = trx40_mobo_connector_map, + }, + { /* MSI MPG X570S Carbon Max Wifi */ + .id = USB_ID(0x0db0, 0x419c), + .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map, + }, + { /* MSI MAG X570S Torpedo Max */ + .id = USB_ID(0x0db0, 0xa073), + .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map, + }, + { /* MSI TRX40 */ + .id = USB_ID(0x0db0, 0x543d), + .map = trx40_mobo_map, + .connector_map = trx40_mobo_connector_map, + }, + { /* Asrock TRX40 Creator */ + .id = USB_ID(0x26ce, 0x0a01), + .map = trx40_mobo_map, + .connector_map = trx40_mobo_connector_map, + }, + { /* Lenovo ThinkStation P620 Rear */ + .id = USB_ID(0x17aa, 0x1046), + .map = lenovo_p620_rear_map, + }, + { + /* Sennheiser Communications Headset [PC 8] */ + .id = USB_ID(0x1395, 0x0025), + .map = sennheiser_pc8_map, + }, + { + /* Microsoft USB Link headset */ + .id = USB_ID(0x045e, 0x083c), + .map = ms_usb_link_map, + }, + { + /* KTMicro USB */ + .id = USB_ID(0X31b2, 0x0022), + .map = s31b2_0022_map, + }, { 0 } /* terminator */ }; @@ -489,37 +711,37 @@ static struct usbmix_ctl_map usbmix_ctl_maps[] = { * Control map entries for UAC3 BADD profiles */ -static struct usbmix_name_map uac3_badd_generic_io_map[] = { +static const struct usbmix_name_map uac3_badd_generic_io_map[] = { { UAC3_BADD_FU_ID2, "Generic Out Playback" }, { UAC3_BADD_FU_ID5, "Generic In Capture" }, { 0 } /* terminator */ }; -static struct usbmix_name_map uac3_badd_headphone_map[] = { +static const struct usbmix_name_map uac3_badd_headphone_map[] = { { UAC3_BADD_FU_ID2, "Headphone Playback" }, { 0 } /* terminator */ }; -static struct usbmix_name_map uac3_badd_speaker_map[] = { +static const struct usbmix_name_map uac3_badd_speaker_map[] = { { UAC3_BADD_FU_ID2, "Speaker Playback" }, { 0 } /* terminator */ }; -static struct usbmix_name_map uac3_badd_microphone_map[] = { +static const struct usbmix_name_map uac3_badd_microphone_map[] = { { UAC3_BADD_FU_ID5, "Mic Capture" }, { 0 } /* terminator */ }; /* Covers also 'headset adapter' profile */ -static struct usbmix_name_map uac3_badd_headset_map[] = { +static const struct usbmix_name_map uac3_badd_headset_map[] = { { UAC3_BADD_FU_ID2, "Headset Playback" }, { UAC3_BADD_FU_ID5, "Headset Capture" }, { UAC3_BADD_FU_ID7, "Sidetone Mixing" }, { 0 } /* terminator */ }; -static struct usbmix_name_map uac3_badd_speakerphone_map[] = { +static const struct usbmix_name_map uac3_badd_speakerphone_map[] = { { UAC3_BADD_FU_ID2, "Speaker Playback" }, { UAC3_BADD_FU_ID5, "Mic Capture" }, { 0 } /* terminator */ }; -static struct usbmix_ctl_map uac3_badd_usbmix_ctl_maps[] = { +static const struct usbmix_ctl_map uac3_badd_usbmix_ctl_maps[] = { { .id = UAC3_FUNCTION_SUBCLASS_GENERIC_IO, .map = uac3_badd_generic_io_map, diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 85ae0ff2382a..f7189990dd6a 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * USB Audio Driver for ALSA * @@ -11,24 +12,12 @@ * * Audio Advantage Micro II support added by: * Przemek Rudy (prudy1@o2.pl) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <linux/bitfield.h> #include <linux/hid.h> #include <linux/init.h> +#include <linux/input.h> #include <linux/math64.h> #include <linux/slab.h> #include <linux/usb.h> @@ -37,6 +26,7 @@ #include <sound/asoundef.h> #include <sound/core.h> #include <sound/control.h> +#include <sound/hda_verbs.h> #include <sound/hwdep.h> #include <sound/info.h> #include <sound/tlv.h> @@ -45,8 +35,11 @@ #include "mixer.h" #include "mixer_quirks.h" #include "mixer_scarlett.h" +#include "mixer_scarlett2.h" #include "mixer_us16x08.h" +#include "mixer_s1810c.h" #include "helper.h" +#include "fcp.h" struct std_mono_table { unsigned int unitid, control, cmask; @@ -63,13 +56,13 @@ struct std_mono_table { * version, we keep it mono for simplicity. */ static int snd_create_std_mono_ctl_offset(struct usb_mixer_interface *mixer, - unsigned int unitid, - unsigned int control, - unsigned int cmask, - int val_type, - unsigned int idx_off, - const char *name, - snd_kcontrol_tlv_rw_t *tlv_callback) + unsigned int unitid, + unsigned int control, + unsigned int cmask, + int val_type, + unsigned int idx_off, + const char *name, + snd_kcontrol_tlv_rw_t *tlv_callback) { struct usb_mixer_elem_info *cval; struct snd_kcontrol *kctl; @@ -86,7 +79,8 @@ static int snd_create_std_mono_ctl_offset(struct usb_mixer_interface *mixer, cval->idx_off = idx_off; /* get_min_max() is called only for integer volumes later, - * so provide a short-cut for booleans */ + * so provide a short-cut for booleans + */ cval->min = 0; cval->max = 1; cval->res = 0; @@ -116,28 +110,30 @@ static int snd_create_std_mono_ctl_offset(struct usb_mixer_interface *mixer, } static int snd_create_std_mono_ctl(struct usb_mixer_interface *mixer, - unsigned int unitid, - unsigned int control, - unsigned int cmask, - int val_type, - const char *name, - snd_kcontrol_tlv_rw_t *tlv_callback) + unsigned int unitid, + unsigned int control, + unsigned int cmask, + int val_type, + const char *name, + snd_kcontrol_tlv_rw_t *tlv_callback) { return snd_create_std_mono_ctl_offset(mixer, unitid, control, cmask, - val_type, 0 /* Offset */, name, tlv_callback); + val_type, 0 /* Offset */, + name, tlv_callback); } /* * Create a set of standard UAC controls from a table */ static int snd_create_std_mono_table(struct usb_mixer_interface *mixer, - struct std_mono_table *t) + const struct std_mono_table *t) { int err; - while (t->name != NULL) { + while (t->name) { err = snd_create_std_mono_ctl(mixer, t->unitid, t->control, - t->cmask, t->val_type, t->name, t->tlv_callback); + t->cmask, t->val_type, t->name, + t->tlv_callback); if (err < 0) return err; t++; @@ -169,7 +165,8 @@ static int add_single_ctl_with_resume(struct usb_mixer_interface *mixer, return -ENOMEM; } kctl->private_free = snd_usb_mixer_elem_free; - return snd_usb_mixer_add_control(list, kctl); + /* don't use snd_usb_mixer_add_control() here, this is a special list element */ + return snd_usb_mixer_add_list(list, kctl, false); } /* @@ -195,6 +192,7 @@ static const struct rc_config { { USB_ID(0x041e, 0x3042), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 */ { USB_ID(0x041e, 0x30df), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 Pro */ { USB_ID(0x041e, 0x3237), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 Pro */ + { USB_ID(0x041e, 0x3263), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 Pro */ { USB_ID(0x041e, 0x3048), 2, 2, 6, 6, 2, 0x6e91 }, /* Toshiba SB0500 */ }; @@ -215,12 +213,11 @@ static void snd_usb_soundblaster_remote_complete(struct urb *urb) if (code == rc->mute_code) snd_usb_mixer_notify_id(mixer, rc->mute_mixer_id); mixer->rc_code = code; - wmb(); wake_up(&mixer->rc_waitq); } static long snd_usb_sbrc_hwdep_read(struct snd_hwdep *hw, char __user *buf, - long count, loff_t *offset) + long count, loff_t *offset) { struct usb_mixer_interface *mixer = hw->private_data; int err; @@ -240,7 +237,7 @@ static long snd_usb_sbrc_hwdep_read(struct snd_hwdep *hw, char __user *buf, } static __poll_t snd_usb_sbrc_hwdep_poll(struct snd_hwdep *hw, struct file *file, - poll_table *wait) + poll_table *wait) { struct usb_mixer_interface *mixer = hw->private_data; @@ -291,7 +288,7 @@ static int snd_usb_soundblaster_remote_init(struct usb_mixer_interface *mixer) mixer->rc_setup_packet->wLength = cpu_to_le16(len); usb_fill_control_urb(mixer->rc_urb, mixer->chip->dev, usb_rcvctrlpipe(mixer->chip->dev, 0), - (u8*)mixer->rc_setup_packet, mixer->rc_buffer, len, + (u8 *)mixer->rc_setup_packet, mixer->rc_buffer, len, snd_usb_soundblaster_remote_complete, mixer); return 0; } @@ -310,27 +307,26 @@ static int snd_audigy2nx_led_update(struct usb_mixer_interface *mixer, struct snd_usb_audio *chip = mixer->chip; int err; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; if (chip->usb_id == USB_ID(0x041e, 0x3042)) err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), 0x24, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - !value, 0, NULL, 0); + usb_sndctrlpipe(chip->dev, 0), 0x24, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + !value, 0, NULL, 0); /* USB X-Fi S51 Pro */ if (chip->usb_id == USB_ID(0x041e, 0x30df)) err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), 0x24, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - !value, 0, NULL, 0); + usb_sndctrlpipe(chip->dev, 0), 0x24, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + !value, 0, NULL, 0); else err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), 0x24, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - value, index + 2, NULL, 0); - snd_usb_unlock_shutdown(chip); + usb_sndctrlpipe(chip->dev, 0), 0x24, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + value, index + 2, NULL, 0); return err; } @@ -383,17 +379,17 @@ static int snd_audigy2nx_controls_create(struct usb_mixer_interface *mixer) struct snd_kcontrol_new knew; /* USB X-Fi S51 doesn't have a CMSS LED */ - if ((mixer->chip->usb_id == USB_ID(0x041e, 0x3042)) && i == 0) + if (mixer->chip->usb_id == USB_ID(0x041e, 0x3042) && i == 0) continue; /* USB X-Fi S51 Pro doesn't have one either */ - if ((mixer->chip->usb_id == USB_ID(0x041e, 0x30df)) && i == 0) + if (mixer->chip->usb_id == USB_ID(0x041e, 0x30df) && i == 0) continue; if (i > 1 && /* Live24ext has 2 LEDs only */ (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || mixer->chip->usb_id == USB_ID(0x041e, 0x3042) || mixer->chip->usb_id == USB_ID(0x041e, 0x30df) || mixer->chip->usb_id == USB_ID(0x041e, 0x3048))) - break; + break; knew = snd_audigy2nx_control; knew.name = snd_audigy2nx_led_names[i]; @@ -441,15 +437,14 @@ static void snd_audigy2nx_proc_read(struct snd_info_entry *entry, for (i = 0; jacks[i].name; ++i) { snd_iprintf(buffer, "%s: ", jacks[i].name); - err = snd_usb_lock_shutdown(mixer->chip); - if (err < 0) + CLASS(snd_usb_lock, pm)(mixer->chip); + if (pm.err < 0) return; err = snd_usb_ctl_msg(mixer->chip->dev, usb_rcvctrlpipe(mixer->chip->dev, 0), UAC_GET_MEM, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0, jacks[i].unitid << 8, buf, 3); - snd_usb_unlock_shutdown(mixer->chip); if (err == 3 && (buf[0] == 3 || buf[0] == 6)) snd_iprintf(buffer, "%02x %02x\n", buf[1], buf[2]); else @@ -477,21 +472,18 @@ static int snd_emu0204_ch_switch_update(struct usb_mixer_interface *mixer, int value) { struct snd_usb_audio *chip = mixer->chip; - int err; unsigned char buf[2]; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; buf[0] = 0x01; buf[1] = value ? 0x02 : 0x01; - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), UAC_SET_CUR, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, - 0x0400, 0x0e00, buf, 2); - snd_usb_unlock_shutdown(chip); - return err; + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), UAC_SET_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + 0x0400, 0x0e00, buf, 2); } static int snd_emu0204_ch_switch_put(struct snd_kcontrol *kcontrol, @@ -519,7 +511,7 @@ static int snd_emu0204_ch_switch_resume(struct usb_mixer_elem_list *list) list->kctl->private_value); } -static struct snd_kcontrol_new snd_emu0204_control = { +static const struct snd_kcontrol_new snd_emu0204_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Front Jack Channels", .info = snd_emu0204_ch_switch_info, @@ -535,6 +527,265 @@ static int snd_emu0204_controls_create(struct usb_mixer_interface *mixer) &snd_emu0204_control, NULL); } +#if IS_REACHABLE(CONFIG_INPUT) +/* + * Sony DualSense controller (PS5) jack detection + * + * Since this is an UAC 1 device, it doesn't support jack detection. + * However, the controller hid-playstation driver reports HP & MIC + * insert events through a dedicated input device. + */ + +#define SND_DUALSENSE_JACK_OUT_TERM_ID 3 +#define SND_DUALSENSE_JACK_IN_TERM_ID 4 + +struct dualsense_mixer_elem_info { + struct usb_mixer_elem_info info; + struct input_handler ih; + struct input_device_id id_table[2]; + bool connected; +}; + +static void snd_dualsense_ih_event(struct input_handle *handle, + unsigned int type, unsigned int code, + int value) +{ + struct dualsense_mixer_elem_info *mei; + struct usb_mixer_elem_list *me; + + if (type != EV_SW) + return; + + mei = container_of(handle->handler, struct dualsense_mixer_elem_info, ih); + me = &mei->info.head; + + if ((me->id == SND_DUALSENSE_JACK_OUT_TERM_ID && code == SW_HEADPHONE_INSERT) || + (me->id == SND_DUALSENSE_JACK_IN_TERM_ID && code == SW_MICROPHONE_INSERT)) { + mei->connected = !!value; + snd_ctl_notify(me->mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &me->kctl->id); + } +} + +static bool snd_dualsense_ih_match(struct input_handler *handler, + struct input_dev *dev) +{ + struct dualsense_mixer_elem_info *mei; + struct usb_device *snd_dev; + char *input_dev_path, *usb_dev_path; + size_t usb_dev_path_len; + bool match = false; + + mei = container_of(handler, struct dualsense_mixer_elem_info, ih); + snd_dev = mei->info.head.mixer->chip->dev; + + input_dev_path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL); + if (!input_dev_path) { + dev_warn(&snd_dev->dev, "Failed to get input dev path\n"); + return false; + } + + usb_dev_path = kobject_get_path(&snd_dev->dev.kobj, GFP_KERNEL); + if (!usb_dev_path) { + dev_warn(&snd_dev->dev, "Failed to get USB dev path\n"); + goto free_paths; + } + + /* + * Ensure the VID:PID matched input device supposedly owned by the + * hid-playstation driver belongs to the actual hardware handled by + * the current USB audio device, which implies input_dev_path being + * a subpath of usb_dev_path. + * + * This verification is necessary when there is more than one identical + * controller attached to the host system. + */ + usb_dev_path_len = strlen(usb_dev_path); + if (usb_dev_path_len >= strlen(input_dev_path)) + goto free_paths; + + usb_dev_path[usb_dev_path_len] = '/'; + match = !memcmp(input_dev_path, usb_dev_path, usb_dev_path_len + 1); + +free_paths: + kfree(input_dev_path); + kfree(usb_dev_path); + + return match; +} + +static int snd_dualsense_ih_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int err; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = handler->name; + + err = input_register_handle(handle); + if (err) + goto err_free; + + err = input_open_device(handle); + if (err) + goto err_unregister; + + return 0; + +err_unregister: + input_unregister_handle(handle); +err_free: + kfree(handle); + return err; +} + +static void snd_dualsense_ih_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static void snd_dualsense_ih_start(struct input_handle *handle) +{ + struct dualsense_mixer_elem_info *mei; + struct usb_mixer_elem_list *me; + int status = -1; + + mei = container_of(handle->handler, struct dualsense_mixer_elem_info, ih); + me = &mei->info.head; + + if (me->id == SND_DUALSENSE_JACK_OUT_TERM_ID && + test_bit(SW_HEADPHONE_INSERT, handle->dev->swbit)) + status = test_bit(SW_HEADPHONE_INSERT, handle->dev->sw); + else if (me->id == SND_DUALSENSE_JACK_IN_TERM_ID && + test_bit(SW_MICROPHONE_INSERT, handle->dev->swbit)) + status = test_bit(SW_MICROPHONE_INSERT, handle->dev->sw); + + if (status >= 0) { + mei->connected = !!status; + snd_ctl_notify(me->mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &me->kctl->id); + } +} + +static int snd_dualsense_jack_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct dualsense_mixer_elem_info *mei = snd_kcontrol_chip(kctl); + + ucontrol->value.integer.value[0] = mei->connected; + + return 0; +} + +static const struct snd_kcontrol_new snd_dualsense_jack_control = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ctl_boolean_mono_info, + .get = snd_dualsense_jack_get, +}; + +static int snd_dualsense_resume_jack(struct usb_mixer_elem_list *list) +{ + snd_ctl_notify(list->mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &list->kctl->id); + return 0; +} + +static void snd_dualsense_mixer_elem_free(struct snd_kcontrol *kctl) +{ + struct dualsense_mixer_elem_info *mei = snd_kcontrol_chip(kctl); + + if (mei->ih.event) + input_unregister_handler(&mei->ih); + + snd_usb_mixer_elem_free(kctl); +} + +static int snd_dualsense_jack_create(struct usb_mixer_interface *mixer, + const char *name, bool is_output) +{ + struct dualsense_mixer_elem_info *mei; + struct input_device_id *idev_id; + struct snd_kcontrol *kctl; + int err; + + mei = kzalloc(sizeof(*mei), GFP_KERNEL); + if (!mei) + return -ENOMEM; + + snd_usb_mixer_elem_init_std(&mei->info.head, mixer, + is_output ? SND_DUALSENSE_JACK_OUT_TERM_ID : + SND_DUALSENSE_JACK_IN_TERM_ID); + + mei->info.head.resume = snd_dualsense_resume_jack; + mei->info.val_type = USB_MIXER_BOOLEAN; + mei->info.channels = 1; + mei->info.min = 0; + mei->info.max = 1; + + kctl = snd_ctl_new1(&snd_dualsense_jack_control, mei); + if (!kctl) { + kfree(mei); + return -ENOMEM; + } + + strscpy(kctl->id.name, name, sizeof(kctl->id.name)); + kctl->private_free = snd_dualsense_mixer_elem_free; + + err = snd_usb_mixer_add_control(&mei->info.head, kctl); + if (err) + return err; + + idev_id = &mei->id_table[0]; + idev_id->flags = INPUT_DEVICE_ID_MATCH_VENDOR | INPUT_DEVICE_ID_MATCH_PRODUCT | + INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_SWBIT; + idev_id->vendor = USB_ID_VENDOR(mixer->chip->usb_id); + idev_id->product = USB_ID_PRODUCT(mixer->chip->usb_id); + idev_id->evbit[BIT_WORD(EV_SW)] = BIT_MASK(EV_SW); + if (is_output) + idev_id->swbit[BIT_WORD(SW_HEADPHONE_INSERT)] = BIT_MASK(SW_HEADPHONE_INSERT); + else + idev_id->swbit[BIT_WORD(SW_MICROPHONE_INSERT)] = BIT_MASK(SW_MICROPHONE_INSERT); + + mei->ih.event = snd_dualsense_ih_event; + mei->ih.match = snd_dualsense_ih_match; + mei->ih.connect = snd_dualsense_ih_connect; + mei->ih.disconnect = snd_dualsense_ih_disconnect; + mei->ih.start = snd_dualsense_ih_start; + mei->ih.name = name; + mei->ih.id_table = mei->id_table; + + err = input_register_handler(&mei->ih); + if (err) { + dev_warn(&mixer->chip->dev->dev, + "Could not register input handler: %d\n", err); + mei->ih.event = NULL; + } + + return 0; +} + +static int snd_dualsense_controls_create(struct usb_mixer_interface *mixer) +{ + int err; + + err = snd_dualsense_jack_create(mixer, "Headphone Jack", true); + if (err < 0) + return err; + + return snd_dualsense_jack_create(mixer, "Headset Mic Jack", false); +} +#endif /* IS_REACHABLE(CONFIG_INPUT) */ + /* ASUS Xonar U1 / U3 controls */ static int snd_xonar_u1_switch_get(struct snd_kcontrol *kcontrol, @@ -548,17 +799,14 @@ static int snd_xonar_u1_switch_update(struct usb_mixer_interface *mixer, unsigned char status) { struct snd_usb_audio *chip = mixer->chip; - int err; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), 0x08, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - 50, 0, &status, 1); - snd_usb_unlock_shutdown(chip); - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), 0x08, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + 50, 0, &status, 1); } static int snd_xonar_u1_switch_put(struct snd_kcontrol *kcontrol, @@ -587,7 +835,7 @@ static int snd_xonar_u1_switch_resume(struct usb_mixer_elem_list *list) list->kctl->private_value); } -static struct snd_kcontrol_new snd_xonar_u1_output_switch = { +static const struct snd_kcontrol_new snd_xonar_u1_output_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Digital Playback Switch", .info = snd_ctl_boolean_mono_info, @@ -603,85 +851,199 @@ static int snd_xonar_u1_controls_create(struct usb_mixer_interface *mixer) &snd_xonar_u1_output_switch, NULL); } +/* Digidesign Mbox 1 helper functions */ + +static int snd_mbox1_is_spdif_synced(struct snd_usb_audio *chip) +{ + unsigned char buff[3]; + int err; + int is_spdif_synced; + + /* Read clock source */ + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), 0x81, + USB_DIR_IN | + USB_TYPE_CLASS | + USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3); + if (err < 0) + return err; + + /* spdif sync: buff is all zeroes */ + is_spdif_synced = !(buff[0] | buff[1] | buff[2]); + return is_spdif_synced; +} + +static int snd_mbox1_set_clk_source(struct snd_usb_audio *chip, int rate_or_zero) +{ + /* 2 possibilities: Internal -> expects sample rate + * S/PDIF sync -> expects rate = 0 + */ + unsigned char buff[3]; + + buff[0] = (rate_or_zero >> 0) & 0xff; + buff[1] = (rate_or_zero >> 8) & 0xff; + buff[2] = (rate_or_zero >> 16) & 0xff; + + /* Set clock source */ + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), 0x1, + USB_TYPE_CLASS | + USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3); +} + +static int snd_mbox1_is_spdif_input(struct snd_usb_audio *chip) +{ + /* Hardware gives 2 possibilities: ANALOG Source -> 0x01 + * S/PDIF Source -> 0x02 + */ + int err; + unsigned char source[1]; + + /* Read input source */ + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), 0x81, + USB_DIR_IN | + USB_TYPE_CLASS | + USB_RECIP_INTERFACE, 0x00, 0x500, source, 1); + if (err < 0) + return err; + + return (source[0] == 2); +} + +static int snd_mbox1_set_input_source(struct snd_usb_audio *chip, int is_spdif) +{ + /* NB: Setting the input source to S/PDIF resets the clock source to S/PDIF + * Hardware expects 2 possibilities: ANALOG Source -> 0x01 + * S/PDIF Source -> 0x02 + */ + unsigned char buff[1]; + + buff[0] = (is_spdif & 1) + 1; + + /* Set input source */ + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), 0x1, + USB_TYPE_CLASS | + USB_RECIP_INTERFACE, 0x00, 0x500, buff, 1); +} + /* Digidesign Mbox 1 clock source switch (internal/spdif) */ -static int snd_mbox1_switch_get(struct snd_kcontrol *kctl, - struct snd_ctl_elem_value *ucontrol) +static int snd_mbox1_clk_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) { + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct snd_usb_audio *chip = list->mixer->chip; + int err; + + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + + err = snd_mbox1_is_spdif_synced(chip); + if (err < 0) + return err; + + kctl->private_value = err; ucontrol->value.enumerated.item[0] = kctl->private_value; return 0; } -static int snd_mbox1_switch_update(struct usb_mixer_interface *mixer, int val) +static int snd_mbox1_clk_switch_update(struct usb_mixer_interface *mixer, int is_spdif_sync) { struct snd_usb_audio *chip = mixer->chip; int err; - unsigned char buff[3]; - err = snd_usb_lock_shutdown(chip); + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + + err = snd_mbox1_is_spdif_input(chip); if (err < 0) return err; - /* Prepare for magic command to toggle clock source */ - err = snd_usb_ctl_msg(chip->dev, - usb_rcvctrlpipe(chip->dev, 0), 0x81, - USB_DIR_IN | - USB_TYPE_CLASS | - USB_RECIP_INTERFACE, 0x00, 0x500, buff, 1); + err = snd_mbox1_is_spdif_synced(chip); if (err < 0) - goto err; - err = snd_usb_ctl_msg(chip->dev, - usb_rcvctrlpipe(chip->dev, 0), 0x81, - USB_DIR_IN | - USB_TYPE_CLASS | - USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3); + return err; + + /* FIXME: hardcoded sample rate */ + err = snd_mbox1_set_clk_source(chip, is_spdif_sync ? 0 : 48000); if (err < 0) - goto err; - - /* 2 possibilities: Internal -> send sample rate - * S/PDIF sync -> send zeroes - * NB: Sample rate locked to 48kHz on purpose to - * prevent user from resetting the sample rate - * while S/PDIF sync is enabled and confusing - * this configuration. - */ - if (val == 0) { - buff[0] = 0x80; - buff[1] = 0xbb; - buff[2] = 0x00; - } else { - buff[0] = buff[1] = buff[2] = 0x00; - } + return err; - /* Send the magic command to toggle the clock source */ - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), 0x1, - USB_TYPE_CLASS | - USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3); + return snd_mbox1_is_spdif_synced(chip); +} + +static int snd_mbox1_clk_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + int err; + bool cur_val, new_val; + + cur_val = kctl->private_value; + new_val = ucontrol->value.enumerated.item[0]; + if (cur_val == new_val) + return 0; + + kctl->private_value = new_val; + err = snd_mbox1_clk_switch_update(mixer, new_val); + return err < 0 ? err : 1; +} + +static int snd_mbox1_clk_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[2] = { + "Internal", + "S/PDIF" + }; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static int snd_mbox1_clk_switch_resume(struct usb_mixer_elem_list *list) +{ + return snd_mbox1_clk_switch_update(list->mixer, list->kctl->private_value); +} + +/* Digidesign Mbox 1 input source switch (analog/spdif) */ + +static int snd_mbox1_src_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = kctl->private_value; + return 0; +} + +static int snd_mbox1_src_switch_update(struct usb_mixer_interface *mixer, int is_spdif_input) +{ + struct snd_usb_audio *chip = mixer->chip; + int err; + + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + + err = snd_mbox1_is_spdif_input(chip); if (err < 0) - goto err; - err = snd_usb_ctl_msg(chip->dev, - usb_rcvctrlpipe(chip->dev, 0), 0x81, - USB_DIR_IN | - USB_TYPE_CLASS | - USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3); + return err; + + err = snd_mbox1_set_input_source(chip, is_spdif_input); if (err < 0) - goto err; - err = snd_usb_ctl_msg(chip->dev, - usb_rcvctrlpipe(chip->dev, 0), 0x81, - USB_DIR_IN | - USB_TYPE_CLASS | - USB_RECIP_ENDPOINT, 0x100, 0x2, buff, 3); + return err; + + err = snd_mbox1_is_spdif_input(chip); if (err < 0) - goto err; + return err; -err: - snd_usb_unlock_shutdown(chip); - return err; + return snd_mbox1_is_spdif_synced(chip); } -static int snd_mbox1_switch_put(struct snd_kcontrol *kctl, - struct snd_ctl_elem_value *ucontrol) +static int snd_mbox1_src_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); struct usb_mixer_interface *mixer = list->mixer; @@ -694,47 +1056,66 @@ static int snd_mbox1_switch_put(struct snd_kcontrol *kctl, return 0; kctl->private_value = new_val; - err = snd_mbox1_switch_update(mixer, new_val); + err = snd_mbox1_src_switch_update(mixer, new_val); return err < 0 ? err : 1; } -static int snd_mbox1_switch_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) +static int snd_mbox1_src_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) { static const char *const texts[2] = { - "Internal", + "Analog", "S/PDIF" }; return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); } -static int snd_mbox1_switch_resume(struct usb_mixer_elem_list *list) +static int snd_mbox1_src_switch_resume(struct usb_mixer_elem_list *list) { - return snd_mbox1_switch_update(list->mixer, list->kctl->private_value); + return snd_mbox1_src_switch_update(list->mixer, list->kctl->private_value); } -static struct snd_kcontrol_new snd_mbox1_switch = { +static const struct snd_kcontrol_new snd_mbox1_clk_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Clock Source", .index = 0, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, - .info = snd_mbox1_switch_info, - .get = snd_mbox1_switch_get, - .put = snd_mbox1_switch_put, + .info = snd_mbox1_clk_switch_info, + .get = snd_mbox1_clk_switch_get, + .put = snd_mbox1_clk_switch_put, .private_value = 0 }; -static int snd_mbox1_create_sync_switch(struct usb_mixer_interface *mixer) +static const struct snd_kcontrol_new snd_mbox1_src_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Source", + .index = 1, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_mbox1_src_switch_info, + .get = snd_mbox1_src_switch_get, + .put = snd_mbox1_src_switch_put, + .private_value = 0 +}; + +static int snd_mbox1_controls_create(struct usb_mixer_interface *mixer) { - return add_single_ctl_with_resume(mixer, 0, - snd_mbox1_switch_resume, - &snd_mbox1_switch, NULL); + int err; + + err = add_single_ctl_with_resume(mixer, 0, + snd_mbox1_clk_switch_resume, + &snd_mbox1_clk_switch, NULL); + if (err < 0) + return err; + + return add_single_ctl_with_resume(mixer, 1, + snd_mbox1_src_switch_resume, + &snd_mbox1_src_switch, NULL); } /* Native Instruments device quirks */ -#define _MAKE_NI_CONTROL(bRequest,wIndex) ((bRequest) << 16 | (wIndex)) +#define _MAKE_NI_CONTROL(bRequest, wIndex) ((bRequest) << 16 | (wIndex)) static int snd_ni_control_init_val(struct usb_mixer_interface *mixer, struct snd_kcontrol *kctl) @@ -754,7 +1135,7 @@ static int snd_ni_control_init_val(struct usb_mixer_interface *mixer, return err; } - kctl->private_value |= (value << 24); + kctl->private_value |= ((unsigned int)value << 24); return 0; } @@ -769,17 +1150,14 @@ static int snd_ni_update_cur_val(struct usb_mixer_elem_list *list) { struct snd_usb_audio *chip = list->mixer->chip; unsigned int pval = list->kctl->private_value; - int err; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; - err = usb_control_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), - (pval >> 16) & 0xff, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, - pval >> 24, pval & 0xffff, NULL, 0, 1000); - snd_usb_unlock_shutdown(chip); - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + return usb_control_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + (pval >> 16) & 0xff, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + pval >> 24, pval & 0xffff, NULL, 0, 1000); } static int snd_nativeinstruments_control_put(struct snd_kcontrol *kcontrol, @@ -799,7 +1177,7 @@ static int snd_nativeinstruments_control_put(struct snd_kcontrol *kcontrol, return err < 0 ? err : 1; } -static struct snd_kcontrol_new snd_nativeinstruments_ta6_mixers[] = { +static const struct snd_kcontrol_new snd_nativeinstruments_ta6_mixers[] = { { .name = "Direct Thru Channel A", .private_value = _MAKE_NI_CONTROL(0x01, 0x03), @@ -818,7 +1196,7 @@ static struct snd_kcontrol_new snd_nativeinstruments_ta6_mixers[] = { }, }; -static struct snd_kcontrol_new snd_nativeinstruments_ta10_mixers[] = { +static const struct snd_kcontrol_new snd_nativeinstruments_ta10_mixers[] = { { .name = "Direct Thru Channel A", .private_value = _MAKE_NI_CONTROL(0x01, 0x03), @@ -886,7 +1264,7 @@ static int snd_nativeinstruments_create_mixer(struct usb_mixer_interface *mixer, /* M-Audio FastTrack Ultra quirks */ /* FTU Effect switch (also used by C400/C600) */ static int snd_ftu_eff_switch_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) + struct snd_ctl_elem_info *uinfo) { static const char *const texts[8] = { "Room 1", "Room 2", "Room 3", "Hall 1", @@ -910,17 +1288,17 @@ static int snd_ftu_eff_switch_init(struct usb_mixer_interface *mixer, err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, pval & 0xff00, - snd_usb_ctrl_intf(mixer->chip) | ((pval & 0xff) << 8), + snd_usb_ctrl_intf(mixer->hostif) | ((pval & 0xff) << 8), value, 2); if (err < 0) return err; - kctl->private_value |= value[0] << 24; + kctl->private_value |= (unsigned int)value[0] << 24; return 0; } static int snd_ftu_eff_switch_get(struct snd_kcontrol *kctl, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { ucontrol->value.enumerated.item[0] = kctl->private_value >> 24; return 0; @@ -931,27 +1309,24 @@ static int snd_ftu_eff_switch_update(struct usb_mixer_elem_list *list) struct snd_usb_audio *chip = list->mixer->chip; unsigned int pval = list->kctl->private_value; unsigned char value[2]; - int err; value[0] = pval >> 24; value[1] = 0; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), - UAC_SET_CUR, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, - pval & 0xff00, - snd_usb_ctrl_intf(chip) | ((pval & 0xff) << 8), - value, 2); - snd_usb_unlock_shutdown(chip); - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + pval & 0xff00, + snd_usb_ctrl_intf(list->mixer->hostif) | ((pval & 0xff) << 8), + value, 2); } static int snd_ftu_eff_switch_put(struct snd_kcontrol *kctl, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); unsigned int pval = list->kctl->private_value; @@ -969,7 +1344,7 @@ static int snd_ftu_eff_switch_put(struct snd_kcontrol *kctl, } static int snd_ftu_create_effect_switch(struct usb_mixer_interface *mixer, - int validx, int bUnitID) + int validx, int bUnitID) { static struct snd_kcontrol_new template = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, @@ -1006,24 +1381,24 @@ static int snd_ftu_create_volume_ctls(struct usb_mixer_interface *mixer) for (out = 0; out < 8; out++) { control = out + 1; for (in = 0; in < 8; in++) { - cmask = 1 << in; + cmask = BIT(in); snprintf(name, sizeof(name), - "AIn%d - Out%d Capture Volume", - in + 1, out + 1); + "AIn%d - Out%d Capture Volume", + in + 1, out + 1); err = snd_create_std_mono_ctl(mixer, id, control, - cmask, val_type, name, - &snd_usb_mixer_vol_tlv); + cmask, val_type, name, + &snd_usb_mixer_vol_tlv); if (err < 0) return err; } for (in = 8; in < 16; in++) { - cmask = 1 << in; + cmask = BIT(in); snprintf(name, sizeof(name), - "DIn%d - Out%d Playback Volume", - in - 7, out + 1); + "DIn%d - Out%d Playback Volume", + in - 7, out + 1); err = snd_create_std_mono_ctl(mixer, id, control, - cmask, val_type, name, - &snd_usb_mixer_vol_tlv); + cmask, val_type, name, + &snd_usb_mixer_vol_tlv); if (err < 0) return err; } @@ -1082,12 +1457,12 @@ static int snd_ftu_create_effect_return_ctls(struct usb_mixer_interface *mixer) const unsigned int control = 7; for (ch = 0; ch < 4; ++ch) { - cmask = 1 << ch; + cmask = BIT(ch); snprintf(name, sizeof(name), - "Effect Return %d Volume", ch + 1); + "Effect Return %d Volume", ch + 1); err = snd_create_std_mono_ctl(mixer, id, control, - cmask, val_type, name, - snd_usb_mixer_vol_tlv); + cmask, val_type, name, + snd_usb_mixer_vol_tlv); if (err < 0) return err; } @@ -1106,22 +1481,22 @@ static int snd_ftu_create_effect_send_ctls(struct usb_mixer_interface *mixer) const unsigned int control = 9; for (ch = 0; ch < 8; ++ch) { - cmask = 1 << ch; + cmask = BIT(ch); snprintf(name, sizeof(name), - "Effect Send AIn%d Volume", ch + 1); + "Effect Send AIn%d Volume", ch + 1); err = snd_create_std_mono_ctl(mixer, id, control, cmask, - val_type, name, - snd_usb_mixer_vol_tlv); + val_type, name, + snd_usb_mixer_vol_tlv); if (err < 0) return err; } for (ch = 8; ch < 16; ++ch) { - cmask = 1 << ch; + cmask = BIT(ch); snprintf(name, sizeof(name), - "Effect Send DIn%d Volume", ch - 7); + "Effect Send DIn%d Volume", ch - 7); err = snd_create_std_mono_ctl(mixer, id, control, cmask, - val_type, name, - snd_usb_mixer_vol_tlv); + val_type, name, + snd_usb_mixer_vol_tlv); if (err < 0) return err; } @@ -1168,17 +1543,17 @@ void snd_emuusb_set_samplerate(struct snd_usb_audio *chip, { struct usb_mixer_interface *mixer; struct usb_mixer_elem_info *cval; - int unitid = 12; /* SamleRate ExtensionUnit ID */ + int unitid = 12; /* SampleRate ExtensionUnit ID */ list_for_each_entry(mixer, &chip->mixer_list, list) { - cval = mixer_elem_list_to_info(mixer->id_elems[unitid]); - if (cval) { + if (mixer->id_elems[unitid]) { + cval = mixer_elem_list_to_info(mixer->id_elems[unitid]); snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, cval->control << 8, samplerate_id); snd_usb_mixer_notify_id(mixer, unitid); + break; } - break; } } @@ -1211,19 +1586,19 @@ static int snd_c400_create_vol_ctls(struct usb_mixer_interface *mixer) for (out = 0; out < num_outs; out++) { if (chan < num_outs) { snprintf(name, sizeof(name), - "PCM%d-Out%d Playback Volume", - chan + 1, out + 1); + "PCM%d-Out%d Playback Volume", + chan + 1, out + 1); } else { snprintf(name, sizeof(name), - "In%d-Out%d Playback Volume", - chan - num_outs + 1, out + 1); + "In%d-Out%d Playback Volume", + chan - num_outs + 1, out + 1); } - cmask = (out == 0) ? 0 : 1 << (out - 1); + cmask = (out == 0) ? 0 : BIT(out - 1); offset = chan * num_outs; err = snd_create_std_mono_ctl_offset(mixer, id, control, - cmask, val_type, offset, name, - &snd_usb_mixer_vol_tlv); + cmask, val_type, offset, name, + &snd_usb_mixer_vol_tlv); if (err < 0) return err; } @@ -1242,7 +1617,7 @@ static int snd_c400_create_effect_volume_ctl(struct usb_mixer_interface *mixer) const unsigned int cmask = 0; return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, - name, snd_usb_mixer_vol_tlv); + name, snd_usb_mixer_vol_tlv); } /* This control needs a volume quirk, see mixer.c */ @@ -1255,7 +1630,7 @@ static int snd_c400_create_effect_duration_ctl(struct usb_mixer_interface *mixer const unsigned int cmask = 0; return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, - name, snd_usb_mixer_vol_tlv); + name, snd_usb_mixer_vol_tlv); } /* This control needs a volume quirk, see mixer.c */ @@ -1268,7 +1643,7 @@ static int snd_c400_create_effect_feedback_ctl(struct usb_mixer_interface *mixer const unsigned int cmask = 0; return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, - name, NULL); + name, NULL); } static int snd_c400_create_effect_vol_ctls(struct usb_mixer_interface *mixer) @@ -1297,18 +1672,18 @@ static int snd_c400_create_effect_vol_ctls(struct usb_mixer_interface *mixer) for (chan = 0; chan < num_outs + num_ins; chan++) { if (chan < num_outs) { snprintf(name, sizeof(name), - "Effect Send DOut%d", - chan + 1); + "Effect Send DOut%d", + chan + 1); } else { snprintf(name, sizeof(name), - "Effect Send AIn%d", - chan - num_outs + 1); + "Effect Send AIn%d", + chan - num_outs + 1); } - cmask = (chan == 0) ? 0 : 1 << (chan - 1); + cmask = (chan == 0) ? 0 : BIT(chan - 1); err = snd_create_std_mono_ctl(mixer, id, control, - cmask, val_type, name, - &snd_usb_mixer_vol_tlv); + cmask, val_type, name, + &snd_usb_mixer_vol_tlv); if (err < 0) return err; } @@ -1343,14 +1718,14 @@ static int snd_c400_create_effect_ret_vol_ctls(struct usb_mixer_interface *mixer for (chan = 0; chan < num_outs; chan++) { snprintf(name, sizeof(name), - "Effect Return %d", - chan + 1); + "Effect Return %d", + chan + 1); cmask = (chan == 0) ? 0 : - 1 << (chan + (chan % 2) * num_outs - 1); + BIT(chan + (chan % 2) * num_outs - 1); err = snd_create_std_mono_ctl_offset(mixer, id, control, - cmask, val_type, offset, name, - &snd_usb_mixer_vol_tlv); + cmask, val_type, offset, name, + &snd_usb_mixer_vol_tlv); if (err < 0) return err; } @@ -1398,7 +1773,7 @@ static int snd_c400_create_mixer(struct usb_mixer_interface *mixer) * are valid they presents mono controls as L and R channels of * stereo. So we provide a good mixer here. */ -static struct std_mono_table ebox44_table[] = { +static const struct std_mono_table ebox44_table[] = { { .unitid = 4, .control = 1, @@ -1491,7 +1866,7 @@ static struct std_mono_table ebox44_table[] = { * */ static int snd_microii_spdif_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) + struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; uinfo->count = 1; @@ -1499,7 +1874,7 @@ static int snd_microii_spdif_info(struct snd_kcontrol *kcontrol, } static int snd_microii_spdif_default_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); struct snd_usb_audio *chip = list->mixer->chip; @@ -1510,9 +1885,9 @@ static int snd_microii_spdif_default_get(struct snd_kcontrol *kcontrol, unsigned char data[3]; int rate; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; ucontrol->value.iec958.status[0] = kcontrol->private_value & 0xff; ucontrol->value.iec958.status[1] = (kcontrol->private_value >> 8) & 0xff; @@ -1528,24 +1903,21 @@ static int snd_microii_spdif_default_get(struct snd_kcontrol *kcontrol, ep = get_endpoint(alts, 0)->bEndpointAddress; err = snd_usb_ctl_msg(chip->dev, - usb_rcvctrlpipe(chip->dev, 0), - UAC_GET_CUR, - USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN, - UAC_EP_CS_ATTR_SAMPLE_RATE << 8, - ep, - data, - sizeof(data)); + usb_rcvctrlpipe(chip->dev, 0), + UAC_GET_CUR, + USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, + ep, + data, + sizeof(data)); if (err < 0) - goto end; + return err; rate = data[0] | (data[1] << 8) | (data[2] << 16); ucontrol->value.iec958.status[3] = (rate == 48000) ? IEC958_AES3_CON_FS_48000 : IEC958_AES3_CON_FS_44100; - err = 0; - end: - snd_usb_unlock_shutdown(chip); - return err; + return 0; } static int snd_microii_spdif_default_update(struct usb_mixer_elem_list *list) @@ -1555,48 +1927,44 @@ static int snd_microii_spdif_default_update(struct usb_mixer_elem_list *list) u8 reg; int err; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; reg = ((pval >> 4) & 0xf0) | (pval & 0x0f); err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), - UAC_SET_CUR, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - reg, - 2, - NULL, - 0); + usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + reg, + 2, + NULL, + 0); if (err < 0) - goto end; + return err; reg = (pval & IEC958_AES0_NONAUDIO) ? 0xa0 : 0x20; reg |= (pval >> 12) & 0x0f; err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), - UAC_SET_CUR, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - reg, - 3, - NULL, - 0); - if (err < 0) - goto end; - - end: - snd_usb_unlock_shutdown(chip); + usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + reg, + 3, + NULL, + 0); return err; } static int snd_microii_spdif_default_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); unsigned int pval, pval_old; int err; - pval = pval_old = kcontrol->private_value; + pval = kcontrol->private_value; + pval_old = pval; pval &= 0xfffff0f0; pval |= (ucontrol->value.iec958.status[1] & 0x0f) << 8; pval |= (ucontrol->value.iec958.status[0] & 0x0f); @@ -1617,7 +1985,7 @@ static int snd_microii_spdif_default_put(struct snd_kcontrol *kcontrol, } static int snd_microii_spdif_mask_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { ucontrol->value.iec958.status[0] = 0x0f; ucontrol->value.iec958.status[1] = 0xff; @@ -1628,7 +1996,7 @@ static int snd_microii_spdif_mask_get(struct snd_kcontrol *kcontrol, } static int snd_microii_spdif_switch_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { ucontrol->value.integer.value[0] = !(kcontrol->private_value & 0x02); @@ -1639,27 +2007,23 @@ static int snd_microii_spdif_switch_update(struct usb_mixer_elem_list *list) { struct snd_usb_audio *chip = list->mixer->chip; u8 reg = list->kctl->private_value; - int err; - - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), - UAC_SET_CUR, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - reg, - 9, - NULL, - 0); - - snd_usb_unlock_shutdown(chip); - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + reg, + 9, + NULL, + 0); } static int snd_microii_spdif_switch_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); u8 reg; @@ -1674,7 +2038,7 @@ static int snd_microii_spdif_switch_put(struct snd_kcontrol *kcontrol, return err < 0 ? err : 1; } -static struct snd_kcontrol_new snd_microii_mixer_spdif[] = { +static const struct snd_kcontrol_new snd_microii_mixer_spdif[] = { { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), @@ -1703,7 +2067,7 @@ static struct snd_kcontrol_new snd_microii_mixer_spdif[] = { static int snd_microii_controls_create(struct usb_mixer_interface *mixer) { int err, i; - static usb_mixer_elem_resume_func_t resume_funcs[] = { + static const usb_mixer_elem_resume_func_t resume_funcs[] = { snd_microii_spdif_default_update, NULL, snd_microii_spdif_switch_update @@ -1734,21 +2098,18 @@ static int snd_soundblaster_e1_switch_update(struct usb_mixer_interface *mixer, unsigned char state) { struct snd_usb_audio *chip = mixer->chip; - int err; unsigned char buff[2]; buff[0] = 0x02; buff[1] = state ? 0x02 : 0x00; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), HID_REQ_SET_REPORT, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - 0x0202, 3, buff, 2); - snd_usb_unlock_shutdown(chip); - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), HID_REQ_SET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0x0202, 3, buff, 2); } static int snd_soundblaster_e1_switch_put(struct snd_kcontrol *kcontrol, @@ -1781,7 +2142,7 @@ static int snd_soundblaster_e1_switch_info(struct snd_kcontrol *kcontrol, return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); } -static struct snd_kcontrol_new snd_soundblaster_e1_input_switch = { +static const struct snd_kcontrol_new snd_soundblaster_e1_input_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Input Source", .info = snd_soundblaster_e1_switch_info, @@ -1798,26 +2159,309 @@ static int snd_soundblaster_e1_switch_create(struct usb_mixer_interface *mixer) NULL); } -static void dell_dock_init_vol(struct snd_usb_audio *chip, int ch, int id) +/* + * Dell WD15 dock jack detection + * + * The WD15 contains an ALC4020 USB audio controller and ALC3263 audio codec + * from Realtek. It is a UAC 1 device, and UAC 1 does not support jack + * detection. Instead, jack detection works by sending HD Audio commands over + * vendor-type USB messages. + */ + +#define HDA_VERB_CMD(V, N, D) (((N) << 20) | ((V) << 8) | (D)) + +#define REALTEK_HDA_VALUE 0x0038 + +#define REALTEK_HDA_SET 62 +#define REALTEK_MANUAL_MODE 72 +#define REALTEK_HDA_GET_OUT 88 +#define REALTEK_HDA_GET_IN 89 + +#define REALTEK_AUDIO_FUNCTION_GROUP 0x01 +#define REALTEK_LINE1 0x1a +#define REALTEK_VENDOR_REGISTERS 0x20 +#define REALTEK_HP_OUT 0x21 + +#define REALTEK_CBJ_CTRL2 0x50 + +#define REALTEK_JACK_INTERRUPT_NODE 5 + +#define REALTEK_MIC_FLAG 0x100 + +static int realtek_hda_set(struct snd_usb_audio *chip, u32 cmd) +{ + struct usb_device *dev = chip->dev; + __be32 buf = cpu_to_be32(cmd); + + return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), REALTEK_HDA_SET, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_OUT, + REALTEK_HDA_VALUE, 0, &buf, sizeof(buf)); +} + +static int realtek_hda_get(struct snd_usb_audio *chip, u32 cmd, u32 *value) { + struct usb_device *dev = chip->dev; + int err; + __be32 buf = cpu_to_be32(cmd); + + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), REALTEK_HDA_GET_OUT, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_OUT, + REALTEK_HDA_VALUE, 0, &buf, sizeof(buf)); + if (err < 0) + return err; + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), REALTEK_HDA_GET_IN, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_IN, + REALTEK_HDA_VALUE, 0, &buf, sizeof(buf)); + if (err < 0) + return err; + + *value = be32_to_cpu(buf); + return 0; +} + +static int realtek_ctl_connector_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); + struct snd_usb_audio *chip = cval->head.mixer->chip; + u32 pv = kcontrol->private_value; + u32 node_id = pv & 0xff; + u32 sense; + u32 cbj_ctrl2; + bool presence; + int err; + + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + err = realtek_hda_get(chip, + HDA_VERB_CMD(AC_VERB_GET_PIN_SENSE, node_id, 0), + &sense); + if (err < 0) + return err; + if (pv & REALTEK_MIC_FLAG) { + err = realtek_hda_set(chip, + HDA_VERB_CMD(AC_VERB_SET_COEF_INDEX, + REALTEK_VENDOR_REGISTERS, + REALTEK_CBJ_CTRL2)); + if (err < 0) + return err; + err = realtek_hda_get(chip, + HDA_VERB_CMD(AC_VERB_GET_PROC_COEF, + REALTEK_VENDOR_REGISTERS, 0), + &cbj_ctrl2); + if (err < 0) + return err; + } + + presence = sense & AC_PINSENSE_PRESENCE; + if (pv & REALTEK_MIC_FLAG) + presence = presence && (cbj_ctrl2 & 0x0070) == 0x0070; + ucontrol->value.integer.value[0] = presence; + return 0; +} + +static const struct snd_kcontrol_new realtek_connector_ctl_ro = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "", /* will be filled later manually */ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ctl_boolean_mono_info, + .get = realtek_ctl_connector_get, +}; + +static int realtek_resume_jack(struct usb_mixer_elem_list *list) +{ + snd_ctl_notify(list->mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &list->kctl->id); + return 0; +} + +static int realtek_add_jack(struct usb_mixer_interface *mixer, + char *name, u32 val, int unitid, + const struct snd_kcontrol_new *kctl_new) +{ + struct usb_mixer_elem_info *cval; + struct snd_kcontrol *kctl; + + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (!cval) + return -ENOMEM; + snd_usb_mixer_elem_init_std(&cval->head, mixer, unitid); + cval->head.resume = realtek_resume_jack; + cval->val_type = USB_MIXER_BOOLEAN; + cval->channels = 1; + cval->min = 0; + cval->max = 1; + kctl = snd_ctl_new1(kctl_new, cval); + if (!kctl) { + kfree(cval); + return -ENOMEM; + } + kctl->private_value = val; + strscpy(kctl->id.name, name, sizeof(kctl->id.name)); + kctl->private_free = snd_usb_mixer_elem_free; + return snd_usb_mixer_add_control(&cval->head, kctl); +} + +static int dell_dock_mixer_create(struct usb_mixer_interface *mixer) +{ + int err; + struct usb_device *dev = mixer->chip->dev; + + /* Power down the audio codec to avoid loud pops in the next step. */ + realtek_hda_set(mixer->chip, + HDA_VERB_CMD(AC_VERB_SET_POWER_STATE, + REALTEK_AUDIO_FUNCTION_GROUP, + AC_PWRST_D3)); + + /* + * Turn off 'manual mode' in case it was enabled. This removes the need + * to power cycle the dock after it was attached to a Windows machine. + */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), REALTEK_MANUAL_MODE, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_OUT, + 0, 0, NULL, 0); + + err = realtek_add_jack(mixer, "Line Out Jack", REALTEK_LINE1, + REALTEK_JACK_INTERRUPT_NODE, + &realtek_connector_ctl_ro); + if (err < 0) + return err; + err = realtek_add_jack(mixer, "Headphone Jack", REALTEK_HP_OUT, + REALTEK_JACK_INTERRUPT_NODE, + &realtek_connector_ctl_ro); + if (err < 0) + return err; + err = realtek_add_jack(mixer, "Headset Mic Jack", + REALTEK_HP_OUT | REALTEK_MIC_FLAG, + REALTEK_JACK_INTERRUPT_NODE, + &realtek_connector_ctl_ro); + if (err < 0) + return err; + return 0; +} + +static void dell_dock_init_vol(struct usb_mixer_interface *mixer, int ch, int id) +{ + struct snd_usb_audio *chip = mixer->chip; u16 buf = 0; snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC_SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, - ch, snd_usb_ctrl_intf(chip) | (id << 8), + (UAC_FU_VOLUME << 8) | ch, + snd_usb_ctrl_intf(mixer->hostif) | (id << 8), &buf, 2); } static int dell_dock_mixer_init(struct usb_mixer_interface *mixer) { /* fix to 0dB playback volumes */ - dell_dock_init_vol(mixer->chip, 1, 16); - dell_dock_init_vol(mixer->chip, 2, 16); - dell_dock_init_vol(mixer->chip, 1, 19); - dell_dock_init_vol(mixer->chip, 2, 19); + dell_dock_init_vol(mixer, 1, 16); + dell_dock_init_vol(mixer, 2, 16); + dell_dock_init_vol(mixer, 1, 19); + dell_dock_init_vol(mixer, 2, 19); + return 0; +} + +/* + * HP Thunderbolt Dock G2 jack detection + * + * Similar to the Dell WD15/WD19, but with different commands. + */ + +#define HP_DOCK_JACK_INTERRUPT_NODE 7 + +#define HP_DOCK_GET 37 + +#define HP_DOCK_JACK_PRESENCE 0xffb8 +#define HP_DOCK_JACK_PRESENCE_BIT BIT(2) + +#define HP_DOCK_MIC_SENSE 0xf753 +#define HP_DOCK_MIC_SENSE_COMPLETE_BIT BIT(4) + +#define HP_DOCK_MIC_SENSE_MASK (BIT(2) | BIT(1) | BIT(0)) +/* #define HP_DOCK_MIC_SENSE_PRESENT 0x2 */ +#define HP_DOCK_MIC_SENSE_NOT_PRESENT 0x4 + +static int hp_dock_ctl_connector_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); + struct snd_usb_audio *chip = cval->head.mixer->chip; + u32 pv = kcontrol->private_value; + bool presence; + int err; + u8 buf; + + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + + err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + HP_DOCK_GET, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_IN, + 0, HP_DOCK_JACK_PRESENCE, &buf, sizeof(buf)); + if (err < 0) + return err; + + presence = !(buf & HP_DOCK_JACK_PRESENCE_BIT); + + if (pv && presence) { + for (int i = 0; i < 20; i++) { + err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + HP_DOCK_GET, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_IN, + 0, HP_DOCK_MIC_SENSE, &buf, sizeof(buf)); + if (err < 0) + return err; + + /* Mic sense is complete, we have a result. */ + if (buf & HP_DOCK_MIC_SENSE_COMPLETE_BIT) + break; + + msleep(100); + } + + /* + * If we reach the retry limit without mic sense having + * completed, buf will contain HP_DOCK_MIC_SENSE_PRESENT, + * thus presence remains true even when detection fails. + */ + if ((buf & HP_DOCK_MIC_SENSE_MASK) == HP_DOCK_MIC_SENSE_NOT_PRESENT) + presence = false; + } + ucontrol->value.integer.value[0] = presence; return 0; } +static const struct snd_kcontrol_new hp_dock_connector_ctl_ro = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "", /* will be filled later manually */ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ctl_boolean_mono_info, + .get = hp_dock_ctl_connector_get, +}; + +static int hp_dock_mixer_create(struct usb_mixer_interface *mixer) +{ + int err; + + err = realtek_add_jack(mixer, "Headsets Playback Jack", 0, + HP_DOCK_JACK_INTERRUPT_NODE, + &hp_dock_connector_ctl_ro); + if (err < 0) + return err; + + err = realtek_add_jack(mixer, "Headset Capture Jack", 1, + HP_DOCK_JACK_INTERRUPT_NODE, + &hp_dock_connector_ctl_ro); + if (err < 0) + return err; + + return 0; +} + + /* RME Class Compliant device quirks */ #define SND_RME_GET_STATUS1 23 @@ -1832,15 +2476,15 @@ static int dell_dock_mixer_init(struct usb_mixer_interface *mixer) #define SND_RME_CLK_FREQMUL_SHIFT 18 #define SND_RME_CLK_FREQMUL_MASK 0x7 #define SND_RME_CLK_SYSTEM(x) \ - ((x >> SND_RME_CLK_SYSTEM_SHIFT) & SND_RME_CLK_SYSTEM_MASK) + (((x) >> SND_RME_CLK_SYSTEM_SHIFT) & SND_RME_CLK_SYSTEM_MASK) #define SND_RME_CLK_AES(x) \ - ((x >> SND_RME_CLK_AES_SHIFT) & SND_RME_CLK_AES_SPDIF_MASK) + (((x) >> SND_RME_CLK_AES_SHIFT) & SND_RME_CLK_AES_SPDIF_MASK) #define SND_RME_CLK_SPDIF(x) \ - ((x >> SND_RME_CLK_SPDIF_SHIFT) & SND_RME_CLK_AES_SPDIF_MASK) + (((x) >> SND_RME_CLK_SPDIF_SHIFT) & SND_RME_CLK_AES_SPDIF_MASK) #define SND_RME_CLK_SYNC(x) \ - ((x >> SND_RME_CLK_SYNC_SHIFT) & SND_RME_CLK_SYNC_MASK) + (((x) >> SND_RME_CLK_SYNC_SHIFT) & SND_RME_CLK_SYNC_MASK) #define SND_RME_CLK_FREQMUL(x) \ - ((x >> SND_RME_CLK_FREQMUL_SHIFT) & SND_RME_CLK_FREQMUL_MASK) + (((x) >> SND_RME_CLK_FREQMUL_SHIFT) & SND_RME_CLK_FREQMUL_MASK) #define SND_RME_CLK_AES_LOCK 0x1 #define SND_RME_CLK_AES_SYNC 0x4 #define SND_RME_CLK_SPDIF_LOCK 0x2 @@ -1849,9 +2493,9 @@ static int dell_dock_mixer_init(struct usb_mixer_interface *mixer) #define SND_RME_SPDIF_FORMAT_SHIFT 5 #define SND_RME_BINARY_MASK 0x1 #define SND_RME_SPDIF_IF(x) \ - ((x >> SND_RME_SPDIF_IF_SHIFT) & SND_RME_BINARY_MASK) + (((x) >> SND_RME_SPDIF_IF_SHIFT) & SND_RME_BINARY_MASK) #define SND_RME_SPDIF_FORMAT(x) \ - ((x >> SND_RME_SPDIF_FORMAT_SHIFT) & SND_RME_BINARY_MASK) + (((x) >> SND_RME_SPDIF_FORMAT_SHIFT) & SND_RME_BINARY_MASK) static const u32 snd_rme_rate_table[] = { 32000, 44100, 48000, 50000, @@ -1860,6 +2504,7 @@ static const u32 snd_rme_rate_table[] = { 256000, 352800, 384000, 400000, 512000, 705600, 768000, 800000 }; + /* maximum number of items for AES and S/PDIF rates for above table */ #define SND_RME_RATE_IDX_AES_SPDIF_NUM 12 @@ -1899,14 +2544,11 @@ static int snd_rme_get_status1(struct snd_kcontrol *kcontrol, { struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); struct snd_usb_audio *chip = list->mixer->chip; - int err; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; - err = snd_rme_read_value(chip, SND_RME_GET_STATUS1, status1); - snd_usb_unlock_shutdown(chip); - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + return snd_rme_read_value(chip, SND_RME_GET_STATUS1, status1); } static int snd_rme_rate_get(struct snd_kcontrol *kcontrol, @@ -2023,22 +2665,19 @@ static int snd_rme_current_freq_get(struct snd_kcontrol *kcontrol, unsigned int freq; int err; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; err = snd_rme_read_value(chip, SND_RME_GET_STATUS1, &status1); if (err < 0) - goto end; + return err; err = snd_rme_read_value(chip, SND_RME_GET_CURRENT_FREQ, &den); if (err < 0) - goto end; + return err; freq = (den == 0) ? 0 : div64_u64(num, den); freq <<= SND_RME_CLK_FREQMUL(status1); ucontrol->value.integer.value[0] = freq; - -end: - snd_usb_unlock_shutdown(chip); - return err; + return 0; } static int snd_rme_rate_info(struct snd_kcontrol *kcontrol, @@ -2105,7 +2744,7 @@ static int snd_rme_sync_source_info(struct snd_kcontrol *kcontrol, ARRAY_SIZE(sync_sources), sync_sources); } -static struct snd_kcontrol_new snd_rme_controls[] = { +static const struct snd_kcontrol_new snd_rme_controls[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "AES Rate", @@ -2192,10 +2831,1514 @@ static int snd_rme_controls_create(struct usb_mixer_interface *mixer) return 0; } +/* + * RME Babyface Pro (FS) + * + * These devices exposes a couple of DSP functions via request to EP0. + * Switches are available via control registers, while routing is controlled + * by controlling the volume on each possible crossing point. + * Volume control is linear, from -inf (dec. 0) to +6dB (dec. 65536) with + * 0dB being at dec. 32768. + */ +enum { + SND_BBFPRO_CTL_REG1 = 0, + SND_BBFPRO_CTL_REG2 +}; + +#define SND_BBFPRO_CTL_REG_MASK 1 +#define SND_BBFPRO_CTL_IDX_MASK 0xff +#define SND_BBFPRO_CTL_IDX_SHIFT 1 +#define SND_BBFPRO_CTL_VAL_MASK 1 +#define SND_BBFPRO_CTL_VAL_SHIFT 9 +#define SND_BBFPRO_CTL_REG1_CLK_MASTER 0 +#define SND_BBFPRO_CTL_REG1_CLK_OPTICAL 1 +#define SND_BBFPRO_CTL_REG1_SPDIF_PRO 7 +#define SND_BBFPRO_CTL_REG1_SPDIF_EMPH 8 +#define SND_BBFPRO_CTL_REG1_SPDIF_OPTICAL 10 +#define SND_BBFPRO_CTL_REG2_48V_AN1 0 +#define SND_BBFPRO_CTL_REG2_48V_AN2 1 +#define SND_BBFPRO_CTL_REG2_SENS_IN3 2 +#define SND_BBFPRO_CTL_REG2_SENS_IN4 3 +#define SND_BBFPRO_CTL_REG2_PAD_AN1 4 +#define SND_BBFPRO_CTL_REG2_PAD_AN2 5 + +#define SND_BBFPRO_MIXER_MAIN_OUT_CH_OFFSET 992 +#define SND_BBFPRO_MIXER_IDX_MASK 0x3ff +#define SND_BBFPRO_MIXER_VAL_MASK 0x3ffff +#define SND_BBFPRO_MIXER_VAL_SHIFT 9 +#define SND_BBFPRO_MIXER_VAL_MIN 0 // -inf +#define SND_BBFPRO_MIXER_VAL_MAX 65536 // +6dB + +#define SND_BBFPRO_GAIN_CHANNEL_MASK 0x03 +#define SND_BBFPRO_GAIN_CHANNEL_SHIFT 7 +#define SND_BBFPRO_GAIN_VAL_MASK 0x7f +#define SND_BBFPRO_GAIN_VAL_MIN 0 +#define SND_BBFPRO_GAIN_VAL_MIC_MAX 65 +#define SND_BBFPRO_GAIN_VAL_LINE_MAX 18 // 9db in 0.5db incraments + +#define SND_BBFPRO_USBREQ_CTL_REG1 0x10 +#define SND_BBFPRO_USBREQ_CTL_REG2 0x17 +#define SND_BBFPRO_USBREQ_GAIN 0x1a +#define SND_BBFPRO_USBREQ_MIXER 0x12 + +static int snd_bbfpro_ctl_update(struct usb_mixer_interface *mixer, u8 reg, + u8 index, u8 value) +{ + u16 usb_req, usb_idx, usb_val; + struct snd_usb_audio *chip = mixer->chip; + + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + + if (reg == SND_BBFPRO_CTL_REG1) { + usb_req = SND_BBFPRO_USBREQ_CTL_REG1; + if (index == SND_BBFPRO_CTL_REG1_CLK_OPTICAL) { + usb_idx = 3; + usb_val = value ? 3 : 0; + } else { + usb_idx = BIT(index); + usb_val = value ? usb_idx : 0; + } + } else { + usb_req = SND_BBFPRO_USBREQ_CTL_REG2; + usb_idx = BIT(index); + usb_val = value ? usb_idx : 0; + } + + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), usb_req, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + usb_val, usb_idx, NULL, 0); +} + +static int snd_bbfpro_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 reg, idx, val; + int pv; + + pv = kcontrol->private_value; + reg = pv & SND_BBFPRO_CTL_REG_MASK; + idx = (pv >> SND_BBFPRO_CTL_IDX_SHIFT) & SND_BBFPRO_CTL_IDX_MASK; + val = kcontrol->private_value >> SND_BBFPRO_CTL_VAL_SHIFT; + + if ((reg == SND_BBFPRO_CTL_REG1 && + idx == SND_BBFPRO_CTL_REG1_CLK_OPTICAL) || + (reg == SND_BBFPRO_CTL_REG2 && + (idx == SND_BBFPRO_CTL_REG2_SENS_IN3 || + idx == SND_BBFPRO_CTL_REG2_SENS_IN4))) { + ucontrol->value.enumerated.item[0] = val; + } else { + ucontrol->value.integer.value[0] = val; + } + return 0; +} + +static int snd_bbfpro_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + u8 reg, idx; + int pv; + + pv = kcontrol->private_value; + reg = pv & SND_BBFPRO_CTL_REG_MASK; + idx = (pv >> SND_BBFPRO_CTL_IDX_SHIFT) & SND_BBFPRO_CTL_IDX_MASK; + + if (reg == SND_BBFPRO_CTL_REG1 && + idx == SND_BBFPRO_CTL_REG1_CLK_OPTICAL) { + static const char * const texts[2] = { + "AutoSync", + "Internal" + }; + return snd_ctl_enum_info(uinfo, 1, 2, texts); + } else if (reg == SND_BBFPRO_CTL_REG2 && + (idx == SND_BBFPRO_CTL_REG2_SENS_IN3 || + idx == SND_BBFPRO_CTL_REG2_SENS_IN4)) { + static const char * const texts[2] = { + "-10dBV", + "+4dBu" + }; + return snd_ctl_enum_info(uinfo, 1, 2, texts); + } + + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + return 0; +} + +static int snd_bbfpro_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int err; + u8 reg, idx; + int old_value, pv, val; + + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + struct usb_mixer_interface *mixer = list->mixer; + + pv = kcontrol->private_value; + reg = pv & SND_BBFPRO_CTL_REG_MASK; + idx = (pv >> SND_BBFPRO_CTL_IDX_SHIFT) & SND_BBFPRO_CTL_IDX_MASK; + old_value = (pv >> SND_BBFPRO_CTL_VAL_SHIFT) & SND_BBFPRO_CTL_VAL_MASK; + + if ((reg == SND_BBFPRO_CTL_REG1 && + idx == SND_BBFPRO_CTL_REG1_CLK_OPTICAL) || + (reg == SND_BBFPRO_CTL_REG2 && + (idx == SND_BBFPRO_CTL_REG2_SENS_IN3 || + idx == SND_BBFPRO_CTL_REG2_SENS_IN4))) { + val = ucontrol->value.enumerated.item[0]; + } else { + val = ucontrol->value.integer.value[0]; + } + + if (val > 1) + return -EINVAL; + + if (val == old_value) + return 0; + + kcontrol->private_value = reg + | ((idx & SND_BBFPRO_CTL_IDX_MASK) << SND_BBFPRO_CTL_IDX_SHIFT) + | ((val & SND_BBFPRO_CTL_VAL_MASK) << SND_BBFPRO_CTL_VAL_SHIFT); + + err = snd_bbfpro_ctl_update(mixer, reg, idx, val); + return err < 0 ? err : 1; +} + +static int snd_bbfpro_ctl_resume(struct usb_mixer_elem_list *list) +{ + u8 reg, idx; + int value, pv; + + pv = list->kctl->private_value; + reg = pv & SND_BBFPRO_CTL_REG_MASK; + idx = (pv >> SND_BBFPRO_CTL_IDX_SHIFT) & SND_BBFPRO_CTL_IDX_MASK; + value = (pv >> SND_BBFPRO_CTL_VAL_SHIFT) & SND_BBFPRO_CTL_VAL_MASK; + + return snd_bbfpro_ctl_update(list->mixer, reg, idx, value); +} + +static int snd_bbfpro_gain_update(struct usb_mixer_interface *mixer, + u8 channel, u8 gain) +{ + struct snd_usb_audio *chip = mixer->chip; + + if (channel < 2) { + // XLR preamp: 3-bit fine, 5-bit coarse; special case >60 + if (gain < 60) + gain = ((gain % 3) << 5) | (gain / 3); + else + gain = ((gain % 6) << 5) | (60 / 3); + } + + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), + SND_BBFPRO_USBREQ_GAIN, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + gain, channel, NULL, 0); +} + +static int snd_bbfpro_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value = kcontrol->private_value & SND_BBFPRO_GAIN_VAL_MASK; + + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int snd_bbfpro_gain_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int pv, channel; + + pv = kcontrol->private_value; + channel = (pv >> SND_BBFPRO_GAIN_CHANNEL_SHIFT) & + SND_BBFPRO_GAIN_CHANNEL_MASK; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = SND_BBFPRO_GAIN_VAL_MIN; + + if (channel < 2) + uinfo->value.integer.max = SND_BBFPRO_GAIN_VAL_MIC_MAX; + else + uinfo->value.integer.max = SND_BBFPRO_GAIN_VAL_LINE_MAX; + + return 0; +} + +static int snd_bbfpro_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int pv, channel, old_value, value, err; + + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + struct usb_mixer_interface *mixer = list->mixer; + + pv = kcontrol->private_value; + channel = (pv >> SND_BBFPRO_GAIN_CHANNEL_SHIFT) & + SND_BBFPRO_GAIN_CHANNEL_MASK; + old_value = pv & SND_BBFPRO_GAIN_VAL_MASK; + value = ucontrol->value.integer.value[0]; + + if (value < SND_BBFPRO_GAIN_VAL_MIN) + return -EINVAL; + + if (channel < 2) { + if (value > SND_BBFPRO_GAIN_VAL_MIC_MAX) + return -EINVAL; + } else { + if (value > SND_BBFPRO_GAIN_VAL_LINE_MAX) + return -EINVAL; + } + + if (value == old_value) + return 0; + + err = snd_bbfpro_gain_update(mixer, channel, value); + if (err < 0) + return err; + + kcontrol->private_value = + (channel << SND_BBFPRO_GAIN_CHANNEL_SHIFT) | value; + return 1; +} + +static int snd_bbfpro_gain_resume(struct usb_mixer_elem_list *list) +{ + int pv, channel, value; + struct snd_kcontrol *kctl = list->kctl; + + pv = kctl->private_value; + channel = (pv >> SND_BBFPRO_GAIN_CHANNEL_SHIFT) & + SND_BBFPRO_GAIN_CHANNEL_MASK; + value = pv & SND_BBFPRO_GAIN_VAL_MASK; + + return snd_bbfpro_gain_update(list->mixer, channel, value); +} + +static int snd_bbfpro_vol_update(struct usb_mixer_interface *mixer, u16 index, + u32 value) +{ + struct snd_usb_audio *chip = mixer->chip; + u16 idx; + u16 usb_idx, usb_val; + u32 v; + + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + + idx = index & SND_BBFPRO_MIXER_IDX_MASK; + // 18 bit linear volume, split so 2 bits end up in index. + v = value & SND_BBFPRO_MIXER_VAL_MASK; + usb_idx = idx | (v & 0x3) << 14; + usb_val = (v >> 2) & 0xffff; + + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), + SND_BBFPRO_USBREQ_MIXER, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + usb_val, usb_idx, NULL, 0); +} + +static int snd_bbfpro_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + kcontrol->private_value >> SND_BBFPRO_MIXER_VAL_SHIFT; + return 0; +} + +static int snd_bbfpro_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = SND_BBFPRO_MIXER_VAL_MIN; + uinfo->value.integer.max = SND_BBFPRO_MIXER_VAL_MAX; + return 0; +} + +static int snd_bbfpro_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int err; + u16 idx; + u32 new_val, old_value, uvalue; + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + struct usb_mixer_interface *mixer = list->mixer; + + uvalue = ucontrol->value.integer.value[0]; + idx = kcontrol->private_value & SND_BBFPRO_MIXER_IDX_MASK; + old_value = kcontrol->private_value >> SND_BBFPRO_MIXER_VAL_SHIFT; + + if (uvalue > SND_BBFPRO_MIXER_VAL_MAX) + return -EINVAL; + + if (uvalue == old_value) + return 0; + + new_val = uvalue & SND_BBFPRO_MIXER_VAL_MASK; + + kcontrol->private_value = idx + | (new_val << SND_BBFPRO_MIXER_VAL_SHIFT); + + err = snd_bbfpro_vol_update(mixer, idx, new_val); + return err < 0 ? err : 1; +} + +static int snd_bbfpro_vol_resume(struct usb_mixer_elem_list *list) +{ + int pv = list->kctl->private_value; + u16 idx = pv & SND_BBFPRO_MIXER_IDX_MASK; + u32 val = (pv >> SND_BBFPRO_MIXER_VAL_SHIFT) + & SND_BBFPRO_MIXER_VAL_MASK; + return snd_bbfpro_vol_update(list->mixer, idx, val); +} + +// Predfine elements +static const struct snd_kcontrol_new snd_bbfpro_ctl_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .index = 0, + .info = snd_bbfpro_ctl_info, + .get = snd_bbfpro_ctl_get, + .put = snd_bbfpro_ctl_put +}; + +static const struct snd_kcontrol_new snd_bbfpro_gain_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .index = 0, + .info = snd_bbfpro_gain_info, + .get = snd_bbfpro_gain_get, + .put = snd_bbfpro_gain_put +}; + +static const struct snd_kcontrol_new snd_bbfpro_vol_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .index = 0, + .info = snd_bbfpro_vol_info, + .get = snd_bbfpro_vol_get, + .put = snd_bbfpro_vol_put +}; + +static int snd_bbfpro_ctl_add(struct usb_mixer_interface *mixer, u8 reg, + u8 index, char *name) +{ + struct snd_kcontrol_new knew = snd_bbfpro_ctl_control; + + knew.name = name; + knew.private_value = (reg & SND_BBFPRO_CTL_REG_MASK) + | ((index & SND_BBFPRO_CTL_IDX_MASK) + << SND_BBFPRO_CTL_IDX_SHIFT); + + return add_single_ctl_with_resume(mixer, 0, snd_bbfpro_ctl_resume, + &knew, NULL); +} + +static int snd_bbfpro_gain_add(struct usb_mixer_interface *mixer, u8 channel, + char *name) +{ + struct snd_kcontrol_new knew = snd_bbfpro_gain_control; + + knew.name = name; + knew.private_value = channel << SND_BBFPRO_GAIN_CHANNEL_SHIFT; + + return add_single_ctl_with_resume(mixer, 0, snd_bbfpro_gain_resume, + &knew, NULL); +} + +static int snd_bbfpro_vol_add(struct usb_mixer_interface *mixer, u16 index, + char *name) +{ + struct snd_kcontrol_new knew = snd_bbfpro_vol_control; + + knew.name = name; + knew.private_value = index & SND_BBFPRO_MIXER_IDX_MASK; + + return add_single_ctl_with_resume(mixer, 0, snd_bbfpro_vol_resume, + &knew, NULL); +} + +static int snd_bbfpro_controls_create(struct usb_mixer_interface *mixer) +{ + int err, i, o; + char name[48]; + + static const char * const input[] = { + "AN1", "AN2", "IN3", "IN4", "AS1", "AS2", "ADAT3", + "ADAT4", "ADAT5", "ADAT6", "ADAT7", "ADAT8"}; + + static const char * const output[] = { + "AN1", "AN2", "PH3", "PH4", "AS1", "AS2", "ADAT3", "ADAT4", + "ADAT5", "ADAT6", "ADAT7", "ADAT8"}; + + for (o = 0 ; o < 12 ; ++o) { + for (i = 0 ; i < 12 ; ++i) { + // Line routing + snprintf(name, sizeof(name), + "%s-%s-%s Playback Volume", + (i < 2 ? "Mic" : "Line"), + input[i], output[o]); + err = snd_bbfpro_vol_add(mixer, (26 * o + i), name); + if (err < 0) + return err; + + // PCM routing... yes, it is output remapping + snprintf(name, sizeof(name), + "PCM-%s-%s Playback Volume", + output[i], output[o]); + err = snd_bbfpro_vol_add(mixer, (26 * o + 12 + i), + name); + if (err < 0) + return err; + } + } + + // Main out volume + for (i = 0 ; i < 12 ; ++i) { + snprintf(name, sizeof(name), "Main-Out %s", output[i]); + // Main outs are offset to 992 + err = snd_bbfpro_vol_add(mixer, + i + SND_BBFPRO_MIXER_MAIN_OUT_CH_OFFSET, + name); + if (err < 0) + return err; + } + + // Input gain + for (i = 0 ; i < 4 ; ++i) { + if (i < 2) + snprintf(name, sizeof(name), "Mic-%s Gain", input[i]); + else + snprintf(name, sizeof(name), "Line-%s Gain", input[i]); + + err = snd_bbfpro_gain_add(mixer, i, name); + if (err < 0) + return err; + } + + // Control Reg 1 + err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG1, + SND_BBFPRO_CTL_REG1_CLK_OPTICAL, + "Sample Clock Source"); + if (err < 0) + return err; + + err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG1, + SND_BBFPRO_CTL_REG1_SPDIF_PRO, + "IEC958 Pro Mask"); + if (err < 0) + return err; + + err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG1, + SND_BBFPRO_CTL_REG1_SPDIF_EMPH, + "IEC958 Emphasis"); + if (err < 0) + return err; + + err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG1, + SND_BBFPRO_CTL_REG1_SPDIF_OPTICAL, + "IEC958 Switch"); + if (err < 0) + return err; + + // Control Reg 2 + err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG2, + SND_BBFPRO_CTL_REG2_48V_AN1, + "Mic-AN1 48V"); + if (err < 0) + return err; + + err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG2, + SND_BBFPRO_CTL_REG2_48V_AN2, + "Mic-AN2 48V"); + if (err < 0) + return err; + + err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG2, + SND_BBFPRO_CTL_REG2_SENS_IN3, + "Line-IN3 Sens."); + if (err < 0) + return err; + + err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG2, + SND_BBFPRO_CTL_REG2_SENS_IN4, + "Line-IN4 Sens."); + if (err < 0) + return err; + + err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG2, + SND_BBFPRO_CTL_REG2_PAD_AN1, + "Mic-AN1 PAD"); + if (err < 0) + return err; + + err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG2, + SND_BBFPRO_CTL_REG2_PAD_AN2, + "Mic-AN2 PAD"); + if (err < 0) + return err; + + return 0; +} + +/* + * RME Digiface USB + */ + +#define RME_DIGIFACE_READ_STATUS 17 +#define RME_DIGIFACE_STATUS_REG0L 0 +#define RME_DIGIFACE_STATUS_REG0H 1 +#define RME_DIGIFACE_STATUS_REG1L 2 +#define RME_DIGIFACE_STATUS_REG1H 3 +#define RME_DIGIFACE_STATUS_REG2L 4 +#define RME_DIGIFACE_STATUS_REG2H 5 +#define RME_DIGIFACE_STATUS_REG3L 6 +#define RME_DIGIFACE_STATUS_REG3H 7 + +#define RME_DIGIFACE_CTL_REG1 16 +#define RME_DIGIFACE_CTL_REG2 18 + +/* Reg is overloaded, 0-7 for status halfwords or 16 or 18 for control registers */ +#define RME_DIGIFACE_REGISTER(reg, mask) (((reg) << 16) | (mask)) +#define RME_DIGIFACE_INVERT BIT(31) + +static int snd_rme_digiface_write_reg(struct snd_kcontrol *kcontrol, int item, u16 mask, u16 val) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + struct snd_usb_audio *chip = list->mixer->chip; + struct usb_device *dev = chip->dev; + int err; + + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + item, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + val, mask, NULL, 0); + if (err < 0) + dev_err(&dev->dev, + "unable to issue control set request %d (ret = %d)", + item, err); + return err; +} + +static int snd_rme_digiface_read_status(struct snd_kcontrol *kcontrol, u32 status[4]) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + struct snd_usb_audio *chip = list->mixer->chip; + struct usb_device *dev = chip->dev; + __le32 buf[4]; + int err; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + RME_DIGIFACE_READ_STATUS, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, 0, + buf, sizeof(buf)); + if (err < 0) { + dev_err(&dev->dev, + "unable to issue status read request (ret = %d)", + err); + } else { + for (int i = 0; i < ARRAY_SIZE(buf); i++) + status[i] = le32_to_cpu(buf[i]); + } + return err; +} + +static int snd_rme_digiface_get_status_val(struct snd_kcontrol *kcontrol) +{ + int err; + u32 status[4]; + bool invert = kcontrol->private_value & RME_DIGIFACE_INVERT; + u8 reg = (kcontrol->private_value >> 16) & 0xff; + u16 mask = kcontrol->private_value & 0xffff; + u16 val; + + err = snd_rme_digiface_read_status(kcontrol, status); + if (err < 0) + return err; + + switch (reg) { + /* Status register halfwords */ + case RME_DIGIFACE_STATUS_REG0L ... RME_DIGIFACE_STATUS_REG3H: + break; + case RME_DIGIFACE_CTL_REG1: /* Control register 1, present in halfword 3L */ + reg = RME_DIGIFACE_STATUS_REG3L; + break; + case RME_DIGIFACE_CTL_REG2: /* Control register 2, present in halfword 3H */ + reg = RME_DIGIFACE_STATUS_REG3H; + break; + default: + return -EINVAL; + } + + if (reg & 1) + val = status[reg >> 1] >> 16; + else + val = status[reg >> 1] & 0xffff; + + if (invert) + val ^= mask; + + return field_get(mask, val); +} + +static int snd_rme_digiface_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int freq = snd_rme_digiface_get_status_val(kcontrol); + + if (freq < 0) + return freq; + if (freq >= ARRAY_SIZE(snd_rme_rate_table)) + return -EIO; + + ucontrol->value.integer.value[0] = snd_rme_rate_table[freq]; + return 0; +} + +static int snd_rme_digiface_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int val = snd_rme_digiface_get_status_val(kcontrol); + + if (val < 0) + return val; + + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +static int snd_rme_digiface_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + bool invert = kcontrol->private_value & RME_DIGIFACE_INVERT; + u8 reg = (kcontrol->private_value >> 16) & 0xff; + u16 mask = kcontrol->private_value & 0xffff; + u16 val = field_prep(mask, ucontrol->value.enumerated.item[0]); + + if (invert) + val ^= mask; + + return snd_rme_digiface_write_reg(kcontrol, reg, mask, val); +} + +static int snd_rme_digiface_current_sync_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = snd_rme_digiface_enum_get(kcontrol, ucontrol); + + /* 7 means internal for current sync */ + if (ucontrol->value.enumerated.item[0] == 7) + ucontrol->value.enumerated.item[0] = 0; + + return ret; +} + +static int snd_rme_digiface_sync_state_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 status[4]; + int err; + bool valid, sync; + + err = snd_rme_digiface_read_status(kcontrol, status); + if (err < 0) + return err; + + valid = status[0] & BIT(kcontrol->private_value); + sync = status[0] & BIT(5 + kcontrol->private_value); + + if (!valid) + ucontrol->value.enumerated.item[0] = SND_RME_CLOCK_NOLOCK; + else if (!sync) + ucontrol->value.enumerated.item[0] = SND_RME_CLOCK_LOCK; + else + ucontrol->value.enumerated.item[0] = SND_RME_CLOCK_SYNC; + return 0; +} + +static int snd_rme_digiface_format_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const format[] = { + "ADAT", "S/PDIF" + }; + + return snd_ctl_enum_info(uinfo, 1, + ARRAY_SIZE(format), format); +} + +static int snd_rme_digiface_sync_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const sync_sources[] = { + "Internal", "Input 1", "Input 2", "Input 3", "Input 4" + }; + + return snd_ctl_enum_info(uinfo, 1, + ARRAY_SIZE(sync_sources), sync_sources); +} + +static int snd_rme_digiface_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 200000; + uinfo->value.integer.step = 0; + return 0; +} + +static const struct snd_kcontrol_new snd_rme_digiface_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 1 Sync", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_sync_state_info, + .get = snd_rme_digiface_sync_state_get, + .private_value = 0, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 1 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG0H, BIT(0)) | + RME_DIGIFACE_INVERT, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 1 Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_rate_info, + .get = snd_rme_digiface_rate_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG1L, GENMASK(3, 0)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 2 Sync", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_sync_state_info, + .get = snd_rme_digiface_sync_state_get, + .private_value = 1, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 2 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG0L, BIT(13)) | + RME_DIGIFACE_INVERT, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 2 Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_rate_info, + .get = snd_rme_digiface_rate_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG1L, GENMASK(7, 4)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 3 Sync", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_sync_state_info, + .get = snd_rme_digiface_sync_state_get, + .private_value = 2, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 3 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG0L, BIT(14)) | + RME_DIGIFACE_INVERT, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 3 Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_rate_info, + .get = snd_rme_digiface_rate_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG1L, GENMASK(11, 8)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 4 Sync", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_sync_state_info, + .get = snd_rme_digiface_sync_state_get, + .private_value = 3, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 4 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG0L, GENMASK(15, 12)) | + RME_DIGIFACE_INVERT, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 4 Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_rate_info, + .get = snd_rme_digiface_rate_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG1L, GENMASK(3, 0)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Output 1 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .put = snd_rme_digiface_enum_put, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_CTL_REG2, BIT(0)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Output 2 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .put = snd_rme_digiface_enum_put, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_CTL_REG2, BIT(1)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Output 3 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .put = snd_rme_digiface_enum_put, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_CTL_REG2, BIT(3)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Output 4 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .put = snd_rme_digiface_enum_put, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_CTL_REG2, BIT(4)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Sync Source", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_rme_digiface_sync_source_info, + .get = snd_rme_digiface_enum_get, + .put = snd_rme_digiface_enum_put, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_CTL_REG1, GENMASK(2, 0)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Current Sync Source", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_sync_source_info, + .get = snd_rme_digiface_current_sync_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG0L, GENMASK(12, 10)), + }, + { + /* + * This is writeable, but it is only set by the PCM rate. + * Mixer apps currently need to drive the mixer using raw USB requests, + * so they can also change this that way to configure the rate for + * stand-alone operation when the PCM is closed. + */ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "System Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_rate_info, + .get = snd_rme_digiface_rate_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_CTL_REG1, GENMASK(6, 3)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Current Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_rate_info, + .get = snd_rme_digiface_rate_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG1H, GENMASK(7, 4)), + } +}; + +static int snd_rme_digiface_controls_create(struct usb_mixer_interface *mixer) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(snd_rme_digiface_controls); ++i) { + err = add_single_ctl_with_resume(mixer, 0, + NULL, + &snd_rme_digiface_controls[i], + NULL); + if (err < 0) + return err; + } + + return 0; +} + +/* + * Pioneer DJ / AlphaTheta DJM Mixers + * + * These devices generally have options for soft-switching the playback and + * capture sources in addition to the recording level. Although different + * devices have different configurations, there seems to be canonical values + * for specific capture/playback types: See the definitions of these below. + * + * The wValue is masked with the stereo channel number. e.g. Setting Ch2 to + * capture phono would be 0x0203. Capture, playback and capture level have + * different wIndexes. + */ + +// Capture types +#define SND_DJM_CAP_LINE 0x00 +#define SND_DJM_CAP_CDLINE 0x01 +#define SND_DJM_CAP_DIGITAL 0x02 +#define SND_DJM_CAP_PHONO 0x03 +#define SND_DJM_CAP_PREFADER 0x05 +#define SND_DJM_CAP_PFADER 0x06 +#define SND_DJM_CAP_XFADERA 0x07 +#define SND_DJM_CAP_XFADERB 0x08 +#define SND_DJM_CAP_MIC 0x09 +#define SND_DJM_CAP_AUX 0x0d +#define SND_DJM_CAP_RECOUT 0x0a +#define SND_DJM_CAP_RECOUT_NOMIC 0x0e +#define SND_DJM_CAP_NONE 0x0f +#define SND_DJM_CAP_FXSEND 0x10 +#define SND_DJM_CAP_CH1PFADER 0x11 +#define SND_DJM_CAP_CH2PFADER 0x12 +#define SND_DJM_CAP_CH3PFADER 0x13 +#define SND_DJM_CAP_CH4PFADER 0x14 +#define SND_DJM_CAP_EXT1SEND 0x21 +#define SND_DJM_CAP_EXT2SEND 0x22 +#define SND_DJM_CAP_CH1PREFADER 0x31 +#define SND_DJM_CAP_CH2PREFADER 0x32 +#define SND_DJM_CAP_CH3PREFADER 0x33 +#define SND_DJM_CAP_CH4PREFADER 0x34 + +// Playback types +#define SND_DJM_PB_CH1 0x00 +#define SND_DJM_PB_CH2 0x01 +#define SND_DJM_PB_AUX 0x04 + +#define SND_DJM_WINDEX_CAP 0x8002 +#define SND_DJM_WINDEX_CAPLVL 0x8003 +#define SND_DJM_WINDEX_PB 0x8016 + +// kcontrol->private_value layout +#define SND_DJM_VALUE_MASK 0x0000ffff +#define SND_DJM_GROUP_MASK 0x00ff0000 +#define SND_DJM_DEVICE_MASK 0xff000000 +#define SND_DJM_GROUP_SHIFT 16 +#define SND_DJM_DEVICE_SHIFT 24 + +// device table index +// used for the snd_djm_devices table, so please update accordingly +#define SND_DJM_250MK2_IDX 0x0 +#define SND_DJM_750_IDX 0x1 +#define SND_DJM_850_IDX 0x2 +#define SND_DJM_900NXS2_IDX 0x3 +#define SND_DJM_750MK2_IDX 0x4 +#define SND_DJM_450_IDX 0x5 +#define SND_DJM_A9_IDX 0x6 +#define SND_DJM_V10_IDX 0x7 + +#define SND_DJM_CTL(_name, suffix, _default_value, _windex) { \ + .name = _name, \ + .options = snd_djm_opts_##suffix, \ + .noptions = ARRAY_SIZE(snd_djm_opts_##suffix), \ + .default_value = _default_value, \ + .wIndex = _windex } + +#define SND_DJM_DEVICE(suffix) { \ + .controls = snd_djm_ctls_##suffix, \ + .ncontrols = ARRAY_SIZE(snd_djm_ctls_##suffix) } + +struct snd_djm_device { + const char *name; + const struct snd_djm_ctl *controls; + size_t ncontrols; +}; + +struct snd_djm_ctl { + const char *name; + const u16 *options; + size_t noptions; + u16 default_value; + u16 wIndex; +}; + +static const char *snd_djm_get_label_caplevel_common(u16 wvalue) +{ + switch (wvalue) { + case 0x0000: return "-19dB"; + case 0x0100: return "-15dB"; + case 0x0200: return "-10dB"; + case 0x0300: return "-5dB"; + default: return NULL; + } +}; + +// Models like DJM-A9 or DJM-V10 have different capture levels than others +static const char *snd_djm_get_label_caplevel_high(u16 wvalue) +{ + switch (wvalue) { + case 0x0000: return "+15dB"; + case 0x0100: return "+12dB"; + case 0x0200: return "+9dB"; + case 0x0300: return "+6dB"; + case 0x0400: return "+3dB"; + case 0x0500: return "0dB"; + default: return NULL; + } +}; + +static const char *snd_djm_get_label_cap_common(u16 wvalue) +{ + switch (wvalue & 0x00ff) { + case SND_DJM_CAP_LINE: return "Control Tone LINE"; + case SND_DJM_CAP_CDLINE: return "Control Tone CD/LINE"; + case SND_DJM_CAP_DIGITAL: return "Control Tone DIGITAL"; + case SND_DJM_CAP_PHONO: return "Control Tone PHONO"; + case SND_DJM_CAP_PFADER: return "Post Fader"; + case SND_DJM_CAP_XFADERA: return "Cross Fader A"; + case SND_DJM_CAP_XFADERB: return "Cross Fader B"; + case SND_DJM_CAP_MIC: return "Mic"; + case SND_DJM_CAP_RECOUT: return "Rec Out"; + case SND_DJM_CAP_RECOUT_NOMIC: return "Rec Out without Mic"; + case SND_DJM_CAP_AUX: return "Aux"; + case SND_DJM_CAP_NONE: return "None"; + case SND_DJM_CAP_FXSEND: return "FX SEND"; + case SND_DJM_CAP_CH1PREFADER: return "Pre Fader Ch1"; + case SND_DJM_CAP_CH2PREFADER: return "Pre Fader Ch2"; + case SND_DJM_CAP_CH3PREFADER: return "Pre Fader Ch3"; + case SND_DJM_CAP_CH4PREFADER: return "Pre Fader Ch4"; + case SND_DJM_CAP_CH1PFADER: return "Post Fader Ch1"; + case SND_DJM_CAP_CH2PFADER: return "Post Fader Ch2"; + case SND_DJM_CAP_CH3PFADER: return "Post Fader Ch3"; + case SND_DJM_CAP_CH4PFADER: return "Post Fader Ch4"; + case SND_DJM_CAP_EXT1SEND: return "EXT1 SEND"; + case SND_DJM_CAP_EXT2SEND: return "EXT2 SEND"; + default: return NULL; + } +}; + +// The DJM-850 has different values for CD/LINE and LINE capture +// control options than the other DJM declared in this file. +static const char *snd_djm_get_label_cap_850(u16 wvalue) +{ + switch (wvalue & 0x00ff) { + case 0x00: return "Control Tone CD/LINE"; + case 0x01: return "Control Tone LINE"; + default: return snd_djm_get_label_cap_common(wvalue); + } +}; + +static const char *snd_djm_get_label_caplevel(u8 device_idx, u16 wvalue) +{ + switch (device_idx) { + case SND_DJM_A9_IDX: return snd_djm_get_label_caplevel_high(wvalue); + case SND_DJM_V10_IDX: return snd_djm_get_label_caplevel_high(wvalue); + default: return snd_djm_get_label_caplevel_common(wvalue); + } +}; + +static const char *snd_djm_get_label_cap(u8 device_idx, u16 wvalue) +{ + switch (device_idx) { + case SND_DJM_850_IDX: return snd_djm_get_label_cap_850(wvalue); + default: return snd_djm_get_label_cap_common(wvalue); + } +}; + +static const char *snd_djm_get_label_pb(u16 wvalue) +{ + switch (wvalue & 0x00ff) { + case SND_DJM_PB_CH1: return "Ch1"; + case SND_DJM_PB_CH2: return "Ch2"; + case SND_DJM_PB_AUX: return "Aux"; + default: return NULL; + } +}; + +static const char *snd_djm_get_label(u8 device_idx, u16 wvalue, u16 windex) +{ + switch (windex) { + case SND_DJM_WINDEX_CAPLVL: return snd_djm_get_label_caplevel(device_idx, wvalue); + case SND_DJM_WINDEX_CAP: return snd_djm_get_label_cap(device_idx, wvalue); + case SND_DJM_WINDEX_PB: return snd_djm_get_label_pb(wvalue); + default: return NULL; + } +}; + +// common DJM capture level option values +static const u16 snd_djm_opts_cap_level[] = { + 0x0000, 0x0100, 0x0200, 0x0300 }; + +// DJM-250MK2 +static const u16 snd_djm_opts_250mk2_cap1[] = { + 0x0103, 0x0100, 0x0106, 0x0107, 0x0108, 0x0109, 0x010d, 0x010a }; + +static const u16 snd_djm_opts_250mk2_cap2[] = { + 0x0203, 0x0200, 0x0206, 0x0207, 0x0208, 0x0209, 0x020d, 0x020a }; + +static const u16 snd_djm_opts_250mk2_cap3[] = { + 0x030a, 0x0311, 0x0312, 0x0307, 0x0308, 0x0309, 0x030d }; + +static const u16 snd_djm_opts_250mk2_pb1[] = { 0x0100, 0x0101, 0x0104 }; +static const u16 snd_djm_opts_250mk2_pb2[] = { 0x0200, 0x0201, 0x0204 }; +static const u16 snd_djm_opts_250mk2_pb3[] = { 0x0300, 0x0301, 0x0304 }; + +static const struct snd_djm_ctl snd_djm_ctls_250mk2[] = { + SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Input 1 Capture Switch", 250mk2_cap1, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", 250mk2_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", 250mk2_cap3, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Output 1 Playback Switch", 250mk2_pb1, 0, SND_DJM_WINDEX_PB), + SND_DJM_CTL("Output 2 Playback Switch", 250mk2_pb2, 1, SND_DJM_WINDEX_PB), + SND_DJM_CTL("Output 3 Playback Switch", 250mk2_pb3, 2, SND_DJM_WINDEX_PB) +}; + +// DJM-450 +static const u16 snd_djm_opts_450_cap1[] = { + 0x0103, 0x0100, 0x0106, 0x0107, 0x0108, 0x0109, 0x010d, 0x010a }; + +static const u16 snd_djm_opts_450_cap2[] = { + 0x0203, 0x0200, 0x0206, 0x0207, 0x0208, 0x0209, 0x020d, 0x020a }; + +static const u16 snd_djm_opts_450_cap3[] = { + 0x030a, 0x0311, 0x0312, 0x0307, 0x0308, 0x0309, 0x030d }; + +static const u16 snd_djm_opts_450_pb1[] = { 0x0100, 0x0101, 0x0104 }; +static const u16 snd_djm_opts_450_pb2[] = { 0x0200, 0x0201, 0x0204 }; +static const u16 snd_djm_opts_450_pb3[] = { 0x0300, 0x0301, 0x0304 }; + +static const struct snd_djm_ctl snd_djm_ctls_450[] = { + SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Input 1 Capture Switch", 450_cap1, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", 450_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", 450_cap3, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Output 1 Playback Switch", 450_pb1, 0, SND_DJM_WINDEX_PB), + SND_DJM_CTL("Output 2 Playback Switch", 450_pb2, 1, SND_DJM_WINDEX_PB), + SND_DJM_CTL("Output 3 Playback Switch", 450_pb3, 2, SND_DJM_WINDEX_PB) +}; + +// DJM-750 +static const u16 snd_djm_opts_750_cap1[] = { + 0x0101, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010a, 0x010f }; +static const u16 snd_djm_opts_750_cap2[] = { + 0x0200, 0x0201, 0x0206, 0x0207, 0x0208, 0x0209, 0x020a, 0x020f }; +static const u16 snd_djm_opts_750_cap3[] = { + 0x0300, 0x0301, 0x0306, 0x0307, 0x0308, 0x0309, 0x030a, 0x030f }; +static const u16 snd_djm_opts_750_cap4[] = { + 0x0401, 0x0403, 0x0406, 0x0407, 0x0408, 0x0409, 0x040a, 0x040f }; + +static const struct snd_djm_ctl snd_djm_ctls_750[] = { + SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Input 1 Capture Switch", 750_cap1, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", 750_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", 750_cap3, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 4 Capture Switch", 750_cap4, 0, SND_DJM_WINDEX_CAP) +}; + +// DJM-850 +static const u16 snd_djm_opts_850_cap1[] = { + 0x0100, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010a, 0x010f }; +static const u16 snd_djm_opts_850_cap2[] = { + 0x0200, 0x0201, 0x0206, 0x0207, 0x0208, 0x0209, 0x020a, 0x020f }; +static const u16 snd_djm_opts_850_cap3[] = { + 0x0300, 0x0301, 0x0306, 0x0307, 0x0308, 0x0309, 0x030a, 0x030f }; +static const u16 snd_djm_opts_850_cap4[] = { + 0x0400, 0x0403, 0x0406, 0x0407, 0x0408, 0x0409, 0x040a, 0x040f }; + +static const struct snd_djm_ctl snd_djm_ctls_850[] = { + SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Input 1 Capture Switch", 850_cap1, 1, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", 850_cap2, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", 850_cap3, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 4 Capture Switch", 850_cap4, 1, SND_DJM_WINDEX_CAP) +}; + +// DJM-900NXS2 +static const u16 snd_djm_opts_900nxs2_cap1[] = { + 0x0100, 0x0102, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010a }; +static const u16 snd_djm_opts_900nxs2_cap2[] = { + 0x0200, 0x0202, 0x0203, 0x0206, 0x0207, 0x0208, 0x0209, 0x020a }; +static const u16 snd_djm_opts_900nxs2_cap3[] = { + 0x0300, 0x0302, 0x0303, 0x0306, 0x0307, 0x0308, 0x0309, 0x030a }; +static const u16 snd_djm_opts_900nxs2_cap4[] = { + 0x0400, 0x0402, 0x0403, 0x0406, 0x0407, 0x0408, 0x0409, 0x040a }; +static const u16 snd_djm_opts_900nxs2_cap5[] = { + 0x0507, 0x0508, 0x0509, 0x050a, 0x0511, 0x0512, 0x0513, 0x0514 }; + +static const struct snd_djm_ctl snd_djm_ctls_900nxs2[] = { + SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Input 1 Capture Switch", 900nxs2_cap1, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", 900nxs2_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", 900nxs2_cap3, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 4 Capture Switch", 900nxs2_cap4, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 5 Capture Switch", 900nxs2_cap5, 3, SND_DJM_WINDEX_CAP) +}; + +// DJM-750MK2 +static const u16 snd_djm_opts_750mk2_cap1[] = { + 0x0100, 0x0102, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010a }; +static const u16 snd_djm_opts_750mk2_cap2[] = { + 0x0200, 0x0202, 0x0203, 0x0206, 0x0207, 0x0208, 0x0209, 0x020a }; +static const u16 snd_djm_opts_750mk2_cap3[] = { + 0x0300, 0x0302, 0x0303, 0x0306, 0x0307, 0x0308, 0x0309, 0x030a }; +static const u16 snd_djm_opts_750mk2_cap4[] = { + 0x0400, 0x0402, 0x0403, 0x0406, 0x0407, 0x0408, 0x0409, 0x040a }; +static const u16 snd_djm_opts_750mk2_cap5[] = { + 0x0507, 0x0508, 0x0509, 0x050a, 0x0511, 0x0512, 0x0513, 0x0514 }; + +static const u16 snd_djm_opts_750mk2_pb1[] = { 0x0100, 0x0101, 0x0104 }; +static const u16 snd_djm_opts_750mk2_pb2[] = { 0x0200, 0x0201, 0x0204 }; +static const u16 snd_djm_opts_750mk2_pb3[] = { 0x0300, 0x0301, 0x0304 }; + +static const struct snd_djm_ctl snd_djm_ctls_750mk2[] = { + SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Input 1 Capture Switch", 750mk2_cap1, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", 750mk2_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", 750mk2_cap3, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 4 Capture Switch", 750mk2_cap4, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 5 Capture Switch", 750mk2_cap5, 3, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Output 1 Playback Switch", 750mk2_pb1, 0, SND_DJM_WINDEX_PB), + SND_DJM_CTL("Output 2 Playback Switch", 750mk2_pb2, 1, SND_DJM_WINDEX_PB), + SND_DJM_CTL("Output 3 Playback Switch", 750mk2_pb3, 2, SND_DJM_WINDEX_PB) +}; + +// DJM-A9 +static const u16 snd_djm_opts_a9_cap_level[] = { + 0x0000, 0x0100, 0x0200, 0x0300, 0x0400, 0x0500 }; +static const u16 snd_djm_opts_a9_cap1[] = { + 0x0107, 0x0108, 0x0109, 0x010a, 0x010e, + 0x111, 0x112, 0x113, 0x114, 0x0131, 0x132, 0x133, 0x134 }; +static const u16 snd_djm_opts_a9_cap2[] = { + 0x0201, 0x0202, 0x0203, 0x0205, 0x0206, 0x0207, 0x0208, 0x0209, 0x020a, 0x020e }; +static const u16 snd_djm_opts_a9_cap3[] = { + 0x0301, 0x0302, 0x0303, 0x0305, 0x0306, 0x0307, 0x0308, 0x0309, 0x030a, 0x030e }; +static const u16 snd_djm_opts_a9_cap4[] = { + 0x0401, 0x0402, 0x0403, 0x0405, 0x0406, 0x0407, 0x0408, 0x0409, 0x040a, 0x040e }; +static const u16 snd_djm_opts_a9_cap5[] = { + 0x0501, 0x0502, 0x0503, 0x0505, 0x0506, 0x0507, 0x0508, 0x0509, 0x050a, 0x050e }; + +static const struct snd_djm_ctl snd_djm_ctls_a9[] = { + SND_DJM_CTL("Master Input Level Capture Switch", a9_cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Master Input Capture Switch", a9_cap1, 3, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 1 Capture Switch", a9_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", a9_cap3, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", a9_cap4, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 4 Capture Switch", a9_cap5, 2, SND_DJM_WINDEX_CAP) +}; + +// DJM-V10 +static const u16 snd_djm_opts_v10_cap_level[] = { + 0x0000, 0x0100, 0x0200, 0x0300, 0x0400, 0x0500 +}; + +static const u16 snd_djm_opts_v10_cap1[] = { + 0x0103, + 0x0100, 0x0102, 0x0106, 0x0110, 0x0107, + 0x0108, 0x0109, 0x010a, 0x0121, 0x0122 +}; + +static const u16 snd_djm_opts_v10_cap2[] = { + 0x0200, 0x0202, 0x0206, 0x0210, 0x0207, + 0x0208, 0x0209, 0x020a, 0x0221, 0x0222 +}; + +static const u16 snd_djm_opts_v10_cap3[] = { + 0x0303, + 0x0300, 0x0302, 0x0306, 0x0310, 0x0307, + 0x0308, 0x0309, 0x030a, 0x0321, 0x0322 +}; + +static const u16 snd_djm_opts_v10_cap4[] = { + 0x0403, + 0x0400, 0x0402, 0x0406, 0x0410, 0x0407, + 0x0408, 0x0409, 0x040a, 0x0421, 0x0422 +}; + +static const u16 snd_djm_opts_v10_cap5[] = { + 0x0500, 0x0502, 0x0506, 0x0510, 0x0507, + 0x0508, 0x0509, 0x050a, 0x0521, 0x0522 +}; + +static const u16 snd_djm_opts_v10_cap6[] = { + 0x0603, + 0x0600, 0x0602, 0x0606, 0x0610, 0x0607, + 0x0608, 0x0609, 0x060a, 0x0621, 0x0622 +}; + +static const struct snd_djm_ctl snd_djm_ctls_v10[] = { + SND_DJM_CTL("Master Input Level Capture Switch", v10_cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Input 1 Capture Switch", v10_cap1, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", v10_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", v10_cap3, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 4 Capture Switch", v10_cap4, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 5 Capture Switch", v10_cap5, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 6 Capture Switch", v10_cap6, 0, SND_DJM_WINDEX_CAP) + // playback channels are fixed and controlled by hardware knobs on the mixer +}; + +static const struct snd_djm_device snd_djm_devices[] = { + [SND_DJM_250MK2_IDX] = SND_DJM_DEVICE(250mk2), + [SND_DJM_750_IDX] = SND_DJM_DEVICE(750), + [SND_DJM_850_IDX] = SND_DJM_DEVICE(850), + [SND_DJM_900NXS2_IDX] = SND_DJM_DEVICE(900nxs2), + [SND_DJM_750MK2_IDX] = SND_DJM_DEVICE(750mk2), + [SND_DJM_450_IDX] = SND_DJM_DEVICE(450), + [SND_DJM_A9_IDX] = SND_DJM_DEVICE(a9), + [SND_DJM_V10_IDX] = SND_DJM_DEVICE(v10), +}; + +static int snd_djm_controls_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *info) +{ + unsigned long private_value = kctl->private_value; + u8 device_idx = (private_value & SND_DJM_DEVICE_MASK) >> SND_DJM_DEVICE_SHIFT; + u8 ctl_idx = (private_value & SND_DJM_GROUP_MASK) >> SND_DJM_GROUP_SHIFT; + const struct snd_djm_device *device = &snd_djm_devices[device_idx]; + const char *name; + const struct snd_djm_ctl *ctl; + size_t noptions; + + if (ctl_idx >= device->ncontrols) + return -EINVAL; + + ctl = &device->controls[ctl_idx]; + noptions = ctl->noptions; + if (info->value.enumerated.item >= noptions) + info->value.enumerated.item = noptions - 1; + + name = snd_djm_get_label(device_idx, + ctl->options[info->value.enumerated.item], + ctl->wIndex); + if (!name) + return -EINVAL; + + strscpy(info->value.enumerated.name, name, sizeof(info->value.enumerated.name)); + info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + info->count = 1; + info->value.enumerated.items = noptions; + return 0; +} + +static int snd_djm_controls_update(struct usb_mixer_interface *mixer, + u8 device_idx, u8 group, u16 value) +{ + const struct snd_djm_device *device = &snd_djm_devices[device_idx]; + + if (group >= device->ncontrols || value >= device->controls[group].noptions) + return -EINVAL; + + CLASS(snd_usb_lock, pm)(mixer->chip); + if (pm.err) + return pm.err; + + return snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), + USB_REQ_SET_FEATURE, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + device->controls[group].options[value], + device->controls[group].wIndex, + NULL, 0); +} + +static int snd_djm_controls_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *elem) +{ + elem->value.enumerated.item[0] = kctl->private_value & SND_DJM_VALUE_MASK; + return 0; +} + +static int snd_djm_controls_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *elem) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + unsigned long private_value = kctl->private_value; + + u8 device = (private_value & SND_DJM_DEVICE_MASK) >> SND_DJM_DEVICE_SHIFT; + u8 group = (private_value & SND_DJM_GROUP_MASK) >> SND_DJM_GROUP_SHIFT; + u16 value = elem->value.enumerated.item[0]; + + kctl->private_value = (((unsigned long)device << SND_DJM_DEVICE_SHIFT) | + (group << SND_DJM_GROUP_SHIFT) | + value); + + return snd_djm_controls_update(mixer, device, group, value); +} + +static int snd_djm_controls_resume(struct usb_mixer_elem_list *list) +{ + unsigned long private_value = list->kctl->private_value; + u8 device = (private_value & SND_DJM_DEVICE_MASK) >> SND_DJM_DEVICE_SHIFT; + u8 group = (private_value & SND_DJM_GROUP_MASK) >> SND_DJM_GROUP_SHIFT; + u16 value = (private_value & SND_DJM_VALUE_MASK); + + return snd_djm_controls_update(list->mixer, device, group, value); +} + +static int snd_djm_controls_create(struct usb_mixer_interface *mixer, + const u8 device_idx) +{ + int err, i; + u16 value; + + const struct snd_djm_device *device = &snd_djm_devices[device_idx]; + + struct snd_kcontrol_new knew = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .index = 0, + .info = snd_djm_controls_info, + .get = snd_djm_controls_get, + .put = snd_djm_controls_put + }; + + for (i = 0; i < device->ncontrols; i++) { + value = device->controls[i].default_value; + knew.name = device->controls[i].name; + knew.private_value = + ((unsigned long)device_idx << SND_DJM_DEVICE_SHIFT) | + (i << SND_DJM_GROUP_SHIFT) | + value; + err = snd_djm_controls_update(mixer, device_idx, i, value); + if (err) + return err; + err = add_single_ctl_with_resume(mixer, 0, snd_djm_controls_resume, + &knew, NULL); + if (err) + return err; + } + return 0; +} + int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) { int err = 0; - struct snd_info_entry *entry; err = snd_usb_soundblaster_remote_init(mixer); if (err < 0) @@ -2214,9 +4357,8 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) err = snd_audigy2nx_controls_create(mixer); if (err < 0) break; - if (!snd_card_proc_new(mixer->chip->card, "audigy2nx", &entry)) - snd_info_set_text_ops(entry, mixer, - snd_audigy2nx_proc_read); + snd_card_ro_proc_new(mixer->chip->card, "audigy2nx", + mixer, snd_audigy2nx_proc_read); break; /* EMU0204 */ @@ -2224,6 +4366,13 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) err = snd_emu0204_controls_create(mixer); break; +#if IS_REACHABLE(CONFIG_INPUT) + case USB_ID(0x054c, 0x0ce6): /* Sony DualSense controller (PS5) */ + case USB_ID(0x054c, 0x0df2): /* Sony DualSense Edge controller (PS5) */ + err = snd_dualsense_controls_create(mixer); + break; +#endif /* IS_REACHABLE(CONFIG_INPUT) */ + case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */ case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C400 */ err = snd_c400_create_mixer(mixer); @@ -2245,17 +4394,19 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) break; case USB_ID(0x0dba, 0x1000): /* Digidesign Mbox 1 */ - err = snd_mbox1_create_sync_switch(mixer); + err = snd_mbox1_controls_create(mixer); break; case USB_ID(0x17cc, 0x1011): /* Traktor Audio 6 */ - err = snd_nativeinstruments_create_mixer(mixer, + err = snd_nativeinstruments_create_mixer(/* checkpatch hack */ + mixer, snd_nativeinstruments_ta6_mixers, ARRAY_SIZE(snd_nativeinstruments_ta6_mixers)); break; case USB_ID(0x17cc, 0x1021): /* Traktor Audio 10 */ - err = snd_nativeinstruments_create_mixer(mixer, + err = snd_nativeinstruments_create_mixer(/* checkpatch hack */ + mixer, snd_nativeinstruments_ta10_mixers, ARRAY_SIZE(snd_nativeinstruments_ta10_mixers)); break; @@ -2273,24 +4424,99 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) err = snd_scarlett_controls_create(mixer); break; + case USB_ID(0x1235, 0x8203): /* Focusrite Scarlett 6i6 2nd Gen */ + case USB_ID(0x1235, 0x8204): /* Focusrite Scarlett 18i8 2nd Gen */ + case USB_ID(0x1235, 0x8201): /* Focusrite Scarlett 18i20 2nd Gen */ + case USB_ID(0x1235, 0x8211): /* Focusrite Scarlett Solo 3rd Gen */ + case USB_ID(0x1235, 0x8210): /* Focusrite Scarlett 2i2 3rd Gen */ + case USB_ID(0x1235, 0x8212): /* Focusrite Scarlett 4i4 3rd Gen */ + case USB_ID(0x1235, 0x8213): /* Focusrite Scarlett 8i6 3rd Gen */ + case USB_ID(0x1235, 0x8214): /* Focusrite Scarlett 18i8 3rd Gen */ + case USB_ID(0x1235, 0x8215): /* Focusrite Scarlett 18i20 3rd Gen */ + case USB_ID(0x1235, 0x8216): /* Focusrite Vocaster One */ + case USB_ID(0x1235, 0x8217): /* Focusrite Vocaster Two */ + case USB_ID(0x1235, 0x8218): /* Focusrite Scarlett Solo 4th Gen */ + case USB_ID(0x1235, 0x8219): /* Focusrite Scarlett 2i2 4th Gen */ + case USB_ID(0x1235, 0x821a): /* Focusrite Scarlett 4i4 4th Gen */ + case USB_ID(0x1235, 0x8206): /* Focusrite Clarett 2Pre USB */ + case USB_ID(0x1235, 0x8207): /* Focusrite Clarett 4Pre USB */ + case USB_ID(0x1235, 0x8208): /* Focusrite Clarett 8Pre USB */ + case USB_ID(0x1235, 0x820a): /* Focusrite Clarett+ 2Pre */ + case USB_ID(0x1235, 0x820b): /* Focusrite Clarett+ 4Pre */ + case USB_ID(0x1235, 0x820c): /* Focusrite Clarett+ 8Pre */ + err = snd_scarlett2_init(mixer); + break; + + case USB_ID(0x1235, 0x821b): /* Focusrite Scarlett 16i16 4th Gen */ + case USB_ID(0x1235, 0x821c): /* Focusrite Scarlett 18i16 4th Gen */ + case USB_ID(0x1235, 0x821d): /* Focusrite Scarlett 18i20 4th Gen */ + err = snd_fcp_init(mixer); + break; + case USB_ID(0x041e, 0x323b): /* Creative Sound Blaster E1 */ err = snd_soundblaster_e1_switch_create(mixer); break; case USB_ID(0x0bda, 0x4014): /* Dell WD15 dock */ + err = dell_dock_mixer_create(mixer); + if (err < 0) + break; err = dell_dock_mixer_init(mixer); break; + case USB_ID(0x0bda, 0x402e): /* Dell WD19 dock */ + err = dell_dock_mixer_create(mixer); + break; case USB_ID(0x2a39, 0x3fd2): /* RME ADI-2 Pro */ case USB_ID(0x2a39, 0x3fd3): /* RME ADI-2 DAC */ case USB_ID(0x2a39, 0x3fd4): /* RME */ err = snd_rme_controls_create(mixer); break; + + case USB_ID(0x194f, 0x010c): /* Presonus Studio 1810c */ + err = snd_sc1810_init_mixer(mixer); + break; + case USB_ID(0x194f, 0x010d): /* Presonus Studio 1824c */ + err = snd_sc1810_init_mixer(mixer); + break; + case USB_ID(0x2a39, 0x3fb0): /* RME Babyface Pro FS */ + err = snd_bbfpro_controls_create(mixer); + break; + case USB_ID(0x2a39, 0x3f8c): /* RME Digiface USB */ + case USB_ID(0x2a39, 0x3fa0): /* RME Digiface USB (alternate) */ + err = snd_rme_digiface_controls_create(mixer); + break; + case USB_ID(0x2b73, 0x0017): /* Pioneer DJ DJM-250MK2 */ + err = snd_djm_controls_create(mixer, SND_DJM_250MK2_IDX); + break; + case USB_ID(0x2b73, 0x0013): /* Pioneer DJ DJM-450 */ + err = snd_djm_controls_create(mixer, SND_DJM_450_IDX); + break; + case USB_ID(0x08e4, 0x017f): /* Pioneer DJ DJM-750 */ + err = snd_djm_controls_create(mixer, SND_DJM_750_IDX); + break; + case USB_ID(0x2b73, 0x001b): /* Pioneer DJ DJM-750MK2 */ + err = snd_djm_controls_create(mixer, SND_DJM_750MK2_IDX); + break; + case USB_ID(0x08e4, 0x0163): /* Pioneer DJ DJM-850 */ + err = snd_djm_controls_create(mixer, SND_DJM_850_IDX); + break; + case USB_ID(0x2b73, 0x000a): /* Pioneer DJ DJM-900NXS2 */ + err = snd_djm_controls_create(mixer, SND_DJM_900NXS2_IDX); + break; + case USB_ID(0x2b73, 0x003c): /* Pioneer DJ / AlphaTheta DJM-A9 */ + err = snd_djm_controls_create(mixer, SND_DJM_A9_IDX); + break; + case USB_ID(0x2b73, 0x0034): /* Pioneer DJ DJM-V10 */ + err = snd_djm_controls_create(mixer, SND_DJM_V10_IDX); + break; + case USB_ID(0x03f0, 0x0269): /* HP TB Dock G2 */ + err = hp_dock_mixer_create(mixer); + break; } return err; } -#ifdef CONFIG_PM void snd_usb_mixer_resume_quirk(struct usb_mixer_interface *mixer) { switch (mixer->chip->usb_id) { @@ -2299,7 +4525,6 @@ void snd_usb_mixer_resume_quirk(struct usb_mixer_interface *mixer) break; } } -#endif void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer, int unitid) @@ -2334,7 +4559,8 @@ static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, struct snd_kcontrol *kctl) { /* Approximation using 10 ranges based on output measurement on hw v1.2. - * This seems close to the cubic mapping e.g. alsamixer uses. */ + * This seems close to the cubic mapping e.g. alsamixer uses. + */ static const DECLARE_TLV_DB_RANGE(scale, 0, 1, TLV_DB_MINMAX_ITEM(-5300, -4970), 2, 5, TLV_DB_MINMAX_ITEM(-4710, -4160), @@ -2363,6 +4589,52 @@ static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, } } +/* + * Some Plantronics headsets have control names that don't meet ALSA naming + * standards. This function fixes nonstandard source names. By the time + * this function is called the control name should look like one of these: + * "source names Playback Volume" + * "source names Playback Switch" + * "source names Capture Volume" + * "source names Capture Switch" + * If any of the trigger words are found in the name then the name will + * be changed to: + * "Headset Playback Volume" + * "Headset Playback Switch" + * "Headset Capture Volume" + * "Headset Capture Switch" + * depending on the current suffix. + */ +static void snd_fix_plt_name(struct snd_usb_audio *chip, + struct snd_ctl_elem_id *id) +{ + /* no variant of "Sidetone" should be added to this list */ + static const char * const trigger[] = { + "Earphone", "Microphone", "Receive", "Transmit" + }; + static const char * const suffix[] = { + " Playback Volume", " Playback Switch", + " Capture Volume", " Capture Switch" + }; + int i; + + for (i = 0; i < ARRAY_SIZE(trigger); i++) + if (strstr(id->name, trigger[i])) + goto triggered; + usb_audio_dbg(chip, "no change in %s\n", id->name); + return; + +triggered: + for (i = 0; i < ARRAY_SIZE(suffix); i++) + if (strstr(id->name, suffix[i])) { + usb_audio_dbg(chip, "fixing kctl name %s\n", id->name); + snprintf(id->name, sizeof(id->name), "Headset%s", + suffix[i]); + return; + } + usb_audio_dbg(chip, "something wrong in kctl name %s\n", id->name); +} + void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, int unitid, struct snd_kcontrol *kctl) @@ -2372,12 +4644,25 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, if (unitid == 7 && cval->control == UAC_FU_VOLUME) snd_dragonfly_quirk_db_scale(mixer, cval, kctl); break; - /* lowest playback value is muted on C-Media devices */ - case USB_ID(0x0d8c, 0x000c): - case USB_ID(0x0d8c, 0x0014): - if (strstr(kctl->id.name, "Playback")) - cval->min_mute = 1; - break; } -} + /* lowest playback value is muted on some devices */ + if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE) + if (strstr(kctl->id.name, "Playback")) { + usb_audio_info(mixer->chip, + "applying playback min mute quirk\n"); + cval->min_mute = 1; + } + + /* lowest capture value is muted on some devices */ + if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE) + if (strstr(kctl->id.name, "Capture")) { + usb_audio_info(mixer->chip, + "applying capture min mute quirk\n"); + cval->min_mute = 1; + } + /* ALSA-ify some Plantronics headset control names */ + if (USB_ID_VENDOR(mixer->chip->usb_id) == 0x047f && + (cval->control == UAC_FU_MUTE || cval->control == UAC_FU_VOLUME)) + snd_fix_plt_name(mixer->chip, &kctl->id); +} diff --git a/sound/usb/mixer_quirks.h b/sound/usb/mixer_quirks.h index 52be26db558f..4ba01ba3fe8b 100644 --- a/sound/usb/mixer_quirks.h +++ b/sound/usb/mixer_quirks.h @@ -14,9 +14,7 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, int unitid, struct snd_kcontrol *kctl); -#ifdef CONFIG_PM void snd_usb_mixer_resume_quirk(struct usb_mixer_interface *mixer); -#endif #endif /* SND_USB_MIXER_QUIRKS_H */ diff --git a/sound/usb/mixer_s1810c.c b/sound/usb/mixer_s1810c.c new file mode 100644 index 000000000000..6e09e074c0e7 --- /dev/null +++ b/sound/usb/mixer_s1810c.c @@ -0,0 +1,653 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Presonus Studio 1810c driver for ALSA + * Copyright (C) 2019 Nick Kossifidis <mickflemm@gmail.com> + * + * Based on reverse engineering of the communication protocol + * between the windows driver / Univeral Control (UC) program + * and the device, through usbmon. + * + * For now this bypasses the mixer, with all channels split, + * so that the software can mix with greater flexibility. + * It also adds controls for the 4 buttons on the front of + * the device. + */ + +#include <linux/usb.h> +#include <linux/usb/audio-v2.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/control.h> + +#include "usbaudio.h" +#include "mixer.h" +#include "mixer_quirks.h" +#include "helper.h" +#include "mixer_s1810c.h" + +#define SC1810C_CMD_REQ 160 +#define SC1810C_CMD_REQTYPE \ + (USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT) +#define SC1810C_CMD_F1 0x50617269 +#define SC1810C_CMD_F2 0x14 + +/* + * DISCLAIMER: These are just guesses based on the + * dumps I got. + * + * It seems like a selects between + * device (0), mixer (0x64) and output (0x65) + * + * For mixer (0x64): + * * b selects an input channel (see below). + * * c selects an output channel pair (see below). + * * d selects left (0) or right (1) of that pair. + * * e 0-> disconnect, 0x01000000-> connect, + * 0x0109-> used for stereo-linking channels, + * e is also used for setting volume levels + * in which case b is also set so I guess + * this way it is possible to set the volume + * level from the specified input to the + * specified output. + * + * IN Channels: + * 0 - 7 Mic/Inst/Line (Analog inputs) + * 8 - 9 S/PDIF + * 10 - 17 ADAT + * 18 - 35 DAW (Inputs from the host) + * + * OUT Channels (pairs): + * 0 -> Main out + * 1 -> Line1/2 + * 2 -> Line3/4 + * 3 -> S/PDIF + * 4 -> ADAT? + * + * For device (0): + * * b and c are not used, at least not on the + * dumps I got. + * * d sets the control id to be modified + * (see below). + * * e sets the setting for that control. + * (so for the switches I was interested + * in it's 0/1) + * + * For output (0x65): + * * b is the output channel (see above). + * * c is zero. + * * e I guess the same as with mixer except 0x0109 + * which I didn't see in my dumps. + * + * The two fixed fields have the same values for + * mixer and output but a different set for device. + */ +struct s1810c_ctl_packet { + u32 a; + u32 b; + u32 fixed1; + u32 fixed2; + u32 c; + u32 d; + u32 e; +}; + +#define SC1810C_CTL_LINE_SW 0 +#define SC1810C_CTL_MUTE_SW 1 +#define SC1824C_CTL_MONO_SW 2 +#define SC1810C_CTL_AB_SW 3 +#define SC1810C_CTL_48V_SW 4 + +#define SC1810C_SET_STATE_REQ 161 +#define SC1810C_SET_STATE_REQTYPE SC1810C_CMD_REQTYPE +#define SC1810C_SET_STATE_F1 0x64656D73 +#define SC1810C_SET_STATE_F2 0xF4 + +#define SC1810C_GET_STATE_REQ 162 +#define SC1810C_GET_STATE_REQTYPE \ + (USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN) +#define SC1810C_GET_STATE_F1 SC1810C_SET_STATE_F1 +#define SC1810C_GET_STATE_F2 SC1810C_SET_STATE_F2 + +#define SC1810C_STATE_F1_IDX 2 +#define SC1810C_STATE_F2_IDX 3 + +/* + * This packet includes mixer volumes and + * various other fields, it's an extended + * version of ctl_packet, with a and b + * being zero and different f1/f2. + */ +struct s1810c_state_packet { + u32 fields[63]; +}; + +#define SC1810C_STATE_48V_SW 58 +#define SC1810C_STATE_LINE_SW 59 +#define SC1810C_STATE_MUTE_SW 60 +#define SC1824C_STATE_MONO_SW 61 +#define SC1810C_STATE_AB_SW 62 + +struct s1810_mixer_state { + uint16_t seqnum; + struct mutex usb_mutex; + struct mutex data_mutex; +}; + +static int +snd_s1810c_send_ctl_packet(struct usb_device *dev, u32 a, + u32 b, u32 c, u32 d, u32 e) +{ + struct s1810c_ctl_packet pkt = { 0 }; + int ret = 0; + + pkt.fixed1 = SC1810C_CMD_F1; + pkt.fixed2 = SC1810C_CMD_F2; + + pkt.a = a; + pkt.b = b; + pkt.c = c; + pkt.d = d; + pkt.e = e; + + ret = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + SC1810C_CMD_REQ, + SC1810C_CMD_REQTYPE, 0, 0, &pkt, sizeof(pkt)); + if (ret < 0) { + dev_warn(&dev->dev, "could not send ctl packet\n"); + return ret; + } + return 0; +} + +/* + * When opening Universal Control the program periodically + * sends and receives state packets for syncinc state between + * the device and the host. + * + * Note that if we send only the request to get data back we'll + * get an error, we need to first send an empty state packet and + * then ask to receive a filled. Their seqnumbers must also match. + */ +static int +snd_sc1810c_get_status_field(struct usb_device *dev, + u32 *field, int field_idx, uint16_t *seqnum) +{ + struct s1810c_state_packet pkt_out = { { 0 } }; + struct s1810c_state_packet pkt_in = { { 0 } }; + int ret = 0; + + pkt_out.fields[SC1810C_STATE_F1_IDX] = SC1810C_SET_STATE_F1; + pkt_out.fields[SC1810C_STATE_F2_IDX] = SC1810C_SET_STATE_F2; + ret = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + SC1810C_SET_STATE_REQ, + SC1810C_SET_STATE_REQTYPE, + (*seqnum), 0, &pkt_out, sizeof(pkt_out)); + if (ret < 0) { + dev_warn(&dev->dev, "could not send state packet (%d)\n", ret); + return ret; + } + + ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + SC1810C_GET_STATE_REQ, + SC1810C_GET_STATE_REQTYPE, + (*seqnum), 0, &pkt_in, sizeof(pkt_in)); + if (ret < 0) { + dev_warn(&dev->dev, "could not get state field %u (%d)\n", + field_idx, ret); + return ret; + } + + (*field) = pkt_in.fields[field_idx]; + (*seqnum)++; + return 0; +} + +/* + * This is what I got when bypassing the mixer with + * all channels split. I'm not 100% sure of what's going + * on, I could probably clean this up based on my observations + * but I prefer to keep the same behavior as the windows driver. + */ +static int snd_s1810c_init_mixer_maps(struct snd_usb_audio *chip) +{ + u32 a, b, c, e, n, off, left, right; + struct usb_device *dev = chip->dev; + + switch (chip->usb_id) { + case USB_ID(0x194f, 0x010c): /* 1810c */ + /* Set initial volume levels ? */ + a = 0x64; + e = 0xbc; + for (n = 0; n < 2; n++) { + off = n * 18; + for (b = off; b < 18 + off; b++) { + /* This channel to all outputs ? */ + for (c = 0; c <= 8; c++) { + snd_s1810c_send_ctl_packet(dev, a, b, c, 0, e); + snd_s1810c_send_ctl_packet(dev, a, b, c, 1, e); + } + /* This channel to main output (again) */ + snd_s1810c_send_ctl_packet(dev, a, b, 0, 0, e); + snd_s1810c_send_ctl_packet(dev, a, b, 0, 1, e); + } + /* + * I noticed on UC that DAW channels have different + * initial volumes, so this makes sense. + */ + e = 0xb53bf0; + } + + /* Connect analog outputs ? */ + a = 0x65; + e = 0x01000000; + for (b = 1; b < 3; b++) { + snd_s1810c_send_ctl_packet(dev, a, b, 0, 0, e); + snd_s1810c_send_ctl_packet(dev, a, b, 0, 1, e); + } + snd_s1810c_send_ctl_packet(dev, a, 0, 0, 0, e); + snd_s1810c_send_ctl_packet(dev, a, 0, 0, 1, e); + + /* Set initial volume levels for S/PDIF mappings ? */ + a = 0x64; + e = 0xbc; + c = 3; + for (n = 0; n < 2; n++) { + off = n * 18; + for (b = off; b < 18 + off; b++) { + snd_s1810c_send_ctl_packet(dev, a, b, c, 0, e); + snd_s1810c_send_ctl_packet(dev, a, b, c, 1, e); + } + e = 0xb53bf0; + } + + /* Connect S/PDIF output ? */ + a = 0x65; + e = 0x01000000; + snd_s1810c_send_ctl_packet(dev, a, 3, 0, 0, e); + snd_s1810c_send_ctl_packet(dev, a, 3, 0, 1, e); + + /* Connect all outputs (again) ? */ + a = 0x65; + e = 0x01000000; + for (b = 0; b < 4; b++) { + snd_s1810c_send_ctl_packet(dev, a, b, 0, 0, e); + snd_s1810c_send_ctl_packet(dev, a, b, 0, 1, e); + } + + /* Basic routing to get sound out of the device */ + a = 0x64; + e = 0x01000000; + for (c = 0; c < 4; c++) { + for (b = 0; b < 36; b++) { + if ((c == 0 && b == 18) || /* DAW1/2 -> Main */ + (c == 1 && b == 20) || /* DAW3/4 -> Line3/4 */ + (c == 2 && b == 22) || /* DAW4/5 -> Line5/6 */ + (c == 3 && b == 24)) { /* DAW5/6 -> S/PDIF */ + /* Left */ + snd_s1810c_send_ctl_packet(dev, a, b, c, 0, e); + snd_s1810c_send_ctl_packet(dev, a, b, c, 1, 0); + b++; + /* Right */ + snd_s1810c_send_ctl_packet(dev, a, b, c, 0, 0); + snd_s1810c_send_ctl_packet(dev, a, b, c, 1, e); + } else { + /* Leave the rest disconnected */ + snd_s1810c_send_ctl_packet(dev, a, b, c, 0, 0); + snd_s1810c_send_ctl_packet(dev, a, b, c, 1, 0); + } + } + } + + /* Set initial volume levels for S/PDIF (again) ? */ + a = 0x64; + e = 0xbc; + c = 3; + for (n = 0; n < 2; n++) { + off = n * 18; + for (b = off; b < 18 + off; b++) { + snd_s1810c_send_ctl_packet(dev, a, b, c, 0, e); + snd_s1810c_send_ctl_packet(dev, a, b, c, 1, e); + } + e = 0xb53bf0; + } + + /* Connect S/PDIF outputs (again) ? */ + a = 0x65; + e = 0x01000000; + snd_s1810c_send_ctl_packet(dev, a, 3, 0, 0, e); + snd_s1810c_send_ctl_packet(dev, a, 3, 0, 1, e); + + /* Again ? */ + snd_s1810c_send_ctl_packet(dev, a, 3, 0, 0, e); + snd_s1810c_send_ctl_packet(dev, a, 3, 0, 1, e); + break; + + case USB_ID(0x194f, 0x010d): /* 1824c */ + /* Set all output faders to unity gain */ + a = 0x65; + c = 0x00; + e = 0x01000000; + + for (b = 0; b < 9; b++) { + snd_s1810c_send_ctl_packet(dev, a, b, c, 0, e); + snd_s1810c_send_ctl_packet(dev, a, b, c, 1, e); + } + + /* Set + * Daw 1 -> Line out 1 (left), Daw 2 -> Line out 2 (right) + * Daw 3 -> Line out 3, (left) Daw 4 -> Line out 4 (right) + * Daw 5 -> Line out 5, (left) Daw 6 -> Line out 6 (right) + * Daw 7 -> Line out 7, (left) Daw 8 -> Line out 8 (right) + * Daw 9 -> SPDIF out 1, (left) Daw 10 -> SPDIF out 2 (right) + * Daw 11 -> ADAT out 1, (left) Daw 12 -> ADAT out 2 (right) + * Daw 13 -> ADAT out 3, (left) Daw 14 -> ADAT out 4 (right) + * Daw 15 -> ADAT out 5, (left) Daw 16 -> ADAT out 6 (right) + * Daw 17 -> ADAT out 7, (left) Daw 18 -> ADAT out 8 (right) + * Everything else muted + */ + a = 0x64; + /* The first Daw channel is channel 18 */ + left = 18; + + for (c = 0; c < 9; c++) { + right = left + 1; + + for (b = 0; b < 36; b++) { + if (b == left) { + snd_s1810c_send_ctl_packet(dev, a, b, c, 0, 0x01000000); + snd_s1810c_send_ctl_packet(dev, a, b, c, 1, 0x00); + } else if (b == right) { + snd_s1810c_send_ctl_packet(dev, a, b, c, 0, 0x00); + snd_s1810c_send_ctl_packet(dev, a, b, c, 1, 0x01000000); + } else { + snd_s1810c_send_ctl_packet(dev, a, b, c, 0, 0x00); + snd_s1810c_send_ctl_packet(dev, a, b, c, 1, 0x00); + } + } + left += 2; + } + break; + } + return 0; +} + +/* + * Sync state with the device and retrieve the requested field, + * whose index is specified in (kctl->private_value & 0xFF), + * from the received fields array. + */ +static int +snd_s1810c_get_switch_state(struct usb_mixer_interface *mixer, + struct snd_kcontrol *kctl, u32 *state) +{ + struct snd_usb_audio *chip = mixer->chip; + struct s1810_mixer_state *private = mixer->private_data; + u32 field = 0; + u32 ctl_idx = (u32) (kctl->private_value & 0xFF); + int ret; + + guard(mutex)(&private->usb_mutex); + ret = snd_sc1810c_get_status_field(chip->dev, &field, + ctl_idx, &private->seqnum); + if (ret < 0) + return ret; + + *state = field; + return ret; +} + +/* + * Send a control packet to the device for the control id + * specified in (kctl->private_value >> 8) with value + * specified in (kctl->private_value >> 16). + */ +static int +snd_s1810c_set_switch_state(struct usb_mixer_interface *mixer, + struct snd_kcontrol *kctl) +{ + struct snd_usb_audio *chip = mixer->chip; + struct s1810_mixer_state *private = mixer->private_data; + u32 pval = (u32) kctl->private_value; + u32 ctl_id = (pval >> 8) & 0xFF; + u32 ctl_val = (pval >> 16) & 0x1; + + guard(mutex)(&private->usb_mutex); + return snd_s1810c_send_ctl_packet(chip->dev, 0, 0, 0, ctl_id, ctl_val); +} + +/* Generic get/set/init functions for switch controls */ + +static int +snd_s1810c_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_elem) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + struct s1810_mixer_state *private = mixer->private_data; + u32 pval = (u32) kctl->private_value; + u32 ctl_idx = pval & 0xFF; + u32 state = 0; + int ret; + + guard(mutex)(&private->data_mutex); + ret = snd_s1810c_get_switch_state(mixer, kctl, &state); + if (ret < 0) + return ret; + + switch (ctl_idx) { + case SC1810C_STATE_LINE_SW: + case SC1810C_STATE_AB_SW: + ctl_elem->value.enumerated.item[0] = (int)state; + break; + default: + ctl_elem->value.integer.value[0] = (long)state; + } + + return 0; +} + +static int +snd_s1810c_switch_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_elem) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + struct s1810_mixer_state *private = mixer->private_data; + u32 pval = (u32) kctl->private_value; + u32 ctl_idx = pval & 0xFF; + u32 curval = 0; + u32 newval = 0; + int ret = 0; + + guard(mutex)(&private->data_mutex); + ret = snd_s1810c_get_switch_state(mixer, kctl, &curval); + if (ret < 0) + return ret; + + switch (ctl_idx) { + case SC1810C_STATE_LINE_SW: + case SC1810C_STATE_AB_SW: + newval = (u32) ctl_elem->value.enumerated.item[0]; + break; + default: + newval = (u32) ctl_elem->value.integer.value[0]; + } + + if (curval == newval) + return 0; + + kctl->private_value &= ~(0x1 << 16); + kctl->private_value |= (unsigned int)(newval & 0x1) << 16; + ret = snd_s1810c_set_switch_state(mixer, kctl); + + return (ret < 0) ? 0 : 1; +} + +static int +snd_s1810c_switch_init(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *new_kctl) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + + elem = kzalloc(sizeof(struct usb_mixer_elem_info), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + elem->head.mixer = mixer; + elem->control = 0; + elem->head.id = 0; + elem->channels = 1; + + kctl = snd_ctl_new1(new_kctl, elem); + if (!kctl) { + kfree(elem); + return -ENOMEM; + } + kctl->private_free = snd_usb_mixer_elem_free; + + return snd_usb_mixer_add_control(&elem->head, kctl); +} + +static int +snd_s1810c_line_sw_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[2] = { + "Preamp On (Mic/Inst)", + "Preamp Off (Line in)" + }; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static const struct snd_kcontrol_new snd_s1810c_line_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line 1/2 Source Type", + .info = snd_s1810c_line_sw_info, + .get = snd_s1810c_switch_get, + .put = snd_s1810c_switch_set, + .private_value = (SC1810C_STATE_LINE_SW | SC1810C_CTL_LINE_SW << 8) +}; + +static const struct snd_kcontrol_new snd_s1810c_mute_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mute Main Out Switch", + .info = snd_ctl_boolean_mono_info, + .get = snd_s1810c_switch_get, + .put = snd_s1810c_switch_set, + .private_value = (SC1810C_STATE_MUTE_SW | SC1810C_CTL_MUTE_SW << 8) +}; + +static const struct snd_kcontrol_new snd_s1824c_mono_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mono Main Out Switch", + .info = snd_ctl_boolean_mono_info, + .get = snd_s1810c_switch_get, + .put = snd_s1810c_switch_set, + .private_value = (SC1824C_STATE_MONO_SW | SC1824C_CTL_MONO_SW << 8) +}; + +static const struct snd_kcontrol_new snd_s1810c_48v_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "48V Phantom Power On Mic Inputs Switch", + .info = snd_ctl_boolean_mono_info, + .get = snd_s1810c_switch_get, + .put = snd_s1810c_switch_set, + .private_value = (SC1810C_STATE_48V_SW | SC1810C_CTL_48V_SW << 8) +}; + +static int +snd_s1810c_ab_sw_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[2] = { + "1/2", + "3/4" + }; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static const struct snd_kcontrol_new snd_s1810c_ab_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone 1 Source Route", + .info = snd_s1810c_ab_sw_info, + .get = snd_s1810c_switch_get, + .put = snd_s1810c_switch_set, + .private_value = (SC1810C_STATE_AB_SW | SC1810C_CTL_AB_SW << 8) +}; + +static void snd_sc1810_mixer_state_free(struct usb_mixer_interface *mixer) +{ + struct s1810_mixer_state *private = mixer->private_data; + kfree(private); + mixer->private_data = NULL; +} + +/* Entry point, called from mixer_quirks.c */ +int snd_sc1810_init_mixer(struct usb_mixer_interface *mixer) +{ + struct s1810_mixer_state *private = NULL; + struct snd_usb_audio *chip = mixer->chip; + struct usb_device *dev = chip->dev; + int ret = 0; + + /* Run this only once */ + if (!list_empty(&chip->mixer_list)) + return 0; + + ret = snd_s1810c_init_mixer_maps(chip); + if (ret < 0) + return ret; + + private = kzalloc(sizeof(struct s1810_mixer_state), GFP_KERNEL); + if (!private) + return -ENOMEM; + + mutex_init(&private->usb_mutex); + mutex_init(&private->data_mutex); + + mixer->private_data = private; + mixer->private_free = snd_sc1810_mixer_state_free; + + private->seqnum = 1; + + ret = snd_s1810c_switch_init(mixer, &snd_s1810c_line_sw); + if (ret < 0) + return ret; + + ret = snd_s1810c_switch_init(mixer, &snd_s1810c_mute_sw); + if (ret < 0) + return ret; + + ret = snd_s1810c_switch_init(mixer, &snd_s1810c_48v_sw); + if (ret < 0) + return ret; + + switch (chip->usb_id) { + case USB_ID(0x194f, 0x010c): /* Presonus Studio 1810c */ + dev_info(&dev->dev, + "Presonus Studio 1810c, device_setup: %u\n", chip->setup); + if (chip->setup == 1) + dev_info(&dev->dev, "(8out/18in @ 48kHz)\n"); + else if (chip->setup == 2) + dev_info(&dev->dev, "(6out/8in @ 192kHz)\n"); + else + dev_info(&dev->dev, "(8out/14in @ 96kHz)\n"); + + ret = snd_s1810c_switch_init(mixer, &snd_s1810c_ab_sw); + if (ret < 0) + return ret; + + break; + case USB_ID(0x194f, 0x010d): /* Presonus Studio 1824c */ + ret = snd_s1810c_switch_init(mixer, &snd_s1824c_mono_sw); + if (ret < 0) + return ret; + + break; + } + + return ret; +} diff --git a/sound/usb/mixer_s1810c.h b/sound/usb/mixer_s1810c.h new file mode 100644 index 000000000000..a79a3743cff3 --- /dev/null +++ b/sound/usb/mixer_s1810c.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Presonus Studio 1810c driver for ALSA + * Copyright (C) 2019 Nick Kossifidis <mickflemm@gmail.com> + */ + +int snd_sc1810_init_mixer(struct usb_mixer_interface *mixer); diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c index 4aeb9488a0c9..8babfa3f7c45 100644 --- a/sound/usb/mixer_scarlett.c +++ b/sound/usb/mixer_scarlett.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Scarlett Driver for ALSA * @@ -12,17 +13,6 @@ * * Code cleanup: * David Henningsson <david.henningsson at canonical.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * */ /* @@ -31,7 +21,7 @@ * Auto-detection via UAC2 is not feasible to properly discover the vast * majority of features. It's related to both Linux/ALSA's UAC2 as well as * Focusrite's implementation of it. Eventually quirks may be sufficient but - * right now it's a major headache to work arount these things. + * right now it's a major headache to work around these things. * * NB. Neither the OSX nor the win driver provided by Focusrite performs * discovery, they seem to operate the same as this driver. @@ -152,6 +142,7 @@ enum { SCARLETT_OUTPUTS, SCARLETT_SWITCH_IMPEDANCE, SCARLETT_SWITCH_PAD, + SCARLETT_SWITCH_GAIN, }; enum { @@ -202,6 +193,15 @@ static const struct scarlett_mixer_elem_enum_info opt_pad = { } }; +static const struct scarlett_mixer_elem_enum_info opt_gain = { + .start = 0, + .len = 2, + .offsets = {}, + .names = (char const * const []){ + "Lo", "Hi" + } +}; + static const struct scarlett_mixer_elem_enum_info opt_impedance = { .start = 0, .len = 2, @@ -357,21 +357,21 @@ static int scarlett_ctl_put(struct snd_kcontrol *kctl, return changed; } -static void scarlett_generate_name(int i, char *dst, int offsets[]) +static void scarlett_generate_name(int i, char *dst, size_t size, int offsets[]) { if (i > offsets[SCARLETT_OFFSET_MIX]) - sprintf(dst, "Mix %c", - 'A'+(i - offsets[SCARLETT_OFFSET_MIX] - 1)); + scnprintf(dst, size, "Mix %c", + 'A'+(i - offsets[SCARLETT_OFFSET_MIX] - 1)); else if (i > offsets[SCARLETT_OFFSET_ADAT]) - sprintf(dst, "ADAT %d", i - offsets[SCARLETT_OFFSET_ADAT]); + scnprintf(dst, size, "ADAT %d", i - offsets[SCARLETT_OFFSET_ADAT]); else if (i > offsets[SCARLETT_OFFSET_SPDIF]) - sprintf(dst, "SPDIF %d", i - offsets[SCARLETT_OFFSET_SPDIF]); + scnprintf(dst, size, "SPDIF %d", i - offsets[SCARLETT_OFFSET_SPDIF]); else if (i > offsets[SCARLETT_OFFSET_ANALOG]) - sprintf(dst, "Analog %d", i - offsets[SCARLETT_OFFSET_ANALOG]); + scnprintf(dst, size, "Analog %d", i - offsets[SCARLETT_OFFSET_ANALOG]); else if (i > offsets[SCARLETT_OFFSET_PCM]) - sprintf(dst, "PCM %d", i - offsets[SCARLETT_OFFSET_PCM]); + scnprintf(dst, size, "PCM %d", i - offsets[SCARLETT_OFFSET_PCM]); else - sprintf(dst, "Off"); + scnprintf(dst, size, "Off"); } static int scarlett_ctl_enum_dynamic_info(struct snd_kcontrol *kctl, @@ -391,6 +391,7 @@ static int scarlett_ctl_enum_dynamic_info(struct snd_kcontrol *kctl, /* generate name dynamically based on item number and offset info */ scarlett_generate_name(uinfo->value.enumerated.item, uinfo->value.enumerated.name, + sizeof(uinfo->value.enumerated.name), opt->offsets); return 0; @@ -460,7 +461,7 @@ static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, struct snd_usb_audio *chip = elem->head.mixer->chip; unsigned char buf[2 * MAX_CHANNELS] = {0, }; int wValue = (elem->control << 8) | elem->idx_off; - int idx = snd_usb_ctrl_intf(chip) | (elem->head.id << 8); + int idx = snd_usb_ctrl_intf(elem->head.mixer->hostif) | (elem->head.id << 8); int err; err = snd_usb_ctl_msg(chip->dev, @@ -569,7 +570,7 @@ static int add_new_ctl(struct usb_mixer_interface *mixer, } kctl->private_free = snd_usb_mixer_elem_free; - strlcpy(kctl->id.name, name, sizeof(kctl->id.name)); + strscpy(kctl->id.name, name, sizeof(kctl->id.name)); err = snd_usb_mixer_add_control(&elem->head, kctl); if (err < 0) @@ -633,7 +634,7 @@ static int add_output_ctls(struct usb_mixer_interface *mixer, /********************** device-specific config *************************/ /* untested... */ -static struct scarlett_device_info s6i6_info = { +static const struct scarlett_device_info s6i6_info = { .matrix_in = 18, .matrix_out = 8, .input_len = 6, @@ -662,8 +663,8 @@ static struct scarlett_device_info s6i6_info = { { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, - { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, - { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_GAIN, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_GAIN, .name = NULL}, }, .matrix_mux_init = { @@ -675,7 +676,7 @@ static struct scarlett_device_info s6i6_info = { }; /* untested... */ -static struct scarlett_device_info s8i6_info = { +static const struct scarlett_device_info s8i6_info = { .matrix_in = 18, .matrix_out = 6, .input_len = 8, @@ -714,7 +715,7 @@ static struct scarlett_device_info s8i6_info = { } }; -static struct scarlett_device_info s18i6_info = { +static const struct scarlett_device_info s18i6_info = { .matrix_in = 18, .matrix_out = 6, .input_len = 18, @@ -751,7 +752,7 @@ static struct scarlett_device_info s18i6_info = { } }; -static struct scarlett_device_info s18i8_info = { +static const struct scarlett_device_info s18i8_info = { .matrix_in = 18, .matrix_out = 8, .input_len = 18, @@ -793,7 +794,7 @@ static struct scarlett_device_info s18i8_info = { } }; -static struct scarlett_device_info s18i20_info = { +static const struct scarlett_device_info s18i20_info = { .matrix_in = 18, .matrix_out = 8, .input_len = 18, @@ -843,7 +844,7 @@ static struct scarlett_device_info s18i20_info = { static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer, - struct scarlett_device_info *info) + const struct scarlett_device_info *info) { int i, err; char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; @@ -876,7 +877,8 @@ static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer, return err; break; case SCARLETT_SWITCH_IMPEDANCE: - sprintf(mx, "Input %d Impedance Switch", ctl->num); + scnprintf(mx, sizeof(mx), + "Input %d Impedance Switch", ctl->num); err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, scarlett_ctl_enum_resume, 0x01, 0x09, ctl->num, USB_MIXER_S16, 1, mx, @@ -885,7 +887,8 @@ static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer, return err; break; case SCARLETT_SWITCH_PAD: - sprintf(mx, "Input %d Pad Switch", ctl->num); + scnprintf(mx, sizeof(mx), + "Input %d Pad Switch", ctl->num); err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, scarlett_ctl_enum_resume, 0x01, 0x0b, ctl->num, USB_MIXER_S16, 1, mx, @@ -893,6 +896,16 @@ static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer, if (err < 0) return err; break; + case SCARLETT_SWITCH_GAIN: + scnprintf(mx, sizeof(mx), + "Input %d Gain Switch", ctl->num); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, + scarlett_ctl_enum_resume, 0x01, + 0x08, ctl->num, USB_MIXER_S16, 1, mx, + &opt_gain, &elem); + if (err < 0) + return err; + break; } } @@ -906,7 +919,7 @@ int snd_scarlett_controls_create(struct usb_mixer_interface *mixer) { int err, i, o; char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; - struct scarlett_device_info *info; + const struct scarlett_device_info *info; struct usb_mixer_elem_info *elem; static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' }; @@ -951,8 +964,9 @@ int snd_scarlett_controls_create(struct usb_mixer_interface *mixer) return err; for (o = 0; o < info->matrix_out; o++) { - sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1, - o+'A'); + scnprintf(mx, sizeof(mx), + "Matrix %02d Mix %c Playback Volume", i+1, + o+'A'); err = add_new_ctl(mixer, &usb_scarlett_ctl, scarlett_ctl_resume, 0x3c, 0x00, (i << 3) + (o & 0x07), USB_MIXER_S16, @@ -993,7 +1007,7 @@ int snd_scarlett_controls_create(struct usb_mixer_interface *mixer) err = snd_usb_ctl_msg(mixer->chip->dev, usb_sndctrlpipe(mixer->chip->dev, 0), UAC2_CS_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | - USB_DIR_OUT, 0x0100, snd_usb_ctrl_intf(mixer->chip) | + USB_DIR_OUT, 0x0100, snd_usb_ctrl_intf(mixer->hostif) | (0x29 << 8), sample_rate_buffer, 4); if (err < 0) return err; diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c new file mode 100644 index 000000000000..f2446bf3982c --- /dev/null +++ b/sound/usb/mixer_scarlett2.c @@ -0,0 +1,9434 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Focusrite Scarlett 2 Protocol Driver for ALSA + * (including Scarlett 2nd Gen, 3rd Gen, 4th Gen, Clarett USB, and + * Clarett+ series products) + * + * Supported models: + * - 6i6/18i8/18i20 Gen 2 + * - Solo/2i2/4i4/8i6/18i8/18i20 Gen 3 + * - Solo/2i2/4i4 Gen 4 + * - Clarett 2Pre/4Pre/8Pre USB + * - Clarett+ 2Pre/4Pre/8Pre + * - Vocaster One/Two + * + * Copyright (c) 2018-2025 by Geoffrey D. Bennett <g at b4.vu> + * Copyright (c) 2020-2021 by Vladimir Sadovnikov <sadko4u@gmail.com> + * Copyright (c) 2022 by Christian Colglazier <christian@cacolglazier.com> + * + * Based on the Scarlett (Gen 1) Driver for ALSA: + * + * Copyright (c) 2013 by Tobias Hoffmann + * Copyright (c) 2013 by Robin Gareus <robin at gareus.org> + * Copyright (c) 2002 by Takashi Iwai <tiwai at suse.de> + * Copyright (c) 2014 by Chris J Arges <chris.j.arges at canonical.com> + * + * Many codes borrowed from audio.c by + * Alan Cox (alan at lxorguk.ukuu.org.uk) + * Thomas Sailer (sailer at ife.ee.ethz.ch) + * + * Code cleanup: + * David Henningsson <david.henningsson at canonical.com> + */ + +/* The protocol was reverse engineered by looking at the communication + * between Focusrite Control 2.3.4 and the Focusrite(R) Scarlett 18i20 + * (firmware 1083) using usbmon in July-August 2018. + * + * Scarlett 18i8 support added in April 2019. + * + * Scarlett 6i6 support added in June 2019 (thanks to Martin Wittmann + * for providing usbmon output and testing). + * + * Scarlett 4i4/8i6 Gen 3 support added in May 2020 (thanks to Laurent + * Debricon for donating a 4i4 and to Fredrik Unger for providing 8i6 + * usbmon output and testing). + * + * Scarlett 18i8/18i20 Gen 3 support added in June 2020 (thanks to + * Darren Jaeckel, Alex Sedlack, and Clovis Lunel for providing usbmon + * output, protocol traces and testing). + * + * Support for loading mixer volume and mux configuration from the + * interface during driver initialisation added in May 2021 (thanks to + * Vladimir Sadovnikov for figuring out how). + * + * Support for Solo/2i2 Gen 3 added in May 2021 (thanks to Alexander + * Vorona for 2i2 protocol traces). + * + * Support for phantom power, direct monitoring, speaker switching, + * and talkback added in May-June 2021. + * + * Support for Clarett+ 8Pre added in Aug 2022 by Christian + * Colglazier. + * + * Support for Clarett 8Pre USB added in Sep 2023 (thanks to Philippe + * Perrot for confirmation). + * + * Support for Clarett+ 4Pre and 2Pre added in Sep 2023 (thanks to + * Gregory Rozzo for donating a 4Pre, and David Sherwood and Patrice + * Peterson for usbmon output). + * + * Support for Clarett 2Pre and 4Pre USB added in Oct 2023. + * + * Support for firmware updates added in Dec 2023. + * + * Support for Scarlett Solo/2i2/4i4 Gen 4 added in Dec 2023 (thanks + * to many LinuxMusicians people and to Focusrite for hardware + * donations). + * + * Support for Vocaster One and Two added in Mar 2024 (thanks to many + * LinuxMusicians people and to Focusrite for hardware donations). + * + * This ALSA mixer gives access to (model-dependent): + * - input, output, mixer-matrix muxes + * - mixer-matrix gain stages + * - gain/volume/mute controls + * - level meters + * - line/inst level, pad, and air controls + * - phantom power, direct monitor, speaker switching, and talkback + * controls + * - disable/enable MSD mode + * - disable/enable standalone mode + * - input mute, gain, autogain, safe mode + * - direct monitor mixes + * - compressor and EQ + * - Bluetooth volume + * + * <ditaa> + * /--------------\ 18chn 20chn /--------------\ + * | Hardware in +--+------\ /-------------+--+ ALSA PCM out | + * \--------------/ | | | | \--------------/ + * | | | /-----\ | + * | | | | | | + * | v v v | | + * | +---------------+ | | + * | \ Matrix Mux / | | + * | +-----+-----+ | | + * | | | | + * | |18chn | | + * | | | | + * | | 10chn| | + * | v | | + * | +------------+ | | + * | | Mixer | | | + * | | Matrix | | | + * | | | | | + * | | 18x10 Gain | | | + * | | stages | | | + * | +-----+------+ | | + * | | | | + * |18chn |10chn | |20chn + * | | | | + * | +----------/ | + * | | | + * v v v + * =========================== + * +---------------+ +--—------------+ + * \ Output Mux / \ Capture Mux / + * +---+---+---+ +-----+-----+ + * | | | + * 10chn| | |18chn + * | | | + * /--------------\ | | | /--------------\ + * | S/PDIF, ADAT |<--/ |10chn \-->| ALSA PCM in | + * | Hardware out | | \--------------/ + * \--------------/ | + * v + * +-------------+ Software gain per channel. + * | Master Gain |<-- 18i20 only: Switch per channel + * +------+------+ to select HW or SW gain control. + * | + * |10chn + * /--------------\ | + * | Analogue |<------/ + * | Hardware out | + * \--------------/ + * </ditaa> + * + * Gen 3/4 devices have a Mass Storage Device (MSD) mode where a small + * disk with registration and driver download information is presented + * to the host. To access the full functionality of the device without + * proprietary software, MSD mode can be disabled by: + * - holding down the 48V button for five seconds while powering on + * the device, or + * - using this driver and alsamixer to change the "MSD Mode" setting + * to Off and power-cycling the device + */ + +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/moduleparam.h> + +#include <sound/control.h> +#include <sound/tlv.h> +#include <sound/hwdep.h> + +#include <uapi/sound/scarlett2.h> + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" + +#include "mixer_scarlett2.h" +#include "fcp.h" + +/* device_setup value to allow turning MSD mode back on */ +#define SCARLETT2_MSD_ENABLE 0x02 + +/* device_setup value to disable this mixer driver */ +#define SCARLETT2_DISABLE 0x04 + +/* device_setup value to use the FCP driver instead */ +#define SCARLETT2_USE_FCP_DRIVER 0x08 + +/* some gui mixers can't handle negative ctl values */ +#define SCARLETT2_VOLUME_BIAS 127 + +/* maximum preamp input gain value + * (the corresponding value in dB is per-device) + */ +#define SCARLETT2_MAX_GAIN_VALUE 70 + +/* maximum Bluetooth volume value */ +#define SCARLETT2_MAX_BLUETOOTH_VOLUME 30 + +/* mixer range from -80dB to +12dB in 0.5dB steps */ +#define SCARLETT2_MIXER_MIN_DB -80 +#define SCARLETT2_MIXER_BIAS (-SCARLETT2_MIXER_MIN_DB * 2) +#define SCARLETT2_MIXER_MAX_DB 12 +#define SCARLETT2_MIXER_MAX_VALUE \ + ((SCARLETT2_MIXER_MAX_DB - SCARLETT2_MIXER_MIN_DB) * 2) +#define SCARLETT2_MIXER_VALUE_COUNT (SCARLETT2_MIXER_MAX_VALUE + 1) + +/* map from (dB + 80) * 2 to mixer value + * for dB in 0 .. 184: int(8192 * pow(10, ((dB - 160) / 2 / 20))) + */ +static const u16 scarlett2_mixer_values[SCARLETT2_MIXER_VALUE_COUNT] = { + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 8, 8, + 9, 9, 10, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 23, 24, 25, 27, 29, 30, 32, 34, 36, 38, 41, 43, 46, 48, 51, + 54, 57, 61, 65, 68, 73, 77, 81, 86, 91, 97, 103, 109, 115, + 122, 129, 137, 145, 154, 163, 173, 183, 194, 205, 217, 230, + 244, 259, 274, 290, 307, 326, 345, 365, 387, 410, 434, 460, + 487, 516, 547, 579, 614, 650, 689, 730, 773, 819, 867, 919, + 973, 1031, 1092, 1157, 1225, 1298, 1375, 1456, 1543, 1634, + 1731, 1833, 1942, 2057, 2179, 2308, 2445, 2590, 2744, 2906, + 3078, 3261, 3454, 3659, 3876, 4105, 4349, 4606, 4879, 5168, + 5475, 5799, 6143, 6507, 6892, 7301, 7733, 8192, 8677, 9191, + 9736, 10313, 10924, 11571, 12257, 12983, 13752, 14567, 15430, + 16345, 17313, 18339, 19426, 20577, 21796, 23088, 24456, 25905, + 27440, 29066, 30788, 32612 +}; + +/* Maximum number of analogue outputs */ +#define SCARLETT2_ANALOGUE_MAX 10 + +/* Maximum number of various input controls */ +#define SCARLETT2_LEVEL_SWITCH_MAX 2 +#define SCARLETT2_PAD_SWITCH_MAX 8 +#define SCARLETT2_AIR_SWITCH_MAX 8 +#define SCARLETT2_DSP_SWITCH_MAX 2 +#define SCARLETT2_INPUT_MUTE_SWITCH_MAX 2 +#define SCARLETT2_PHANTOM_SWITCH_MAX 2 +#define SCARLETT2_INPUT_GAIN_MAX 2 + +/* Maximum number of inputs to the mixer */ +#define SCARLETT2_INPUT_MIX_MAX 25 + +/* Maximum number of outputs from the mixer */ +#define SCARLETT2_OUTPUT_MIX_MAX 12 + +/* Maximum number of mixer gain controls */ +#define SCARLETT2_MIX_MAX (SCARLETT2_INPUT_MIX_MAX * SCARLETT2_OUTPUT_MIX_MAX) + +/* Maximum number of direct monitor mixer gain controls + * 1 (Solo) or 2 (2i2) direct monitor selections (Mono & Stereo) + * 2 Mix outputs (A/Left & B/Right) + * 4 Mix inputs + */ +#define SCARLETT2_MONITOR_MIX_MAX (2 * 2 * 4) + +/* Maximum size of the data in the USB mux assignment message: + * 20 inputs, 20 outputs, 25 matrix inputs, 12 spare + */ +#define SCARLETT2_MUX_MAX 77 + +/* Maximum number of sources (sum of input port counts) */ +#define SCARLETT2_MAX_SRCS 52 + +/* Maximum number of meters (sum of output port counts) */ +#define SCARLETT2_MAX_METERS 65 + +/* Compressor parameter data + * + * The compressor parameters are 32-bit fixed point values with 24 + * bits of fraction. Integer values are sufficient for the parameters + * except for ratio which we can set in 0.5:1 steps. + */ +struct compressor_param { + const char *name; + snd_ctl_elem_type_t type; + s32 min; + s32 max; + int scale_bits; +}; + +/* The available compressor parameters on the Vocaster: + * - Enable: Off, On + * - Threshold: -40dB to 0dB + * - Ratio: 1:1 to 50:1 in 0.5:1 steps + * - Knee Width: 0dB to 10dB + * - Attack: 30ms to 127ms + * - Release: 30ms to 127ms + * - Makeup Gain: 0dB to 24dB + */ +static const struct compressor_param compressor_params[] = { + { "Enable", SNDRV_CTL_ELEM_TYPE_BOOLEAN, 0, 1, 0 }, + { "Threshold", SNDRV_CTL_ELEM_TYPE_INTEGER, -40, 0, 24 }, + { "Ratio", SNDRV_CTL_ELEM_TYPE_INTEGER, 2, 100, 23 }, + { "Knee Width", SNDRV_CTL_ELEM_TYPE_INTEGER, 0, 10, 24 }, + { "Attack", SNDRV_CTL_ELEM_TYPE_INTEGER, 30, 127, 24 }, + { "Release", SNDRV_CTL_ELEM_TYPE_INTEGER, 30, 127, 24 }, + { "Makeup Gain", SNDRV_CTL_ELEM_TYPE_INTEGER, 0, 24, 24 }, +}; + +#define SCARLETT2_COMPRESSOR_PARAM_COUNT ARRAY_SIZE(compressor_params) +#define SCARLETT2_COMPRESSOR_CTLS_MAX \ + (SCARLETT2_COMPRESSOR_PARAM_COUNT * SCARLETT2_DSP_SWITCH_MAX) + +/* Maximum number of filter controls */ +#define SCARLETT2_PRECOMP_FLT_CTLS_MAX (2 * SCARLETT2_DSP_SWITCH_MAX) +#define SCARLETT2_PEQ_FLT_CTLS_MAX (3 * SCARLETT2_DSP_SWITCH_MAX) + +/* Number of biquad filter coefficients */ +#define SCARLETT2_BIQUAD_COEFFS 5 + +/* Maximum number of filter coefficient values */ +#define SCARLETT2_PRECOMP_FLT_VALUES_MAX \ + (SCARLETT2_PRECOMP_FLT_CTLS_MAX * SCARLETT2_BIQUAD_COEFFS) +#define SCARLETT2_PEQ_FLT_VALUES_MAX \ + (SCARLETT2_PEQ_FLT_CTLS_MAX * SCARLETT2_BIQUAD_COEFFS) + +/* Maximum number of PEQ filter slots */ +#define SCARLETT2_PEQ_FLT_SLOTS_MAX 4 + +/* Hardware port types: + * - None (no input to mux) + * - Analogue I/O + * - S/PDIF I/O + * - ADAT I/O + * - Mixer I/O + * - PCM I/O + */ +enum { + SCARLETT2_PORT_TYPE_NONE, + SCARLETT2_PORT_TYPE_ANALOGUE, + SCARLETT2_PORT_TYPE_SPDIF, + SCARLETT2_PORT_TYPE_ADAT, + SCARLETT2_PORT_TYPE_MIX, + SCARLETT2_PORT_TYPE_PCM, + SCARLETT2_PORT_TYPE_COUNT +}; + +/* I/O count of each port type kept in struct scarlett2_ports */ +enum { + SCARLETT2_PORT_IN, + SCARLETT2_PORT_OUT, + SCARLETT2_PORT_DIRNS +}; + +/* Dim/Mute buttons on the 18i20 */ +enum { + SCARLETT2_BUTTON_MUTE, + SCARLETT2_BUTTON_DIM, + SCARLETT2_DIM_MUTE_COUNT +}; + +/* Autogain target values */ + +#define SCARLETT2_AG_TARGET_MIN (-30) + +enum { + SCARLETT2_AG_HOT_TARGET, + SCARLETT2_AG_MEAN_TARGET, + SCARLETT2_AG_PEAK_TARGET, + SCARLETT2_AG_TARGET_COUNT +}; + +/* Flash Write State */ +enum { + SCARLETT2_FLASH_WRITE_STATE_IDLE, + SCARLETT2_FLASH_WRITE_STATE_SELECTED, + SCARLETT2_FLASH_WRITE_STATE_ERASING, + SCARLETT2_FLASH_WRITE_STATE_WRITE +}; + +static const char *const scarlett2_dim_mute_names[SCARLETT2_DIM_MUTE_COUNT] = { + "Mute Playback Switch", "Dim Playback Switch" +}; + +/* Vocaster One speaker/headphone mute names */ +static const char *const vocaster_one_sp_hp_mute_names[] = { + "Speaker Mute Playback Switch", + "Headphones Mute Playback Switch", + NULL +}; + +/* Vocaster Two speaker/headphone mute names */ +static const char *const vocaster_two_sp_hp_mute_names[] = { + "Speaker Mute Playback Switch", + "Headphones 1 Mute Playback Switch", + "Headphones 2 Mute Playback Switch", + NULL +}; + +/* The autogain_status is set based on the autogain_switch and + * raw_autogain_status values. + * + * If autogain_switch is set, autogain_status is set to 0 (Running). + * The other status values are from the raw_autogain_status value + 1. + */ +static const char *const scarlett2_autogain_status_gen4[] = { + "Running", + "Success", + "SuccessDRover", + "WarnMinGainLimit", + "FailDRunder", + "FailMaxGainLimit", + "FailClipped", + "Cancelled", + "Invalid", + NULL +}; + +static const char *const scarlett2_autogain_status_vocaster[] = { + "Running", + "Success", + "FailPG", + "FailRange", + "WarnMaxCap", + "WarnMinCap", + "Cancelled", + "Invalid", + NULL +}; + +/* Power Status Values */ +enum { + SCARLETT2_POWER_STATUS_EXT, + SCARLETT2_POWER_STATUS_BUS, + SCARLETT2_POWER_STATUS_FAIL, + SCARLETT2_POWER_STATUS_COUNT +}; + +/* Notification callback functions */ +struct scarlett2_notification { + u32 mask; + void (*func)(struct usb_mixer_interface *mixer); +}; + +static void scarlett2_notify_ack(struct usb_mixer_interface *mixer); +static void scarlett2_notify_sync(struct usb_mixer_interface *mixer); +static void scarlett2_notify_dim_mute(struct usb_mixer_interface *mixer); +static void scarlett2_notify_monitor(struct usb_mixer_interface *mixer); +static void scarlett2_notify_volume(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_level(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_pad(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_air(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_dsp(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_mute(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_phantom(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_other(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_select(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_gain(struct usb_mixer_interface *mixer); +static void scarlett2_notify_autogain(struct usb_mixer_interface *mixer); +static void scarlett2_notify_input_safe(struct usb_mixer_interface *mixer); +static void scarlett2_notify_monitor_other(struct usb_mixer_interface *mixer); +static void scarlett2_notify_direct_monitor(struct usb_mixer_interface *mixer); +static void scarlett2_notify_power_status(struct usb_mixer_interface *mixer); +static void scarlett2_notify_pcm_input_switch( + struct usb_mixer_interface *mixer); +static void scarlett2_notify_bluetooth(struct usb_mixer_interface *mixer); + +/* Arrays of notification callback functions */ + +static const struct scarlett2_notification scarlett2_notifications[] = { + { 0x00000001, scarlett2_notify_ack }, + { 0x00000008, scarlett2_notify_sync }, + { 0x00200000, scarlett2_notify_dim_mute }, + { 0x00400000, scarlett2_notify_monitor }, + { 0x00800000, scarlett2_notify_input_other }, + { 0x01000000, scarlett2_notify_monitor_other }, + { 0, NULL } +}; + +static const struct scarlett2_notification scarlett3a_notifications[] = { + { 0x00000001, scarlett2_notify_ack }, + { 0x00800000, scarlett2_notify_input_other }, + { 0x01000000, scarlett2_notify_direct_monitor }, + { 0, NULL } +}; + +static const struct scarlett2_notification vocaster_notifications[] = { + { 0x00000001, scarlett2_notify_ack }, + { 0x00000008, scarlett2_notify_sync }, + { 0x00200000, scarlett2_notify_input_mute }, + { 0x00400000, scarlett2_notify_autogain }, + { 0x04000000, scarlett2_notify_input_dsp }, + { 0x08000000, scarlett2_notify_input_gain }, + { 0x10000000, scarlett2_notify_input_phantom }, + { 0x20000000, scarlett2_notify_bluetooth }, + { 0, NULL } +}; + +static const struct scarlett2_notification scarlett4_solo_notifications[] = { + { 0x00000001, scarlett2_notify_ack }, + { 0x00000008, scarlett2_notify_sync }, + { 0x00400000, scarlett2_notify_input_air }, + { 0x00800000, scarlett2_notify_direct_monitor }, + { 0x01000000, scarlett2_notify_input_level }, + { 0x02000000, scarlett2_notify_input_phantom }, + { 0x04000000, scarlett2_notify_pcm_input_switch }, + { 0, NULL } +}; + +static const struct scarlett2_notification scarlett4_2i2_notifications[] = { + { 0x00000001, scarlett2_notify_ack }, + { 0x00000008, scarlett2_notify_sync }, + { 0x00200000, scarlett2_notify_input_safe }, + { 0x00400000, scarlett2_notify_autogain }, + { 0x00800000, scarlett2_notify_input_air }, + { 0x01000000, scarlett2_notify_direct_monitor }, + { 0x02000000, scarlett2_notify_input_select }, + { 0x04000000, scarlett2_notify_input_level }, + { 0x08000000, scarlett2_notify_input_phantom }, + { 0x10000000, NULL }, /* power status, ignored */ + { 0x40000000, scarlett2_notify_input_gain }, + { 0x80000000, NULL }, /* power status, ignored */ + { 0, NULL } +}; + +static const struct scarlett2_notification scarlett4_4i4_notifications[] = { + { 0x00000001, scarlett2_notify_ack }, + { 0x00000008, scarlett2_notify_sync }, + { 0x00200000, scarlett2_notify_input_safe }, + { 0x00400000, scarlett2_notify_autogain }, + { 0x00800000, scarlett2_notify_input_air }, + { 0x01000000, scarlett2_notify_input_select }, + { 0x02000000, scarlett2_notify_input_level }, + { 0x04000000, scarlett2_notify_input_phantom }, + { 0x08000000, scarlett2_notify_power_status }, /* power external */ + { 0x20000000, scarlett2_notify_input_gain }, + { 0x40000000, scarlett2_notify_power_status }, /* power status */ + { 0x80000000, scarlett2_notify_volume }, + { 0, NULL } +}; + +/* Configuration parameters that can be read and written */ +enum { + SCARLETT2_CONFIG_DIM_MUTE, + SCARLETT2_CONFIG_LINE_OUT_VOLUME, + SCARLETT2_CONFIG_MUTE_SWITCH, + SCARLETT2_CONFIG_SW_HW_SWITCH, + SCARLETT2_CONFIG_MASTER_VOLUME, + SCARLETT2_CONFIG_HEADPHONE_VOLUME, + SCARLETT2_CONFIG_LEVEL_SWITCH, + SCARLETT2_CONFIG_PAD_SWITCH, + SCARLETT2_CONFIG_MSD_SWITCH, + SCARLETT2_CONFIG_AIR_SWITCH, + SCARLETT2_CONFIG_DSP_SWITCH, + SCARLETT2_CONFIG_COMPRESSOR_PARAMS, + SCARLETT2_CONFIG_PRECOMP_FLT_SWITCH, + SCARLETT2_CONFIG_PRECOMP_FLT_PARAMS, + SCARLETT2_CONFIG_PEQ_FLT_SWITCH, + SCARLETT2_CONFIG_PEQ_FLT_PARAMS, + SCARLETT2_CONFIG_INPUT_MUTE_SWITCH, + SCARLETT2_CONFIG_STANDALONE_SWITCH, + SCARLETT2_CONFIG_PHANTOM_SWITCH, + SCARLETT2_CONFIG_PHANTOM_PERSISTENCE, + SCARLETT2_CONFIG_DIRECT_MONITOR, + SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH, + SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE, + SCARLETT2_CONFIG_TALKBACK_MAP, + SCARLETT2_CONFIG_AUTOGAIN_SWITCH, + SCARLETT2_CONFIG_AUTOGAIN_STATUS, + SCARLETT2_CONFIG_AG_HOT_TARGET, + SCARLETT2_CONFIG_AG_MEAN_TARGET, + SCARLETT2_CONFIG_AG_PEAK_TARGET, + SCARLETT2_CONFIG_INPUT_GAIN, + SCARLETT2_CONFIG_SAFE_SWITCH, + SCARLETT2_CONFIG_INPUT_SELECT_SWITCH, + SCARLETT2_CONFIG_INPUT_LINK_SWITCH, + SCARLETT2_CONFIG_POWER_EXT, + SCARLETT2_CONFIG_POWER_LOW, + SCARLETT2_CONFIG_PCM_INPUT_SWITCH, + SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN, + SCARLETT2_CONFIG_BLUETOOTH_VOLUME, + SCARLETT2_CONFIG_SPDIF_MODE, + SCARLETT2_CONFIG_SP_HP_MUTE, + SCARLETT2_CONFIG_COUNT +}; + +/* Autogain target configuration parameters and names */ + +static const int scarlett2_ag_target_configs[] = { + [SCARLETT2_AG_HOT_TARGET] = SCARLETT2_CONFIG_AG_HOT_TARGET, + [SCARLETT2_AG_MEAN_TARGET] = SCARLETT2_CONFIG_AG_MEAN_TARGET, + [SCARLETT2_AG_PEAK_TARGET] = SCARLETT2_CONFIG_AG_PEAK_TARGET +}; + +static const char *const scarlett2_ag_target_names[] = { + "Hot", "Mean", "Peak" +}; + +/* Location, size, and activation command number for the configuration + * parameters. Size is in bits and may be 1, 8, 16, or 32. + * + * Vocaster and 4th Gen devices have a parameter buffer to set certain + * configuration parameters. When pbuf is set, rather than writing to + * the given offset, the channel and value are written to the + * parameter buffer and the activate command is sent to the device. + * + * Some Gen 4 configuration parameters are written with 0x02 for a + * desired value of 0x01, and 0x03 for 0x00. These are indicated with + * mute set to 1. 0x02 and 0x03 are temporary values while the device + * makes the change and the channel and/or corresponding DSP channel + * output is muted. + */ +struct scarlett2_config { + u16 offset; + u8 size; + u8 activate; + u8 pbuf; + u8 mute; +}; + +struct scarlett2_config_set { + const struct scarlett2_notification *notifications; + u16 param_buf_addr; + const unsigned int *input_gain_tlv; + const char *const *autogain_status_texts; + const struct scarlett2_config items[SCARLETT2_CONFIG_COUNT]; +}; + +/* Input gain TLV dB ranges */ + +static const DECLARE_TLV_DB_MINMAX( + db_scale_vocaster_gain, 0, 70 * 100 +); + +static const DECLARE_TLV_DB_MINMAX( + db_scale_gen4_gain, 0, 69 * 100 +); + +/* Gen 2 devices without SW/HW volume switch: 6i6, 18i8 */ + +static const struct scarlett2_config_set scarlett2_config_set_gen2a = { + .notifications = scarlett2_notifications, + .items = { + [SCARLETT2_CONFIG_LINE_OUT_VOLUME] = { + .offset = 0x34, .size = 16, .activate = 1 }, + + [SCARLETT2_CONFIG_MUTE_SWITCH] = { + .offset = 0x5c, .size = 8, .activate = 1 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x7c, .size = 8, .activate = 7 }, + + [SCARLETT2_CONFIG_PAD_SWITCH] = { + .offset = 0x84, .size = 8, .activate = 8 }, + + [SCARLETT2_CONFIG_STANDALONE_SWITCH] = { + .offset = 0x8d, .size = 8, .activate = 6 }, + } +}; + +/* Gen 2 devices with SW/HW volume switch: 18i20 */ + +static const struct scarlett2_config_set scarlett2_config_set_gen2b = { + .notifications = scarlett2_notifications, + .items = { + [SCARLETT2_CONFIG_DIM_MUTE] = { + .offset = 0x31, .size = 8, .activate = 2 }, + + [SCARLETT2_CONFIG_LINE_OUT_VOLUME] = { + .offset = 0x34, .size = 16, .activate = 1 }, + + [SCARLETT2_CONFIG_MUTE_SWITCH] = { + .offset = 0x5c, .size = 8, .activate = 1 }, + + [SCARLETT2_CONFIG_SW_HW_SWITCH] = { + .offset = 0x66, .size = 8, .activate = 3 }, + + [SCARLETT2_CONFIG_MASTER_VOLUME] = { + .offset = 0x76, .size = 16 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x7c, .size = 8, .activate = 7 }, + + [SCARLETT2_CONFIG_PAD_SWITCH] = { + .offset = 0x84, .size = 8, .activate = 8 }, + + [SCARLETT2_CONFIG_STANDALONE_SWITCH] = { + .offset = 0x8d, .size = 8, .activate = 6 }, + } +}; + +/* Gen 3 devices without a mixer (Solo and 2i2) */ +static const struct scarlett2_config_set scarlett2_config_set_gen3a = { + .notifications = scarlett3a_notifications, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x04, .size = 8, .activate = 6 }, + + [SCARLETT2_CONFIG_PHANTOM_PERSISTENCE] = { + .offset = 0x05, .size = 8, .activate = 6 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x06, .size = 8, .activate = 3 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR] = { + .offset = 0x07, .size = 8, .activate = 4 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x08, .size = 1, .activate = 7 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x09, .size = 1, .activate = 8 }, + } +}; + +/* Gen 3 devices without SW/HW volume switch: 4i4, 8i6 */ +static const struct scarlett2_config_set scarlett2_config_set_gen3b = { + .notifications = scarlett2_notifications, + .items = { + [SCARLETT2_CONFIG_LINE_OUT_VOLUME] = { + .offset = 0x34, .size = 16, .activate = 1 }, + + [SCARLETT2_CONFIG_MUTE_SWITCH] = { + .offset = 0x5c, .size = 8, .activate = 1 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x7c, .size = 8, .activate = 7 }, + + [SCARLETT2_CONFIG_PAD_SWITCH] = { + .offset = 0x84, .size = 8, .activate = 8 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x8c, .size = 8, .activate = 8 }, + + [SCARLETT2_CONFIG_STANDALONE_SWITCH] = { + .offset = 0x95, .size = 8, .activate = 6 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x9c, .size = 1, .activate = 8 }, + + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x9d, .size = 8, .activate = 6 }, + + [SCARLETT2_CONFIG_PHANTOM_PERSISTENCE] = { + .offset = 0x9e, .size = 8, .activate = 6 }, + } +}; + +/* Gen 3 devices with SW/HW volume switch: 18i8, 18i20 */ +static const struct scarlett2_config_set scarlett2_config_set_gen3c = { + .notifications = scarlett2_notifications, + .items = { + [SCARLETT2_CONFIG_DIM_MUTE] = { + .offset = 0x31, .size = 8, .activate = 2 }, + + [SCARLETT2_CONFIG_LINE_OUT_VOLUME] = { + .offset = 0x34, .size = 16, .activate = 1 }, + + [SCARLETT2_CONFIG_MUTE_SWITCH] = { + .offset = 0x5c, .size = 8, .activate = 1 }, + + [SCARLETT2_CONFIG_SW_HW_SWITCH] = { + .offset = 0x66, .size = 8, .activate = 3 }, + + [SCARLETT2_CONFIG_MASTER_VOLUME] = { + .offset = 0x76, .size = 16 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x7c, .size = 8, .activate = 7 }, + + [SCARLETT2_CONFIG_PAD_SWITCH] = { + .offset = 0x84, .size = 8, .activate = 8 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x8c, .size = 8, .activate = 8 }, + + [SCARLETT2_CONFIG_STANDALONE_SWITCH] = { + .offset = 0x95, .size = 8, .activate = 6 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x9c, .size = 1, .activate = 8 }, + + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x9d, .size = 8, .activate = 6 }, + + [SCARLETT2_CONFIG_PHANTOM_PERSISTENCE] = { + .offset = 0x9e, .size = 8, .activate = 6 }, + + [SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH] = { + .offset = 0x9f, .size = 1, .activate = 10 }, + + [SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE] = { + .offset = 0xa0, .size = 1, .activate = 10 }, + + [SCARLETT2_CONFIG_TALKBACK_MAP] = { + .offset = 0xb0, .size = 16, .activate = 10 }, + + [SCARLETT2_CONFIG_SPDIF_MODE] = { + .offset = 0x94, .size = 8, .activate = 6 }, + } +}; + +/* Vocaster */ +static const struct scarlett2_config_set scarlett2_config_set_vocaster = { + .notifications = vocaster_notifications, + .param_buf_addr = 0x1bc, + .input_gain_tlv = db_scale_vocaster_gain, + .autogain_status_texts = scarlett2_autogain_status_vocaster, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x9d, .size = 8, .activate = 6 }, + + [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = { + .offset = 0x1c0, .size = 8, .activate = 19, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = { + .offset = 0x1c2, .size = 8, }, + + [SCARLETT2_CONFIG_AG_HOT_TARGET] = { + .offset = 0xc1, .size = 8, .activate = 29, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_GAIN] = { + .offset = 0x9f, .size = 8, .activate = 21, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x9c, .size = 1, .activate = 20, .pbuf = 1 }, + + [SCARLETT2_CONFIG_DSP_SWITCH] = { + .offset = 0x1c4, .size = 8, .activate = 22, .pbuf = 1 }, + + [SCARLETT2_CONFIG_COMPRESSOR_PARAMS] = { + .offset = 0x1c8, .size = 32, .activate = 23 }, + + [SCARLETT2_CONFIG_PRECOMP_FLT_SWITCH] = { + .offset = 0x7c, .size = 32, .activate = 27 }, + + [SCARLETT2_CONFIG_PRECOMP_FLT_PARAMS] = { + .offset = 0x200, .size = 32, .activate = 27 }, + + [SCARLETT2_CONFIG_PEQ_FLT_SWITCH] = { + .offset = 0x84, .size = 32, .activate = 27 }, + + [SCARLETT2_CONFIG_PEQ_FLT_PARAMS] = { + .offset = 0x250, .size = 32, .activate = 27 }, + + [SCARLETT2_CONFIG_INPUT_MUTE_SWITCH] = { + .offset = 0x1be, .size = 8, .activate = 17, .pbuf = 1 }, + + [SCARLETT2_CONFIG_BLUETOOTH_VOLUME] = { + .offset = 0xbf, .size = 8, .activate = 28 }, + + [SCARLETT2_CONFIG_SP_HP_MUTE] = { + .offset = 0xab, .size = 8, .activate = 10 }, + } +}; + +/* Solo Gen 4 */ +static const struct scarlett2_config_set scarlett2_config_set_gen4_solo = { + .notifications = scarlett4_solo_notifications, + .param_buf_addr = 0xd8, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x47, .size = 8, .activate = 4 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR] = { + .offset = 0x108, .size = 8, .activate = 12, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x46, .size = 8, .activate = 9, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x3d, .size = 8, .activate = 10, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x3e, .size = 8, .activate = 11, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PCM_INPUT_SWITCH] = { + .offset = 0x206, .size = 8, .activate = 25, .pbuf = 1 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN] = { + .offset = 0x232, .size = 16, .activate = 26 } + } +}; + +/* 2i2 Gen 4 */ +static const struct scarlett2_config_set scarlett2_config_set_gen4_2i2 = { + .notifications = scarlett4_2i2_notifications, + .param_buf_addr = 0xfc, + .input_gain_tlv = db_scale_gen4_gain, + .autogain_status_texts = scarlett2_autogain_status_gen4, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x49, .size = 8, .activate = 4 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR] = { + .offset = 0x14a, .size = 8, .activate = 16, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = { + .offset = 0x135, .size = 8, .activate = 10, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = { + .offset = 0x137, .size = 8 }, + + [SCARLETT2_CONFIG_AG_MEAN_TARGET] = { + .offset = 0x131, .size = 8, .activate = 29, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AG_PEAK_TARGET] = { + .offset = 0x132, .size = 8, .activate = 30, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x48, .size = 8, .activate = 11, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_INPUT_GAIN] = { + .offset = 0x4b, .size = 8, .activate = 12, .pbuf = 1 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x3c, .size = 8, .activate = 13, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_SAFE_SWITCH] = { + .offset = 0x147, .size = 8, .activate = 14, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x3e, .size = 8, .activate = 15, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_SELECT_SWITCH] = { + .offset = 0x14b, .size = 8, .activate = 17, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_LINK_SWITCH] = { + .offset = 0x14e, .size = 8, .activate = 18, .pbuf = 1 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN] = { + .offset = 0x2a0, .size = 16, .activate = 36 } + } +}; + +/* 4i4 Gen 4 */ +static const struct scarlett2_config_set scarlett2_config_set_gen4_4i4 = { + .notifications = scarlett4_4i4_notifications, + .param_buf_addr = 0x130, + .input_gain_tlv = db_scale_gen4_gain, + .autogain_status_texts = scarlett2_autogain_status_gen4, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x5c, .size = 8, .activate = 4 }, + + [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = { + .offset = 0x13e, .size = 8, .activate = 10, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = { + .offset = 0x140, .size = 8 }, + + [SCARLETT2_CONFIG_AG_MEAN_TARGET] = { + .offset = 0x13a, .size = 8, .activate = 23, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AG_PEAK_TARGET] = { + .offset = 0x13b, .size = 8, .activate = 24, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x5a, .size = 8, .activate = 11, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_INPUT_GAIN] = { + .offset = 0x5e, .size = 8, .activate = 12, .pbuf = 1 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x4e, .size = 8, .activate = 13, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_SAFE_SWITCH] = { + .offset = 0x150, .size = 8, .activate = 14, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x50, .size = 8, .activate = 15, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_SELECT_SWITCH] = { + .offset = 0x153, .size = 8, .activate = 16, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_LINK_SWITCH] = { + .offset = 0x156, .size = 8, .activate = 17, .pbuf = 1 }, + + [SCARLETT2_CONFIG_MASTER_VOLUME] = { + .offset = 0x32, .size = 16 }, + + [SCARLETT2_CONFIG_HEADPHONE_VOLUME] = { + .offset = 0x3a, .size = 16 }, + + [SCARLETT2_CONFIG_POWER_EXT] = { + .offset = 0x168, .size = 8 }, + + [SCARLETT2_CONFIG_POWER_LOW] = { + .offset = 0x16d, .size = 8 } + } +}; + +/* Clarett USB and Clarett+ devices: 2Pre, 4Pre, 8Pre */ +static const struct scarlett2_config_set scarlett2_config_set_clarett = { + .notifications = scarlett2_notifications, + .items = { + [SCARLETT2_CONFIG_DIM_MUTE] = { + .offset = 0x31, .size = 8, .activate = 2 }, + + [SCARLETT2_CONFIG_LINE_OUT_VOLUME] = { + .offset = 0x34, .size = 16, .activate = 1 }, + + [SCARLETT2_CONFIG_MUTE_SWITCH] = { + .offset = 0x5c, .size = 8, .activate = 1 }, + + [SCARLETT2_CONFIG_SW_HW_SWITCH] = { + .offset = 0x66, .size = 8, .activate = 3 }, + + [SCARLETT2_CONFIG_MASTER_VOLUME] = { + .offset = 0x76, .size = 16 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x7c, .size = 8, .activate = 7 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x95, .size = 8, .activate = 8 }, + + [SCARLETT2_CONFIG_STANDALONE_SWITCH] = { + .offset = 0x8d, .size = 8, .activate = 6 }, + + [SCARLETT2_CONFIG_SPDIF_MODE] = { + .offset = 0x9e, .size = 8, .activate = 4 }, + } +}; + +/* Description of each hardware port type: + * - id: hardware ID of this port type + * - src_descr: printf format string for mux input selections + * - src_num_offset: added to channel number for the fprintf + * - dst_descr: printf format string for mixer controls + */ +struct scarlett2_port { + u16 id; + const char * const src_descr; + int src_num_offset; + const char * const dst_descr; + const char * const dsp_src_descr; + const char * const dsp_dst_descr; +}; + +static const struct scarlett2_port scarlett2_ports[SCARLETT2_PORT_TYPE_COUNT] = { + [SCARLETT2_PORT_TYPE_NONE] = { + .id = 0x000, + .src_descr = "Off" + }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { + .id = 0x080, + .src_descr = "Analogue %d", + .src_num_offset = 1, + .dst_descr = "Analogue Output %02d Playback" + }, + [SCARLETT2_PORT_TYPE_SPDIF] = { + .id = 0x180, + .src_descr = "S/PDIF %d", + .src_num_offset = 1, + .dst_descr = "S/PDIF Output %d Playback" + }, + [SCARLETT2_PORT_TYPE_ADAT] = { + .id = 0x200, + .src_descr = "ADAT %d", + .src_num_offset = 1, + .dst_descr = "ADAT Output %d Playback" + }, + [SCARLETT2_PORT_TYPE_MIX] = { + .id = 0x300, + .src_descr = "Mix %c", + .src_num_offset = 'A', + .dst_descr = "Mixer Input %02d Capture", + .dsp_src_descr = "DSP %d", + .dsp_dst_descr = "DSP Input %d Capture" + }, + [SCARLETT2_PORT_TYPE_PCM] = { + .id = 0x600, + .src_descr = "PCM %d", + .src_num_offset = 1, + .dst_descr = "PCM %02d Capture" + }, +}; + +/* Number of mux tables: one for each band of sample rates + * (44.1/48kHz, 88.2/96kHz, and 176.4/176kHz) + */ +#define SCARLETT2_MUX_TABLES 3 + +/* Maximum number of entries in a mux table */ +#define SCARLETT2_MAX_MUX_ENTRIES 10 + +/* One entry within mux_assignment defines the port type and range of + * ports to add to the set_mux message. The end of the list is marked + * with count == 0. + */ +struct scarlett2_mux_entry { + u8 port_type; + u8 start; + u8 count; +}; + +/* Maximum number of entries in a mux table */ +#define SCARLETT2_MAX_METER_ENTRIES 9 + +/* One entry within meter_assignment defines the range of mux outputs + * that consecutive meter entries are mapped to. The end of the list + * is marked with count == 0. + */ +struct scarlett2_meter_entry { + u8 start; + u8 count; +}; + +struct scarlett2_device_info { + /* which set of configuration parameters the device uses */ + const struct scarlett2_config_set *config_set; + + /* minimum firmware version required */ + u16 min_firmware_version; + + /* has a downloadable device map */ + u8 has_devmap; + + /* support for main/alt speaker switching */ + u8 has_speaker_switching; + + /* support for talkback microphone */ + u8 has_talkback; + + /* the number of analogue inputs with a software switchable + * level control that can be set to line or instrument + */ + u8 level_input_count; + + /* the first input with a level control (0-based) */ + u8 level_input_first; + + /* the number of analogue inputs with a software switchable + * 10dB pad control + */ + u8 pad_input_count; + + /* the number of analogue inputs with a software switchable + * "air" control + */ + u8 air_input_count; + + /* the first input with an air control (0-based) */ + u8 air_input_first; + + /* number of additional air options + * 0 for air presence only (Gen 3) + * 1 for air presence+drive (Gen 4) + */ + u8 air_option; + + /* the number of analogue inputs with DSP control */ + u8 dsp_input_count; + + /* number of pre-compressor filters */ + u8 precomp_flt_count; + + /* number of parametric EQ filters */ + u8 peq_flt_count; + + /* number of PEQ filters plus unused slots */ + u8 peq_flt_total_count; + + /* the number of analogue inputs with a software switchable + * mute control + */ + u8 mute_input_count; + + /* the number of phantom (48V) software switchable controls */ + u8 phantom_count; + + /* the first input with phantom power control (0-based) */ + u8 phantom_first; + + /* the number of inputs each phantom switch controls */ + u8 inputs_per_phantom; + + /* the number of inputs with software-controllable gain */ + u8 gain_input_count; + + /* the number of inputs with safe mode */ + u8 safe_input_count; + + /* the number of direct monitor options + * (0 = none, 1 = mono only, 2 = mono/stereo) + */ + u8 direct_monitor; + + /* the number of DSP channels */ + u8 dsp_count; + + /* has a Bluetooth module with volume control */ + u8 has_bluetooth; + + /* S/PDIF Source/Digital I/O mode control */ + const char * const spdif_mode_control_name; + const u8 *spdif_mode_values; + const char * const *spdif_mode_texts; + + /* remap analogue outputs; 18i8 Gen 3 has "line 3/4" connected + * internally to the analogue 7/8 outputs + */ + u8 line_out_remap_enable; + u8 line_out_remap[SCARLETT2_ANALOGUE_MAX]; + u8 line_out_unmap[SCARLETT2_ANALOGUE_MAX]; + + /* additional description for the line out volume controls */ + const char * const line_out_descrs[SCARLETT2_ANALOGUE_MAX]; + + /* Vocaster speaker/headphone mute control names */ + const char * const *sp_hp_mute_names; + + /* number of sources/destinations of each port type */ + const int port_count[SCARLETT2_PORT_TYPE_COUNT][SCARLETT2_PORT_DIRNS]; + + /* layout/order of the entries in the set_mux message */ + struct scarlett2_mux_entry mux_assignment[SCARLETT2_MUX_TABLES] + [SCARLETT2_MAX_MUX_ENTRIES]; + + /* map from meter level order returned by + * SCARLETT2_USB_GET_METER to index into mux[] entries (same + * as the order returned by scarlett2_meter_ctl_get()) + */ + struct scarlett2_meter_entry meter_map[SCARLETT2_MAX_METER_ENTRIES]; +}; + +struct scarlett2_data { + struct usb_mixer_interface *mixer; + struct mutex usb_mutex; /* prevent sending concurrent USB requests */ + struct completion cmd_done; + struct mutex data_mutex; /* lock access to this data */ + u8 running; + u8 hwdep_in_use; + u8 selected_flash_segment_id; + u8 flash_write_state; + struct delayed_work work; + const struct scarlett2_device_info *info; + const struct scarlett2_config_set *config_set; + const char *series_name; + __u8 bInterfaceNumber; + __u8 bEndpointAddress; + __u16 wMaxPacketSize; + __u8 bInterval; + u8 num_mux_srcs; + u8 num_mux_dsts; + u8 num_mix_in; + u8 num_mix_out; + u8 num_line_out; + u8 num_monitor_mix_ctls; + u8 num_autogain_status_texts; + u32 firmware_version; + u8 flash_segment_nums[SCARLETT2_SEGMENT_ID_COUNT]; + u8 flash_segment_blocks[SCARLETT2_SEGMENT_ID_COUNT]; + u16 scarlett2_seq; + u8 sync_updated; + u8 vol_updated; + u8 dim_mute_updated; + u8 input_level_updated; + u8 input_pad_updated; + u8 input_air_updated; + u8 input_dsp_updated; + u8 input_mute_updated; + u8 input_phantom_updated; + u8 input_select_updated; + u8 input_gain_updated; + u8 autogain_updated; + u8 input_safe_updated; + u8 pcm_input_switch_updated; + u8 monitor_other_updated; + u8 direct_monitor_updated; + u8 mux_updated; + u8 mix_updated; + u8 speaker_switching_switched; + u8 power_status_updated; + u8 bluetooth_updated; + u8 sync; + u8 master_vol; + u8 headphone_vol; + u8 vol[SCARLETT2_ANALOGUE_MAX]; + u8 vol_sw_hw_switch[SCARLETT2_ANALOGUE_MAX]; + u8 mute_switch[SCARLETT2_ANALOGUE_MAX]; + u8 level_switch[SCARLETT2_LEVEL_SWITCH_MAX]; + u8 pad_switch[SCARLETT2_PAD_SWITCH_MAX]; + u8 dim_mute[SCARLETT2_DIM_MUTE_COUNT]; + u8 sp_hp_mute; + u8 air_switch[SCARLETT2_AIR_SWITCH_MAX]; + u8 dsp_switch[SCARLETT2_DSP_SWITCH_MAX]; + s32 compressor_values[SCARLETT2_COMPRESSOR_CTLS_MAX]; + s32 precomp_flt_values[SCARLETT2_PRECOMP_FLT_VALUES_MAX]; + s32 peq_flt_values[SCARLETT2_PEQ_FLT_VALUES_MAX]; + u8 precomp_flt_switch[SCARLETT2_DSP_SWITCH_MAX]; + u8 peq_flt_switch[SCARLETT2_DSP_SWITCH_MAX]; + u8 input_mute_switch[SCARLETT2_INPUT_MUTE_SWITCH_MAX]; + u8 phantom_switch[SCARLETT2_PHANTOM_SWITCH_MAX]; + u8 phantom_persistence; + u8 input_select_switch; + u8 input_link_switch[SCARLETT2_INPUT_GAIN_MAX]; + u8 gain[SCARLETT2_INPUT_GAIN_MAX]; + u8 autogain_switch[SCARLETT2_INPUT_GAIN_MAX]; + u8 autogain_status[SCARLETT2_INPUT_GAIN_MAX]; + s8 ag_targets[SCARLETT2_AG_TARGET_COUNT]; + u8 safe_switch[SCARLETT2_INPUT_GAIN_MAX]; + u8 pcm_input_switch; + u8 direct_monitor_switch; + u8 speaker_switching_switch; + u8 talkback_switch; + u8 talkback_map[SCARLETT2_OUTPUT_MIX_MAX]; + u8 msd_switch; + u8 standalone_switch; + u8 power_status; + u8 bluetooth_volume; + u8 spdif_mode; + u8 meter_level_map[SCARLETT2_MAX_METERS]; + struct snd_kcontrol *sync_ctl; + struct snd_kcontrol *master_vol_ctl; + struct snd_kcontrol *headphone_vol_ctl; + struct snd_kcontrol *vol_ctls[SCARLETT2_ANALOGUE_MAX]; + struct snd_kcontrol *sw_hw_ctls[SCARLETT2_ANALOGUE_MAX]; + struct snd_kcontrol *mute_ctls[SCARLETT2_ANALOGUE_MAX]; + struct snd_kcontrol *dim_mute_ctls[SCARLETT2_DIM_MUTE_COUNT]; + struct snd_kcontrol *level_ctls[SCARLETT2_LEVEL_SWITCH_MAX]; + struct snd_kcontrol *pad_ctls[SCARLETT2_PAD_SWITCH_MAX]; + struct snd_kcontrol *air_ctls[SCARLETT2_AIR_SWITCH_MAX]; + struct snd_kcontrol *dsp_ctls[SCARLETT2_DSP_SWITCH_MAX]; + struct snd_kcontrol *input_mute_ctls[SCARLETT2_INPUT_MUTE_SWITCH_MAX]; + struct snd_kcontrol *phantom_ctls[SCARLETT2_PHANTOM_SWITCH_MAX]; + struct snd_kcontrol *input_select_ctl; + struct snd_kcontrol *input_link_ctls[SCARLETT2_INPUT_GAIN_MAX]; + struct snd_kcontrol *input_gain_ctls[SCARLETT2_INPUT_GAIN_MAX]; + struct snd_kcontrol *autogain_ctls[SCARLETT2_INPUT_GAIN_MAX]; + struct snd_kcontrol *autogain_status_ctls[SCARLETT2_INPUT_GAIN_MAX]; + struct snd_kcontrol *ag_target_ctls[SCARLETT2_AG_TARGET_COUNT]; + struct snd_kcontrol *safe_ctls[SCARLETT2_INPUT_GAIN_MAX]; + struct snd_kcontrol *pcm_input_switch_ctl; + struct snd_kcontrol *mux_ctls[SCARLETT2_MUX_MAX]; + struct snd_kcontrol *mix_ctls[SCARLETT2_MIX_MAX]; + struct snd_kcontrol *compressor_ctls[SCARLETT2_COMPRESSOR_CTLS_MAX]; + struct snd_kcontrol *precomp_flt_ctls[SCARLETT2_PRECOMP_FLT_CTLS_MAX]; + struct snd_kcontrol *peq_flt_ctls[SCARLETT2_PEQ_FLT_CTLS_MAX]; + struct snd_kcontrol *precomp_flt_switch_ctls[SCARLETT2_DSP_SWITCH_MAX]; + struct snd_kcontrol *peq_flt_switch_ctls[SCARLETT2_DSP_SWITCH_MAX]; + struct snd_kcontrol *direct_monitor_ctl; + struct snd_kcontrol *speaker_switching_ctl; + struct snd_kcontrol *talkback_ctl; + struct snd_kcontrol *power_status_ctl; + struct snd_kcontrol *bluetooth_volume_ctl; + u8 mux[SCARLETT2_MUX_MAX]; + u8 mix[SCARLETT2_MIX_MAX]; + u8 monitor_mix[SCARLETT2_MONITOR_MIX_MAX]; +}; + +/*** Model-specific data ***/ + +static const struct scarlett2_device_info s6i6_gen2_info = { + .config_set = &scarlett2_config_set_gen2a, + .level_input_count = 2, + .pad_input_count = 2, + + .line_out_descrs = { + "Headphones 1 L", + "Headphones 1 R", + "Headphones 2 L", + "Headphones 2 R", + }, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 4, 4 }, + [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 }, + [SCARLETT2_PORT_TYPE_PCM] = { 6, 6 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_PCM, 0, 6 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 6 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 6 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 24, 6 }, + { 0, 24 }, + { 0, 0 }, + } +}; + +static const struct scarlett2_device_info s18i8_gen2_info = { + .config_set = &scarlett2_config_set_gen2a, + .level_input_count = 2, + .pad_input_count = 4, + + .line_out_descrs = { + "Monitor L", + "Monitor R", + "Headphones 1 L", + "Headphones 1 R", + "Headphones 2 L", + "Headphones 2 R", + }, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 6 }, + [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_ADAT] = { 8, 0 }, + [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 }, + [SCARLETT2_PORT_TYPE_PCM] = { 8, 18 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_PCM, 0, 18 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 14 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 10 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 4 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 26, 18 }, + { 0, 26 }, + { 0, 0 }, + } +}; + +static const struct scarlett2_device_info s18i20_gen2_info = { + .config_set = &scarlett2_config_set_gen2b, + + .line_out_descrs = { + "Monitor L", + "Monitor R", + NULL, + NULL, + NULL, + NULL, + "Headphones 1 L", + "Headphones 1 R", + "Headphones 2 L", + "Headphones 2 R", + }, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 10 }, + [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_ADAT] = { 8, 8 }, + [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 }, + [SCARLETT2_PORT_TYPE_PCM] = { 20, 18 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_PCM, 0, 18 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_ADAT, 0, 8 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 14 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_ADAT, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 10 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 6 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 38, 18 }, + { 0, 38 }, + { 0, 0 }, + } +}; + +static const struct scarlett2_device_info solo_gen3_info = { + .config_set = &scarlett2_config_set_gen3a, + .level_input_count = 1, + .level_input_first = 1, + .air_input_count = 1, + .phantom_count = 1, + .inputs_per_phantom = 1, + .direct_monitor = 1, +}; + +static const struct scarlett2_device_info s2i2_gen3_info = { + .config_set = &scarlett2_config_set_gen3a, + .level_input_count = 2, + .air_input_count = 2, + .phantom_count = 1, + .inputs_per_phantom = 2, + .direct_monitor = 2, +}; + +static const struct scarlett2_device_info s4i4_gen3_info = { + .config_set = &scarlett2_config_set_gen3b, + .level_input_count = 2, + .pad_input_count = 2, + .air_input_count = 2, + .phantom_count = 1, + .inputs_per_phantom = 2, + + .line_out_descrs = { + "Monitor L", + "Monitor R", + "Headphones L", + "Headphones R", + }, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 4, 4 }, + [SCARLETT2_PORT_TYPE_MIX] = { 6, 8 }, + [SCARLETT2_PORT_TYPE_PCM] = { 4, 6 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_PCM, 0, 6 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 8 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 16 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 6 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 8 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 16 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 6 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 8 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 16 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 12, 6 }, + { 0, 12 }, + { 0, 0 }, + } +}; + +static const struct scarlett2_device_info s8i6_gen3_info = { + .config_set = &scarlett2_config_set_gen3b, + .level_input_count = 2, + .pad_input_count = 2, + .air_input_count = 2, + .phantom_count = 1, + .inputs_per_phantom = 2, + + .line_out_descrs = { + "Headphones 1 L", + "Headphones 1 R", + "Headphones 2 L", + "Headphones 2 R", + }, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 6, 4 }, + [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_MIX] = { 8, 8 }, + [SCARLETT2_PORT_TYPE_PCM] = { 6, 10 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_PCM, 0, 8 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 8, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 8 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 18 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 8 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 8, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 8 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 18 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 8 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 8, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 8 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 18 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 14, 8 }, + { 0, 6 }, + { 22, 2 }, + { 6, 8 }, + { 0, 0 }, + } +}; + +static const u8 scarlett2_spdif_s18i8_gen3_values[] = { 0, 2, 0xff }; + +static const char * const scarlett2_spdif_s18i8_gen3_texts[] = { + "RCA", + "Optical", + NULL +}; + +static const struct scarlett2_device_info s18i8_gen3_info = { + .config_set = &scarlett2_config_set_gen3c, + .has_speaker_switching = 1, + .level_input_count = 2, + .pad_input_count = 4, + .air_input_count = 4, + .phantom_count = 2, + .inputs_per_phantom = 2, + + .spdif_mode_control_name = "S/PDIF Mode Capture Enum", + .spdif_mode_values = scarlett2_spdif_s18i8_gen3_values, + .spdif_mode_texts = scarlett2_spdif_s18i8_gen3_texts, + + .line_out_remap_enable = 1, + .line_out_remap = { 0, 1, 6, 7, 2, 3, 4, 5 }, + .line_out_unmap = { 0, 1, 4, 5, 6, 7, 2, 3 }, + + .line_out_descrs = { + "Monitor L", + "Monitor R", + "Alt Monitor L", + "Alt Monitor R", + "Headphones 1 L", + "Headphones 1 R", + "Headphones 2 L", + "Headphones 2 R", + }, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 8 }, + [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_ADAT] = { 8, 0 }, + [SCARLETT2_PORT_TYPE_MIX] = { 10, 20 }, + [SCARLETT2_PORT_TYPE_PCM] = { 8, 20 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_PCM, 0, 10 }, + { SCARLETT2_PORT_TYPE_PCM, 12, 8 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 6, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 2, 4 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 10, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 20 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 10 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 10 }, + { SCARLETT2_PORT_TYPE_PCM, 12, 4 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 6, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 2, 4 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 10, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 20 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 10 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 10 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 6, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 2, 4 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 20 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 10 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 30, 10 }, + { 42, 8 }, + { 0, 2 }, + { 6, 2 }, + { 2, 4 }, + { 8, 2 }, + { 40, 2 }, + { 10, 20 }, + { 0, 0 } + } +}; + +static const u8 scarlett2_spdif_s18i20_gen3_values[] = { 0, 6, 1, 0xff }; + +static const char * const scarlett2_spdif_s18i20_gen3_texts[] = { + "S/PDIF RCA", + "S/PDIF Optical", + "Dual ADAT", + NULL +}; + +static const struct scarlett2_device_info s18i20_gen3_info = { + .config_set = &scarlett2_config_set_gen3c, + .has_speaker_switching = 1, + .has_talkback = 1, + .level_input_count = 2, + .pad_input_count = 8, + .air_input_count = 8, + .phantom_count = 2, + .inputs_per_phantom = 4, + + .spdif_mode_control_name = "Digital I/O Mode Capture Enum", + .spdif_mode_values = scarlett2_spdif_s18i20_gen3_values, + .spdif_mode_texts = scarlett2_spdif_s18i20_gen3_texts, + + .line_out_descrs = { + "Monitor 1 L", + "Monitor 1 R", + "Monitor 2 L", + "Monitor 2 R", + NULL, + NULL, + "Headphones 1 L", + "Headphones 1 R", + "Headphones 2 L", + "Headphones 2 R", + }, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 9, 10 }, + [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_ADAT] = { 8, 8 }, + [SCARLETT2_PORT_TYPE_MIX] = { 12, 25 }, + [SCARLETT2_PORT_TYPE_PCM] = { 20, 20 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_PCM, 0, 8 }, + { SCARLETT2_PORT_TYPE_PCM, 10, 10 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_ADAT, 0, 8 }, + { SCARLETT2_PORT_TYPE_PCM, 8, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 25 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 12 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 8 }, + { SCARLETT2_PORT_TYPE_PCM, 10, 8 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_ADAT, 0, 8 }, + { SCARLETT2_PORT_TYPE_PCM, 8, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 25 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 10 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 10 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 24 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 45, 8 }, + { 55, 10 }, + { 0, 20 }, + { 53, 2 }, + { 20, 25 }, + { 0, 0 }, + } +}; + +static const struct scarlett2_device_info vocaster_one_info = { + .config_set = &scarlett2_config_set_vocaster, + .min_firmware_version = 1769, + .has_devmap = 1, + + .phantom_count = 1, + .inputs_per_phantom = 1, + .dsp_count = 1, + .dsp_input_count = 1, + .precomp_flt_count = 2, + .peq_flt_count = 3, + .peq_flt_total_count = 4, + .mute_input_count = 1, + .gain_input_count = 1, + .sp_hp_mute_names = vocaster_one_sp_hp_mute_names, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 2, 4 }, + [SCARLETT2_PORT_TYPE_MIX] = { 9, 9 }, + [SCARLETT2_PORT_TYPE_PCM] = { 4, 10 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_MIX, 8, 1 }, + { SCARLETT2_PORT_TYPE_PCM, 5, 5 }, + { SCARLETT2_PORT_TYPE_MIX, 6, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 5 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 6 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 12, 1 }, + { 18, 5 }, + { 10, 2 }, + { 13, 5 }, + { 4, 6 }, + { 0, 4 }, + { 0, 0 } + } +}; + +static const struct scarlett2_device_info vocaster_two_info = { + .config_set = &scarlett2_config_set_vocaster, + .min_firmware_version = 1769, + .has_devmap = 1, + + .phantom_count = 2, + .inputs_per_phantom = 1, + .dsp_count = 2, + .dsp_input_count = 2, + .precomp_flt_count = 2, + .peq_flt_count = 3, + .peq_flt_total_count = 4, + .mute_input_count = 2, + .gain_input_count = 2, + .has_bluetooth = 1, + .sp_hp_mute_names = vocaster_two_sp_hp_mute_names, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 6, 6 }, + [SCARLETT2_PORT_TYPE_MIX] = { 12, 14 }, + [SCARLETT2_PORT_TYPE_PCM] = { 4, 14 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_MIX, 12, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 6, 8 }, + { SCARLETT2_PORT_TYPE_MIX, 10, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 6 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 10 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 18, 2 }, + { 26, 8 }, + { 16, 2 }, + { 20, 6 }, + { 6, 10 }, + { 0, 6 }, + { 0, 0 } + } +}; + +static const struct scarlett2_device_info solo_gen4_info = { + .config_set = &scarlett2_config_set_gen4_solo, + .min_firmware_version = 2115, + .has_devmap = 1, + + .level_input_count = 1, + .air_input_count = 1, + .air_input_first = 1, + .air_option = 1, + .phantom_count = 1, + .phantom_first = 1, + .inputs_per_phantom = 1, + .direct_monitor = 1, + .dsp_count = 2, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_MIX] = { 8, 6 }, + [SCARLETT2_PORT_TYPE_PCM] = { 2, 4 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_MIX, 4, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 2, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_MIX, 4, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 2, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_MIX, 4, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 2, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 6, 2 }, + { 4, 2 }, + { 8, 4 }, + { 2, 2 }, + { 0, 2 }, + { 0, 0 } + } +}; + +static const struct scarlett2_device_info s2i2_gen4_info = { + .config_set = &scarlett2_config_set_gen4_2i2, + .min_firmware_version = 2115, + .has_devmap = 1, + + .level_input_count = 2, + .air_input_count = 2, + .air_option = 1, + .phantom_count = 1, + .inputs_per_phantom = 2, + .gain_input_count = 2, + .safe_input_count = 2, + .direct_monitor = 2, + .dsp_count = 2, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_MIX] = { 6, 6 }, + [SCARLETT2_PORT_TYPE_PCM] = { 2, 4 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_MIX, 4, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 2, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_MIX, 4, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 2, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_MIX, 4, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 2, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 6, 2 }, + { 4, 2 }, + { 8, 4 }, + { 2, 2 }, + { 0, 2 }, + { 0, 0 } + } +}; + +static const struct scarlett2_device_info s4i4_gen4_info = { + .config_set = &scarlett2_config_set_gen4_4i4, + .min_firmware_version = 2089, + .has_devmap = 1, + + .level_input_count = 2, + .air_input_count = 2, + .air_option = 1, + .phantom_count = 2, + .inputs_per_phantom = 1, + .gain_input_count = 2, + .safe_input_count = 2, + .dsp_count = 2, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 4, 6 }, + [SCARLETT2_PORT_TYPE_MIX] = { 8, 12 }, + [SCARLETT2_PORT_TYPE_PCM] = { 6, 6 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_MIX, 10, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 6 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 10 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_MIX, 10, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 6 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 10 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_MIX, 10, 2 }, + { SCARLETT2_PORT_TYPE_PCM, 0, 6 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 10 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 16, 8 }, + { 6, 10 }, + { 0, 6 }, + { 0, 0 } + } +}; + +static const struct scarlett2_device_info clarett_2pre_info = { + .config_set = &scarlett2_config_set_clarett, + .level_input_count = 2, + .air_input_count = 2, + + .line_out_descrs = { + "Monitor L", + "Monitor R", + "Headphones L", + "Headphones R", + }, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 2, 4 }, + [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 0 }, + [SCARLETT2_PORT_TYPE_ADAT] = { 8, 0 }, + [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 }, + [SCARLETT2_PORT_TYPE_PCM] = { 4, 12 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_PCM, 0, 12 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 8 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 2 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 26 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 22, 12 }, + { 0, 22 }, + { 0, 0 } + } +}; + +static const u8 scarlett2_spdif_clarett_values[] = { 0, 1, 2, 0xff }; + +static const char * const scarlett2_spdif_clarett_texts[] = { + "None", + "Optical", + "RCA", + NULL +}; + +static const struct scarlett2_device_info clarett_4pre_info = { + .config_set = &scarlett2_config_set_clarett, + .level_input_count = 2, + .air_input_count = 4, + + .spdif_mode_control_name = "S/PDIF Source Capture Enum", + .spdif_mode_values = scarlett2_spdif_clarett_values, + .spdif_mode_texts = scarlett2_spdif_clarett_texts, + + .line_out_descrs = { + "Monitor L", + "Monitor R", + "Headphones 1 L", + "Headphones 1 R", + "Headphones 2 L", + "Headphones 2 R", + }, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 6 }, + [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_ADAT] = { 8, 0 }, + [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 }, + [SCARLETT2_PORT_TYPE_PCM] = { 8, 18 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_PCM, 0, 18 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 14 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 12 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 24 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 26, 18 }, + { 0, 26 }, + { 0, 0 } + } +}; + +static const struct scarlett2_device_info clarett_8pre_info = { + .config_set = &scarlett2_config_set_clarett, + .level_input_count = 2, + .air_input_count = 8, + + .spdif_mode_control_name = "S/PDIF Source Capture Enum", + .spdif_mode_values = scarlett2_spdif_clarett_values, + .spdif_mode_texts = scarlett2_spdif_clarett_texts, + + .line_out_descrs = { + "Monitor L", + "Monitor R", + NULL, + NULL, + NULL, + NULL, + "Headphones 1 L", + "Headphones 1 R", + "Headphones 2 L", + "Headphones 2 R", + }, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 10 }, + [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_ADAT] = { 8, 8 }, + [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 }, + [SCARLETT2_PORT_TYPE_PCM] = { 20, 18 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_PCM, 0, 18 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_ADAT, 0, 8 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 14 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_ADAT, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 12 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 22 }, + { 0, 0, 0 }, + } }, + + .meter_map = { + { 38, 18 }, + { 0, 38 }, + { 0, 0 } + } +}; + +struct scarlett2_device_entry { + const u32 usb_id; /* USB device identifier */ + const struct scarlett2_device_info *info; + const char *series_name; +}; + +static const struct scarlett2_device_entry scarlett2_devices[] = { + /* Supported Gen 2 devices */ + { USB_ID(0x1235, 0x8203), &s6i6_gen2_info, "Scarlett Gen 2" }, + { USB_ID(0x1235, 0x8204), &s18i8_gen2_info, "Scarlett Gen 2" }, + { USB_ID(0x1235, 0x8201), &s18i20_gen2_info, "Scarlett Gen 2" }, + + /* Supported Gen 3 devices */ + { USB_ID(0x1235, 0x8211), &solo_gen3_info, "Scarlett Gen 3" }, + { USB_ID(0x1235, 0x8210), &s2i2_gen3_info, "Scarlett Gen 3" }, + { USB_ID(0x1235, 0x8212), &s4i4_gen3_info, "Scarlett Gen 3" }, + { USB_ID(0x1235, 0x8213), &s8i6_gen3_info, "Scarlett Gen 3" }, + { USB_ID(0x1235, 0x8214), &s18i8_gen3_info, "Scarlett Gen 3" }, + { USB_ID(0x1235, 0x8215), &s18i20_gen3_info, "Scarlett Gen 3" }, + + /* Supported Vocaster devices */ + { USB_ID(0x1235, 0x8216), &vocaster_one_info, "Vocaster" }, + { USB_ID(0x1235, 0x8217), &vocaster_two_info, "Vocaster" }, + + /* Supported Gen 4 devices */ + { USB_ID(0x1235, 0x8218), &solo_gen4_info, "Scarlett Gen 4" }, + { USB_ID(0x1235, 0x8219), &s2i2_gen4_info, "Scarlett Gen 4" }, + { USB_ID(0x1235, 0x821a), &s4i4_gen4_info, "Scarlett Gen 4" }, + + /* Supported Clarett USB/Clarett+ devices */ + { USB_ID(0x1235, 0x8206), &clarett_2pre_info, "Clarett USB" }, + { USB_ID(0x1235, 0x8207), &clarett_4pre_info, "Clarett USB" }, + { USB_ID(0x1235, 0x8208), &clarett_8pre_info, "Clarett USB" }, + { USB_ID(0x1235, 0x820a), &clarett_2pre_info, "Clarett+" }, + { USB_ID(0x1235, 0x820b), &clarett_4pre_info, "Clarett+" }, + { USB_ID(0x1235, 0x820c), &clarett_8pre_info, "Clarett+" }, + + /* End of list */ + { 0, NULL }, +}; + +/* get the starting port index number for a given port type/direction */ +static int scarlett2_get_port_start_num( + const int port_count[][SCARLETT2_PORT_DIRNS], + int direction, int port_type) +{ + int i, num = 0; + + for (i = 0; i < port_type; i++) + num += port_count[i][direction]; + + return num; +} + +/*** USB Interactions ***/ + +/* Commands for sending/receiving requests/responses */ +#define SCARLETT2_USB_CMD_INIT 0 +#define SCARLETT2_USB_CMD_REQ 2 +#define SCARLETT2_USB_CMD_RESP 3 + +#define SCARLETT2_USB_INIT_1 0x00000000 +#define SCARLETT2_USB_INIT_2 0x00000002 +#define SCARLETT2_USB_REBOOT 0x00000003 +#define SCARLETT2_USB_GET_METER 0x00001001 +#define SCARLETT2_USB_GET_MIX 0x00002001 +#define SCARLETT2_USB_SET_MIX 0x00002002 +#define SCARLETT2_USB_GET_MUX 0x00003001 +#define SCARLETT2_USB_SET_MUX 0x00003002 +#define SCARLETT2_USB_INFO_FLASH 0x00004000 +#define SCARLETT2_USB_INFO_SEGMENT 0x00004001 +#define SCARLETT2_USB_ERASE_SEGMENT 0x00004002 +#define SCARLETT2_USB_GET_ERASE 0x00004003 +#define SCARLETT2_USB_WRITE_SEGMENT 0x00004004 +#define SCARLETT2_USB_READ_SEGMENT 0x00004005 +#define SCARLETT2_USB_GET_SYNC 0x00006004 +#define SCARLETT2_USB_GET_DATA 0x00800000 +#define SCARLETT2_USB_SET_DATA 0x00800001 +#define SCARLETT2_USB_DATA_CMD 0x00800002 +#define SCARLETT2_USB_INFO_DEVMAP 0x0080000c +#define SCARLETT2_USB_GET_DEVMAP 0x0080000d + +#define SCARLETT2_USB_CONFIG_SAVE 6 + +#define SCARLETT2_USB_METER_LEVELS_GET_MAGIC 1 + +#define SCARLETT2_FLASH_BLOCK_SIZE 4096 +#define SCARLETT2_FLASH_RW_MAX 1024 +#define SCARLETT2_SEGMENT_NUM_MIN 1 +#define SCARLETT2_SEGMENT_NUM_MAX 4 + +#define SCARLETT2_SEGMENT_SETTINGS_NAME "App_Settings" +#define SCARLETT2_SEGMENT_FIRMWARE_NAME "App_Upgrade" + +/* Gen 4 device firmware provides access to a base64-encoded + * zlib-compressed JSON description of the device's capabilities and + * configuration. This device map is made available in + * /proc/asound/cardX/device-map.json.zz.b64 + */ +#define SCARLETT2_DEVMAP_BLOCK_SIZE 1024 +#define SCARLETT2_DEVMAP_FILENAME "device-map.json.zz.b64" + +/* proprietary request/response format */ +struct scarlett2_usb_packet { + __le32 cmd; + __le16 size; + __le16 seq; + __le32 error; + __le32 pad; + u8 data[]; +}; + +static void scarlett2_fill_request_header(struct scarlett2_data *private, + struct scarlett2_usb_packet *req, + u32 cmd, u16 req_size) +{ + /* sequence must go up by 1 for each request */ + u16 seq = private->scarlett2_seq++; + + req->cmd = cpu_to_le32(cmd); + req->size = cpu_to_le16(req_size); + req->seq = cpu_to_le16(seq); + req->error = 0; + req->pad = 0; +} + +static int scarlett2_usb_tx(struct usb_device *dev, int interface, + void *buf, u16 size) +{ + return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + SCARLETT2_USB_CMD_REQ, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + 0, interface, buf, size); +} + +static int scarlett2_usb_rx(struct usb_device *dev, int interface, + u32 usb_req, void *buf, u16 size) +{ + return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + usb_req, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + 0, interface, buf, size); +} + +/* Send a proprietary format request to the Scarlett interface */ +static int scarlett2_usb( + struct usb_mixer_interface *mixer, u32 cmd, + void *req_data, u16 req_size, void *resp_data, u16 resp_size) +{ + struct scarlett2_data *private = mixer->private_data; + struct usb_device *dev = mixer->chip->dev; + struct scarlett2_usb_packet *req __free(kfree) = NULL; + struct scarlett2_usb_packet *resp __free(kfree) = NULL; + size_t req_buf_size = struct_size(req, data, req_size); + size_t resp_buf_size = struct_size(resp, data, resp_size); + int retries = 0; + const int max_retries = 5; + int err; + + req = kmalloc(req_buf_size, GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kmalloc(resp_buf_size, GFP_KERNEL); + if (!resp) + return -ENOMEM; + + guard(mutex)(&private->usb_mutex); + + /* build request message and send it */ + + scarlett2_fill_request_header(private, req, cmd, req_size); + + if (req_size) + memcpy(req->data, req_data, req_size); + +retry: + err = scarlett2_usb_tx(dev, private->bInterfaceNumber, + req, req_buf_size); + + if (err != req_buf_size) { + if (err == -EPROTO && ++retries <= max_retries) { + msleep(5 * (1 << (retries - 1))); + goto retry; + } + usb_audio_err( + mixer->chip, + "%s USB request result cmd %x was %d\n", + private->series_name, cmd, err); + return -EINVAL; + } + + if (!wait_for_completion_timeout(&private->cmd_done, + msecs_to_jiffies(1000))) { + usb_audio_err( + mixer->chip, + "%s USB request timed out, cmd %x\n", + private->series_name, cmd); + + return -ETIMEDOUT; + } + + /* send a second message to get the response */ + + err = scarlett2_usb_rx(dev, private->bInterfaceNumber, + SCARLETT2_USB_CMD_RESP, + resp, resp_buf_size); + + /* validate the response */ + + if (err != resp_buf_size) { + + /* ESHUTDOWN and EPROTO are valid responses to a + * reboot request + */ + if (cmd == SCARLETT2_USB_REBOOT && + (err == -ESHUTDOWN || err == -EPROTO)) + return 0; + + usb_audio_err( + mixer->chip, + "%s USB response result cmd %x was %d expected %zu\n", + private->series_name, cmd, err, resp_buf_size); + return -EINVAL; + } + + /* cmd/seq/size should match except when initialising + * seq sent = 1, response = 0 + */ + if (resp->cmd != req->cmd || + (resp->seq != req->seq && + (le16_to_cpu(req->seq) != 1 || resp->seq != 0)) || + resp_size != le16_to_cpu(resp->size) || + resp->error || + resp->pad) { + usb_audio_err( + mixer->chip, + "%s USB invalid response; " + "cmd tx/rx %d/%d seq %d/%d size %d/%d " + "error %d pad %d\n", + private->series_name, + le32_to_cpu(req->cmd), le32_to_cpu(resp->cmd), + le16_to_cpu(req->seq), le16_to_cpu(resp->seq), + resp_size, le16_to_cpu(resp->size), + le32_to_cpu(resp->error), + le32_to_cpu(resp->pad)); + return -EINVAL; + } + + if (resp_data && resp_size > 0) + memcpy(resp_data, resp->data, resp_size); + + return err; +} + +/* Send a USB message to get data; result placed in *buf */ +static int scarlett2_usb_get( + struct usb_mixer_interface *mixer, + int offset, void *buf, int size) +{ + struct { + __le32 offset; + __le32 size; + } __packed req; + + req.offset = cpu_to_le32(offset); + req.size = cpu_to_le32(size); + return scarlett2_usb(mixer, SCARLETT2_USB_GET_DATA, + &req, sizeof(req), buf, size); +} + +/* Return true if the given configuration item is present in the + * configuration set used by this device. + */ +static int scarlett2_has_config_item( + struct scarlett2_data *private, int config_item_num) +{ + return !!private->config_set->items[config_item_num].offset; +} + +/* Send a USB message to get configuration parameters; result placed in *buf */ +static int scarlett2_usb_get_config( + struct usb_mixer_interface *mixer, + int config_item_num, int count, void *buf) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_config *config_item = + &private->config_set->items[config_item_num]; + int size, err, i; + u8 *buf_8; + u8 value; + + /* Check that the configuration item is present in the + * configuration set used by this device + */ + if (!config_item->offset) + return -EFAULT; + + /* Writes to the parameter buffer are always 1 byte */ + size = config_item->size ? config_item->size : 8; + + /* For byte-sized parameters, retrieve directly into buf */ + if (size >= 8) { + size = size / 8 * count; + err = scarlett2_usb_get(mixer, config_item->offset, buf, size); + if (err < 0) + return err; + if (size == 2) { + u16 *buf_16 = buf; + + for (i = 0; i < count; i++, buf_16++) + *buf_16 = le16_to_cpu(*(__le16 *)buf_16); + } else if (size == 4) { + u32 *buf_32 = buf; + + for (i = 0; i < count; i++, buf_32++) + *buf_32 = le32_to_cpu(*(__le32 *)buf_32); + } + return 0; + } + + /* For bit-sized parameters, retrieve into value */ + err = scarlett2_usb_get(mixer, config_item->offset, &value, 1); + if (err < 0) + return err; + + /* then unpack from value into buf[] */ + buf_8 = buf; + for (i = 0; i < 8 && i < count; i++, value >>= 1) + *buf_8++ = value & 1; + + return 0; +} + +/* Send a SCARLETT2_USB_SET_DATA command. + * offset: location in the device's data space + * size: size in bytes of the value (1, 2, 4) + */ +static int scarlett2_usb_set_data( + struct usb_mixer_interface *mixer, + int offset, int size, int value) +{ + struct scarlett2_data *private = mixer->private_data; + struct { + __le32 offset; + __le32 size; + __le32 value; + } __packed req; + + req.offset = cpu_to_le32(offset); + req.size = cpu_to_le32(size); + req.value = cpu_to_le32(value); + return scarlett2_usb(private->mixer, SCARLETT2_USB_SET_DATA, + &req, sizeof(u32) * 2 + size, NULL, 0); +} + +/* Send a SCARLETT2_USB_SET_DATA command with multiple values. + * offset: location in the device's data space + * size: size in bytes of each value (1, 2, 4) + * count: number of values + */ +static int scarlett2_usb_set_data_buf( + struct usb_mixer_interface *mixer, + int offset, int size, int count, void *buf) +{ + struct scarlett2_data *private = mixer->private_data; + int bytes = size * count; + struct { + __le32 offset; + __le32 size; + u8 data[]; + } __packed *req; + int err; + int buf_size = struct_size(req, data, bytes); + + req = kmalloc(buf_size, GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->offset = cpu_to_le32(offset); + req->size = cpu_to_le32(bytes); + if (size == 1) { + memcpy(req->data, buf, count); + } else if (size == 2) { + u16 *buf_16 = buf; + int i; + + for (i = 0; i < count; i++) + ((__le16 *)req->data)[i] = cpu_to_le16(buf_16[i]); + } else { + u32 *buf_32 = buf; + int i; + + for (i = 0; i < count; i++) + ((__le32 *)req->data)[i] = cpu_to_le32(buf_32[i]); + } + + err = scarlett2_usb(private->mixer, SCARLETT2_USB_SET_DATA, + req, buf_size, NULL, 0); + + kfree(req); + return err; +} + +/* Send a SCARLETT2_USB_DATA_CMD command. + * Configuration changes require activation with this after they have + * been uploaded by a previous SCARLETT2_USB_SET_DATA. + * The value for activate needed is determined by the configuration + * item. + */ +static int scarlett2_usb_activate_config( + struct usb_mixer_interface *mixer, int activate) +{ + __le32 req; + + req = cpu_to_le32(activate); + return scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD, + &req, sizeof(req), NULL, 0); +} + +/* Send USB messages to set a SCARLETT2_CONFIG_* parameter */ +static int scarlett2_usb_set_config( + struct usb_mixer_interface *mixer, + int config_item_num, int index, int value) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_config_set *config_set = private->config_set; + const struct scarlett2_config *config_item = + &config_set->items[config_item_num]; + int offset, size; + int err; + + /* Check that the configuration item is present in the + * configuration set used by this device + */ + if (!config_item->offset) + return -EFAULT; + + /* Write via the parameter buffer? */ + if (config_item->pbuf) { + if (!config_set->param_buf_addr) + return -EFAULT; + + /* Place index in param_buf_addr + 1 */ + err = scarlett2_usb_set_data( + mixer, config_set->param_buf_addr + 1, 1, index); + if (err < 0) + return err; + + /* Place value in param_buf_addr */ + err = scarlett2_usb_set_data( + mixer, config_set->param_buf_addr, 1, value); + if (err < 0) + return err; + + /* Activate the write through the parameter buffer */ + return scarlett2_usb_activate_config( + mixer, config_item->activate); + } + + /* Direct writes (not via the parameter buffer) need NVRAM + * save and support bit-modification + */ + + /* Cancel any pending NVRAM save */ + cancel_delayed_work_sync(&private->work); + + /* Convert config_item->size in bits to size in bytes and + * calculate offset + */ + if (config_item->size >= 8) { + size = config_item->size / 8; + offset = config_item->offset + index * size; + + /* If updating a bit, retrieve the old value, set/clear the + * bit as needed, and update value + */ + } else { + u8 tmp; + + size = 1; + offset = config_item->offset; + + err = scarlett2_usb_get(mixer, offset, &tmp, 1); + if (err < 0) + return err; + + if (value) + tmp |= (1 << index); + else + tmp &= ~(1 << index); + + value = tmp; + } + + /* Write the new value */ + err = scarlett2_usb_set_data(mixer, offset, size, value); + if (err < 0) + return err; + + /* Activate the change */ + err = scarlett2_usb_activate_config(mixer, config_item->activate); + if (err < 0) + return err; + + /* Interfaces with parameter buffer writes don't need a + * separate save step + */ + if (config_set->param_buf_addr) + return 0; + + /* Schedule the change to be written to NVRAM */ + if (config_item->activate != SCARLETT2_USB_CONFIG_SAVE) + schedule_delayed_work(&private->work, msecs_to_jiffies(2000)); + + return 0; +} + +/* Send USB messages to set a SCARLETT2_CONFIG_* parameter with + * multiple values + */ +static int scarlett2_usb_set_config_buf( + struct usb_mixer_interface *mixer, + int config_item_num, int index, int count, void *buf) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_config_set *config_set = private->config_set; + const struct scarlett2_config *config_item = + &config_set->items[config_item_num]; + int offset, size; + int err; + + /* Check that the configuration item is present in the + * configuration set used by this device + */ + if (!config_item->offset) + return -EFAULT; + + /* Convert config_item->size in bits to size in bytes and + * calculate offset + */ + if (config_item->size >= 8) { + size = config_item->size / 8; + offset = config_item->offset + index * size; + + /* Bit updates not supported */ + } else { + return -EFAULT; + } + + /* Write the new values */ + err = scarlett2_usb_set_data_buf(mixer, offset, size, count, buf); + if (err < 0) + return err; + + /* Activate the change */ + return scarlett2_usb_activate_config(mixer, config_item->activate); +} + +/* Send SCARLETT2_USB_DATA_CMD SCARLETT2_USB_CONFIG_SAVE */ +static void scarlett2_config_save(struct usb_mixer_interface *mixer) +{ + int err; + + err = scarlett2_usb_activate_config(mixer, SCARLETT2_USB_CONFIG_SAVE); + if (err < 0) + usb_audio_err(mixer->chip, "config save failed: %d\n", err); +} + +/* Delayed work to save config */ +static void scarlett2_config_save_work(struct work_struct *work) +{ + struct scarlett2_data *private = + container_of(work, struct scarlett2_data, work.work); + + scarlett2_config_save(private->mixer); +} + +/* Send a USB message to get sync status; result placed in *sync */ +static int scarlett2_usb_get_sync_status( + struct usb_mixer_interface *mixer, + u8 *sync) +{ + __le32 data; + int err; + + err = scarlett2_usb(mixer, SCARLETT2_USB_GET_SYNC, + NULL, 0, &data, sizeof(data)); + if (err < 0) + return err; + + *sync = !!data; + return 0; +} + +/* Return true if the device has a mixer that we can control */ +static int scarlett2_has_mixer(struct scarlett2_data *private) +{ + return !!private->info->mux_assignment[0][0].count; +} + +/* Map from mixer value to (db + 80) * 2 + * (reverse of scarlett2_mixer_values[]) + */ +static int scarlett2_mixer_value_to_db(int value) +{ + int i; + + for (i = 0; i < SCARLETT2_MIXER_VALUE_COUNT; i++) + if (scarlett2_mixer_values[i] >= value) + return i; + return SCARLETT2_MIXER_MAX_VALUE; +} + +/* Send a USB message to get the volumes for all inputs of one mix + * and put the values into private->mix[] + */ +static int scarlett2_usb_get_mix(struct usb_mixer_interface *mixer, + int mix_num) +{ + struct scarlett2_data *private = mixer->private_data; + + int num_mixer_in = private->num_mix_in; + int err, i, j; + + struct { + __le16 mix_num; + __le16 count; + } __packed req; + + __le16 data[SCARLETT2_INPUT_MIX_MAX]; + + req.mix_num = cpu_to_le16(mix_num); + req.count = cpu_to_le16(num_mixer_in); + + err = scarlett2_usb(mixer, SCARLETT2_USB_GET_MIX, + &req, sizeof(req), + data, num_mixer_in * sizeof(u16)); + if (err < 0) + return err; + + for (i = 0, j = mix_num * num_mixer_in; i < num_mixer_in; i++, j++) + private->mix[j] = scarlett2_mixer_value_to_db( + le16_to_cpu(data[i])); + + return 0; +} + +/* Send a USB message to set the volumes for all inputs of one mix + * (values obtained from private->mix[]) + */ +static int scarlett2_usb_set_mix(struct usb_mixer_interface *mixer, + int mix_num) +{ + struct scarlett2_data *private = mixer->private_data; + + struct { + __le16 mix_num; + __le16 data[SCARLETT2_INPUT_MIX_MAX]; + } __packed req; + + int i, j; + int num_mixer_in = private->num_mix_in; + + req.mix_num = cpu_to_le16(mix_num); + + for (i = 0, j = mix_num * num_mixer_in; i < num_mixer_in; i++, j++) + req.data[i] = cpu_to_le16( + scarlett2_mixer_values[private->mix[j]] + ); + + return scarlett2_usb(mixer, SCARLETT2_USB_SET_MIX, + &req, (num_mixer_in + 1) * sizeof(u16), + NULL, 0); +} + +/* Convert a port number index (per info->port_count) to a hardware ID */ +static u32 scarlett2_mux_src_num_to_id( + const int port_count[][SCARLETT2_PORT_DIRNS], int num) +{ + int port_type; + + for (port_type = 0; + port_type < SCARLETT2_PORT_TYPE_COUNT; + port_type++) { + if (num < port_count[port_type][SCARLETT2_PORT_IN]) + return scarlett2_ports[port_type].id | num; + num -= port_count[port_type][SCARLETT2_PORT_IN]; + } + + /* Oops */ + return 0; +} + +/* Convert a hardware ID to a port number index */ +static u32 scarlett2_mux_id_to_num( + const int port_count[][SCARLETT2_PORT_DIRNS], int direction, u32 id) +{ + int port_type; + int port_num = 0; + + for (port_type = 0; + port_type < SCARLETT2_PORT_TYPE_COUNT; + port_type++) { + int base = scarlett2_ports[port_type].id; + int count = port_count[port_type][direction]; + + if (id >= base && id < base + count) + return port_num + id - base; + port_num += count; + } + + /* Oops */ + return -1; +} + +/* Convert one mux entry from the interface and load into private->mux[] */ +static void scarlett2_usb_populate_mux(struct scarlett2_data *private, + u32 mux_entry) +{ + const struct scarlett2_device_info *info = private->info; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + + int dst_idx, src_idx; + + dst_idx = scarlett2_mux_id_to_num(port_count, SCARLETT2_PORT_OUT, + mux_entry & 0xFFF); + if (dst_idx < 0) + return; + + if (dst_idx >= private->num_mux_dsts) { + usb_audio_err(private->mixer->chip, + "BUG: scarlett2_mux_id_to_num(%06x, OUT): %d >= %d", + mux_entry, dst_idx, private->num_mux_dsts); + return; + } + + src_idx = scarlett2_mux_id_to_num(port_count, SCARLETT2_PORT_IN, + mux_entry >> 12); + if (src_idx < 0) + return; + + if (src_idx >= private->num_mux_srcs) { + usb_audio_err(private->mixer->chip, + "BUG: scarlett2_mux_id_to_num(%06x, IN): %d >= %d", + mux_entry, src_idx, private->num_mux_srcs); + return; + } + + private->mux[dst_idx] = src_idx; +} + +/* Update the meter level map + * + * The meter level data from the interface (SCARLETT2_USB_GET_METER + * request) is returned in mux_assignment order, but to avoid exposing + * that to userspace, scarlett2_meter_ctl_get() rearranges the data + * into scarlett2_ports order using the meter_level_map[] array which + * is set up by this function. + * + * In addition, the meter level data values returned from the + * interface are invalid for destinations where: + * + * - the source is "Off"; therefore we set those values to zero (map + * value of 255) + * + * - the source is assigned to a previous (with respect to the + * mux_assignment order) destination; therefore we set those values + * to the value previously reported for that source + */ +static void scarlett2_update_meter_level_map(struct scarlett2_data *private) +{ + const struct scarlett2_device_info *info = private->info; + const struct scarlett2_meter_entry *entry; + + /* sources already assigned to a destination + * value is 255 for None, otherwise the value of i + * (index into array returned by + * scarlett2_usb_get_meter_levels()) + */ + u8 seen_src[SCARLETT2_MAX_SRCS] = { 1 }; + u8 seen_src_value[SCARLETT2_MAX_SRCS] = { 255 }; + + /* index in meter_map[] order */ + int i = 0; + + /* go through the meter_map[] entries */ + for (entry = info->meter_map; + entry->count; + entry++) { + + /* fill in each meter_level_map[] entry */ + int j, mux_idx; + + for (j = 0, mux_idx = entry->start; + j < entry->count; + i++, j++, mux_idx++) { + + /* convert mux_idx using line_out_unmap[] */ + int map_mux_idx = ( + info->line_out_remap_enable && + mux_idx < private->num_line_out + ) ? info->line_out_unmap[mux_idx] + : mux_idx; + + /* check which source is connected, and if + * that source is already connected elsewhere, + * use that existing connection's destination + * for this meter entry instead + */ + int mux_src = private->mux[mux_idx]; + + if (!seen_src[mux_src]) { + seen_src[mux_src] = 1; + seen_src_value[mux_src] = i; + } + private->meter_level_map[map_mux_idx] = + seen_src_value[mux_src]; + } + } +} + +/* Send USB message to get mux inputs and then populate private->mux[] */ +static int scarlett2_usb_get_mux(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int count = private->num_mux_dsts; + int err, i; + + struct { + __le16 num; + __le16 count; + } __packed req; + + __le32 data[SCARLETT2_MUX_MAX]; + + private->mux_updated = 0; + + req.num = 0; + req.count = cpu_to_le16(count); + + err = scarlett2_usb(mixer, SCARLETT2_USB_GET_MUX, + &req, sizeof(req), + data, count * sizeof(u32)); + if (err < 0) + return err; + + for (i = 0; i < count; i++) + scarlett2_usb_populate_mux(private, le32_to_cpu(data[i])); + + scarlett2_update_meter_level_map(private); + + return 0; +} + +/* Send USB messages to set mux inputs */ +static int scarlett2_usb_set_mux(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + int table; + + struct { + __le16 pad; + __le16 num; + __le32 data[SCARLETT2_MUX_MAX]; + } __packed req; + + req.pad = 0; + + /* set mux settings for each rate */ + for (table = 0; table < SCARLETT2_MUX_TABLES; table++) { + const struct scarlett2_mux_entry *entry; + + /* i counts over the output array */ + int i = 0, err; + + req.num = cpu_to_le16(table); + + /* loop through each entry */ + for (entry = info->mux_assignment[table]; + entry->count; + entry++) { + int j; + int port_type = entry->port_type; + int port_idx = entry->start; + int mux_idx = scarlett2_get_port_start_num(port_count, + SCARLETT2_PORT_OUT, port_type) + port_idx; + int dst_id = scarlett2_ports[port_type].id + port_idx; + + /* Empty slots */ + if (!dst_id) { + for (j = 0; j < entry->count; j++) + req.data[i++] = 0; + continue; + } + + /* Non-empty mux slots use the lower 12 bits + * for the destination and next 12 bits for + * the source + */ + for (j = 0; j < entry->count; j++) { + int src_id = scarlett2_mux_src_num_to_id( + port_count, private->mux[mux_idx++]); + req.data[i++] = cpu_to_le32(dst_id | + src_id << 12); + dst_id++; + } + } + + err = scarlett2_usb(mixer, SCARLETT2_USB_SET_MUX, + &req, (i + 1) * sizeof(u32), + NULL, 0); + if (err < 0) + return err; + } + + scarlett2_update_meter_level_map(private); + + return 0; +} + +/* Send USB message to get meter levels */ +static int scarlett2_usb_get_meter_levels(struct usb_mixer_interface *mixer, + u16 num_meters, u16 *levels) +{ + struct { + __le16 pad; + __le16 num_meters; + __le32 magic; + } __packed req; + __le32 resp[SCARLETT2_MAX_METERS]; + int i, err; + + req.pad = 0; + req.num_meters = cpu_to_le16(num_meters); + req.magic = cpu_to_le32(SCARLETT2_USB_METER_LEVELS_GET_MAGIC); + err = scarlett2_usb(mixer, SCARLETT2_USB_GET_METER, + &req, sizeof(req), resp, num_meters * sizeof(u32)); + if (err < 0) + return err; + + /* copy, convert to u16 */ + for (i = 0; i < num_meters; i++) + levels[i] = le32_to_cpu(resp[i]); + + return 0; +} + +/* For config items with mute=1, xor bits 0 & 1 together to get the + * current/next state. This won't have any effect on values which are + * only ever 0/1. + */ +static uint8_t scarlett2_decode_muteable(uint8_t v) +{ + return (v ^ (v >> 1)) & 1; +} + +/*** Control Functions ***/ + +/* helper function to create a new control */ +static int scarlett2_add_new_ctl(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *ncontrol, + int index, int channels, const char *name, + struct snd_kcontrol **kctl_return) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + int err; + + elem = kzalloc(sizeof(*elem), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + /* We set USB_MIXER_BESPOKEN type, so that the core USB mixer code + * ignores them for resume and other operations. + * Also, the head.id field is set to 0, as we don't use this field. + */ + elem->head.mixer = mixer; + elem->control = index; + elem->head.id = 0; + elem->channels = channels; + elem->val_type = USB_MIXER_BESPOKEN; + + kctl = snd_ctl_new1(ncontrol, elem); + if (!kctl) { + kfree(elem); + return -ENOMEM; + } + kctl->private_free = snd_usb_mixer_elem_free; + + strscpy(kctl->id.name, name, sizeof(kctl->id.name)); + + err = snd_usb_mixer_add_control(&elem->head, kctl); + if (err < 0) + return err; + + if (kctl_return) + *kctl_return = kctl; + + return 0; +} + +/*** Firmware Version Control ***/ + +static int scarlett2_firmware_version_ctl_get( + struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + + ucontrol->value.integer.value[0] = private->firmware_version; + + return 0; +} + +static int scarlett2_firmware_version_ctl_info( + struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + + return 0; +} + +static const struct snd_kcontrol_new scarlett2_firmware_version_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .name = "", + .info = scarlett2_firmware_version_ctl_info, + .get = scarlett2_firmware_version_ctl_get +}; + +static int scarlett2_add_firmware_version_ctl( + struct usb_mixer_interface *mixer) +{ + return scarlett2_add_new_ctl(mixer, &scarlett2_firmware_version_ctl, + 0, 0, "Firmware Version", NULL); +} + +/*** Minimum Firmware Version Control ***/ + +static int scarlett2_min_firmware_version_ctl_get( + struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + + ucontrol->value.integer.value[0] = private->info->min_firmware_version; + + return 0; +} + +static int scarlett2_min_firmware_version_ctl_info( + struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + + return 0; +} + +static const struct snd_kcontrol_new scarlett2_min_firmware_version_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .name = "", + .info = scarlett2_min_firmware_version_ctl_info, + .get = scarlett2_min_firmware_version_ctl_get +}; + +static int scarlett2_add_min_firmware_version_ctl( + struct usb_mixer_interface *mixer) +{ + return scarlett2_add_new_ctl(mixer, &scarlett2_min_firmware_version_ctl, + 0, 0, "Minimum Firmware Version", NULL); +} + +/*** Sync Control ***/ + +/* Update sync control after receiving notification that the status + * has changed + */ +static int scarlett2_update_sync(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + private->sync_updated = 0; + return scarlett2_usb_get_sync_status(mixer, &private->sync); +} + +static int scarlett2_sync_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + static const char *texts[2] = { + "Unlocked", "Locked" + }; + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int scarlett2_sync_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->sync_updated) { + err = scarlett2_update_sync(mixer); + if (err < 0) + return err; + } + ucontrol->value.enumerated.item[0] = private->sync; + return 0; +} + +static const struct snd_kcontrol_new scarlett2_sync_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .name = "", + .info = scarlett2_sync_ctl_info, + .get = scarlett2_sync_ctl_get +}; + +static int scarlett2_add_sync_ctl(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + /* devices without a mixer also don't support reporting sync status */ + if (!scarlett2_has_mixer(private)) + return 0; + + return scarlett2_add_new_ctl(mixer, &scarlett2_sync_ctl, + 0, 1, "Sync Status", &private->sync_ctl); +} + +/*** Autogain Switch and Status Controls ***/ + +/* Forward declarations as phantom power and autogain can disable each other */ +static int scarlett2_check_input_phantom_updated(struct usb_mixer_interface *); +static int scarlett2_phantom_is_switching(struct scarlett2_data *, int); + +/* Set the access mode of a control to read-only (val = 0) or + * read-write (val = 1). + */ +static void scarlett2_set_ctl_access(struct snd_kcontrol *kctl, int val) +{ + if (val) + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_WRITE; + else + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_WRITE; +} + +/* Check if autogain is running on any input */ +static int scarlett2_autogain_is_running(struct scarlett2_data *private) +{ + int i; + + /* autogain_status[] is 0 if autogain is running */ + for (i = 0; i < private->info->gain_input_count; i++) + if (!private->autogain_status[i]) + return 1; + + return 0; +} + +static int scarlett2_update_autogain(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int err, i; + u8 raw_autogain_status[SCARLETT2_INPUT_GAIN_MAX]; + s8 ag_target_values[SCARLETT2_AG_TARGET_COUNT]; + + private->autogain_updated = 0; + + if (!info->gain_input_count) + return 0; + + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_AUTOGAIN_SWITCH, + info->gain_input_count, private->autogain_switch); + if (err < 0) + return err; + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_AUTOGAIN_STATUS, + info->gain_input_count, raw_autogain_status); + if (err < 0) + return err; + + /* Translate autogain_switch and raw_autogain_status into + * autogain_status. + * + * When autogain_switch[] is set, the status is the first + * element in scarlett2_autogain_status_texts[] (Running). The + * subsequent elements correspond to the status value from the + * device (raw_autogain_status[]) + 1. The last element is + * "Invalid", in case the device reports a status outside the + * range of scarlett2_autogain_status_texts[]. + */ + for (i = 0; i < info->gain_input_count; i++) + if (private->autogain_switch[i]) + private->autogain_status[i] = 0; + else if (raw_autogain_status[i] < + private->num_autogain_status_texts - 1) + private->autogain_status[i] = + raw_autogain_status[i] + 1; + else + private->autogain_status[i] = + private->num_autogain_status_texts - 1; + + + for (i = 0; i < SCARLETT2_AG_TARGET_COUNT; i++) + if (scarlett2_has_config_item(private, + scarlett2_ag_target_configs[i])) { + err = scarlett2_usb_get_config( + mixer, scarlett2_ag_target_configs[i], + 1, &ag_target_values[i]); + if (err < 0) + return err; + } + + /* convert from negative dBFS as used by the device */ + for (i = 0; i < SCARLETT2_AG_TARGET_COUNT; i++) + private->ag_targets[i] = -ag_target_values[i]; + + return 0; +} + +/* Update access mode for controls affected by autogain */ +static void scarlett2_autogain_update_access(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int val = !scarlett2_autogain_is_running(private); + int i; + + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_INPUT_SELECT_SWITCH)) + scarlett2_set_ctl_access(private->input_select_ctl, val); + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_INPUT_LINK_SWITCH)) + for (i = 0; i < info->gain_input_count; i++) + scarlett2_set_ctl_access(private->input_link_ctls[i], + val); + for (i = 0; i < info->gain_input_count; i++) + scarlett2_set_ctl_access(private->input_gain_ctls[i], val); + for (i = 0; i < info->safe_input_count; i++) + scarlett2_set_ctl_access(private->safe_ctls[i], val); + for (i = 0; i < info->level_input_count; i++) + scarlett2_set_ctl_access(private->level_ctls[i], val); + for (i = 0; i < info->air_input_count; i++) + scarlett2_set_ctl_access(private->air_ctls[i], val); + for (i = 0; i < info->mute_input_count; i++) + scarlett2_set_ctl_access(private->input_mute_ctls[i], val); + for (i = 0; i < info->phantom_count; i++) + scarlett2_set_ctl_access(private->phantom_ctls[i], val); + for (i = 0; i < info->dsp_input_count; i++) + scarlett2_set_ctl_access(private->dsp_ctls[i], val); + + for (i = 0; i < SCARLETT2_AG_TARGET_COUNT; i++) + if (scarlett2_has_config_item(private, + scarlett2_ag_target_configs[i])) + scarlett2_set_ctl_access( + private->ag_target_ctls[i], val); +} + +/* Notify of access mode change for all controls read-only while + * autogain runs. + */ +static void scarlett2_autogain_notify_access(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int i; + + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_INPUT_SELECT_SWITCH)) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, + &private->input_select_ctl->id); + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_INPUT_LINK_SWITCH)) + for (i = 0; i < info->gain_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, + &private->input_link_ctls[i]->id); + for (i = 0; i < info->gain_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, + &private->input_gain_ctls[i]->id); + for (i = 0; i < info->safe_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, + &private->safe_ctls[i]->id); + for (i = 0; i < info->level_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, + &private->level_ctls[i]->id); + for (i = 0; i < info->air_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, + &private->air_ctls[i]->id); + for (i = 0; i < info->dsp_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, + &private->dsp_ctls[i]->id); + for (i = 0; i < info->mute_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, + &private->input_mute_ctls[i]->id); + for (i = 0; i < info->phantom_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, + &private->phantom_ctls[i]->id); + + for (i = 0; i < SCARLETT2_AG_TARGET_COUNT; i++) + if (scarlett2_has_config_item(private, + scarlett2_ag_target_configs[i])) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, + &private->ag_target_ctls[i]->id); +} + +/* Call scarlett2_update_autogain() and + * scarlett2_autogain_update_access() if autogain_updated is set. + */ +static int scarlett2_check_autogain_updated( + struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int err; + + if (!private->autogain_updated) + return 0; + + err = scarlett2_update_autogain(mixer); + if (err < 0) + return err; + + scarlett2_autogain_update_access(mixer); + + return 0; +} + +/* If autogain_updated is set when a *_ctl_put() function for a + * control that is meant to be read-only while autogain is running, + * update the autogain status and access mode of affected controls. + * Return -EPERM if autogain is running. + */ +static int scarlett2_check_put_during_autogain( + struct usb_mixer_interface *mixer) +{ + int err = scarlett2_check_autogain_updated(mixer); + + if (err < 0) + return err; + + if (scarlett2_autogain_is_running(mixer->private_data)) + return -EPERM; + + return 0; +} + +static int scarlett2_autogain_switch_ctl_info( + struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + err = scarlett2_check_input_phantom_updated(mixer); + if (err < 0) + return err; + + return snd_ctl_boolean_mono_info(kctl, uinfo); +} + +static int scarlett2_autogain_switch_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_autogain_updated(mixer); + if (err < 0) + return err; + + ucontrol->value.enumerated.item[0] = + private->autogain_switch[elem->control]; + return 0; +} + +static int scarlett2_autogain_status_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_autogain_updated(mixer); + if (err < 0) + return err; + + ucontrol->value.enumerated.item[0] = + private->autogain_status[elem->control]; + return 0; +} + +static int scarlett2_autogain_switch_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_input_phantom_updated(mixer); + if (err < 0) + return err; + + if (scarlett2_phantom_is_switching(private, index)) + return -EPERM; + + oval = private->autogain_switch[index]; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->autogain_switch[index] = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_AUTOGAIN_SWITCH, index, val); + + scarlett2_autogain_update_access(mixer); + scarlett2_autogain_notify_access(mixer); + + return err < 0 ? err : 1; +} + +static int scarlett2_autogain_status_ctl_info( + struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + + return snd_ctl_enum_info( + uinfo, 1, + private->num_autogain_status_texts, + private->config_set->autogain_status_texts); +} + +static const struct snd_kcontrol_new scarlett2_autogain_switch_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_autogain_switch_ctl_info, + .get = scarlett2_autogain_switch_ctl_get, + .put = scarlett2_autogain_switch_ctl_put +}; + +static const struct snd_kcontrol_new scarlett2_autogain_status_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .name = "", + .info = scarlett2_autogain_status_ctl_info, + .get = scarlett2_autogain_status_ctl_get, +}; + +/*** Autogain Target Controls ***/ + +static int scarlett2_ag_target_ctl_info( + struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_autogain_updated(mixer); + if (err < 0) + return err; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = SCARLETT2_AG_TARGET_MIN; + uinfo->value.integer.max = 0; + uinfo->value.integer.step = 1; + + return 0; +} + +static int scarlett2_ag_target_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->autogain_updated) { + err = scarlett2_update_autogain(mixer); + if (err < 0) + return err; + } + + ucontrol->value.integer.value[0] = private->ag_targets[elem->control]; + return 0; +} + +static int scarlett2_ag_target_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_put_during_autogain(mixer); + if (err < 0) + return err; + + oval = private->ag_targets[index]; + val = clamp(ucontrol->value.integer.value[0], + (long)SCARLETT2_AG_TARGET_MIN, 0L); + + if (oval == val) + return 0; + + private->ag_targets[index] = val; + + /* Send new value to the device */ + err = scarlett2_usb_set_config( + mixer, scarlett2_ag_target_configs[index], 1, -val); + + return err < 0 ? err : 1; +} + +static const DECLARE_TLV_DB_MINMAX( + db_scale_ag_target, SCARLETT2_AG_TARGET_MIN * 100, 0 +); + +static const struct snd_kcontrol_new scarlett2_ag_target_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett2_ag_target_ctl_info, + .get = scarlett2_ag_target_ctl_get, + .put = scarlett2_ag_target_ctl_put, + .tlv = { .p = db_scale_ag_target } +}; + +/*** Input Select Control ***/ + +static int scarlett2_update_input_select(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int link_count = info->gain_input_count; + int err; + + private->input_select_updated = 0; + + if (!scarlett2_has_config_item(private, + SCARLETT2_CONFIG_INPUT_SELECT_SWITCH) || + !link_count) + return 0; + + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_INPUT_SELECT_SWITCH, + 1, &private->input_select_switch); + if (err < 0) + return err; + + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_INPUT_LINK_SWITCH, + link_count, private->input_link_switch); + if (err < 0) + return err; + + return 0; +} + +static int scarlett2_input_select_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->input_select_updated) { + err = scarlett2_update_input_select(mixer); + if (err < 0) + return err; + } + ucontrol->value.enumerated.item[0] = private->input_select_switch; + return 0; +} + +static int scarlett2_input_select_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_put_during_autogain(mixer); + if (err < 0) + return err; + + oval = private->input_select_switch; + val = ucontrol->value.integer.value[0]; + + if (val < 0) + val = 0; + else if (val >= info->gain_input_count) + val = info->gain_input_count - 1; + + if (oval == val) + return 0; + + private->input_select_switch = val; + + /* Send new value to the device */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_INPUT_SELECT_SWITCH, + 0, val); + + return err < 0 ? err : 1; +} + +static int scarlett2_input_select_ctl_info( + struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int inputs = private->info->gain_input_count; + int i, err; + char **values __free(kfree) = NULL; + + values = kcalloc(inputs, sizeof(char *), GFP_KERNEL); + if (!values) + return -ENOMEM; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_autogain_updated(mixer); + if (err < 0) + return err; + + /* Loop through each input */ + for (i = 0; i < inputs; i++) { + values[i] = kasprintf(GFP_KERNEL, "Input %d", i + 1); + if (!values[i]) { + err = -ENOMEM; + goto clear; + } + } + + err = snd_ctl_enum_info(uinfo, 1, i, + (const char * const *)values); + +clear: + for (i = 0; i < inputs; i++) + kfree(values[i]); + + return err; +} + +static const struct snd_kcontrol_new scarlett2_input_select_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_input_select_ctl_info, + .get = scarlett2_input_select_ctl_get, + .put = scarlett2_input_select_ctl_put, +}; + +/*** Input Link Switch Controls ***/ + +/* snd_ctl_boolean_mono_info() with autogain-updated check + * (for controls that are read-only while autogain is running) + */ +static int scarlett2_autogain_disables_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_autogain_updated(mixer); + if (err < 0) + return err; + + return snd_ctl_boolean_mono_info(kctl, uinfo); +} + +static int scarlett2_input_link_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->input_select_updated) { + err = scarlett2_update_input_select(mixer); + if (err < 0) + return err; + } + ucontrol->value.enumerated.item[0] = + private->input_link_switch[elem->control]; + return 0; +} + +static int scarlett2_input_link_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_put_during_autogain(mixer); + if (err < 0) + return err; + + oval = private->input_link_switch[index]; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->input_link_switch[index] = val; + + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_INPUT_LINK_SWITCH, index, val); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_input_link_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_autogain_disables_ctl_info, + .get = scarlett2_input_link_ctl_get, + .put = scarlett2_input_link_ctl_put +}; + +/*** Input Gain Controls ***/ + +static int scarlett2_update_input_gain(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + + private->input_gain_updated = 0; + + if (!info->gain_input_count) + return 0; + + return scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_INPUT_GAIN, + info->gain_input_count, private->gain); +} + +static int scarlett2_input_gain_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_autogain_updated(mixer); + if (err < 0) + return err; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SCARLETT2_MAX_GAIN_VALUE; + uinfo->value.integer.step = 1; + + return 0; +} + +static int scarlett2_input_gain_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->input_gain_updated) { + err = scarlett2_update_input_gain(mixer); + if (err < 0) + return err; + } + ucontrol->value.integer.value[0] = + private->gain[elem->control]; + return 0; +} + +static int scarlett2_input_gain_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_put_during_autogain(mixer); + if (err < 0) + return err; + + oval = private->gain[index]; + val = ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->gain[index] = val; + + /* Send gain change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_INPUT_GAIN, + index, val); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_input_gain_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett2_input_gain_ctl_info, + .get = scarlett2_input_gain_ctl_get, + .put = scarlett2_input_gain_ctl_put, + .private_value = 0, /* max value */ +}; + +/*** Safe Controls ***/ + +static int scarlett2_update_input_safe(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + + private->input_safe_updated = 0; + + if (!info->safe_input_count) + return 0; + + return scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_SAFE_SWITCH, + info->safe_input_count, private->safe_switch); +} + +static int scarlett2_safe_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->input_safe_updated) { + err = scarlett2_update_input_safe(mixer); + if (err < 0) + return err; + } + ucontrol->value.integer.value[0] = + private->safe_switch[elem->control]; + return 0; +} + +static int scarlett2_safe_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_put_during_autogain(mixer); + if (err < 0) + return err; + + oval = private->safe_switch[index]; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->safe_switch[index] = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_SAFE_SWITCH, + index, val); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_safe_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_autogain_disables_ctl_info, + .get = scarlett2_safe_ctl_get, + .put = scarlett2_safe_ctl_put, +}; + +/*** PCM Input Control ***/ + +static int scarlett2_update_pcm_input_switch(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int err; + + private->pcm_input_switch_updated = 0; + + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_PCM_INPUT_SWITCH, + 1, &private->pcm_input_switch); + if (err < 0) + return err; + + return 0; +} + +static int scarlett2_pcm_input_switch_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = elem->head.mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->pcm_input_switch_updated) { + err = scarlett2_update_pcm_input_switch(mixer); + if (err < 0) + return err; + } + ucontrol->value.enumerated.item[0] = private->pcm_input_switch; + return 0; +} + +static int scarlett2_pcm_input_switch_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->pcm_input_switch; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->pcm_input_switch = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_PCM_INPUT_SWITCH, + 0, val); + + return err < 0 ? err : 1; +} + +static int scarlett2_pcm_input_switch_ctl_info( + struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + static const char *const values[2] = { + "Direct", "Mixer" + }; + + return snd_ctl_enum_info( + uinfo, 1, 2, values); +} + +static const struct snd_kcontrol_new scarlett2_pcm_input_switch_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_pcm_input_switch_ctl_info, + .get = scarlett2_pcm_input_switch_ctl_get, + .put = scarlett2_pcm_input_switch_ctl_put +}; + +/*** Analogue Line Out Volume Controls ***/ + +/* Update hardware volume controls after receiving notification that + * they have changed + */ +static int scarlett2_update_volumes(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + s16 vol; + int err, i; + + private->vol_updated = 0; + + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_MASTER_VOLUME)) { + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_MASTER_VOLUME, + 1, &vol); + if (err < 0) + return err; + + private->master_vol = clamp(vol + SCARLETT2_VOLUME_BIAS, + 0, SCARLETT2_VOLUME_BIAS); + + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_SW_HW_SWITCH)) + for (i = 0; i < private->num_line_out; i++) + if (private->vol_sw_hw_switch[i]) + private->vol[i] = private->master_vol; + } + + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_HEADPHONE_VOLUME)) { + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_HEADPHONE_VOLUME, + 1, &vol); + if (err < 0) + return err; + + private->headphone_vol = clamp(vol + SCARLETT2_VOLUME_BIAS, + 0, SCARLETT2_VOLUME_BIAS); + } + + return 0; +} + +static int scarlett2_volume_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SCARLETT2_VOLUME_BIAS; + uinfo->value.integer.step = 1; + return 0; +} + +static int scarlett2_master_volume_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->vol_updated) { + err = scarlett2_update_volumes(mixer); + if (err < 0) + return err; + } + ucontrol->value.integer.value[0] = private->master_vol; + return 0; +} + +static int scarlett2_headphone_volume_ctl_get( + struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->vol_updated) { + err = scarlett2_update_volumes(mixer); + if (err < 0) + return err; + } + ucontrol->value.integer.value[0] = private->headphone_vol; + return 0; +} + +static int line_out_remap(struct scarlett2_data *private, int index) +{ + const struct scarlett2_device_info *info = private->info; + + if (!info->line_out_remap_enable) + return index; + + if (index >= private->num_line_out) + return index; + + return info->line_out_remap[index]; +} + +static int scarlett2_volume_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = line_out_remap(private, elem->control); + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->vol_updated) { + err = scarlett2_update_volumes(mixer); + if (err < 0) + return err; + } + ucontrol->value.integer.value[0] = private->vol[index]; + return 0; +} + +static int scarlett2_volume_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = line_out_remap(private, elem->control); + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->vol[index]; + val = ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->vol[index] = val; + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_LINE_OUT_VOLUME, + index, val - SCARLETT2_VOLUME_BIAS); + + return err < 0 ? err : 1; +} + +static const DECLARE_TLV_DB_MINMAX( + db_scale_scarlett2_volume, -SCARLETT2_VOLUME_BIAS * 100, 0 +); + +static const struct snd_kcontrol_new scarlett2_master_volume_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett2_volume_ctl_info, + .get = scarlett2_master_volume_ctl_get, + .private_value = 0, /* max value */ + .tlv = { .p = db_scale_scarlett2_volume } +}; + +static const struct snd_kcontrol_new scarlett2_headphone_volume_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett2_volume_ctl_info, + .get = scarlett2_headphone_volume_ctl_get, + .private_value = 0, /* max value */ + .tlv = { .p = db_scale_scarlett2_volume } +}; + +static const struct snd_kcontrol_new scarlett2_line_out_volume_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett2_volume_ctl_info, + .get = scarlett2_volume_ctl_get, + .put = scarlett2_volume_ctl_put, + .private_value = 0, /* max value */ + .tlv = { .p = db_scale_scarlett2_volume } +}; + +/*** Mute Switch Controls ***/ + +static int scarlett2_update_dim_mute(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int err, i; + u8 mute; + + private->dim_mute_updated = 0; + + if (!scarlett2_has_config_item(private, SCARLETT2_CONFIG_SW_HW_SWITCH)) + return 0; + + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_DIM_MUTE, + SCARLETT2_DIM_MUTE_COUNT, private->dim_mute); + if (err < 0) + return err; + + for (i = 0; i < SCARLETT2_DIM_MUTE_COUNT; i++) + private->dim_mute[i] = !!private->dim_mute[i]; + + mute = private->dim_mute[SCARLETT2_BUTTON_MUTE]; + + for (i = 0; i < private->num_line_out; i++) + if (private->vol_sw_hw_switch[i]) + private->mute_switch[i] = mute; + + return 0; +} + +static int scarlett2_mute_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = line_out_remap(private, elem->control); + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->dim_mute_updated) { + err = scarlett2_update_dim_mute(mixer); + if (err < 0) + return err; + } + ucontrol->value.integer.value[0] = private->mute_switch[index]; + return 0; +} + +static int scarlett2_mute_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = line_out_remap(private, elem->control); + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->mute_switch[index]; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->mute_switch[index] = val; + + /* Send mute change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_MUTE_SWITCH, + index, val); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_mute_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = snd_ctl_boolean_mono_info, + .get = scarlett2_mute_ctl_get, + .put = scarlett2_mute_ctl_put, +}; + +/*** HW/SW Volume Switch Controls ***/ + +static void scarlett2_sw_hw_ctl_ro(struct scarlett2_data *private, int index) +{ + private->sw_hw_ctls[index]->vd[0].access &= + ~SNDRV_CTL_ELEM_ACCESS_WRITE; +} + +static void scarlett2_sw_hw_ctl_rw(struct scarlett2_data *private, int index) +{ + private->sw_hw_ctls[index]->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_WRITE; +} + +static int scarlett2_sw_hw_enum_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const values[2] = { + "SW", "HW" + }; + + return snd_ctl_enum_info(uinfo, 1, 2, values); +} + +static int scarlett2_sw_hw_enum_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + int index = line_out_remap(private, elem->control); + + ucontrol->value.enumerated.item[0] = private->vol_sw_hw_switch[index]; + return 0; +} + +static void scarlett2_vol_ctl_set_writable(struct usb_mixer_interface *mixer, + int index, int value) +{ + struct scarlett2_data *private = mixer->private_data; + struct snd_card *card = mixer->chip->card; + + /* Set/Clear write bits */ + if (value) { + private->vol_ctls[index]->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_WRITE; + private->mute_ctls[index]->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_WRITE; + } else { + private->vol_ctls[index]->vd[0].access &= + ~SNDRV_CTL_ELEM_ACCESS_WRITE; + private->mute_ctls[index]->vd[0].access &= + ~SNDRV_CTL_ELEM_ACCESS_WRITE; + } + + /* Notify of write bit and possible value change */ + snd_ctl_notify(card, + SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO, + &private->vol_ctls[index]->id); + snd_ctl_notify(card, + SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO, + &private->mute_ctls[index]->id); +} + +static int scarlett2_sw_hw_change(struct usb_mixer_interface *mixer, + int ctl_index, int val) +{ + struct scarlett2_data *private = mixer->private_data; + int index = line_out_remap(private, ctl_index); + int err; + + private->vol_sw_hw_switch[index] = val; + + /* Change access mode to RO (hardware controlled volume) + * or RW (software controlled volume) + */ + scarlett2_vol_ctl_set_writable(mixer, ctl_index, !val); + + /* Reset volume/mute to master volume/mute */ + private->vol[index] = private->master_vol; + private->mute_switch[index] = private->dim_mute[SCARLETT2_BUTTON_MUTE]; + + /* Set SW volume to current HW volume */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_LINE_OUT_VOLUME, + index, private->master_vol - SCARLETT2_VOLUME_BIAS); + if (err < 0) + return err; + + /* Set SW mute to current HW mute */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_MUTE_SWITCH, + index, private->dim_mute[SCARLETT2_BUTTON_MUTE]); + if (err < 0) + return err; + + /* Send SW/HW switch change to the device */ + return scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_SW_HW_SWITCH, + index, val); +} + +static int scarlett2_sw_hw_enum_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int ctl_index = elem->control; + int index = line_out_remap(private, ctl_index); + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->vol_sw_hw_switch[index]; + val = !!ucontrol->value.enumerated.item[0]; + + if (oval == val) + return 0; + + err = scarlett2_sw_hw_change(mixer, ctl_index, val); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_sw_hw_enum_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_sw_hw_enum_ctl_info, + .get = scarlett2_sw_hw_enum_ctl_get, + .put = scarlett2_sw_hw_enum_ctl_put, +}; + +/*** Line Level/Instrument Level Switch Controls ***/ + +static int scarlett2_update_input_level(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + + private->input_level_updated = 0; + + if (!info->level_input_count) + return 0; + + return scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_LEVEL_SWITCH, + info->level_input_count + info->level_input_first, + private->level_switch); +} + +static int scarlett2_level_enum_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const values[2] = { + "Line", "Inst" + }; + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_autogain_updated(mixer); + if (err < 0) + return err; + + return snd_ctl_enum_info(uinfo, 1, 2, values); +} + +static int scarlett2_level_enum_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int index = elem->control + info->level_input_first; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->input_level_updated) { + err = scarlett2_update_input_level(mixer); + if (err < 0) + return err; + } + ucontrol->value.enumerated.item[0] = scarlett2_decode_muteable( + private->level_switch[index]); + return 0; +} + +static int scarlett2_level_enum_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int index = elem->control + info->level_input_first; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_put_during_autogain(mixer); + if (err < 0) + return err; + + oval = private->level_switch[index]; + val = !!ucontrol->value.enumerated.item[0]; + + if (oval == val) + return 0; + + private->level_switch[index] = val; + + /* To set the Gen 4 muteable controls, bit 1 gets set instead */ + if (private->config_set->items[SCARLETT2_CONFIG_LEVEL_SWITCH].mute) + val = (!val) | 0x02; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_LEVEL_SWITCH, + index, val); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_level_enum_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_level_enum_ctl_info, + .get = scarlett2_level_enum_ctl_get, + .put = scarlett2_level_enum_ctl_put, +}; + +/*** Pad Switch Controls ***/ + +static int scarlett2_update_input_pad(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + + private->input_pad_updated = 0; + + if (!info->pad_input_count) + return 0; + + return scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_PAD_SWITCH, + info->pad_input_count, private->pad_switch); +} + +static int scarlett2_pad_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->input_pad_updated) { + err = scarlett2_update_input_pad(mixer); + if (err < 0) + return err; + } + ucontrol->value.integer.value[0] = + private->pad_switch[elem->control]; + return 0; +} + +static int scarlett2_pad_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->pad_switch[index]; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->pad_switch[index] = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_PAD_SWITCH, + index, val); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_pad_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = snd_ctl_boolean_mono_info, + .get = scarlett2_pad_ctl_get, + .put = scarlett2_pad_ctl_put, +}; + +/*** Air Switch Controls ***/ + +static int scarlett2_update_input_air(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + + private->input_air_updated = 0; + + if (!info->air_input_count) + return 0; + + return scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_AIR_SWITCH, + info->air_input_count, private->air_switch); +} + +static int scarlett2_air_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->input_air_updated) { + err = scarlett2_update_input_air(mixer); + if (err < 0) + return err; + } + ucontrol->value.integer.value[0] = private->air_switch[elem->control]; + return 0; +} + +static int scarlett2_air_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_put_during_autogain(mixer); + if (err < 0) + return err; + + oval = private->air_switch[index]; + val = ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->air_switch[index] = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_AIR_SWITCH, + index, val); + + return err < 0 ? err : 1; +} + +static int scarlett2_air_with_drive_ctl_info( + struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + static const char *const values[3] = { + "Off", "Presence", "Presence + Drive" + }; + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_autogain_updated(mixer); + if (err < 0) + return err; + + return snd_ctl_enum_info(uinfo, 1, 3, values); +} + +static const struct snd_kcontrol_new scarlett2_air_ctl[2] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = snd_ctl_boolean_mono_info, + .get = scarlett2_air_ctl_get, + .put = scarlett2_air_ctl_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_air_with_drive_ctl_info, + .get = scarlett2_air_ctl_get, + .put = scarlett2_air_ctl_put, + } +}; + +/*** DSP Switch Control ***/ + +static int scarlett2_update_input_dsp(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + + private->input_dsp_updated = 0; + + if (!info->dsp_input_count) + return 0; + + return scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_DSP_SWITCH, + info->dsp_input_count, private->dsp_switch); +} + +static int scarlett2_dsp_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->input_dsp_updated) { + err = scarlett2_update_input_dsp(mixer); + if (err < 0) + return err; + } + ucontrol->value.integer.value[0] = private->dsp_switch[elem->control]; + return 0; +} + +static int scarlett2_dsp_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_put_during_autogain(mixer); + if (err < 0) + return err; + + oval = private->dsp_switch[index]; + val = ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->dsp_switch[index] = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_DSP_SWITCH, + index, val); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_dsp_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_autogain_disables_ctl_info, + .get = scarlett2_dsp_ctl_get, + .put = scarlett2_dsp_ctl_put, +}; + +/*** DSP Compressor Parameter Controls ***/ + +static int scarlett2_update_compressor_values(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int err, i, j; + + if (!info->dsp_input_count) + return 0; + + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_COMPRESSOR_PARAMS, + SCARLETT2_COMPRESSOR_PARAM_COUNT * info->dsp_input_count, + private->compressor_values); + + if (err < 0) + return err; + + for (i = 0; i < SCARLETT2_COMPRESSOR_PARAM_COUNT; i++) { + const struct compressor_param *param = &compressor_params[i]; + + for (j = 0; j < info->dsp_input_count; j++) { + int idx = i + j * SCARLETT2_COMPRESSOR_PARAM_COUNT; + int val = private->compressor_values[idx]; + + val >>= param->scale_bits; + val = clamp(val, param->min, param->max); + private->compressor_values[idx] = val; + } + } + + return 0; +} + +static int scarlett2_compressor_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + + ucontrol->value.integer.value[0] = + private->compressor_values[elem->control]; + return 0; +} + +static int scarlett2_compressor_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int channel = index / SCARLETT2_COMPRESSOR_PARAM_COUNT; + int param_index = index % SCARLETT2_COMPRESSOR_PARAM_COUNT; + const struct compressor_param *param = &compressor_params[param_index]; + int oval, val, err; + s32 scaled_val; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_put_during_autogain(mixer); + if (err < 0) + return err; + + oval = private->compressor_values[index]; + val = ucontrol->value.integer.value[0]; + if (oval == val) + return 0; + + private->compressor_values[index] = val; + + scaled_val = val << param->scale_bits; + + /* Send change to the device */ + + /* The channel needs to be put in the parameter buffer index + * field (param_buf_addr + 1); the value field isn't used in + * this case. + */ + err = scarlett2_usb_set_data( + mixer, private->config_set->param_buf_addr + 1, 1, channel); + if (err < 0) + return err; + + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_COMPRESSOR_PARAMS, index, scaled_val); + + return err < 0 ? err : 1; +} + +static int scarlett2_compressor_ctl_info( + struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int control = elem->control % SCARLETT2_COMPRESSOR_PARAM_COUNT; + + uinfo->type = compressor_params[control].type; + uinfo->count = 1; + uinfo->value.integer.min = compressor_params[control].min; + uinfo->value.integer.max = compressor_params[control].max; + uinfo->value.integer.step = 1; + return 0; +} + +static const struct snd_kcontrol_new scarlett2_compressor_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "", + .info = scarlett2_compressor_ctl_info, + .get = scarlett2_compressor_ctl_get, + .put = scarlett2_compressor_ctl_put, +}; + +/*** DSP Pre-Compressor and PEQ Filter Controls ***/ + +static int scarlett2_precomp_flt_switch_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + + ucontrol->value.integer.value[0] = private->precomp_flt_switch[elem->control]; + + return 0; +} + +static int scarlett2_peq_flt_switch_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + + ucontrol->value.integer.value[0] = + private->peq_flt_switch[elem->control]; + + return 0; +} + +static int scarlett2_precomp_flt_switch_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->precomp_flt_switch[elem->control]; + val = ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->precomp_flt_switch[elem->control] = val; + + /* Send change to the device */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_PRECOMP_FLT_SWITCH, + elem->control, val); + + return err < 0 ? err : 1; +} + +static int scarlett2_peq_flt_switch_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->peq_flt_switch[elem->control]; + val = ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->peq_flt_switch[elem->control] = val; + + /* Send change to the device */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_PEQ_FLT_SWITCH, + elem->control, val); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_precomp_flt_switch_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "", + .info = snd_ctl_boolean_mono_info, + .get = scarlett2_precomp_flt_switch_ctl_get, + .put = scarlett2_precomp_flt_switch_ctl_put, +}; + +static const struct snd_kcontrol_new scarlett2_peq_flt_switch_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "", + .info = snd_ctl_boolean_mono_info, + .get = scarlett2_peq_flt_switch_ctl_get, + .put = scarlett2_peq_flt_switch_ctl_put, +}; + +static int scarlett2_update_filter_values(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int err, i, j, k, src_idx, dst_idx; + s32 peq_flt_values[SCARLETT2_DSP_SWITCH_MAX * + SCARLETT2_PEQ_FLT_SLOTS_MAX * + SCARLETT2_BIQUAD_COEFFS]; + + if (!info->dsp_input_count) + return 0; + + /* Get filter switch values */ + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_PRECOMP_FLT_SWITCH, + info->dsp_input_count, private->precomp_flt_switch); + if (err < 0) + return err; + + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_PEQ_FLT_SWITCH, + info->dsp_input_count * info->peq_flt_count, + private->peq_flt_switch); + if (err < 0) + return err; + + /* Get pre-compressor filter values directly */ + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_PRECOMP_FLT_PARAMS, + info->dsp_input_count * + info->precomp_flt_count * + SCARLETT2_BIQUAD_COEFFS, + private->precomp_flt_values); + + if (err < 0) + return err; + + /* PEQ filter values need to be copied via buffer because of + * padding after peq_flt_count up to peq_flt_total_count + */ + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_PEQ_FLT_PARAMS, + info->dsp_input_count * + info->peq_flt_total_count * + SCARLETT2_BIQUAD_COEFFS, + peq_flt_values); + if (err < 0) + return err; + + for (i = 0, dst_idx = 0; i < info->dsp_input_count; i++) { + src_idx = i * + info->peq_flt_total_count * + SCARLETT2_BIQUAD_COEFFS; + for (j = 0; j < info->peq_flt_count; j++) + for (k = 0; + k < SCARLETT2_BIQUAD_COEFFS; + k++, src_idx++, dst_idx++) + private->peq_flt_values[dst_idx] = + peq_flt_values[src_idx]; + } + + return 0; +} + +static int scarlett2_precomp_flt_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + int i, idx; + + for (i = 0, idx = elem->control * SCARLETT2_BIQUAD_COEFFS; + i < SCARLETT2_BIQUAD_COEFFS; + i++, idx++) + ucontrol->value.integer.value[i] = + private->precomp_flt_values[idx]; + + return 0; +} + +static int scarlett2_peq_flt_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + int i, idx; + + for (i = 0, idx = elem->control * SCARLETT2_BIQUAD_COEFFS; + i < SCARLETT2_BIQUAD_COEFFS; + i++, idx++) + ucontrol->value.integer.value[i] = + private->peq_flt_values[idx]; + + return 0; +} + +static int scarlett2_precomp_flt_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control * SCARLETT2_BIQUAD_COEFFS; + int i, oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_put_during_autogain(mixer); + if (err < 0) + return err; + + /* Check if any of the values have changed; if not, return */ + for (i = 0; i < SCARLETT2_BIQUAD_COEFFS; i++) { + oval = private->precomp_flt_values[index + i]; + val = ucontrol->value.integer.value[i]; + if (oval != val) + break; + } + + if (i == SCARLETT2_BIQUAD_COEFFS) + return 0; + + /* Update the values */ + for (i = 0; i < SCARLETT2_BIQUAD_COEFFS; i++) + private->precomp_flt_values[index + i] = + ucontrol->value.integer.value[i]; + + /* Send change to the device */ + err = scarlett2_usb_set_data( + mixer, private->config_set->param_buf_addr, 1, index); + if (err < 0) + return err; + + err = scarlett2_usb_set_config_buf( + mixer, SCARLETT2_CONFIG_PRECOMP_FLT_PARAMS, + index, SCARLETT2_BIQUAD_COEFFS, + &private->precomp_flt_values[index]); + + return err < 0 ? err : 1; +} + +static int scarlett2_peq_flt_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int src_index = elem->control * SCARLETT2_BIQUAD_COEFFS; + int dst_index = ( + elem->control / + info->peq_flt_count * + info->peq_flt_total_count + + elem->control % info->peq_flt_count + ) * SCARLETT2_BIQUAD_COEFFS; + int i, oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_put_during_autogain(mixer); + if (err < 0) + return err; + + /* Check if any of the values have changed; if not, return */ + for (i = 0; i < SCARLETT2_BIQUAD_COEFFS; i++) { + oval = private->peq_flt_values[src_index + i]; + val = ucontrol->value.integer.value[i]; + if (oval != val) + break; + } + + if (i == SCARLETT2_BIQUAD_COEFFS) + return 0; + + /* Update the values */ + for (i = 0; i < SCARLETT2_BIQUAD_COEFFS; i++) + private->peq_flt_values[src_index + i] = + ucontrol->value.integer.value[i]; + + /* Send change to the device */ + err = scarlett2_usb_set_data( + mixer, private->config_set->param_buf_addr, 1, dst_index); + if (err < 0) + return err; + + err = scarlett2_usb_set_config_buf( + mixer, SCARLETT2_CONFIG_PEQ_FLT_PARAMS, + dst_index, SCARLETT2_BIQUAD_COEFFS, + &private->peq_flt_values[src_index]); + + return err < 0 ? err : 1; +} + +static int scarlett2_flt_ctl_info( + struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = SCARLETT2_BIQUAD_COEFFS; + uinfo->value.integer.min = INT_MIN; + uinfo->value.integer.max = INT_MAX; + uinfo->value.integer.step = 1; + return 0; +} + +static const struct snd_kcontrol_new scarlett2_precomp_flt_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "", + .info = scarlett2_flt_ctl_info, + .get = scarlett2_precomp_flt_ctl_get, + .put = scarlett2_precomp_flt_ctl_put, +}; + +static const struct snd_kcontrol_new scarlett2_peq_flt_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "", + .info = scarlett2_flt_ctl_info, + .get = scarlett2_peq_flt_ctl_get, + .put = scarlett2_peq_flt_ctl_put, +}; + +/*** Input Mute Switch Controls ***/ + +static int scarlett2_update_input_mute(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + + private->input_mute_updated = 0; + + if (!info->mute_input_count) + return 0; + + return scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_INPUT_MUTE_SWITCH, + info->mute_input_count, private->input_mute_switch); +} + +static int scarlett2_input_mute_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->input_mute_updated) { + err = scarlett2_update_input_mute(mixer); + if (err < 0) + return err; + } + ucontrol->value.integer.value[0] = + private->input_mute_switch[elem->control]; + return 0; +} + +static int scarlett2_input_mute_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_put_during_autogain(mixer); + if (err < 0) + return err; + + oval = private->input_mute_switch[index]; + val = ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->input_mute_switch[index] = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_INPUT_MUTE_SWITCH, + index, val); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_input_mute_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_autogain_disables_ctl_info, + .get = scarlett2_input_mute_ctl_get, + .put = scarlett2_input_mute_ctl_put, +}; + +/*** Phantom Switch Controls ***/ + +static int scarlett2_update_input_phantom(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int err; + + private->input_phantom_updated = 0; + + if (!info->phantom_count) + return 0; + + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_PHANTOM_SWITCH, + info->phantom_count, private->phantom_switch); + if (err < 0) + return err; + + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_PHANTOM_PERSISTENCE)) { + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_PHANTOM_PERSISTENCE, + 1, &private->phantom_persistence); + if (err < 0) + return err; + } + + return 0; +} + +/* Check if phantom power on the given input is currently changing state */ +static int scarlett2_phantom_is_switching( + struct scarlett2_data *private, int line_num) +{ + const struct scarlett2_device_info *info = private->info; + int index = line_num / info->inputs_per_phantom; + + return !!(private->phantom_switch[index] & 0x02); +} + +/* Update autogain controls' access mode when phantom power changes state */ +static void scarlett2_phantom_update_access(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int i; + + /* Disable autogain controls if phantom power is changing state */ + for (i = 0; i < info->gain_input_count; i++) { + int val = !scarlett2_phantom_is_switching(private, i); + + scarlett2_set_ctl_access(private->autogain_ctls[i], val); + } +} + +/* Notify of access mode change for autogain which can't be enabled + * while phantom power is changing. + */ +static void scarlett2_phantom_notify_access(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int i; + + for (i = 0; i < info->gain_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, + &private->autogain_ctls[i]->id); +} + +/* Call scarlett2_update_input_phantom() and + * scarlett2_phantom_update_access() if input_phantom_updated is set. + */ +static int scarlett2_check_input_phantom_updated( + struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int err; + + if (!private->input_phantom_updated) + return 0; + + err = scarlett2_update_input_phantom(mixer); + if (err < 0) + return err; + + scarlett2_phantom_update_access(mixer); + + return 0; +} + +static int scarlett2_phantom_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_input_phantom_updated(mixer); + if (err < 0) + return err; + + ucontrol->value.integer.value[0] = scarlett2_decode_muteable( + private->phantom_switch[elem->control]); + return 0; +} + +static int scarlett2_phantom_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int index = elem->control; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_check_put_during_autogain(mixer); + if (err < 0) + return err; + + oval = private->phantom_switch[index]; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->phantom_switch[index] = val; + + /* To set the Gen 4 muteable controls, bit 1 gets set */ + if (private->config_set->items[SCARLETT2_CONFIG_PHANTOM_SWITCH].mute) + val = (!val) | 0x02; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_PHANTOM_SWITCH, + index + info->phantom_first, val); + scarlett2_phantom_update_access(mixer); + scarlett2_phantom_notify_access(mixer); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_phantom_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_autogain_disables_ctl_info, + .get = scarlett2_phantom_ctl_get, + .put = scarlett2_phantom_ctl_put, +}; + +/*** Phantom Persistence Control ***/ + +static int scarlett2_phantom_persistence_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + + ucontrol->value.integer.value[0] = private->phantom_persistence; + return 0; +} + +static int scarlett2_phantom_persistence_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->phantom_persistence; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->phantom_persistence = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_PHANTOM_PERSISTENCE, index, val); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_phantom_persistence_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = snd_ctl_boolean_mono_info, + .get = scarlett2_phantom_persistence_ctl_get, + .put = scarlett2_phantom_persistence_ctl_put, +}; + +/*** Speaker Switching Control ***/ + +static int scarlett2_update_monitor_other(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int err; + + /* monitor_other_enable[0] enables speaker switching + * monitor_other_enable[1] enables talkback + */ + u8 monitor_other_enable[2]; + + /* monitor_other_switch[0] activates the alternate speakers + * monitor_other_switch[1] activates talkback + */ + u8 monitor_other_switch[2]; + + private->monitor_other_updated = 0; + + /* if it doesn't do speaker switching then it also doesn't do + * talkback + */ + if (!info->has_speaker_switching) + return 0; + + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE, + 2, monitor_other_enable); + if (err < 0) + return err; + + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH, + 2, monitor_other_switch); + if (err < 0) + return err; + + if (!monitor_other_enable[0]) + private->speaker_switching_switch = 0; + else + private->speaker_switching_switch = monitor_other_switch[0] + 1; + + if (info->has_talkback) { + u16 bitmap; + int i; + + if (!monitor_other_enable[1]) + private->talkback_switch = 0; + else + private->talkback_switch = monitor_other_switch[1] + 1; + + err = scarlett2_usb_get_config(mixer, + SCARLETT2_CONFIG_TALKBACK_MAP, + 1, &bitmap); + if (err < 0) + return err; + for (i = 0; i < private->num_mix_out; i++, bitmap >>= 1) + private->talkback_map[i] = bitmap & 1; + } + + return 0; +} + +static int scarlett2_speaker_switch_enum_ctl_info( + struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + static const char *const values[3] = { + "Off", "Main", "Alt" + }; + + return snd_ctl_enum_info(uinfo, 1, 3, values); +} + +static int scarlett2_speaker_switch_enum_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->monitor_other_updated) { + err = scarlett2_update_monitor_other(mixer); + if (err < 0) + return err; + } + ucontrol->value.enumerated.item[0] = private->speaker_switching_switch; + return 0; +} + +/* when speaker switching gets enabled, switch the main/alt speakers + * to HW volume and disable those controls + */ +static int scarlett2_speaker_switch_enable(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + int i, err; + + for (i = 0; i < 4; i++) { + int index = line_out_remap(private, i); + + /* switch the main/alt speakers to HW volume */ + if (!private->vol_sw_hw_switch[index]) { + err = scarlett2_sw_hw_change(private->mixer, i, 1); + if (err < 0) + return err; + } + + /* disable the line out SW/HW switch */ + scarlett2_sw_hw_ctl_ro(private, i); + snd_ctl_notify(card, + SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, + &private->sw_hw_ctls[i]->id); + } + + /* when the next monitor-other notify comes in, update the mux + * configuration + */ + private->speaker_switching_switched = 1; + + return 0; +} + +/* when speaker switching gets disabled, reenable the hw/sw controls + * and invalidate the routing + */ +static void scarlett2_speaker_switch_disable(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + int i; + + /* enable the line out SW/HW switch */ + for (i = 0; i < 4; i++) { + scarlett2_sw_hw_ctl_rw(private, i); + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, + &private->sw_hw_ctls[i]->id); + } + + /* when the next monitor-other notify comes in, update the mux + * configuration + */ + private->speaker_switching_switched = 1; +} + +static int scarlett2_speaker_switch_enum_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->speaker_switching_switch; + val = min(ucontrol->value.enumerated.item[0], 2U); + + if (oval == val) + return 0; + + private->speaker_switching_switch = val; + + /* enable/disable speaker switching */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE, + 0, !!val); + if (err < 0) + return err; + + /* if speaker switching is enabled, select main or alt */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH, + 0, val == 2); + if (err < 0) + return err; + + /* update controls if speaker switching gets enabled or disabled */ + if (!oval && val) + err = scarlett2_speaker_switch_enable(mixer); + else if (oval && !val) + scarlett2_speaker_switch_disable(mixer); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_speaker_switch_enum_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_speaker_switch_enum_ctl_info, + .get = scarlett2_speaker_switch_enum_ctl_get, + .put = scarlett2_speaker_switch_enum_ctl_put, +}; + +static int scarlett2_add_speaker_switch_ctl(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + + if (!info->has_speaker_switching) + return 0; + + return scarlett2_add_new_ctl( + mixer, &scarlett2_speaker_switch_enum_ctl, + 0, 1, "Speaker Switching Playback Enum", + &private->speaker_switching_ctl); +} + +/*** Talkback and Talkback Map Controls ***/ + +static int scarlett2_talkback_enum_ctl_info( + struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + static const char *const values[3] = { + "Disabled", "Off", "On" + }; + + return snd_ctl_enum_info(uinfo, 1, 3, values); +} + +static int scarlett2_talkback_enum_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->monitor_other_updated) { + err = scarlett2_update_monitor_other(mixer); + if (err < 0) + return err; + } + ucontrol->value.enumerated.item[0] = private->talkback_switch; + return 0; +} + +static int scarlett2_talkback_enum_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->talkback_switch; + val = min(ucontrol->value.enumerated.item[0], 2U); + + if (oval == val) + return 0; + + private->talkback_switch = val; + + /* enable/disable talkback */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE, + 1, !!val); + if (err < 0) + return err; + + /* if talkback is enabled, select main or alt */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH, + 1, val == 2); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_talkback_enum_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_talkback_enum_ctl_info, + .get = scarlett2_talkback_enum_ctl_get, + .put = scarlett2_talkback_enum_ctl_put, +}; + +static int scarlett2_talkback_map_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + + ucontrol->value.integer.value[0] = private->talkback_map[index]; + + return 0; +} + +static int scarlett2_talkback_map_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err, i; + u16 bitmap = 0; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->talkback_map[index]; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->talkback_map[index] = val; + + for (i = 0; i < private->num_mix_out; i++) + bitmap |= private->talkback_map[i] << i; + + /* Send updated bitmap to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_TALKBACK_MAP, + 0, bitmap); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_talkback_map_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = snd_ctl_boolean_mono_info, + .get = scarlett2_talkback_map_ctl_get, + .put = scarlett2_talkback_map_ctl_put, +}; + +static int scarlett2_add_talkback_ctls(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int err, i; + char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + if (!info->has_talkback) + return 0; + + err = scarlett2_add_new_ctl( + mixer, &scarlett2_talkback_enum_ctl, + 0, 1, "Talkback Playback Enum", + &private->talkback_ctl); + if (err < 0) + return err; + + for (i = 0; i < private->num_mix_out; i++) { + snprintf(s, sizeof(s), + "Talkback Mix %c Playback Switch", i + 'A'); + err = scarlett2_add_new_ctl(mixer, &scarlett2_talkback_map_ctl, + i, 1, s, NULL); + if (err < 0) + return err; + } + + return 0; +} + +/*** Dim/Mute Controls ***/ + +static int scarlett2_dim_mute_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->dim_mute_updated) { + err = scarlett2_update_dim_mute(mixer); + if (err < 0) + return err; + } + ucontrol->value.integer.value[0] = private->dim_mute[elem->control]; + return 0; +} + +static int scarlett2_dim_mute_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err, i; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->dim_mute[index]; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->dim_mute[index] = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_DIM_MUTE, + index, val); + if (index == SCARLETT2_BUTTON_MUTE) + for (i = 0; i < private->num_line_out; i++) { + int line_index = line_out_remap(private, i); + + if (private->vol_sw_hw_switch[line_index]) { + private->mute_switch[line_index] = val; + snd_ctl_notify(mixer->chip->card, + SNDRV_CTL_EVENT_MASK_VALUE, + &private->mute_ctls[i]->id); + } + } + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_dim_mute_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = snd_ctl_boolean_mono_info, + .get = scarlett2_dim_mute_ctl_get, + .put = scarlett2_dim_mute_ctl_put +}; + +/*** Vocaster Speaker/Headphone Mute Controls ***/ + +static int scarlett2_sp_hp_mute_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + + ucontrol->value.integer.value[0] = + !!(private->sp_hp_mute & (1 << elem->control)); + + return 0; +} + +static int scarlett2_sp_hp_mute_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + val = private->sp_hp_mute; + + if (ucontrol->value.integer.value[0]) + val |= (1 << index); + else + val &= ~(1 << index); + + if (val == private->sp_hp_mute) + return 0; + + private->sp_hp_mute = val; + + /* Send change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_SP_HP_MUTE, + 0, val); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_sp_hp_mute_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = snd_ctl_boolean_mono_info, + .get = scarlett2_sp_hp_mute_ctl_get, + .put = scarlett2_sp_hp_mute_ctl_put +}; + +/*** Create the analogue output controls ***/ + +static int scarlett2_add_line_out_ctls(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int err, i; + char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + /* Add R/O HW volume control */ + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_MASTER_VOLUME)) { + snprintf(s, sizeof(s), "Master HW Playback Volume"); + err = scarlett2_add_new_ctl(mixer, + &scarlett2_master_volume_ctl, + 0, 1, s, &private->master_vol_ctl); + if (err < 0) + return err; + } + + /* Add R/O headphone volume control */ + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_HEADPHONE_VOLUME)) { + snprintf(s, sizeof(s), "Headphone Playback Volume"); + err = scarlett2_add_new_ctl(mixer, + &scarlett2_headphone_volume_ctl, + 0, 1, s, + &private->headphone_vol_ctl); + if (err < 0) + return err; + } + + /* Add Vocaster speaker/headphone mute controls */ + if (private->info->sp_hp_mute_names) + for (i = 0; private->info->sp_hp_mute_names[i]; i++) { + err = scarlett2_add_new_ctl( + mixer, &scarlett2_sp_hp_mute_ctl, + i, 1, private->info->sp_hp_mute_names[i], + NULL); + if (err < 0) + return err; + } + + /* Remaining controls are only applicable if the device + * has per-channel line-out volume controls. + */ + if (!scarlett2_has_config_item(private, + SCARLETT2_CONFIG_LINE_OUT_VOLUME)) + return 0; + + /* Add volume controls */ + for (i = 0; i < private->num_line_out; i++) { + int index = line_out_remap(private, i); + + /* Fader */ + if (info->line_out_descrs[i]) + snprintf(s, sizeof(s), + "Line %02d (%s) Playback Volume", + i + 1, info->line_out_descrs[i]); + else + snprintf(s, sizeof(s), + "Line %02d Playback Volume", + i + 1); + err = scarlett2_add_new_ctl(mixer, + &scarlett2_line_out_volume_ctl, + i, 1, s, &private->vol_ctls[i]); + if (err < 0) + return err; + + /* Mute Switch */ + snprintf(s, sizeof(s), + "Line %02d Mute Playback Switch", + i + 1); + err = scarlett2_add_new_ctl(mixer, + &scarlett2_mute_ctl, + i, 1, s, + &private->mute_ctls[i]); + if (err < 0) + return err; + + /* SW/HW Switch */ + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_SW_HW_SWITCH)) { + + /* Make the fader and mute controls read-only if the + * SW/HW switch is set to HW + */ + if (private->vol_sw_hw_switch[index]) + scarlett2_vol_ctl_set_writable(mixer, i, 0); + + scnprintf(s, sizeof(s), + "Line Out %02d Volume Control Playback Enum", + i + 1); + err = scarlett2_add_new_ctl(mixer, + &scarlett2_sw_hw_enum_ctl, + i, 1, s, + &private->sw_hw_ctls[i]); + if (err < 0) + return err; + + /* Make the switch read-only if the line is + * involved in speaker switching + */ + if (private->speaker_switching_switch && i < 4) + scarlett2_sw_hw_ctl_ro(private, i); + } + } + + /* Add dim/mute controls */ + if (scarlett2_has_config_item(private, SCARLETT2_CONFIG_DIM_MUTE)) + for (i = 0; i < SCARLETT2_DIM_MUTE_COUNT; i++) { + err = scarlett2_add_new_ctl( + mixer, &scarlett2_dim_mute_ctl, + i, 1, scarlett2_dim_mute_names[i], + &private->dim_mute_ctls[i]); + if (err < 0) + return err; + } + + return 0; +} + +/*** Create the analogue input controls ***/ + +static int scarlett2_add_dsp_ctls(struct usb_mixer_interface *mixer, int i) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int j, err; + char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + const char *compr_fmt = "Line In %d Compressor %s"; + const char *flt_switch_fmt = "Line In %d %s Filter Enable"; + const char *flt_fmt = "Line In %d %s Coefficients %d"; + + /* Add compressor controls */ + for (j = 0; j < SCARLETT2_COMPRESSOR_PARAM_COUNT; j++) { + const struct compressor_param *param = &compressor_params[j]; + int idx = i * SCARLETT2_COMPRESSOR_PARAM_COUNT + j; + + scnprintf(s, sizeof(s), compr_fmt, i + 1, param->name); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_compressor_ctl, + i * SCARLETT2_COMPRESSOR_PARAM_COUNT + j, + 1, s, &private->compressor_ctls[idx]); + if (err < 0) + return err; + } + + /* Add filter enable controls */ + scnprintf(s, sizeof(s), flt_switch_fmt, i + 1, "Pre-Comp"); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_precomp_flt_switch_ctl, + i, 1, s, &private->precomp_flt_switch_ctls[i]); + if (err < 0) + return err; + + scnprintf(s, sizeof(s), flt_switch_fmt, i + 1, "PEQ"); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_peq_flt_switch_ctl, + i, 1, s, &private->peq_flt_switch_ctls[i]); + if (err < 0) + return err; + + /* Add filter coefficient controls */ + for (j = 0; j < info->precomp_flt_count; j++) { + scnprintf(s, sizeof(s), flt_fmt, i + 1, "Pre-Comp", j + 1); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_precomp_flt_ctl, + i * info->precomp_flt_count + j, + 1, s, &private->precomp_flt_switch_ctls[j]); + if (err < 0) + return err; + } + + for (j = 0; j < info->peq_flt_count; j++) { + scnprintf(s, sizeof(s), flt_fmt, i + 1, "PEQ", j + 1); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_peq_flt_ctl, + i * info->peq_flt_count + j, + 1, s, &private->peq_flt_switch_ctls[j]); + if (err < 0) + return err; + } + + return 0; +} + +static int scarlett2_add_line_in_ctls(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int err, i; + char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + const char *fmt = "Line In %d %s Capture %s"; + const char *fmt2 = "Line In %d-%d %s Capture %s"; + + /* Add input level (line/inst) controls */ + for (i = 0; i < info->level_input_count; i++) { + scnprintf(s, sizeof(s), fmt, i + 1 + info->level_input_first, + "Level", "Enum"); + err = scarlett2_add_new_ctl(mixer, &scarlett2_level_enum_ctl, + i, 1, s, &private->level_ctls[i]); + if (err < 0) + return err; + } + + /* Add input pad controls */ + for (i = 0; i < info->pad_input_count; i++) { + scnprintf(s, sizeof(s), fmt, i + 1, "Pad", "Switch"); + err = scarlett2_add_new_ctl(mixer, &scarlett2_pad_ctl, + i, 1, s, &private->pad_ctls[i]); + if (err < 0) + return err; + } + + /* Add input air controls */ + for (i = 0; i < info->air_input_count; i++) { + scnprintf(s, sizeof(s), fmt, i + 1 + info->air_input_first, + "Air", info->air_option ? "Enum" : "Switch"); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_air_ctl[info->air_option], + i, 1, s, &private->air_ctls[i]); + if (err < 0) + return err; + } + + /* Add input DSP controls */ + for (i = 0; i < info->dsp_input_count; i++) { + scnprintf(s, sizeof(s), fmt, i + 1, "DSP", "Switch"); + err = scarlett2_add_new_ctl(mixer, &scarlett2_dsp_ctl, + i, 1, s, &private->dsp_ctls[i]); + if (err < 0) + return err; + + err = scarlett2_add_dsp_ctls(mixer, i); + if (err < 0) + return err; + } + + /* Add input mute controls */ + for (i = 0; i < info->mute_input_count; i++) { + scnprintf(s, sizeof(s), fmt, i + 1, "Mute", "Switch"); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_input_mute_ctl, + i, 1, s, &private->input_mute_ctls[i]); + if (err < 0) + return err; + } + + /* Add input phantom controls */ + if (info->inputs_per_phantom == 1) { + for (i = 0; i < info->phantom_count; i++) { + scnprintf(s, sizeof(s), fmt, + i + 1 + info->phantom_first, + "Phantom Power", "Switch"); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_phantom_ctl, + i, 1, s, &private->phantom_ctls[i]); + if (err < 0) + return err; + } + } else if (info->inputs_per_phantom > 1) { + for (i = 0; i < info->phantom_count; i++) { + int from = i * info->inputs_per_phantom + 1; + int to = (i + 1) * info->inputs_per_phantom; + + scnprintf(s, sizeof(s), fmt2, from, to, + "Phantom Power", "Switch"); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_phantom_ctl, + i, 1, s, &private->phantom_ctls[i]); + if (err < 0) + return err; + } + } + if (info->phantom_count && + scarlett2_has_config_item(private, + SCARLETT2_CONFIG_PHANTOM_PERSISTENCE)) { + err = scarlett2_add_new_ctl( + mixer, &scarlett2_phantom_persistence_ctl, 0, 1, + "Phantom Power Persistence Capture Switch", NULL); + if (err < 0) + return err; + } + + /* Add input select/link controls */ + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_INPUT_SELECT_SWITCH)) { + err = scarlett2_add_new_ctl( + mixer, &scarlett2_input_select_ctl, 0, 1, + "Input Select Capture Enum", + &private->input_select_ctl); + if (err < 0) + return err; + } + + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_INPUT_LINK_SWITCH)) { + for (i = 0; i < info->gain_input_count; i++) { + scnprintf(s, sizeof(s), + "Line In %d Link Capture Switch", i + 1); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_input_link_ctl, + i, 1, s, &private->input_link_ctls[i]); + if (err < 0) + return err; + } + } + + /* Add software-controllable input gain controls */ + for (i = 0; i < info->gain_input_count; i++) { + scnprintf(s, sizeof(s), fmt, i + 1, + "Gain", "Volume"); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_input_gain_ctl, + i, 1, s, &private->input_gain_ctls[i]); + if (err < 0) + return err; + private->input_gain_ctls[i]->tlv.p = + private->config_set->input_gain_tlv; + + scnprintf(s, sizeof(s), fmt, i + 1, + "Autogain", "Switch"); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_autogain_switch_ctl, + i, 1, s, &private->autogain_ctls[i]); + if (err < 0) + return err; + + scnprintf(s, sizeof(s), fmt, i + 1, + "Autogain Status", "Enum"); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_autogain_status_ctl, + i, 1, s, &private->autogain_status_ctls[i]); + } + + /* Add autogain target controls */ + for (i = 0; i < SCARLETT2_AG_TARGET_COUNT; i++) + if (scarlett2_has_config_item(private, + scarlett2_ag_target_configs[i])) { + + scnprintf(s, sizeof(s), "Autogain %s Target", + scarlett2_ag_target_names[i]); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_ag_target_ctl, + i, 1, s, &private->ag_target_ctls[i]); + if (err < 0) + return err; + } + + /* Add safe-mode input switch controls */ + for (i = 0; i < info->safe_input_count; i++) { + scnprintf(s, sizeof(s), fmt, i + 1, + "Safe", "Switch"); + err = scarlett2_add_new_ctl( + mixer, &scarlett2_safe_ctl, + i, 1, s, &private->safe_ctls[i]); + if (err < 0) + return err; + } + + /* Add PCM Input Switch control */ + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_PCM_INPUT_SWITCH)) { + err = scarlett2_add_new_ctl( + mixer, &scarlett2_pcm_input_switch_ctl, 0, 1, + "PCM Input Capture Switch", + &private->pcm_input_switch_ctl); + if (err < 0) + return err; + } + + return 0; +} + +/*** Mixer Volume Controls ***/ + +static int scarlett2_update_mix(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int i, err; + + private->mix_updated = 0; + + for (i = 0; i < private->num_mix_out; i++) { + err = scarlett2_usb_get_mix(mixer, i); + if (err < 0) + return err; + } + + return 1; +} + +static int scarlett2_mixer_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SCARLETT2_MIXER_MAX_VALUE; + uinfo->value.integer.step = 1; + return 0; +} + +static int scarlett2_mixer_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->mix_updated) { + err = scarlett2_update_mix(mixer); + if (err < 0) + return err; + } + ucontrol->value.integer.value[0] = private->mix[elem->control]; + return 0; +} + +static int scarlett2_mixer_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int oval, val, mix_num, err; + int index = elem->control; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->mix[index]; + val = clamp(ucontrol->value.integer.value[0], + 0L, (long)SCARLETT2_MIXER_MAX_VALUE); + mix_num = index / private->num_mix_in; + + if (oval == val) + return 0; + + private->mix[index] = val; + err = scarlett2_usb_set_mix(mixer, mix_num); + + return err < 0 ? err : 1; +} + +static const DECLARE_TLV_DB_MINMAX( + db_scale_scarlett2_mixer, + SCARLETT2_MIXER_MIN_DB * 100, + SCARLETT2_MIXER_MAX_DB * 100 +); + +static const struct snd_kcontrol_new scarlett2_mixer_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett2_mixer_ctl_info, + .get = scarlett2_mixer_ctl_get, + .put = scarlett2_mixer_ctl_put, + .private_value = SCARLETT2_MIXER_MAX_DB, /* max value */ + .tlv = { .p = db_scale_scarlett2_mixer } +}; + +static int scarlett2_add_mixer_ctls(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int err, i, j; + int index; + char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + for (i = 0, index = 0; i < private->num_mix_out; i++) + for (j = 0; j < private->num_mix_in; j++, index++) { + snprintf(s, sizeof(s), + "Mix %c Input %02d Playback Volume", + 'A' + i, j + 1); + err = scarlett2_add_new_ctl(mixer, &scarlett2_mixer_ctl, + index, 1, s, + &private->mix_ctls[index]); + if (err < 0) + return err; + } + + return 0; +} + +/*** Direct Monitor Control ***/ + +static int scarlett2_update_direct_monitor(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + private->direct_monitor_updated = 0; + + if (!private->info->direct_monitor) + return 0; + + return scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_DIRECT_MONITOR, + 1, &private->direct_monitor_switch); +} + +static int scarlett2_update_monitor_mix(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int err, i; + u16 mix_values[SCARLETT2_MONITOR_MIX_MAX]; + + if (!private->num_monitor_mix_ctls) + return 0; + + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN, + private->num_monitor_mix_ctls, mix_values); + if (err < 0) + return err; + + for (i = 0; i < private->num_monitor_mix_ctls; i++) + private->monitor_mix[i] = scarlett2_mixer_value_to_db( + mix_values[i]); + + return 0; +} + +static int scarlett2_direct_monitor_ctl_get( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->direct_monitor_updated) { + err = scarlett2_update_direct_monitor(mixer); + if (err < 0) + return err; + } + ucontrol->value.enumerated.item[0] = private->direct_monitor_switch; + return 0; +} + +static int scarlett2_direct_monitor_ctl_put( + struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = elem->control; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->direct_monitor_switch; + val = min(ucontrol->value.enumerated.item[0], 2U); + + if (oval == val) + return 0; + + private->direct_monitor_switch = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_DIRECT_MONITOR, index, val); + + return err < 0 ? err : 1; +} + +static int scarlett2_direct_monitor_stereo_enum_ctl_info( + struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + static const char *const values[3] = { + "Off", "Mono", "Stereo" + }; + + return snd_ctl_enum_info(uinfo, 1, 3, values); +} + +/* Direct Monitor for Solo is mono-only and only needs a boolean control + * Direct Monitor for 2i2 is selectable between Off/Mono/Stereo + */ +static const struct snd_kcontrol_new scarlett2_direct_monitor_ctl[2] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = snd_ctl_boolean_mono_info, + .get = scarlett2_direct_monitor_ctl_get, + .put = scarlett2_direct_monitor_ctl_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_direct_monitor_stereo_enum_ctl_info, + .get = scarlett2_direct_monitor_ctl_get, + .put = scarlett2_direct_monitor_ctl_put, + } +}; + +static int scarlett2_monitor_mix_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + + ucontrol->value.integer.value[0] = private->monitor_mix[elem->control]; + + return 0; +} + +static int scarlett2_monitor_mix_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int oval, val, err; + int index = elem->control; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->monitor_mix[index]; + val = clamp(ucontrol->value.integer.value[0], + 0L, (long)SCARLETT2_MIXER_MAX_VALUE); + + if (oval == val) + return 0; + + private->monitor_mix[index] = val; + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN, + index, scarlett2_mixer_values[val]); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_monitor_mix_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett2_mixer_ctl_info, + .get = scarlett2_monitor_mix_ctl_get, + .put = scarlett2_monitor_mix_ctl_put, + .private_value = SCARLETT2_MIXER_MAX_DB, /* max value */ + .tlv = { .p = db_scale_scarlett2_mixer } +}; + +static int scarlett2_add_direct_monitor_ctls(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + const char *s; + int err, i, j, k, index; + + if (!info->direct_monitor) + return 0; + + s = info->direct_monitor == 1 + ? "Direct Monitor Playback Switch" + : "Direct Monitor Playback Enum"; + + err = scarlett2_add_new_ctl( + mixer, &scarlett2_direct_monitor_ctl[info->direct_monitor - 1], + 0, 1, s, &private->direct_monitor_ctl); + if (err < 0) + return err; + + if (!private->num_monitor_mix_ctls) + return 0; + + /* 1 or 2 direct monitor selections (Mono & Stereo) */ + for (i = 0, index = 0; i < info->direct_monitor; i++) { + const char * const format = + "Monitor %sMix %c Input %02d Playback Volume"; + const char *mix_type; + + if (info->direct_monitor == 1) + mix_type = ""; + else if (i == 0) + mix_type = "1 "; + else + mix_type = "2 "; + + /* 2 Mix outputs, A/Left & B/Right */ + for (j = 0; j < 2; j++) + + /* Mix inputs */ + for (k = 0; k < private->num_mix_in; k++, index++) { + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + scnprintf(name, sizeof(name), format, + mix_type, 'A' + j, k + 1); + + err = scarlett2_add_new_ctl( + mixer, &scarlett2_monitor_mix_ctl, + index, 1, name, NULL); + if (err < 0) + return err; + } + } + + return 0; +} + +/*** Mux Source Selection Controls ***/ + +static int scarlett2_mux_src_enum_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + const struct scarlett2_device_info *info = private->info; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + unsigned int item = uinfo->value.enumerated.item; + int items = private->num_mux_srcs; + int port_type; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = elem->channels; + uinfo->value.enumerated.items = items; + + if (item >= items) + item = uinfo->value.enumerated.item = items - 1; + + for (port_type = 0; + port_type < SCARLETT2_PORT_TYPE_COUNT; + port_type++) { + if (item < port_count[port_type][SCARLETT2_PORT_IN]) { + const struct scarlett2_port *port = + &scarlett2_ports[port_type]; + + if (port_type == SCARLETT2_PORT_TYPE_MIX && + item >= private->num_mix_out) + scnprintf(uinfo->value.enumerated.name, + sizeof(uinfo->value.enumerated.name), + port->dsp_src_descr, + item - private->num_mix_out + 1); + else + scnprintf(uinfo->value.enumerated.name, + sizeof(uinfo->value.enumerated.name), + port->src_descr, + item + port->src_num_offset); + + return 0; + } + item -= port_count[port_type][SCARLETT2_PORT_IN]; + } + + return -EINVAL; +} + +static int scarlett2_mux_src_enum_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = line_out_remap(private, elem->control); + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->mux_updated) { + err = scarlett2_usb_get_mux(mixer); + if (err < 0) + return err; + } + ucontrol->value.enumerated.item[0] = private->mux[index]; + return 0; +} + +static int scarlett2_mux_src_enum_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int index = line_out_remap(private, elem->control); + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->mux[index]; + val = min(ucontrol->value.enumerated.item[0], + private->num_mux_srcs - 1U); + + if (oval == val) + return 0; + + private->mux[index] = val; + err = scarlett2_usb_set_mux(mixer); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_mux_src_enum_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_mux_src_enum_ctl_info, + .get = scarlett2_mux_src_enum_ctl_get, + .put = scarlett2_mux_src_enum_ctl_put, +}; + +static int scarlett2_add_mux_enums(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + int port_type, channel, i; + + for (i = 0, port_type = 0; + port_type < SCARLETT2_PORT_TYPE_COUNT; + port_type++) { + for (channel = 0; + channel < port_count[port_type][SCARLETT2_PORT_OUT]; + channel++, i++) { + int err; + char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int channel_num = channel + 1; + const struct scarlett2_port *port = + &scarlett2_ports[port_type]; + const char *descr = port->dst_descr; + + if (port_type == SCARLETT2_PORT_TYPE_MIX && + channel >= private->num_mix_in) { + channel_num -= private->num_mix_in; + descr = port->dsp_dst_descr; + } + + snprintf(s, sizeof(s) - 5, descr, channel_num); + strcat(s, " Enum"); + + err = scarlett2_add_new_ctl(mixer, + &scarlett2_mux_src_enum_ctl, + i, 1, s, + &private->mux_ctls[i]); + if (err < 0) + return err; + } + } + + return 0; +} + +/*** Meter Controls ***/ + +static int scarlett2_meter_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 4095; + uinfo->value.integer.step = 1; + return 0; +} + +static int scarlett2_meter_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + u8 *meter_level_map = private->meter_level_map; + u16 meter_levels[SCARLETT2_MAX_METERS]; + int i, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + err = scarlett2_usb_get_meter_levels(mixer, elem->channels, + meter_levels); + if (err < 0) + return err; + + /* copy & translate from meter_levels[] using meter_level_map[] */ + for (i = 0; i < elem->channels; i++) { + int idx = meter_level_map[i]; + int value; + + if (idx == 255) + value = 0; + else + value = meter_levels[idx]; + + ucontrol->value.integer.value[i] = value; + } + + return 0; +} + +static const struct snd_kcontrol_new scarlett2_meter_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .name = "", + .info = scarlett2_meter_ctl_info, + .get = scarlett2_meter_ctl_get +}; + +static int scarlett2_add_meter_ctl(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + /* devices without a mixer also don't support reporting levels */ + if (!scarlett2_has_mixer(private)) + return 0; + + return scarlett2_add_new_ctl(mixer, &scarlett2_meter_ctl, + 0, private->num_mux_dsts, + "Level Meter", NULL); +} + +/*** MSD Controls ***/ + +static int scarlett2_msd_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + + ucontrol->value.integer.value[0] = private->msd_switch; + return 0; +} + +static int scarlett2_msd_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->msd_switch; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->msd_switch = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_MSD_SWITCH, + 0, val); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_msd_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = snd_ctl_boolean_mono_info, + .get = scarlett2_msd_ctl_get, + .put = scarlett2_msd_ctl_put, +}; + +static int scarlett2_add_msd_ctl(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + if (!scarlett2_has_config_item(private, SCARLETT2_CONFIG_MSD_SWITCH)) + return 0; + + /* If MSD mode is off, hide the switch by default */ + if (!private->msd_switch && !(mixer->chip->setup & SCARLETT2_MSD_ENABLE)) + return 0; + + /* Add MSD control */ + return scarlett2_add_new_ctl(mixer, &scarlett2_msd_ctl, + 0, 1, "MSD Mode Switch", NULL); +} + +/*** Standalone Control ***/ + +static int scarlett2_standalone_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + + ucontrol->value.integer.value[0] = private->standalone_switch; + return 0; +} + +static int scarlett2_standalone_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->standalone_switch; + val = !!ucontrol->value.integer.value[0]; + + if (oval == val) + return 0; + + private->standalone_switch = val; + + /* Send switch change to the device */ + err = scarlett2_usb_set_config(mixer, + SCARLETT2_CONFIG_STANDALONE_SWITCH, + 0, val); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_standalone_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = snd_ctl_boolean_mono_info, + .get = scarlett2_standalone_ctl_get, + .put = scarlett2_standalone_ctl_put, +}; + +static int scarlett2_add_standalone_ctl(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + if (!scarlett2_has_config_item(private, + SCARLETT2_CONFIG_STANDALONE_SWITCH)) + return 0; + + /* Add standalone control */ + return scarlett2_add_new_ctl(mixer, &scarlett2_standalone_ctl, + 0, 1, "Standalone Switch", NULL); +} + +/*** Power Status ***/ + +static int scarlett2_update_power_status(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int err; + u8 power_ext, power_low; + + private->power_status_updated = 0; + + err = scarlett2_usb_get_config(mixer, SCARLETT2_CONFIG_POWER_EXT, + 1, &power_ext); + if (err < 0) + return err; + + err = scarlett2_usb_get_config(mixer, SCARLETT2_CONFIG_POWER_LOW, + 1, &power_low); + if (err < 0) + return err; + + if (power_low) + private->power_status = SCARLETT2_POWER_STATUS_FAIL; + else if (power_ext) + private->power_status = SCARLETT2_POWER_STATUS_EXT; + else + private->power_status = SCARLETT2_POWER_STATUS_BUS; + + return 0; +} + +static int scarlett2_power_status_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->power_status_updated) { + err = scarlett2_update_power_status(mixer); + if (err < 0) + return err; + } + ucontrol->value.integer.value[0] = private->power_status; + return 0; +} + +static int scarlett2_power_status_ctl_info( + struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + static const char *const values[3] = { + "External", "Bus", "Fail" + }; + + return snd_ctl_enum_info(uinfo, 1, 3, values); +} + +static const struct snd_kcontrol_new scarlett2_power_status_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .name = "", + .info = scarlett2_power_status_ctl_info, + .get = scarlett2_power_status_ctl_get, +}; + +static int scarlett2_add_power_status_ctl(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + if (!scarlett2_has_config_item(private, + SCARLETT2_CONFIG_POWER_EXT)) + return 0; + + /* Add power status control */ + return scarlett2_add_new_ctl(mixer, &scarlett2_power_status_ctl, + 0, 1, "Power Status Card Enum", + &private->power_status_ctl); +} + +/*** Bluetooth Volume ***/ + +static int scarlett2_update_bluetooth_volume(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int err; + + private->bluetooth_updated = 0; + + if (!private->info->has_bluetooth) + return 0; + + err = scarlett2_usb_get_config(mixer, + SCARLETT2_CONFIG_BLUETOOTH_VOLUME, + 1, &private->bluetooth_volume); + if (err < 0) + return err; + + return 0; +} + +static int scarlett2_bluetooth_volume_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + if (private->bluetooth_updated) { + err = scarlett2_update_bluetooth_volume(mixer); + if (err < 0) + return err; + } + ucontrol->value.integer.value[0] = private->bluetooth_volume; + return 0; +} + +static int scarlett2_bluetooth_volume_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int oval, val, err; + + guard(mutex)(&private->data_mutex); + + if (private->hwdep_in_use) + return -EBUSY; + + oval = private->bluetooth_volume; + val = clamp(ucontrol->value.integer.value[0], + 0L, (long)SCARLETT2_MAX_BLUETOOTH_VOLUME); + + if (oval == val) + return 0; + + private->bluetooth_volume = val; + err = scarlett2_usb_set_config(mixer, + SCARLETT2_CONFIG_BLUETOOTH_VOLUME, + 0, val); + + return err < 0 ? err : 1; +} + +static int scarlett2_bluetooth_volume_ctl_info( + struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SCARLETT2_MAX_BLUETOOTH_VOLUME; + uinfo->value.integer.step = 1; + return 0; +} + +static const struct snd_kcontrol_new scarlett2_bluetooth_volume_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_bluetooth_volume_ctl_info, + .get = scarlett2_bluetooth_volume_ctl_get, + .put = scarlett2_bluetooth_volume_ctl_put, +}; + +static int scarlett2_add_bluetooth_volume_ctl( + struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + if (!private->info->has_bluetooth) + return 0; + + /* Add Bluetooth volume control */ + return scarlett2_add_new_ctl(mixer, &scarlett2_bluetooth_volume_ctl, + 0, 1, "Bluetooth Capture Volume", + &private->bluetooth_volume_ctl); +} + +/*** S/PDIF Mode Controls ***/ + +static int scarlett2_update_spdif_mode(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int err, i; + u8 mode; + const u8 *mode_values = private->info->spdif_mode_values; + + if (!private->info->spdif_mode_control_name) + return 0; + + err = scarlett2_usb_get_config(mixer, SCARLETT2_CONFIG_SPDIF_MODE, + 1, &mode); + if (err < 0) + return err; + + private->spdif_mode = 0; + + for (i = 0; *mode_values != 0xff; i++, mode_values++) + if (*mode_values == mode) { + private->spdif_mode = i; + break; + } + + return 0; +} + +static int scarlett2_spdif_mode_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + const char * const *mode_texts = private->info->spdif_mode_texts; + int count = 0; + + while (*mode_texts++) + count++; + + return snd_ctl_enum_info(uinfo, 1, count, + private->info->spdif_mode_texts); +} + +static int scarlett2_spdif_mode_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett2_data *private = elem->head.mixer->private_data; + + ucontrol->value.enumerated.item[0] = private->spdif_mode; + return 0; +} + +static int scarlett2_spdif_mode_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct usb_mixer_interface *mixer = elem->head.mixer; + struct scarlett2_data *private = mixer->private_data; + int oval, val, err; + int i; + + guard(mutex)(&private->data_mutex); + + oval = private->spdif_mode; + val = ucontrol->value.enumerated.item[0]; + + if (val < 0) + return -EINVAL; + + for (i = 0; i <= val; i++) + if (private->info->spdif_mode_values[i] == 0xff) + return -EINVAL; + + if (oval == val) + return 0; + + private->spdif_mode = val; + + err = scarlett2_usb_set_config( + mixer, SCARLETT2_CONFIG_SPDIF_MODE, 0, + private->info->spdif_mode_values[val]); + + return err < 0 ? err : 1; +} + +static const struct snd_kcontrol_new scarlett2_spdif_mode_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett2_spdif_mode_ctl_info, + .get = scarlett2_spdif_mode_ctl_get, + .put = scarlett2_spdif_mode_ctl_put, +}; + +static int scarlett2_add_spdif_mode_ctl(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + if (!private->info->spdif_mode_control_name) + return 0; + + return scarlett2_add_new_ctl(mixer, &scarlett2_spdif_mode_ctl, + 0, 1, + private->info->spdif_mode_control_name, + NULL); +} + +/*** Notification Handlers ***/ + +/* Notify on sync change */ +static void scarlett2_notify_sync(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + private->sync_updated = 1; + + snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->sync_ctl->id); +} + +/* Notify on monitor change (Gen 2/3) */ +static void scarlett2_notify_monitor(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + int i; + + if (!scarlett2_has_config_item(private, SCARLETT2_CONFIG_SW_HW_SWITCH)) + return; + + private->vol_updated = 1; + + snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->master_vol_ctl->id); + + for (i = 0; i < private->num_line_out; i++) + if (private->vol_sw_hw_switch[line_out_remap(private, i)]) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->vol_ctls[i]->id); +} + +/* Notify on volume change (Gen 4) */ +static void scarlett2_notify_volume(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + private->vol_updated = 1; + + snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->master_vol_ctl->id); + snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->headphone_vol_ctl->id); +} + +/* Notify on dim/mute change */ +static void scarlett2_notify_dim_mute(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + int i; + + if (!scarlett2_has_config_item(private, SCARLETT2_CONFIG_SW_HW_SWITCH)) + return; + + private->dim_mute_updated = 1; + + for (i = 0; i < SCARLETT2_DIM_MUTE_COUNT; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->dim_mute_ctls[i]->id); + + for (i = 0; i < private->num_line_out; i++) + if (private->vol_sw_hw_switch[line_out_remap(private, i)]) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->mute_ctls[i]->id); +} + +/* Notify on input level switch change */ +static void scarlett2_notify_input_level(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int i; + + private->input_level_updated = 1; + + for (i = 0; i < info->level_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->level_ctls[i]->id); +} + +/* Notify on input pad switch change */ +static void scarlett2_notify_input_pad(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int i; + + private->input_pad_updated = 1; + + for (i = 0; i < info->pad_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->pad_ctls[i]->id); +} + +/* Notify on input air switch change */ +static void scarlett2_notify_input_air(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int i; + + private->input_air_updated = 1; + + for (i = 0; i < info->air_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->air_ctls[i]->id); +} + +/* Notify on input DSP switch change */ +static void scarlett2_notify_input_dsp(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int i; + + private->input_dsp_updated = 1; + + for (i = 0; i < info->dsp_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->dsp_ctls[i]->id); +} + +/* Notify on input mute switch change */ +static void scarlett2_notify_input_mute(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int i; + + private->input_mute_updated = 1; + + for (i = 0; i < info->mute_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->input_mute_ctls[i]->id); +} + +/* Notify on input phantom switch change */ +static void scarlett2_notify_input_phantom(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int i; + + private->input_phantom_updated = 1; + + for (i = 0; i < info->phantom_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->phantom_ctls[i]->id); + + scarlett2_phantom_notify_access(mixer); +} + +/* Notify on "input other" change (level/pad/air/phantom) */ +static void scarlett2_notify_input_other(struct usb_mixer_interface *mixer) +{ + scarlett2_notify_input_level(mixer); + scarlett2_notify_input_pad(mixer); + scarlett2_notify_input_air(mixer); + scarlett2_notify_input_phantom(mixer); +} + +/* Notify on input select change */ +static void scarlett2_notify_input_select(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int i; + + if (!scarlett2_has_config_item(private, + SCARLETT2_CONFIG_INPUT_SELECT_SWITCH)) + return; + + private->input_select_updated = 1; + + snd_ctl_notify(card, + SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO, + &private->input_select_ctl->id); + + for (i = 0; i < info->gain_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->input_link_ctls[i]->id); +} + +/* Notify on input gain change */ +static void scarlett2_notify_input_gain(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int i; + + if (!info->gain_input_count) + return; + + private->input_gain_updated = 1; + + for (i = 0; i < info->gain_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->input_gain_ctls[i]->id); +} + +/* Notify on autogain change */ +static void scarlett2_notify_autogain(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int i; + + if (!info->gain_input_count) + return; + + private->autogain_updated = 1; + + for (i = 0; i < info->gain_input_count; i++) { + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->autogain_ctls[i]->id); + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->autogain_status_ctls[i]->id); + } + + for (i = 0; i < SCARLETT2_AG_TARGET_COUNT; i++) + if (scarlett2_has_config_item(private, + scarlett2_ag_target_configs[i])) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, + &private->ag_target_ctls[i]->id); + + scarlett2_autogain_notify_access(mixer); +} + +/* Notify on input safe switch change */ +static void scarlett2_notify_input_safe(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int i; + + if (!info->safe_input_count) + return; + + private->input_safe_updated = 1; + + for (i = 0; i < info->safe_input_count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->safe_ctls[i]->id); +} + +/* Notify on "monitor other" change (speaker switching, talkback) */ +static void scarlett2_notify_monitor_other(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + + private->monitor_other_updated = 1; + + if (info->has_speaker_switching) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->speaker_switching_ctl->id); + + if (info->has_talkback) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->talkback_ctl->id); + + /* if speaker switching was recently enabled or disabled, + * invalidate the dim/mute and mux enum controls + */ + if (private->speaker_switching_switched) { + int i; + + scarlett2_notify_dim_mute(mixer); + + private->speaker_switching_switched = 0; + private->mux_updated = 1; + + for (i = 0; i < private->num_mux_dsts; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->mux_ctls[i]->id); + } +} + +/* Notify on direct monitor switch change */ +static void scarlett2_notify_direct_monitor(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + int count = private->num_mix_in * private->num_mix_out; + int i; + + private->direct_monitor_updated = 1; + + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->direct_monitor_ctl->id); + + if (!scarlett2_has_mixer(private)) + return; + + private->mix_updated = 1; + + /* Notify of change to the mix controls */ + for (i = 0; i < count; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->mix_ctls[i]->id); +} + +/* Notify on power change */ +static void scarlett2_notify_power_status(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + + private->power_status_updated = 1; + + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->power_status_ctl->id); +} + +/* Notify on mux change */ +static void scarlett2_notify_mux(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + int i; + + private->mux_updated = 1; + + for (i = 0; i < private->num_mux_dsts; i++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->mux_ctls[i]->id); +} + +/* Notify on PCM input switch change */ +static void scarlett2_notify_pcm_input_switch(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + + private->pcm_input_switch_updated = 1; + + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->pcm_input_switch_ctl->id); + + scarlett2_notify_mux(mixer); +} + +/* Notify on Bluetooth change */ +static void scarlett2_notify_bluetooth(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + + if (!private->info->has_bluetooth) + return; + + private->bluetooth_updated = 1; + + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &private->bluetooth_volume_ctl->id); +} + +/* Handle acknowledgement that a command was received; let + * scarlett2_usb() know that it can proceed + */ +static void scarlett2_notify_ack(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + /* if running == 0, ignore ACKs */ + if (private->running) + complete(&private->cmd_done); +} + +/* Interrupt callback */ +static void scarlett2_notify(struct urb *urb) +{ + struct usb_mixer_interface *mixer = urb->context; + int len = urb->actual_length; + int ustatus = urb->status; + u32 data; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_notification *notifications = + private->config_set->notifications; + + if (ustatus != 0 || len != 8) + goto requeue; + + data = le32_to_cpu(*(__le32 *)urb->transfer_buffer); + + /* Ignore notifications except ACK during initialisation. + * ACK is 0x00000001 on every device. + */ + if (private->running < 2) + data &= 1; + + while (data && notifications->mask) { + if (data & notifications->mask) { + data &= ~notifications->mask; + if (notifications->func) + notifications->func(mixer); + } + notifications++; + } + + if (data) + usb_audio_warn(mixer->chip, + "%s: Unhandled notification: 0x%08x\n", + __func__, data); + +requeue: + if (ustatus != -ENOENT && + ustatus != -ECONNRESET && + ustatus != -ESHUTDOWN) { + urb->dev = mixer->chip->dev; + usb_submit_urb(urb, GFP_ATOMIC); + } else { + complete(&private->cmd_done); + } +} + +/*** Cleanup/Suspend Callbacks ***/ + +static void scarlett2_private_free(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + cancel_delayed_work_sync(&private->work); + kfree(private); + mixer->private_data = NULL; +} + +static void scarlett2_private_suspend(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + if (cancel_delayed_work_sync(&private->work)) + scarlett2_config_save(private->mixer); +} + +/*** Initialisation ***/ + +static void scarlett2_count_io(struct scarlett2_data *private) +{ + const struct scarlett2_device_info *info = private->info; + const struct scarlett2_config_set *config_set = info->config_set; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + int port_type, srcs = 0, dsts = 0, i; + + /* Count the number of mux sources and destinations */ + for (port_type = 0; + port_type < SCARLETT2_PORT_TYPE_COUNT; + port_type++) { + srcs += port_count[port_type][SCARLETT2_PORT_IN]; + dsts += port_count[port_type][SCARLETT2_PORT_OUT]; + } + + private->num_mux_srcs = srcs; + private->num_mux_dsts = dsts; + + /* Mixer inputs are mux outputs and vice versa. + * Scarlett Gen 4 DSP I/O uses SCARLETT2_PORT_TYPE_MIX but + * doesn't have mixer controls. + */ + private->num_mix_in = + port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_OUT] - + info->dsp_count; + + private->num_mix_out = + port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_IN] - + info->dsp_count; + + /* Number of analogue line outputs */ + private->num_line_out = + port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT]; + + /* Number of monitor mix controls */ + private->num_monitor_mix_ctls = + info->direct_monitor * 2 * private->num_mix_in; + + /* Number of autogain status texts */ + if (config_set->autogain_status_texts) { + const char * const *texts = config_set->autogain_status_texts; + + for (i = 0; texts[i]; i++) + ; + private->num_autogain_status_texts = i; + } +} + +/* Look through the interface descriptors for the Focusrite Control + * interface (bInterfaceClass = 255 Vendor Specific Class) and set + * bInterfaceNumber, bEndpointAddress, wMaxPacketSize, and bInterval + * in private + */ +static int scarlett2_find_fc_interface(struct usb_device *dev, + struct scarlett2_data *private) +{ + struct usb_host_config *config = dev->actconfig; + int i; + + for (i = 0; i < config->desc.bNumInterfaces; i++) { + struct usb_interface *intf = config->interface[i]; + struct usb_interface_descriptor *desc = + &intf->altsetting[0].desc; + struct usb_endpoint_descriptor *epd; + + if (desc->bInterfaceClass != 255) + continue; + + epd = get_endpoint(intf->altsetting, 0); + private->bInterfaceNumber = desc->bInterfaceNumber; + private->bEndpointAddress = usb_endpoint_num(epd); + private->wMaxPacketSize = le16_to_cpu(epd->wMaxPacketSize); + private->bInterval = epd->bInterval; + return 0; + } + + return -EINVAL; +} + +/* Initialise private data */ +static int scarlett2_init_private(struct usb_mixer_interface *mixer, + const struct scarlett2_device_entry *entry) +{ + struct scarlett2_data *private = + kzalloc(sizeof(struct scarlett2_data), GFP_KERNEL); + + if (!private) + return -ENOMEM; + + mutex_init(&private->usb_mutex); + mutex_init(&private->data_mutex); + INIT_DELAYED_WORK(&private->work, scarlett2_config_save_work); + + mixer->private_data = private; + mixer->private_free = scarlett2_private_free; + mixer->private_suspend = scarlett2_private_suspend; + + private->info = entry->info; + private->config_set = entry->info->config_set; + private->series_name = entry->series_name; + scarlett2_count_io(private); + private->scarlett2_seq = 0; + private->mixer = mixer; + + return scarlett2_find_fc_interface(mixer->chip->dev, private); +} + +/* Submit a URB to receive notifications from the device */ +static int scarlett2_init_notify(struct usb_mixer_interface *mixer) +{ + struct usb_device *dev = mixer->chip->dev; + struct scarlett2_data *private = mixer->private_data; + unsigned int pipe = usb_rcvintpipe(dev, private->bEndpointAddress); + void *transfer_buffer; + + if (mixer->urb) { + usb_audio_err(mixer->chip, + "%s: mixer urb already in use!\n", __func__); + return 0; + } + + if (usb_pipe_type_check(dev, pipe)) + return -EINVAL; + + mixer->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!mixer->urb) + return -ENOMEM; + + transfer_buffer = kmalloc(private->wMaxPacketSize, GFP_KERNEL); + if (!transfer_buffer) + return -ENOMEM; + + usb_fill_int_urb(mixer->urb, dev, pipe, + transfer_buffer, private->wMaxPacketSize, + scarlett2_notify, mixer, private->bInterval); + + init_completion(&private->cmd_done); + + return usb_submit_urb(mixer->urb, GFP_KERNEL); +} + +/* Cargo cult proprietary initialisation sequence */ +static int scarlett2_usb_init(struct usb_mixer_interface *mixer) +{ + struct usb_device *dev = mixer->chip->dev; + struct scarlett2_data *private = mixer->private_data; + u8 step0_buf[24]; + u8 step2_buf[84]; + int err; + + if (usb_pipe_type_check(dev, usb_sndctrlpipe(dev, 0))) + return -EINVAL; + + /* step 0 */ + err = scarlett2_usb_rx(dev, private->bInterfaceNumber, + SCARLETT2_USB_CMD_INIT, + step0_buf, sizeof(step0_buf)); + if (err < 0) + return err; + + /* Set up the interrupt polling for notifications. + * When running is: + * 0: all notifications are ignored + * 1: only ACKs are handled + * 2: all notifications are handled + */ + err = scarlett2_init_notify(mixer); + if (err < 0) + return err; + + /* sleep for a moment in case of an outstanding ACK */ + msleep(20); + + /* start handling ACKs, but no other notifications until the + * ALSA controls have been created + */ + private->running = 1; + + /* step 1 */ + private->scarlett2_seq = 1; + err = scarlett2_usb(mixer, SCARLETT2_USB_INIT_1, NULL, 0, NULL, 0); + if (err < 0) + return err; + + /* step 2 */ + private->scarlett2_seq = 1; + err = scarlett2_usb(mixer, SCARLETT2_USB_INIT_2, + NULL, 0, + step2_buf, sizeof(step2_buf)); + if (err < 0) + return err; + + /* extract 4-byte firmware version from step2_buf[8] */ + private->firmware_version = le32_to_cpu(*(__le32 *)(step2_buf + 8)); + usb_audio_info(mixer->chip, + "Firmware version %d\n", + private->firmware_version); + + return 0; +} + +/* Get the flash segment numbers for the App_Settings and App_Upgrade + * segments and put them in the private data + */ +static int scarlett2_get_flash_segment_nums(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int err, count, i; + + struct { + __le32 size; + __le32 count; + u8 unknown[8]; + } __packed flash_info; + + struct { + __le32 size; + __le32 flags; + char name[16]; + } __packed segment_info; + + err = scarlett2_usb(mixer, SCARLETT2_USB_INFO_FLASH, + NULL, 0, + &flash_info, sizeof(flash_info)); + if (err < 0) + return err; + + count = le32_to_cpu(flash_info.count); + + /* sanity check count */ + if (count < SCARLETT2_SEGMENT_NUM_MIN || + count > SCARLETT2_SEGMENT_NUM_MAX + 1) { + usb_audio_err(mixer->chip, + "invalid flash segment count: %d\n", count); + return -EINVAL; + } + + for (i = 0; i < count; i++) { + __le32 segment_num_req = cpu_to_le32(i); + int flash_segment_id; + + err = scarlett2_usb(mixer, SCARLETT2_USB_INFO_SEGMENT, + &segment_num_req, sizeof(segment_num_req), + &segment_info, sizeof(segment_info)); + if (err < 0) { + usb_audio_err(mixer->chip, + "failed to get flash segment info %d: %d\n", + i, err); + return err; + } + + if (!strncmp(segment_info.name, + SCARLETT2_SEGMENT_SETTINGS_NAME, 16)) + flash_segment_id = SCARLETT2_SEGMENT_ID_SETTINGS; + else if (!strncmp(segment_info.name, + SCARLETT2_SEGMENT_FIRMWARE_NAME, 16)) + flash_segment_id = SCARLETT2_SEGMENT_ID_FIRMWARE; + else + continue; + + private->flash_segment_nums[flash_segment_id] = i; + private->flash_segment_blocks[flash_segment_id] = + le32_to_cpu(segment_info.size) / + SCARLETT2_FLASH_BLOCK_SIZE; + } + + /* segment 0 is App_Gold and we never want to touch that, so + * use 0 as the "not-found" value + */ + if (!private->flash_segment_nums[SCARLETT2_SEGMENT_ID_SETTINGS]) { + usb_audio_err(mixer->chip, + "failed to find flash segment %s\n", + SCARLETT2_SEGMENT_SETTINGS_NAME); + return -EINVAL; + } + if (!private->flash_segment_nums[SCARLETT2_SEGMENT_ID_FIRMWARE]) { + usb_audio_err(mixer->chip, + "failed to find flash segment %s\n", + SCARLETT2_SEGMENT_FIRMWARE_NAME); + return -EINVAL; + } + + return 0; +} + +/* Read configuration from the interface on start */ +static int scarlett2_read_configs(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + int err, i; + + if (scarlett2_has_config_item(private, SCARLETT2_CONFIG_MSD_SWITCH)) { + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_MSD_SWITCH, + 1, &private->msd_switch); + if (err < 0) + return err; + } + + if (private->firmware_version < info->min_firmware_version) { + usb_audio_err(mixer->chip, + "Focusrite %s firmware version %d is too old; " + "need %d", + private->series_name, + private->firmware_version, + info->min_firmware_version); + return 0; + } + + /* no other controls are created if MSD mode is on */ + if (private->msd_switch) + return 0; + + err = scarlett2_update_input_level(mixer); + if (err < 0) + return err; + + err = scarlett2_update_input_pad(mixer); + if (err < 0) + return err; + + err = scarlett2_update_input_air(mixer); + if (err < 0) + return err; + + err = scarlett2_update_input_dsp(mixer); + if (err < 0) + return err; + + err = scarlett2_update_compressor_values(mixer); + if (err < 0) + return err; + + err = scarlett2_update_filter_values(mixer); + if (err < 0) + return err; + + err = scarlett2_update_input_mute(mixer); + if (err < 0) + return err; + + err = scarlett2_update_input_phantom(mixer); + if (err < 0) + return err; + + err = scarlett2_update_direct_monitor(mixer); + if (err < 0) + return err; + + /* the rest of the configuration is for devices with a mixer */ + if (!scarlett2_has_mixer(private)) + return 0; + + err = scarlett2_update_monitor_mix(mixer); + if (err < 0) + return err; + + err = scarlett2_update_monitor_other(mixer); + if (err < 0) + return err; + + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_STANDALONE_SWITCH)) { + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_STANDALONE_SWITCH, + 1, &private->standalone_switch); + if (err < 0) + return err; + } + + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_POWER_EXT)) { + err = scarlett2_update_power_status(mixer); + if (err < 0) + return err; + } + + err = scarlett2_update_sync(mixer); + if (err < 0) + return err; + + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_LINE_OUT_VOLUME)) { + s16 sw_vol[SCARLETT2_ANALOGUE_MAX]; + + /* read SW line out volume */ + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_LINE_OUT_VOLUME, + private->num_line_out, &sw_vol); + if (err < 0) + return err; + + for (i = 0; i < private->num_line_out; i++) + private->vol[i] = clamp( + sw_vol[i] + SCARLETT2_VOLUME_BIAS, + 0, SCARLETT2_VOLUME_BIAS); + + /* read SW mute */ + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_MUTE_SWITCH, + private->num_line_out, &private->mute_switch); + if (err < 0) + return err; + + for (i = 0; i < private->num_line_out; i++) + private->mute_switch[i] = + !!private->mute_switch[i]; + + /* read SW/HW switches */ + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_SW_HW_SWITCH)) { + err = scarlett2_usb_get_config( + mixer, SCARLETT2_CONFIG_SW_HW_SWITCH, + private->num_line_out, + &private->vol_sw_hw_switch); + if (err < 0) + return err; + + for (i = 0; i < private->num_line_out; i++) + private->vol_sw_hw_switch[i] = + !!private->vol_sw_hw_switch[i]; + } + } + + err = scarlett2_update_volumes(mixer); + if (err < 0) + return err; + + err = scarlett2_update_dim_mute(mixer); + if (err < 0) + return err; + + err = scarlett2_update_input_select(mixer); + if (err < 0) + return err; + + err = scarlett2_update_input_gain(mixer); + if (err < 0) + return err; + + err = scarlett2_update_autogain(mixer); + if (err < 0) + return err; + + err = scarlett2_update_input_safe(mixer); + if (err < 0) + return err; + + if (scarlett2_has_config_item(private, + SCARLETT2_CONFIG_PCM_INPUT_SWITCH)) { + err = scarlett2_update_pcm_input_switch(mixer); + if (err < 0) + return err; + } + + err = scarlett2_update_bluetooth_volume(mixer); + if (err < 0) + return err; + + err = scarlett2_update_spdif_mode(mixer); + if (err < 0) + return err; + + err = scarlett2_update_mix(mixer); + if (err < 0) + return err; + + return scarlett2_usb_get_mux(mixer); +} + +static const struct scarlett2_device_entry *get_scarlett2_device_entry( + struct usb_mixer_interface *mixer) +{ + const struct scarlett2_device_entry *entry = scarlett2_devices; + + /* Find entry in scarlett2_devices */ + while (entry->usb_id && entry->usb_id != mixer->chip->usb_id) + entry++; + if (!entry->usb_id) + return NULL; + + return entry; +} + +static int snd_scarlett2_controls_create( + struct usb_mixer_interface *mixer, + const struct scarlett2_device_entry *entry) +{ + struct scarlett2_data *private; + int err; + + /* Initialise private data */ + err = scarlett2_init_private(mixer, entry); + if (err < 0) + return err; + + private = mixer->private_data; + + /* Send proprietary USB initialisation sequence */ + err = scarlett2_usb_init(mixer); + if (err < 0) + return err; + + /* Get the upgrade & settings flash segment numbers */ + err = scarlett2_get_flash_segment_nums(mixer); + if (err < 0) + return err; + + /* Add firmware version control */ + err = scarlett2_add_firmware_version_ctl(mixer); + if (err < 0) + return err; + + /* Add minimum firmware version control */ + err = scarlett2_add_min_firmware_version_ctl(mixer); + if (err < 0) + return err; + + /* Read volume levels and controls from the interface */ + err = scarlett2_read_configs(mixer); + if (err < 0) + return err; + + /* Create the MSD control */ + err = scarlett2_add_msd_ctl(mixer); + if (err < 0) + return err; + + /* If MSD mode is enabled, or if the firmware version is too + * old, don't create any other controls + */ + if (private->msd_switch || + private->firmware_version < private->info->min_firmware_version) + return 0; + + /* Create the analogue output controls */ + err = scarlett2_add_line_out_ctls(mixer); + if (err < 0) + return err; + + /* Create the analogue input controls */ + err = scarlett2_add_line_in_ctls(mixer); + if (err < 0) + return err; + + /* Create the input, output, and mixer mux input selections */ + err = scarlett2_add_mux_enums(mixer); + if (err < 0) + return err; + + /* Create the matrix mixer controls */ + err = scarlett2_add_mixer_ctls(mixer); + if (err < 0) + return err; + + /* Create the level meter controls */ + err = scarlett2_add_meter_ctl(mixer); + if (err < 0) + return err; + + /* Create the sync control */ + err = scarlett2_add_sync_ctl(mixer); + if (err < 0) + return err; + + /* Create the direct monitor control(s) */ + err = scarlett2_add_direct_monitor_ctls(mixer); + if (err < 0) + return err; + + /* Create the speaker switching control */ + err = scarlett2_add_speaker_switch_ctl(mixer); + if (err < 0) + return err; + + /* Create the talkback controls */ + err = scarlett2_add_talkback_ctls(mixer); + if (err < 0) + return err; + + /* Create the standalone control */ + err = scarlett2_add_standalone_ctl(mixer); + if (err < 0) + return err; + + /* Create the power status control */ + err = scarlett2_add_power_status_ctl(mixer); + if (err < 0) + return err; + + /* Create the Bluetooth volume control */ + err = scarlett2_add_bluetooth_volume_ctl(mixer); + if (err < 0) + return err; + + /* Create the S/PDIF mode control */ + err = scarlett2_add_spdif_mode_ctl(mixer); + if (err < 0) + return err; + + /* Set the access mode of controls disabled during + * autogain/phantom power switching. + */ + if (private->info->gain_input_count) { + scarlett2_autogain_update_access(mixer); + scarlett2_phantom_update_access(mixer); + } + + /* Start handling all notifications */ + private->running = 2; + + return 0; +} + +/*** hwdep interface ***/ + +/* Set private->hwdep_in_use; prevents access to the ALSA controls + * while doing a config erase/firmware upgrade. + */ +static void scarlett2_lock(struct scarlett2_data *private) +{ + guard(mutex)(&private->data_mutex); + private->hwdep_in_use = 1; +} + +/* Call SCARLETT2_USB_GET_ERASE to get the erase progress */ +static int scarlett2_get_erase_progress(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int segment_id, segment_num, err; + u8 erase_resp; + + struct { + __le32 segment_num; + __le32 pad; + } __packed erase_req; + + segment_id = private->selected_flash_segment_id; + segment_num = private->flash_segment_nums[segment_id]; + + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) + return -EFAULT; + + /* Send the erase progress request */ + erase_req.segment_num = cpu_to_le32(segment_num); + erase_req.pad = 0; + + err = scarlett2_usb(mixer, SCARLETT2_USB_GET_ERASE, + &erase_req, sizeof(erase_req), + &erase_resp, sizeof(erase_resp)); + if (err < 0) + return err; + + return erase_resp; +} + +/* Repeatedly call scarlett2_get_erase_progress() until it returns + * 0xff (erase complete) or we've waited 10 seconds (it usually takes + * <3 seconds). + */ +static int scarlett2_wait_for_erase(struct usb_mixer_interface *mixer) +{ + int i, err; + + for (i = 0; i < 100; i++) { + err = scarlett2_get_erase_progress(mixer); + if (err < 0) + return err; + + if (err == 0xff) + return 0; + + msleep(100); + } + + return -ETIMEDOUT; +} + +/* Reboot the device; wait for the erase to complete if one is in + * progress. + */ +static int scarlett2_reboot(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + if (private->flash_write_state == + SCARLETT2_FLASH_WRITE_STATE_ERASING) { + int err = scarlett2_wait_for_erase(mixer); + + if (err < 0) + return err; + } + + return scarlett2_usb(mixer, SCARLETT2_USB_REBOOT, NULL, 0, NULL, 0); +} + +/* Select a flash segment for reading/erasing/writing */ +static int scarlett2_ioctl_select_flash_segment( + struct usb_mixer_interface *mixer, + unsigned long arg) +{ + struct scarlett2_data *private = mixer->private_data; + int segment_id, segment_num; + + if (get_user(segment_id, (int __user *)arg)) + return -EFAULT; + + /* Check the segment ID and segment number */ + if (segment_id < 0 || segment_id >= SCARLETT2_SEGMENT_ID_COUNT) + return -EINVAL; + + segment_num = private->flash_segment_nums[segment_id]; + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) { + usb_audio_err(mixer->chip, + "%s: invalid segment number %d\n", + __func__, segment_id); + return -EFAULT; + } + + /* If erasing, wait for it to complete */ + if (private->flash_write_state == SCARLETT2_FLASH_WRITE_STATE_ERASING) { + int err = scarlett2_wait_for_erase(mixer); + + if (err < 0) + return err; + } + + /* Save the selected segment ID and set the state to SELECTED */ + private->selected_flash_segment_id = segment_id; + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_SELECTED; + + return 0; +} + +/* Erase the previously-selected flash segment */ +static int scarlett2_ioctl_erase_flash_segment( + struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int segment_id, segment_num, err; + + struct { + __le32 segment_num; + __le32 pad; + } __packed erase_req; + + if (private->flash_write_state != SCARLETT2_FLASH_WRITE_STATE_SELECTED) + return -EINVAL; + + segment_id = private->selected_flash_segment_id; + segment_num = private->flash_segment_nums[segment_id]; + + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) + return -EFAULT; + + /* Prevent access to ALSA controls that access the device from + * here on + */ + scarlett2_lock(private); + + /* Send the erase request */ + erase_req.segment_num = cpu_to_le32(segment_num); + erase_req.pad = 0; + + err = scarlett2_usb(mixer, SCARLETT2_USB_ERASE_SEGMENT, + &erase_req, sizeof(erase_req), + NULL, 0); + if (err < 0) + return err; + + /* On success, change the state from SELECTED to ERASING */ + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_ERASING; + + return 0; +} + +/* Get the erase progress from the device */ +static int scarlett2_ioctl_get_erase_progress( + struct usb_mixer_interface *mixer, + unsigned long arg) +{ + struct scarlett2_data *private = mixer->private_data; + struct scarlett2_flash_segment_erase_progress progress; + int segment_id, segment_num, err; + u8 erase_resp; + + struct { + __le32 segment_num; + __le32 pad; + } __packed erase_req; + + /* Check that we're erasing */ + if (private->flash_write_state != SCARLETT2_FLASH_WRITE_STATE_ERASING) + return -EINVAL; + + segment_id = private->selected_flash_segment_id; + segment_num = private->flash_segment_nums[segment_id]; + + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) + return -EFAULT; + + /* Send the erase progress request */ + erase_req.segment_num = cpu_to_le32(segment_num); + erase_req.pad = 0; + + err = scarlett2_usb(mixer, SCARLETT2_USB_GET_ERASE, + &erase_req, sizeof(erase_req), + &erase_resp, sizeof(erase_resp)); + if (err < 0) + return err; + + progress.progress = erase_resp; + progress.num_blocks = private->flash_segment_blocks[segment_id]; + + if (copy_to_user((void __user *)arg, &progress, sizeof(progress))) + return -EFAULT; + + /* If the erase is complete, change the state from ERASING to + * WRITE. + */ + if (progress.progress == 0xff) + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_WRITE; + + return 0; +} + +static int scarlett2_hwdep_open(struct snd_hwdep *hw, struct file *file) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct scarlett2_data *private = mixer->private_data; + + /* If erasing, wait for it to complete */ + if (private->flash_write_state == + SCARLETT2_FLASH_WRITE_STATE_ERASING) { + int err = scarlett2_wait_for_erase(mixer); + + if (err < 0) + return err; + } + + /* Set the state to IDLE */ + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_IDLE; + + return 0; +} + +static int scarlett2_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct usb_mixer_interface *mixer = hw->private_data; + + switch (cmd) { + + case SCARLETT2_IOCTL_PVERSION: + return put_user(SCARLETT2_HWDEP_VERSION, + (int __user *)arg) ? -EFAULT : 0; + + case SCARLETT2_IOCTL_REBOOT: + return scarlett2_reboot(mixer); + + case SCARLETT2_IOCTL_SELECT_FLASH_SEGMENT: + return scarlett2_ioctl_select_flash_segment(mixer, arg); + + case SCARLETT2_IOCTL_ERASE_FLASH_SEGMENT: + return scarlett2_ioctl_erase_flash_segment(mixer); + + case SCARLETT2_IOCTL_GET_ERASE_PROGRESS: + return scarlett2_ioctl_get_erase_progress(mixer, arg); + + default: + return -ENOIOCTLCMD; + } +} + +static long scarlett2_hwdep_read(struct snd_hwdep *hw, + char __user *buf, + long count, loff_t *offset) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct scarlett2_data *private = mixer->private_data; + int segment_id, segment_num, err; + int flash_size; + + /* SCARLETT2_USB_READ_SEGMENT request data */ + struct { + __le32 segment_num; + __le32 offset; + __le32 len; + } __packed req; + + u8 *resp __free(kfree) = NULL; + + /* Flash segment must first be selected */ + if (private->flash_write_state != SCARLETT2_FLASH_WRITE_STATE_SELECTED) + return -EINVAL; + + /* Get the selected flash segment number */ + segment_id = private->selected_flash_segment_id; + if (segment_id < 0 || segment_id >= SCARLETT2_SEGMENT_ID_COUNT) + return -EINVAL; + + segment_num = private->flash_segment_nums[segment_id]; + if (segment_num < 0 || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) + return -EFAULT; + + /* Validate the offset and count */ + if (count < 0 || *offset < 0) + return -EINVAL; + + /* Reached EOF? */ + flash_size = private->flash_segment_blocks[segment_id] * + SCARLETT2_FLASH_BLOCK_SIZE; + if (!count || *offset >= flash_size) + return 0; + + /* Limit the numbers of bytes read to SCARLETT2_FLASH_RW_MAX */ + if (count > SCARLETT2_FLASH_RW_MAX) + count = SCARLETT2_FLASH_RW_MAX; + + /* Limit read to EOF */ + if (*offset + count >= flash_size) + count = flash_size - *offset; + + /* Create and send the request */ + req.segment_num = cpu_to_le32(segment_num); + req.offset = cpu_to_le32(*offset); + req.len = cpu_to_le32(count); + + resp = kzalloc(count, GFP_KERNEL); + if (!resp) + return -ENOMEM; + + err = scarlett2_usb(mixer, SCARLETT2_USB_READ_SEGMENT, + &req, sizeof(req), resp, count); + if (err < 0) + return err; + + /* Copy the response to userspace */ + if (copy_to_user(buf, resp, count)) + return -EFAULT; + + *offset += count; + return count; +} + +static long scarlett2_hwdep_write(struct snd_hwdep *hw, + const char __user *buf, + long count, loff_t *offset) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct scarlett2_data *private = mixer->private_data; + int segment_id, segment_num, err, len; + int flash_size; + + /* SCARLETT2_USB_WRITE_SEGMENT request data */ + struct { + __le32 segment_num; + __le32 offset; + __le32 pad; + u8 data[]; + } __packed *req __free(kfree) = NULL; + + /* Calculate the maximum permitted in data[] */ + const size_t max_data_size = SCARLETT2_FLASH_RW_MAX - + offsetof(typeof(*req), data); + + /* If erasing, wait for it to complete */ + if (private->flash_write_state == + SCARLETT2_FLASH_WRITE_STATE_ERASING) { + err = scarlett2_wait_for_erase(mixer); + if (err < 0) + return err; + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_WRITE; + + /* Check that an erase has been done & completed */ + } else if (private->flash_write_state != + SCARLETT2_FLASH_WRITE_STATE_WRITE) { + return -EINVAL; + } + + /* Check that we're writing to the upgrade firmware */ + segment_id = private->selected_flash_segment_id; + if (segment_id != SCARLETT2_SEGMENT_ID_FIRMWARE) + return -EINVAL; + + segment_num = private->flash_segment_nums[segment_id]; + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) + return -EFAULT; + + /* Validate the offset and count */ + flash_size = private->flash_segment_blocks[segment_id] * + SCARLETT2_FLASH_BLOCK_SIZE; + + if (count < 0 || *offset < 0 || *offset + count >= flash_size) + return -ENOSPC; + + if (!count) + return 0; + + /* Limit the *req size to SCARLETT2_FLASH_RW_MAX */ + if (count > max_data_size) + count = max_data_size; + + /* Create and send the request */ + len = struct_size(req, data, count); + req = kzalloc(len, GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->segment_num = cpu_to_le32(segment_num); + req->offset = cpu_to_le32(*offset); + req->pad = 0; + + if (copy_from_user(req->data, buf, count)) + return -EFAULT; + + err = scarlett2_usb(mixer, SCARLETT2_USB_WRITE_SEGMENT, + req, len, NULL, 0); + if (err < 0) + return err; + + *offset += count; + return count; +} + +static int scarlett2_hwdep_release(struct snd_hwdep *hw, struct file *file) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct scarlett2_data *private = mixer->private_data; + + /* Return from the SELECTED or WRITE state to IDLE. + * The ERASING state is left as-is, and checked on next open. + */ + if (private && + private->hwdep_in_use && + private->flash_write_state != SCARLETT2_FLASH_WRITE_STATE_ERASING) + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_IDLE; + + return 0; +} + +static int scarlett2_hwdep_init(struct usb_mixer_interface *mixer) +{ + struct snd_hwdep *hw; + int err; + + err = snd_hwdep_new(mixer->chip->card, "Focusrite Control", 0, &hw); + if (err < 0) + return err; + + hw->private_data = mixer; + hw->exclusive = 1; + hw->ops.open = scarlett2_hwdep_open; + hw->ops.ioctl = scarlett2_hwdep_ioctl; + hw->ops.read = scarlett2_hwdep_read; + hw->ops.write = scarlett2_hwdep_write; + hw->ops.release = scarlett2_hwdep_release; + + return 0; +} + +/*** device-map file ***/ + +static ssize_t scarlett2_devmap_read( + struct snd_info_entry *entry, + void *file_private_data, + struct file *file, + char __user *buf, + size_t count, + loff_t pos) +{ + struct usb_mixer_interface *mixer = entry->private_data; + u8 *resp_buf __free(kfree) = NULL; + const size_t block_size = SCARLETT2_DEVMAP_BLOCK_SIZE; + size_t copied = 0; + + if (pos >= entry->size) + return 0; + + if (pos + count > entry->size) + count = entry->size - pos; + + resp_buf = kmalloc(block_size, GFP_KERNEL); + if (!resp_buf) + return -ENOMEM; + + while (count > 0) { + /* SCARLETT2_USB_GET_DEVMAP reads only on block boundaries, + * so we need to read a whole block and copy the requested + * chunk to userspace. + */ + + __le32 req; + int err; + + /* offset within the block that we're reading */ + size_t offset = pos % block_size; + + /* read_size is block_size except for the last block */ + size_t block_start = pos - offset; + size_t read_size = min_t(size_t, + block_size, + entry->size - block_start); + + /* size of the chunk to copy to userspace */ + size_t copy_size = min_t(size_t, count, read_size - offset); + + /* request the block */ + req = cpu_to_le32(pos / block_size); + err = scarlett2_usb(mixer, SCARLETT2_USB_GET_DEVMAP, + &req, sizeof(req), resp_buf, read_size); + if (err < 0) + return copied ? copied : err; + + if (copy_to_user(buf, resp_buf + offset, copy_size)) + return -EFAULT; + + buf += copy_size; + pos += copy_size; + copied += copy_size; + count -= copy_size; + } + + return copied; +} + +static const struct snd_info_entry_ops scarlett2_devmap_ops = { + .read = scarlett2_devmap_read, +}; + +static int scarlett2_devmap_init(struct usb_mixer_interface *mixer) +{ + struct snd_card *card = mixer->chip->card; + struct scarlett2_data *private = mixer->private_data; + const struct scarlett2_device_info *info = private->info; + __le16 config_len_buf[2]; + int config_len; + struct snd_info_entry *entry; + int err; + + /* If the device doesn't support the DEVMAP commands, don't + * create the /proc/asound/cardX/scarlett.json.zlib entry + */ + if (!info->has_devmap) + return 0; + + err = scarlett2_usb(mixer, SCARLETT2_USB_INFO_DEVMAP, + NULL, 0, &config_len_buf, sizeof(config_len_buf)); + if (err < 0) + return err; + + config_len = le16_to_cpu(config_len_buf[1]); + + err = snd_card_proc_new(card, SCARLETT2_DEVMAP_FILENAME, &entry); + if (err < 0) + return err; + + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = mixer; + entry->c.ops = &scarlett2_devmap_ops; + entry->size = config_len; + entry->mode = S_IFREG | 0444; + + return 0; +} + +int snd_scarlett2_init(struct usb_mixer_interface *mixer) +{ + struct snd_usb_audio *chip = mixer->chip; + const struct scarlett2_device_entry *entry; + int err; + + /* only use UAC_VERSION_2 */ + if (!mixer->protocol) + return 0; + + /* check if the user wants to use the FCP driver instead */ + if (chip->setup & SCARLETT2_USE_FCP_DRIVER) + return snd_fcp_init(mixer); + + /* find entry in scarlett2_devices */ + entry = get_scarlett2_device_entry(mixer); + if (!entry) { + usb_audio_err(mixer->chip, + "%s: missing device entry for %04x:%04x\n", + __func__, + USB_ID_VENDOR(chip->usb_id), + USB_ID_PRODUCT(chip->usb_id)); + return 0; + } + + if (chip->setup & SCARLETT2_DISABLE) { + usb_audio_info(chip, + "Focusrite %s Mixer Driver disabled " + "by modprobe options (snd_usb_audio " + "vid=0x%04x pid=0x%04x device_setup=%d)\n", + entry->series_name, + USB_ID_VENDOR(chip->usb_id), + USB_ID_PRODUCT(chip->usb_id), + SCARLETT2_DISABLE); + return 0; + } + + usb_audio_info(chip, + "Focusrite %s Mixer Driver enabled (pid=0x%04x); " + "report any issues to " + "https://github.com/geoffreybennett/scarlett-gen2/issues", + entry->series_name, + USB_ID_PRODUCT(chip->usb_id)); + + err = snd_scarlett2_controls_create(mixer, entry); + if (err < 0) { + usb_audio_err(mixer->chip, + "Error initialising %s Mixer Driver: %d", + entry->series_name, + err); + return err; + } + + err = scarlett2_hwdep_init(mixer); + if (err < 0) { + usb_audio_err(mixer->chip, + "Error creating %s hwdep device: %d", + entry->series_name, + err); + return err; + } + + err = scarlett2_devmap_init(mixer); + if (err < 0) + usb_audio_err(mixer->chip, + "Error creating %s devmap entry: %d", + entry->series_name, + err); + + return err; +} diff --git a/sound/usb/mixer_scarlett2.h b/sound/usb/mixer_scarlett2.h new file mode 100644 index 000000000000..d209362cf41a --- /dev/null +++ b/sound/usb/mixer_scarlett2.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USB_MIXER_SCARLETT2_H +#define __USB_MIXER_SCARLETT2_H + +int snd_scarlett2_init(struct usb_mixer_interface *mixer); + +#endif /* __USB_MIXER_SCARLETT2_H */ diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c index 26ed23b18b77..1c5712c31f5e 100644 --- a/sound/usb/mixer_us16x08.c +++ b/sound/usb/mixer_us16x08.c @@ -1,18 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Tascam US-16x08 ALSA driver * * Copyright (c) 2016 by Detlef Urban (onkel@paraair.de) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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/slab.h> @@ -162,12 +152,11 @@ static int snd_us16x08_recv_urb(struct snd_usb_audio *chip, unsigned char *buf, int size) { - mutex_lock(&chip->mutex); + guard(mutex)(&chip->mutex); snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), SND_US16X08_URB_METER_REQUEST, SND_US16X08_URB_METER_REQUESTTYPE, 0, 0, buf, size); - mutex_unlock(&chip->mutex); return 0; } @@ -190,7 +179,7 @@ static int snd_us16x08_route_info(struct snd_kcontrol *kcontrol, static int snd_us16x08_route_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; /* route has no bias */ @@ -202,7 +191,7 @@ static int snd_us16x08_route_get(struct snd_kcontrol *kcontrol, static int snd_us16x08_route_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); struct snd_usb_audio *chip = elem->head.mixer->chip; int index = ucontrol->id.index; char buf[sizeof(route_msg)]; @@ -259,7 +248,7 @@ static int snd_us16x08_master_info(struct snd_kcontrol *kcontrol, static int snd_us16x08_master_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; ucontrol->value.integer.value[0] = elem->cache_val[index]; @@ -270,7 +259,7 @@ static int snd_us16x08_master_get(struct snd_kcontrol *kcontrol, static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); struct snd_usb_audio *chip = elem->head.mixer->chip; char buf[sizeof(mix_msg_out)]; int val, err; @@ -307,7 +296,7 @@ static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol, static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); struct snd_usb_audio *chip = elem->head.mixer->chip; char buf[sizeof(mix_msg_out)]; int val, err = 0; @@ -339,7 +328,7 @@ static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol, elem->cached |= 1; elem->cache_val[0] = val; } else { - usb_audio_dbg(chip, "Failed to set buss param, err:%d\n", err); + usb_audio_dbg(chip, "Failed to set bus parameter, err:%d\n", err); } return err > 0 ? 1 : 0; @@ -348,7 +337,7 @@ static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol, static int snd_us16x08_bus_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); switch (elem->head.id) { case SND_US16X08_ID_BUSS_OUT: @@ -369,7 +358,7 @@ static int snd_us16x08_bus_get(struct snd_kcontrol *kcontrol, static int snd_us16x08_channel_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; ucontrol->value.integer.value[0] = elem->cache_val[index]; @@ -380,7 +369,7 @@ static int snd_us16x08_channel_get(struct snd_kcontrol *kcontrol, static int snd_us16x08_channel_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); struct snd_usb_audio *chip = elem->head.mixer->chip; char buf[sizeof(mix_msg_in)]; int val, err; @@ -427,7 +416,7 @@ static int snd_us16x08_mix_info(struct snd_kcontrol *kcontrol, static int snd_us16x08_comp_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); struct snd_us16x08_comp_store *store = elem->private_data; int index = ucontrol->id.index; int val_idx = COMP_STORE_IDX(elem->head.id); @@ -440,7 +429,7 @@ static int snd_us16x08_comp_get(struct snd_kcontrol *kcontrol, static int snd_us16x08_comp_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); struct snd_usb_audio *chip = elem->head.mixer->chip; struct snd_us16x08_comp_store *store = elem->private_data; int index = ucontrol->id.index; @@ -495,7 +484,7 @@ static int snd_us16x08_eqswitch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { int val; - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); struct snd_us16x08_eq_store *store = elem->private_data; int index = ucontrol->id.index; @@ -510,7 +499,7 @@ static int snd_us16x08_eqswitch_get(struct snd_kcontrol *kcontrol, static int snd_us16x08_eqswitch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); struct snd_usb_audio *chip = elem->head.mixer->chip; struct snd_us16x08_eq_store *store = elem->private_data; int index = ucontrol->id.index; @@ -554,7 +543,7 @@ static int snd_us16x08_eq_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { int val; - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); struct snd_us16x08_eq_store *store = elem->private_data; int index = ucontrol->id.index; int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1; @@ -570,7 +559,7 @@ static int snd_us16x08_eq_get(struct snd_kcontrol *kcontrol, static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); struct snd_usb_audio *chip = elem->head.mixer->chip; struct snd_us16x08_eq_store *store = elem->private_data; int index = ucontrol->id.index; @@ -617,7 +606,7 @@ static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol, static int snd_us16x08_meter_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - uinfo->count = 1; + uinfo->count = 34; uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->value.integer.max = 0x7FFF; uinfo->value.integer.min = 0; @@ -647,10 +636,10 @@ static int snd_get_meter_comp_index(struct snd_us16x08_meter_store *store) } } else { /* skip channels with no compressor active */ - while (!store->comp_store->val[ + while (store->comp_index <= SND_US16X08_MAX_CHANNELS + && !store->comp_store->val[ COMP_STORE_IDX(SND_US16X08_ID_COMP_SWITCH)] - [store->comp_index - 1] - && store->comp_index <= SND_US16X08_MAX_CHANNELS) { + [store->comp_index - 1]) { store->comp_index++; } ret = store->comp_index++; @@ -694,10 +683,10 @@ static int snd_us16x08_meter_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { int i, set; - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); struct snd_usb_audio *chip = elem->head.mixer->chip; struct snd_us16x08_meter_store *store = elem->private_data; - u8 meter_urb[64]; + u8 meter_urb[64] = {0}; switch (kcontrol->private_value) { case 0: { @@ -754,7 +743,7 @@ static int snd_us16x08_meter_get(struct snd_kcontrol *kcontrol, static int snd_us16x08_meter_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); struct snd_us16x08_meter_store *store = elem->private_data; int val; @@ -770,7 +759,7 @@ static int snd_us16x08_meter_put(struct snd_kcontrol *kcontrol, return 1; } -static struct snd_kcontrol_new snd_us16x08_ch_boolean_ctl = { +static const struct snd_kcontrol_new snd_us16x08_ch_boolean_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -780,7 +769,7 @@ static struct snd_kcontrol_new snd_us16x08_ch_boolean_ctl = { .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1) }; -static struct snd_kcontrol_new snd_us16x08_ch_int_ctl = { +static const struct snd_kcontrol_new snd_us16x08_ch_int_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -790,7 +779,7 @@ static struct snd_kcontrol_new snd_us16x08_ch_int_ctl = { .private_value = SND_US16X08_KCSET(SND_US16X08_FADER_BIAS, 1, 0, 133) }; -static struct snd_kcontrol_new snd_us16x08_pan_int_ctl = { +static const struct snd_kcontrol_new snd_us16x08_pan_int_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -800,7 +789,7 @@ static struct snd_kcontrol_new snd_us16x08_pan_int_ctl = { .private_value = SND_US16X08_KCSET(SND_US16X08_FADER_BIAS, 1, 0, 255) }; -static struct snd_kcontrol_new snd_us16x08_master_ctl = { +static const struct snd_kcontrol_new snd_us16x08_master_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 1, @@ -810,7 +799,7 @@ static struct snd_kcontrol_new snd_us16x08_master_ctl = { .private_value = SND_US16X08_KCSET(SND_US16X08_FADER_BIAS, 1, 0, 133) }; -static struct snd_kcontrol_new snd_us16x08_route_ctl = { +static const struct snd_kcontrol_new snd_us16x08_route_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 8, @@ -820,7 +809,7 @@ static struct snd_kcontrol_new snd_us16x08_route_ctl = { .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 9) }; -static struct snd_kcontrol_new snd_us16x08_bus_ctl = { +static const struct snd_kcontrol_new snd_us16x08_bus_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 1, @@ -830,7 +819,7 @@ static struct snd_kcontrol_new snd_us16x08_bus_ctl = { .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1) }; -static struct snd_kcontrol_new snd_us16x08_compswitch_ctl = { +static const struct snd_kcontrol_new snd_us16x08_compswitch_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -840,7 +829,7 @@ static struct snd_kcontrol_new snd_us16x08_compswitch_ctl = { .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1) }; -static struct snd_kcontrol_new snd_us16x08_comp_threshold_ctl = { +static const struct snd_kcontrol_new snd_us16x08_comp_threshold_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -851,7 +840,7 @@ static struct snd_kcontrol_new snd_us16x08_comp_threshold_ctl = { 0, 0x20) }; -static struct snd_kcontrol_new snd_us16x08_comp_ratio_ctl = { +static const struct snd_kcontrol_new snd_us16x08_comp_ratio_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -862,7 +851,7 @@ static struct snd_kcontrol_new snd_us16x08_comp_ratio_ctl = { sizeof(ratio_map) - 1), /*max*/ }; -static struct snd_kcontrol_new snd_us16x08_comp_gain_ctl = { +static const struct snd_kcontrol_new snd_us16x08_comp_gain_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -872,7 +861,7 @@ static struct snd_kcontrol_new snd_us16x08_comp_gain_ctl = { .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x14) }; -static struct snd_kcontrol_new snd_us16x08_comp_attack_ctl = { +static const struct snd_kcontrol_new snd_us16x08_comp_attack_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -883,7 +872,7 @@ static struct snd_kcontrol_new snd_us16x08_comp_attack_ctl = { SND_US16X08_KCSET(SND_US16X08_COMP_ATTACK_BIAS, 1, 0, 0xc6), }; -static struct snd_kcontrol_new snd_us16x08_comp_release_ctl = { +static const struct snd_kcontrol_new snd_us16x08_comp_release_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -894,7 +883,7 @@ static struct snd_kcontrol_new snd_us16x08_comp_release_ctl = { SND_US16X08_KCSET(SND_US16X08_COMP_RELEASE_BIAS, 1, 0, 0x63), }; -static struct snd_kcontrol_new snd_us16x08_eq_gain_ctl = { +static const struct snd_kcontrol_new snd_us16x08_eq_gain_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -904,7 +893,7 @@ static struct snd_kcontrol_new snd_us16x08_eq_gain_ctl = { .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 24), }; -static struct snd_kcontrol_new snd_us16x08_eq_low_freq_ctl = { +static const struct snd_kcontrol_new snd_us16x08_eq_low_freq_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -914,7 +903,7 @@ static struct snd_kcontrol_new snd_us16x08_eq_low_freq_ctl = { .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x1F), }; -static struct snd_kcontrol_new snd_us16x08_eq_mid_freq_ctl = { +static const struct snd_kcontrol_new snd_us16x08_eq_mid_freq_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -924,7 +913,7 @@ static struct snd_kcontrol_new snd_us16x08_eq_mid_freq_ctl = { .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x3F) }; -static struct snd_kcontrol_new snd_us16x08_eq_mid_width_ctl = { +static const struct snd_kcontrol_new snd_us16x08_eq_mid_width_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -934,7 +923,7 @@ static struct snd_kcontrol_new snd_us16x08_eq_mid_width_ctl = { .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x06) }; -static struct snd_kcontrol_new snd_us16x08_eq_high_freq_ctl = { +static const struct snd_kcontrol_new snd_us16x08_eq_high_freq_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -945,7 +934,7 @@ static struct snd_kcontrol_new snd_us16x08_eq_high_freq_ctl = { SND_US16X08_KCSET(SND_US16X08_EQ_HIGHFREQ_BIAS, 1, 0, 0x1F) }; -static struct snd_kcontrol_new snd_us16x08_eq_switch_ctl = { +static const struct snd_kcontrol_new snd_us16x08_eq_switch_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 16, @@ -955,7 +944,7 @@ static struct snd_kcontrol_new snd_us16x08_eq_switch_ctl = { .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1) }; -static struct snd_kcontrol_new snd_us16x08_meter_ctl = { +static const struct snd_kcontrol_new snd_us16x08_meter_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .count = 1, @@ -1086,7 +1075,7 @@ static int add_new_ctl(struct usb_mixer_interface *mixer, else kctl->private_free = snd_usb_mixer_elem_free; - strlcpy(kctl->id.name, name, sizeof(kctl->id.name)); + strscpy(kctl->id.name, name, sizeof(kctl->id.name)); err = snd_usb_mixer_add_control(&elem->head, kctl); if (err < 0) @@ -1119,7 +1108,7 @@ static const struct snd_us16x08_control_params eq_controls[] = { .control_id = SND_US16X08_ID_EQLOWFREQ, .type = USB_MIXER_U8, .num_channels = 16, - .name = "EQ Low Frequence", + .name = "EQ Low Frequency", }, { /* EQ mid low gain */ .kcontrol_new = &snd_us16x08_eq_gain_ctl, @@ -1133,7 +1122,7 @@ static const struct snd_us16x08_control_params eq_controls[] = { .control_id = SND_US16X08_ID_EQLOWMIDFREQ, .type = USB_MIXER_U8, .num_channels = 16, - .name = "EQ MidLow Frequence", + .name = "EQ MidLow Frequency", }, { /* EQ mid low Q */ .kcontrol_new = &snd_us16x08_eq_mid_width_ctl, @@ -1154,7 +1143,7 @@ static const struct snd_us16x08_control_params eq_controls[] = { .control_id = SND_US16X08_ID_EQHIGHMIDFREQ, .type = USB_MIXER_U8, .num_channels = 16, - .name = "EQ MidHigh Frequence", + .name = "EQ MidHigh Frequency", }, { /* EQ mid high Q */ .kcontrol_new = &snd_us16x08_eq_mid_width_ctl, @@ -1175,7 +1164,7 @@ static const struct snd_us16x08_control_params eq_controls[] = { .control_id = SND_US16X08_ID_EQHIGHFREQ, .type = USB_MIXER_U8, .num_channels = 16, - .name = "EQ High Frequence", + .name = "EQ High Frequency", }, }; diff --git a/sound/usb/mixer_us16x08.h b/sound/usb/mixer_us16x08.h index 56ff16c0698f..7b8583dd3b07 100644 --- a/sound/usb/mixer_us16x08.h +++ b/sound/usb/mixer_us16x08.h @@ -108,7 +108,7 @@ struct snd_us16x08_meter_store { }; struct snd_us16x08_control_params { - struct snd_kcontrol_new *kcontrol_new; + const struct snd_kcontrol_new *kcontrol_new; int control_id; int type; int num_channels; diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 382847154227..54d01dfd820f 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -1,17 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/init.h> @@ -29,26 +17,33 @@ #include "usbaudio.h" #include "card.h" #include "quirks.h" -#include "debug.h" #include "endpoint.h" #include "helper.h" #include "pcm.h" #include "clock.h" #include "power.h" +#include "media.h" +#include "implicit.h" #define SUBSTREAM_FLAG_DATA_EP_STARTED 0 #define SUBSTREAM_FLAG_SYNC_EP_STARTED 1 /* return the estimated delay based on USB frame counters */ -snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs, - unsigned int rate) +static snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime) { - int current_frame_number; - int frame_diff; + unsigned int current_frame_number; + unsigned int frame_diff; int est_delay; + int queued; - if (!subs->last_delay) - return 0; /* short path */ + if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) { + queued = bytes_to_frames(runtime, subs->inflight_bytes); + if (!queued) + return 0; + } else if (!subs->running) { + return 0; + } current_frame_number = usb_get_current_frame_number(subs->dev); /* @@ -60,14 +55,14 @@ snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs, /* Approximation based on number of samples per USB frame (ms), some truncation for 44.1 but the estimate is good enough */ - est_delay = frame_diff * rate / 1000; - if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) - est_delay = subs->last_delay - est_delay; - else - est_delay = subs->last_delay + est_delay; + est_delay = frame_diff * runtime->rate / 1000; + + if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) { + est_delay = queued - est_delay; + if (est_delay < 0) + est_delay = 0; + } - if (est_delay < 0) - est_delay = 0; return est_delay; } @@ -76,46 +71,50 @@ snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs, */ static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream) { - struct snd_usb_substream *subs = substream->runtime->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usb_substream *subs = runtime->private_data; unsigned int hwptr_done; if (atomic_read(&subs->stream->chip->shutdown)) return SNDRV_PCM_POS_XRUN; - spin_lock(&subs->lock); - hwptr_done = subs->hwptr_done; - substream->runtime->delay = snd_usb_pcm_delay(subs, - substream->runtime->rate); - spin_unlock(&subs->lock); - return hwptr_done / (substream->runtime->frame_bits >> 3); + scoped_guard(spinlock, &subs->lock) { + hwptr_done = subs->hwptr_done; + runtime->delay = snd_usb_pcm_delay(subs, runtime); + } + return bytes_to_frames(runtime, hwptr_done); } /* * find a matching audio format */ -static struct audioformat *find_format(struct snd_usb_substream *subs) +static const struct audioformat * +find_format(struct list_head *fmt_list_head, snd_pcm_format_t format, + unsigned int rate, unsigned int channels, bool strict_match, + struct snd_usb_substream *subs) { - struct audioformat *fp; - struct audioformat *found = NULL; + const struct audioformat *fp; + const struct audioformat *found = NULL; int cur_attr = 0, attr; - list_for_each_entry(fp, &subs->fmt_list, list) { - if (!(fp->formats & pcm_format_to_bits(subs->pcm_format))) - continue; - if (fp->channels != subs->channels) - continue; - if (subs->cur_rate < fp->rate_min || - subs->cur_rate > fp->rate_max) + list_for_each_entry(fp, fmt_list_head, list) { + if (strict_match) { + if (!(fp->formats & pcm_format_to_bits(format))) + continue; + if (fp->channels != channels) + continue; + } + if (rate < fp->rate_min || rate > fp->rate_max) continue; - if (! (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)) { + if (!(fp->rates & SNDRV_PCM_RATE_CONTINUOUS)) { unsigned int i; for (i = 0; i < fp->nr_rates; i++) - if (fp->rate_table[i] == subs->cur_rate) + if (fp->rate_table[i] == rate) break; if (i >= fp->nr_rates) continue; } attr = fp->ep_attr & USB_ENDPOINT_SYNCTYPE; - if (! found) { + if (!found) { found = fp; cur_attr = attr; continue; @@ -125,7 +124,7 @@ static struct audioformat *find_format(struct snd_usb_substream *subs) * this is a workaround for the case like * M-audio audiophile USB. */ - if (attr != cur_attr) { + if (subs && attr != cur_attr) { if ((attr == USB_ENDPOINT_SYNC_ASYNC && subs->direction == SNDRV_PCM_STREAM_PLAYBACK) || (attr == USB_ENDPOINT_SYNC_ADAPTIVE && @@ -149,36 +148,76 @@ static struct audioformat *find_format(struct snd_usb_substream *subs) return found; } -static int init_pitch_v1(struct snd_usb_audio *chip, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt) +const struct audioformat * +snd_usb_find_format(struct list_head *fmt_list_head, snd_pcm_format_t format, + unsigned int rate, unsigned int channels, bool strict_match, + struct snd_usb_substream *subs) +{ + return find_format(fmt_list_head, format, rate, channels, strict_match, + subs); +} +EXPORT_SYMBOL_GPL(snd_usb_find_format); + +static const struct audioformat * +find_substream_format(struct snd_usb_substream *subs, + const struct snd_pcm_hw_params *params) +{ + return find_format(&subs->fmt_list, params_format(params), + params_rate(params), params_channels(params), + true, subs); +} + +const struct audioformat * +snd_usb_find_substream_format(struct snd_usb_substream *subs, + const struct snd_pcm_hw_params *params) +{ + return find_substream_format(subs, params); +} +EXPORT_SYMBOL_GPL(snd_usb_find_substream_format); + +bool snd_usb_pcm_has_fixed_rate(struct snd_usb_substream *subs) +{ + const struct audioformat *fp; + struct snd_usb_audio *chip; + int rate = -1; + + if (!subs) + return false; + chip = subs->stream->chip; + if (!(chip->quirk_flags & QUIRK_FLAG_FIXED_RATE)) + return false; + list_for_each_entry(fp, &subs->fmt_list, list) { + if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) + return false; + if (fp->nr_rates < 1) + continue; + if (fp->nr_rates > 1) + return false; + if (rate < 0) { + rate = fp->rate_table[0]; + continue; + } + if (rate != fp->rate_table[0]) + return false; + } + return true; +} + +static int init_pitch_v1(struct snd_usb_audio *chip, int ep) { struct usb_device *dev = chip->dev; - unsigned int ep; unsigned char data[1]; int err; - if (get_iface_desc(alts)->bNumEndpoints < 1) - return -EINVAL; - ep = get_endpoint(alts, 0)->bEndpointAddress; - data[0] = 1; err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, UAC_EP_CS_ATTR_PITCH_CONTROL << 8, ep, data, sizeof(data)); - if (err < 0) { - usb_audio_err(chip, "%d:%d: cannot set enable PITCH\n", - iface, ep); - return err; - } - - return 0; + return err; } -static int init_pitch_v2(struct snd_usb_audio *chip, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt) +static int init_pitch_v2(struct snd_usb_audio *chip, int ep) { struct usb_device *dev = chip->dev; unsigned char data[1]; @@ -189,34 +228,56 @@ static int init_pitch_v2(struct snd_usb_audio *chip, int iface, USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT, UAC2_EP_CS_PITCH << 8, 0, data, sizeof(data)); - if (err < 0) { - usb_audio_err(chip, "%d:%d: cannot set enable PITCH (v2)\n", - iface, fmt->altsetting); - return err; - } - - return 0; + return err; } /* * initialize the pitch control and sample rate */ -int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt) +int snd_usb_init_pitch(struct snd_usb_audio *chip, + const struct audioformat *fmt) { + int err; + /* if endpoint doesn't have pitch control, bail out */ if (!(fmt->attributes & UAC_EP_CS_ATTR_PITCH_CONTROL)) return 0; + usb_audio_dbg(chip, "enable PITCH for EP 0x%x\n", fmt->endpoint); + switch (fmt->protocol) { case UAC_VERSION_1: + err = init_pitch_v1(chip, fmt->endpoint); + break; + case UAC_VERSION_2: + err = init_pitch_v2(chip, fmt->endpoint); + break; default: - return init_pitch_v1(chip, iface, alts, fmt); + return 0; + } - case UAC_VERSION_2: - return init_pitch_v2(chip, iface, alts, fmt); + if (err < 0) { + usb_audio_err(chip, "failed to enable PITCH for EP 0x%x\n", + fmt->endpoint); + return err; } + + return 0; +} + +static bool stop_endpoints(struct snd_usb_substream *subs, bool keep_pending) +{ + bool stopped = 0; + + if (test_and_clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags)) { + snd_usb_endpoint_stop(subs->sync_endpoint, keep_pending); + stopped = true; + } + if (test_and_clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags)) { + snd_usb_endpoint_stop(subs->data_endpoint, keep_pending); + stopped = true; + } + return stopped; } static int start_endpoints(struct snd_usb_substream *subs) @@ -227,210 +288,83 @@ static int start_endpoints(struct snd_usb_substream *subs) return -EINVAL; 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); - - ep->data_subs = subs; - err = snd_usb_endpoint_start(ep); + err = snd_usb_endpoint_start(subs->data_endpoint); if (err < 0) { clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags); - return err; + goto error; } } if (subs->sync_endpoint && !test_and_set_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags)) { - struct snd_usb_endpoint *ep = subs->sync_endpoint; - - if (subs->data_endpoint->iface != subs->sync_endpoint->iface || - subs->data_endpoint->altsetting != subs->sync_endpoint->altsetting) { - err = usb_set_interface(subs->dev, - subs->sync_endpoint->iface, - subs->sync_endpoint->altsetting); - if (err < 0) { - clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags); - dev_err(&subs->dev->dev, - "%d:%d: cannot set interface (%d)\n", - subs->sync_endpoint->iface, - subs->sync_endpoint->altsetting, err); - return -EIO; - } - } - - dev_dbg(&subs->dev->dev, "Starting sync EP @%p\n", ep); - - ep->sync_slave = subs->data_endpoint; - err = snd_usb_endpoint_start(ep); + err = snd_usb_endpoint_start(subs->sync_endpoint); if (err < 0) { clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags); - return err; + goto error; } } return 0; -} - -static void stop_endpoints(struct snd_usb_substream *subs, bool wait) -{ - if (test_and_clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags)) - snd_usb_endpoint_stop(subs->sync_endpoint); - - if (test_and_clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags)) - snd_usb_endpoint_stop(subs->data_endpoint); - if (wait) { - snd_usb_endpoint_sync_pending_stop(subs->sync_endpoint); - snd_usb_endpoint_sync_pending_stop(subs->data_endpoint); - } + error: + stop_endpoints(subs, false); + return err; } -static int search_roland_implicit_fb(struct usb_device *dev, int ifnum, - unsigned int altsetting, - struct usb_host_interface **alts, - unsigned int *ep) +static void sync_pending_stops(struct snd_usb_substream *subs) { - struct usb_interface *iface; - struct usb_interface_descriptor *altsd; - struct usb_endpoint_descriptor *epd; - - iface = usb_ifnum_to_if(dev, ifnum); - if (!iface || iface->num_altsetting < altsetting + 1) - return -ENOENT; - *alts = &iface->altsetting[altsetting]; - altsd = get_iface_desc(*alts); - if (altsd->bAlternateSetting != altsetting || - altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC || - (altsd->bInterfaceSubClass != 2 && - altsd->bInterfaceProtocol != 2 ) || - altsd->bNumEndpoints < 1) - return -ENOENT; - epd = get_endpoint(*alts, 0); - if (!usb_endpoint_is_isoc_in(epd) || - (epd->bmAttributes & USB_ENDPOINT_USAGE_MASK) != - USB_ENDPOINT_USAGE_IMPLICIT_FB) - return -ENOENT; - *ep = epd->bEndpointAddress; - return 0; + snd_usb_endpoint_sync_pending_stop(subs->sync_endpoint); + snd_usb_endpoint_sync_pending_stop(subs->data_endpoint); } -static int set_sync_ep_implicit_fb_quirk(struct snd_usb_substream *subs, - struct usb_device *dev, - struct usb_interface_descriptor *altsd, - unsigned int attr) +/* PCM sync_stop callback */ +static int snd_usb_pcm_sync_stop(struct snd_pcm_substream *substream) { - struct usb_host_interface *alts; - struct usb_interface *iface; - unsigned int ep; - unsigned int ifnum; - - /* Implicit feedback sync EPs consumers are always playback EPs */ - if (subs->direction != SNDRV_PCM_STREAM_PLAYBACK) - return 0; - - switch (subs->stream->chip->usb_id) { - case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */ - case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C600 */ - ep = 0x81; - ifnum = 3; - goto add_sync_ep_from_ifnum; - case USB_ID(0x0763, 0x2080): /* M-Audio FastTrack Ultra */ - case USB_ID(0x0763, 0x2081): - ep = 0x81; - ifnum = 2; - goto add_sync_ep_from_ifnum; - case USB_ID(0x2466, 0x8003): /* Fractal Audio Axe-Fx II */ - ep = 0x86; - ifnum = 2; - goto add_sync_ep_from_ifnum; - case USB_ID(0x2466, 0x8010): /* Fractal Audio Axe-Fx III */ - ep = 0x81; - ifnum = 2; - goto add_sync_ep_from_ifnum; - case USB_ID(0x1397, 0x0002): /* Behringer UFX1204 */ - ep = 0x81; - ifnum = 1; - goto add_sync_ep_from_ifnum; - } - - if (attr == USB_ENDPOINT_SYNC_ASYNC && - altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC && - altsd->bInterfaceProtocol == 2 && - altsd->bNumEndpoints == 1 && - USB_ID_VENDOR(subs->stream->chip->usb_id) == 0x0582 /* Roland */ && - search_roland_implicit_fb(dev, altsd->bInterfaceNumber + 1, - altsd->bAlternateSetting, - &alts, &ep) >= 0) { - goto add_sync_ep; - } - - /* No quirk */ - return 0; - -add_sync_ep_from_ifnum: - iface = usb_ifnum_to_if(dev, ifnum); - - if (!iface || iface->num_altsetting == 0) - return -EINVAL; - - alts = &iface->altsetting[1]; - -add_sync_ep: - subs->sync_endpoint = snd_usb_add_endpoint(subs->stream->chip, - alts, ep, !subs->direction, - SND_USB_ENDPOINT_TYPE_DATA); - if (!subs->sync_endpoint) - return -EINVAL; - - subs->data_endpoint->sync_master = subs->sync_endpoint; + struct snd_usb_substream *subs = substream->runtime->private_data; + sync_pending_stops(subs); return 0; } -static int set_sync_endpoint(struct snd_usb_substream *subs, - struct audioformat *fmt, - struct usb_device *dev, - struct usb_host_interface *alts, - struct usb_interface_descriptor *altsd) +/* Set up sync endpoint */ +int snd_usb_audioformat_set_sync_ep(struct snd_usb_audio *chip, + struct audioformat *fmt) { - int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK; - unsigned int ep, attr; - bool implicit_fb; + struct usb_device *dev = chip->dev; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + unsigned int ep, attr, sync_attr; + bool is_playback; int err; - /* we need a sync pipe in async OUT or adaptive IN mode */ - /* check the number of EP, since some devices have broken - * descriptors which fool us. if it has only one EP, - * assume it as adaptive-out or sync-in. - */ - attr = fmt->ep_attr & USB_ENDPOINT_SYNCTYPE; - - if ((is_playback && (attr != USB_ENDPOINT_SYNC_ASYNC)) || - (!is_playback && (attr != USB_ENDPOINT_SYNC_ADAPTIVE))) { + if (fmt->sync_ep) + return 0; /* already set up */ - /* - * In these modes the notion of sync_endpoint is irrelevant. - * Reset pointers to avoid using stale data from previously - * used settings, e.g. when configuration and endpoints were - * changed - */ + alts = snd_usb_get_host_interface(chip, fmt->iface, fmt->altsetting); + if (!alts) + return 0; + altsd = get_iface_desc(alts); - subs->sync_endpoint = NULL; - subs->data_endpoint->sync_master = NULL; - } + err = snd_usb_parse_implicit_fb_quirk(chip, fmt, alts); + if (err > 0) + return 0; /* matched */ - err = set_sync_ep_implicit_fb_quirk(subs, dev, altsd, attr); - if (err < 0) - return err; + /* + * Generic sync EP handling + */ - if (altsd->bNumEndpoints < 2) + if (fmt->ep_idx > 0 || altsd->bNumEndpoints < 2) return 0; + is_playback = !(get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN); + attr = fmt->ep_attr & USB_ENDPOINT_SYNCTYPE; if ((is_playback && (attr == USB_ENDPOINT_SYNC_SYNC || attr == USB_ENDPOINT_SYNC_ADAPTIVE)) || (!is_playback && attr != USB_ENDPOINT_SYNC_ADAPTIVE)) return 0; + sync_attr = get_endpoint(alts, 1)->bmAttributes; + /* * In case of illegal SYNC_NONE for OUT endpoint, we keep going to see * if we don't find a sync endpoint, as on M-Audio Transit. In case of @@ -441,7 +375,7 @@ static int set_sync_endpoint(struct snd_usb_substream *subs, /* ... and check descriptor size before accessing bSynchAddress because there is a version of the SB Audigy 2 NX firmware lacking the audio fields in the endpoint descriptors */ - if ((get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_ISOC || + if ((sync_attr & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_ISOC || (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && get_endpoint(alts, 1)->bSynchAddress != 0)) { dev_err(&dev->dev, @@ -456,6 +390,7 @@ static int set_sync_endpoint(struct snd_usb_substream *subs, } ep = get_endpoint(alts, 1)->bEndpointAddress; if (get_endpoint(alts, 0)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && + get_endpoint(alts, 0)->bSynchAddress != 0 && ((is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress | USB_DIR_IN)) || (!is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress & ~USB_DIR_IN)))) { dev_err(&dev->dev, @@ -467,250 +402,20 @@ static int set_sync_endpoint(struct snd_usb_substream *subs, return -EINVAL; } - implicit_fb = (get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_USAGE_MASK) - == USB_ENDPOINT_USAGE_IMPLICIT_FB; - - subs->sync_endpoint = snd_usb_add_endpoint(subs->stream->chip, - alts, ep, !subs->direction, - implicit_fb ? - SND_USB_ENDPOINT_TYPE_DATA : - SND_USB_ENDPOINT_TYPE_SYNC); - if (!subs->sync_endpoint) { - if (is_playback && attr == USB_ENDPOINT_SYNC_NONE) - return 0; - return -EINVAL; - } - - subs->data_endpoint->sync_master = subs->sync_endpoint; - - return 0; -} - -/* - * find a matching format and set up the interface - */ -static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) -{ - struct usb_device *dev = subs->dev; - struct usb_host_interface *alts; - struct usb_interface_descriptor *altsd; - struct usb_interface *iface; - int err; - - iface = usb_ifnum_to_if(dev, fmt->iface); - if (WARN_ON(!iface)) - return -EINVAL; - alts = usb_altnum_to_altsetting(iface, fmt->altsetting); - altsd = get_iface_desc(alts); - if (WARN_ON(altsd->bAlternateSetting != fmt->altsetting)) - return -EINVAL; - - if (fmt == subs->cur_audiofmt) - return 0; - - /* close the old interface */ - if (subs->interface >= 0 && subs->interface != fmt->iface) { - if (!subs->stream->chip->keep_iface) { - err = usb_set_interface(subs->dev, subs->interface, 0); - if (err < 0) { - dev_err(&dev->dev, - "%d:%d: return to setting 0 failed (%d)\n", - fmt->iface, fmt->altsetting, err); - return -EIO; - } - } - subs->interface = -1; - subs->altset_idx = 0; - } - - /* set interface */ - if (iface->cur_altsetting != alts) { - err = snd_usb_select_mode_quirk(subs, fmt); - if (err < 0) - return -EIO; - - err = usb_set_interface(dev, fmt->iface, fmt->altsetting); - if (err < 0) { - dev_err(&dev->dev, - "%d:%d: usb_set_interface failed (%d)\n", - fmt->iface, fmt->altsetting, err); - return -EIO; - } - dev_dbg(&dev->dev, "setting usb interface %d:%d\n", - fmt->iface, fmt->altsetting); - snd_usb_set_interface_quirk(dev); - } - - subs->interface = fmt->iface; - subs->altset_idx = fmt->altset_idx; - subs->data_endpoint = snd_usb_add_endpoint(subs->stream->chip, - alts, fmt->endpoint, subs->direction, - SND_USB_ENDPOINT_TYPE_DATA); - - if (!subs->data_endpoint) - return -EINVAL; - - err = set_sync_endpoint(subs, fmt, dev, alts, altsd); - if (err < 0) - return err; - - err = snd_usb_init_pitch(subs->stream->chip, fmt->iface, alts, fmt); - if (err < 0) - return err; - - subs->cur_audiofmt = fmt; + fmt->sync_ep = ep; + fmt->sync_iface = altsd->bInterfaceNumber; + fmt->sync_altsetting = altsd->bAlternateSetting; + fmt->sync_ep_idx = 1; + if ((sync_attr & USB_ENDPOINT_USAGE_MASK) == USB_ENDPOINT_USAGE_IMPLICIT_FB) + fmt->implicit_fb = 1; - snd_usb_set_format_quirk(subs, fmt); + dev_dbg(&dev->dev, "%d:%d: found sync_ep=0x%x, iface=%d, alt=%d, implicit_fb=%d\n", + fmt->iface, fmt->altsetting, fmt->sync_ep, fmt->sync_iface, + fmt->sync_altsetting, fmt->implicit_fb); return 0; } -/* - * Return the score of matching two audioformats. - * Veto the audioformat if: - * - It has no channels for some reason. - * - Requested PCM format is not supported. - * - Requested sample rate is not supported. - */ -static int match_endpoint_audioformats(struct snd_usb_substream *subs, - struct audioformat *fp, - struct audioformat *match, int rate, - snd_pcm_format_t pcm_format) -{ - int i; - int score = 0; - - if (fp->channels < 1) { - dev_dbg(&subs->dev->dev, - "%s: (fmt @%p) 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__, - fp, pcm_format); - return 0; - } - - for (i = 0; i < fp->nr_rates; i++) { - if (fp->rate_table[i] == rate) { - score++; - break; - } - } - if (!score) { - dev_dbg(&subs->dev->dev, - "%s: (fmt @%p) no match for rate %d\n", __func__, - fp, rate); - return 0; - } - - if (fp->channels == match->channels) - score++; - - dev_dbg(&subs->dev->dev, - "%s: (fmt @%p) score %d\n", __func__, fp, score); - - return score; -} - -/* - * Configure the sync ep using the rate and pcm format of the data ep. - */ -static int configure_sync_endpoint(struct snd_usb_substream *subs) -{ - int ret; - struct audioformat *fp; - struct audioformat *sync_fp = NULL; - int cur_score = 0; - int sync_period_bytes = subs->period_bytes; - struct snd_usb_substream *sync_subs = - &subs->stream->substream[subs->direction ^ 1]; - - if (subs->sync_endpoint->type != SND_USB_ENDPOINT_TYPE_DATA || - !subs->stream) - return snd_usb_endpoint_set_params(subs->sync_endpoint, - subs->pcm_format, - subs->channels, - subs->period_bytes, - 0, 0, - subs->cur_rate, - subs->cur_audiofmt, - NULL); - - /* Try to find the best matching audioformat. */ - list_for_each_entry(fp, &sync_subs->fmt_list, list) { - int score = match_endpoint_audioformats(subs, - fp, subs->cur_audiofmt, - subs->cur_rate, subs->pcm_format); - - if (score > cur_score) { - sync_fp = fp; - cur_score = score; - } - } - - if (unlikely(sync_fp == NULL)) { - dev_err(&subs->dev->dev, - "%s: no valid audioformat for sync ep %x found\n", - __func__, sync_subs->ep_num); - return -EINVAL; - } - - /* - * Recalculate the period bytes if channel number differ between - * data and sync ep audioformat. - */ - if (sync_fp->channels != subs->channels) { - sync_period_bytes = (subs->period_bytes / subs->channels) * - sync_fp->channels; - dev_dbg(&subs->dev->dev, - "%s: adjusted sync ep period bytes (%d -> %d)\n", - __func__, subs->period_bytes, sync_period_bytes); - } - - ret = snd_usb_endpoint_set_params(subs->sync_endpoint, - subs->pcm_format, - sync_fp->channels, - sync_period_bytes, - 0, 0, - subs->cur_rate, - sync_fp, - NULL); - - return ret; -} - -/* - * configure endpoint params - * - * called during initial setup and upon resume - */ -static int configure_endpoint(struct snd_usb_substream *subs) -{ - int ret; - - /* format changed */ - stop_endpoints(subs, true); - ret = snd_usb_endpoint_set_params(subs->data_endpoint, - subs->pcm_format, - subs->channels, - subs->period_bytes, - subs->period_frames, - subs->buffer_periods, - subs->cur_rate, - subs->cur_audiofmt, - subs->sync_endpoint); - if (ret < 0) - return ret; - - if (subs->sync_endpoint) - ret = configure_sync_endpoint(subs); - - return ret; -} - static int snd_usb_pcm_change_state(struct snd_usb_substream *subs, int state) { int ret; @@ -759,91 +464,202 @@ int snd_usb_pcm_resume(struct snd_usb_stream *as) return 0; } -/* - * hw_params callback - * - * allocate a buffer and set the given audio format. - * - * so far we use a physically linear buffer although packetize transfer - * doesn't need a continuous area. - * if sg buffer is supported on the later version of alsa, we'll follow - * that. - */ -static int snd_usb_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) +static void close_endpoints(struct snd_usb_audio *chip, + struct snd_usb_substream *subs) { - struct snd_usb_substream *subs = substream->runtime->private_data; - struct audioformat *fmt; + if (subs->data_endpoint) { + snd_usb_endpoint_set_sync(chip, subs->data_endpoint, NULL); + snd_usb_endpoint_close(chip, subs->data_endpoint); + subs->data_endpoint = NULL; + } + + if (subs->sync_endpoint) { + snd_usb_endpoint_close(chip, subs->sync_endpoint); + subs->sync_endpoint = NULL; + } +} + +int snd_usb_hw_params(struct snd_usb_substream *subs, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_usb_audio *chip = subs->stream->chip; + const struct audioformat *fmt; + const struct audioformat *sync_fmt; + bool fixed_rate, sync_fixed_rate; int ret; - if (snd_usb_use_vmalloc) - ret = snd_pcm_lib_alloc_vmalloc_buffer(substream, - params_buffer_bytes(hw_params)); - else - ret = snd_pcm_lib_malloc_pages(substream, - params_buffer_bytes(hw_params)); - if (ret < 0) + ret = snd_media_start_pipeline(subs); + if (ret) return ret; - subs->pcm_format = params_format(hw_params); - subs->period_bytes = params_period_bytes(hw_params); - subs->period_frames = params_period_size(hw_params); - subs->buffer_periods = params_periods(hw_params); - subs->channels = params_channels(hw_params); - subs->cur_rate = params_rate(hw_params); - - fmt = find_format(subs); + fixed_rate = snd_usb_pcm_has_fixed_rate(subs); + fmt = find_substream_format(subs, hw_params); if (!fmt) { - dev_dbg(&subs->dev->dev, - "cannot set format: format = %#x, rate = %d, channels = %d\n", - subs->pcm_format, subs->cur_rate, subs->channels); - return -EINVAL; + usb_audio_dbg(chip, + "cannot find format: format=%s, rate=%d, channels=%d\n", + snd_pcm_format_name(params_format(hw_params)), + params_rate(hw_params), params_channels(hw_params)); + ret = -EINVAL; + goto stop_pipeline; + } + + if (fmt->implicit_fb) { + sync_fmt = snd_usb_find_implicit_fb_sync_format(chip, fmt, + hw_params, + !subs->direction, + &sync_fixed_rate); + if (!sync_fmt) { + usb_audio_dbg(chip, + "cannot find sync format: ep=0x%x, iface=%d:%d, format=%s, rate=%d, channels=%d\n", + fmt->sync_ep, fmt->sync_iface, + fmt->sync_altsetting, + snd_pcm_format_name(params_format(hw_params)), + params_rate(hw_params), params_channels(hw_params)); + ret = -EINVAL; + goto stop_pipeline; + } + } else { + sync_fmt = fmt; + sync_fixed_rate = fixed_rate; } - ret = snd_usb_lock_shutdown(subs->stream->chip); + ret = snd_usb_lock_shutdown(chip); if (ret < 0) - return ret; + goto stop_pipeline; ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D0); if (ret < 0) goto unlock; - ret = set_format(subs, fmt); - if (ret < 0) + if (subs->data_endpoint) { + if (snd_usb_endpoint_compatible(chip, subs->data_endpoint, + fmt, hw_params)) + goto unlock; + if (stop_endpoints(subs, false)) + sync_pending_stops(subs); + close_endpoints(chip, subs); + } + + subs->data_endpoint = snd_usb_endpoint_open(chip, fmt, hw_params, false, fixed_rate); + if (!subs->data_endpoint) { + ret = -EINVAL; goto unlock; + } + + if (fmt->sync_ep) { + subs->sync_endpoint = snd_usb_endpoint_open(chip, sync_fmt, + hw_params, + fmt == sync_fmt, + sync_fixed_rate); + if (!subs->sync_endpoint) { + ret = -EINVAL; + goto unlock; + } - subs->interface = fmt->iface; - subs->altset_idx = fmt->altset_idx; - subs->need_setup_ep = true; + snd_usb_endpoint_set_sync(chip, subs->data_endpoint, + subs->sync_endpoint); + } + + scoped_guard(mutex, &chip->mutex) { + subs->cur_audiofmt = fmt; + } + + if (!subs->data_endpoint->need_setup) + goto unlock; + + if (subs->sync_endpoint) { + ret = snd_usb_endpoint_set_params(chip, subs->sync_endpoint); + if (ret < 0) + goto unlock; + } + + ret = snd_usb_endpoint_set_params(chip, subs->data_endpoint); unlock: - snd_usb_unlock_shutdown(subs->stream->chip); + if (ret < 0) + close_endpoints(chip, subs); + + snd_usb_unlock_shutdown(chip); + stop_pipeline: + if (ret < 0) + snd_media_stop_pipeline(subs); + return ret; } +EXPORT_SYMBOL_GPL(snd_usb_hw_params); + +/* + * hw_params callback + * + * allocate a buffer and set the given audio format. + * + * so far we use a physically linear buffer although packetize transfer + * doesn't need a continuous area. + * if sg buffer is supported on the later version of alsa, we'll follow + * that. + */ +static int snd_usb_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_usb_substream *subs = substream->runtime->private_data; + + return snd_usb_hw_params(subs, hw_params); +} + +int snd_usb_hw_free(struct snd_usb_substream *subs) +{ + struct snd_usb_audio *chip = subs->stream->chip; + + snd_media_stop_pipeline(subs); + scoped_guard(mutex, &chip->mutex) { + subs->cur_audiofmt = NULL; + } + CLASS(snd_usb_lock, pm)(chip); + if (!pm.err) { + if (stop_endpoints(subs, false)) + sync_pending_stops(subs); + close_endpoints(chip, subs); + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_usb_hw_free); /* * hw_free callback * * reset the audio format and release the buffer */ -static int snd_usb_hw_free(struct snd_pcm_substream *substream) +static int snd_usb_pcm_hw_free(struct snd_pcm_substream *substream) { struct snd_usb_substream *subs = substream->runtime->private_data; - subs->cur_audiofmt = NULL; - subs->cur_rate = 0; - subs->period_bytes = 0; - if (!snd_usb_lock_shutdown(subs->stream->chip)) { - stop_endpoints(subs, true); - snd_usb_endpoint_deactivate(subs->sync_endpoint); - snd_usb_endpoint_deactivate(subs->data_endpoint); - snd_usb_unlock_shutdown(subs->stream->chip); - } + return snd_usb_hw_free(subs); +} - if (snd_usb_use_vmalloc) - return snd_pcm_lib_free_vmalloc_buffer(substream); - else - return snd_pcm_lib_free_pages(substream); +/* free-wheeling mode? (e.g. dmix) */ +static int in_free_wheeling_mode(struct snd_pcm_runtime *runtime) +{ + return runtime->stop_threshold > runtime->buffer_size; +} + +/* check whether early start is needed for playback stream */ +static int lowlatency_playback_available(struct snd_pcm_runtime *runtime, + struct snd_usb_substream *subs) +{ + struct snd_usb_audio *chip = subs->stream->chip; + + if (subs->direction == SNDRV_PCM_STREAM_CAPTURE) + return false; + /* disabled via module option? */ + if (!chip->lowlatency) + return false; + if (in_free_wheeling_mode(runtime)) + return false; + /* implicit feedback mode has own operation mode */ + if (snd_usb_endpoint_implicit_feedback_sink(subs->data_endpoint)) + return false; + return true; } /* @@ -855,75 +671,69 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_usb_substream *subs = runtime->private_data; - struct usb_host_interface *alts; - struct usb_interface *iface; + struct snd_usb_audio *chip = subs->stream->chip; + int retry = 0; int ret; - if (! subs->cur_audiofmt) { - dev_err(&subs->dev->dev, "no format is specified!\n"); - return -ENXIO; - } - - ret = snd_usb_lock_shutdown(subs->stream->chip); - if (ret < 0) - return ret; - if (snd_BUG_ON(!subs->data_endpoint)) { - ret = -EIO; - goto unlock; - } - - snd_usb_endpoint_sync_pending_stop(subs->sync_endpoint); - snd_usb_endpoint_sync_pending_stop(subs->data_endpoint); + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + if (snd_BUG_ON(!subs->data_endpoint)) + return -EIO; ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D0); if (ret < 0) - goto unlock; - - ret = set_format(subs, subs->cur_audiofmt); - if (ret < 0) - goto unlock; - - if (subs->need_setup_ep) { - - 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) - goto unlock; + return ret; - ret = configure_endpoint(subs); + again: + if (subs->sync_endpoint) { + ret = snd_usb_endpoint_prepare(chip, subs->sync_endpoint); if (ret < 0) - goto unlock; - subs->need_setup_ep = false; + return ret; } - /* some unit conversions in runtime */ - subs->data_endpoint->maxframesize = - bytes_to_frames(runtime, subs->data_endpoint->maxpacksize); - subs->data_endpoint->curframesize = - bytes_to_frames(runtime, subs->data_endpoint->curpacksize); + ret = snd_usb_endpoint_prepare(chip, subs->data_endpoint); + if (ret < 0) + return ret; + else if (ret > 0) + snd_usb_set_format_quirk(subs, subs->cur_audiofmt); + ret = 0; /* reset the pointer */ + subs->buffer_bytes = frames_to_bytes(runtime, runtime->buffer_size); + subs->inflight_bytes = 0; subs->hwptr_done = 0; subs->transfer_done = 0; - subs->last_delay = 0; subs->last_frame_number = 0; + subs->period_elapsed_pending = 0; runtime->delay = 0; - /* for playback, submit the URBs now; otherwise, the first hwptr_done - * updates for all URBs would happen at the same time when starting */ - if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) + subs->lowlatency_playback = lowlatency_playback_available(runtime, subs); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + !subs->lowlatency_playback) { ret = start_endpoints(subs); + /* if XRUN happens at starting streams (possibly with implicit + * fb case), restart again, but only try once. + */ + if (ret == -EPIPE && !retry++) { + sync_pending_stops(subs); + goto again; + } + } - unlock: - snd_usb_unlock_shutdown(subs->stream->chip); return ret; } +/* + * h/w constraints + */ + +#ifdef HW_CONST_DEBUG +#define hwc_debug(fmt, args...) pr_debug(fmt, ##args) +#else +#define hwc_debug(fmt, args...) do { } while(0) +#endif + static const struct snd_pcm_hardware snd_usb_hardware = { .info = SNDRV_PCM_INFO_MMAP | @@ -932,16 +742,18 @@ static const struct snd_pcm_hardware snd_usb_hardware = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE, - .buffer_bytes_max = 1024 * 1024, + .channels_min = 1, + .channels_max = 256, + .buffer_bytes_max = INT_MAX, /* limited by BUFFER_TIME later */ .period_bytes_min = 64, - .period_bytes_max = 512 * 1024, + .period_bytes_max = INT_MAX, /* limited by PERIOD_TIME later */ .periods_min = 2, .periods_max = 1024, }; static int hw_check_valid_format(struct snd_usb_substream *subs, struct snd_pcm_hw_params *params, - struct audioformat *fp) + const struct audioformat *fp) { struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); struct snd_interval *ct = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); @@ -956,7 +768,7 @@ static int hw_check_valid_format(struct snd_usb_substream *subs, check_fmts.bits[1] = (u32)(fp->formats >> 32); snd_mask_intersect(&check_fmts, fmts); if (snd_mask_empty(&check_fmts)) { - hwc_debug(" > check: no supported format %d\n", fp->format); + hwc_debug(" > check: no supported format 0x%llx\n", fp->formats); return 0; } /* check the channels */ @@ -984,33 +796,12 @@ static int hw_check_valid_format(struct snd_usb_substream *subs, return 1; } -static int hw_rule_rate(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) +static int apply_hw_params_minmax(struct snd_interval *it, unsigned int rmin, + unsigned int rmax) { - struct snd_usb_substream *subs = rule->private; - struct audioformat *fp; - struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); - unsigned int rmin, rmax; int changed; - hwc_debug("hw_rule_rate: (%d,%d)\n", it->min, it->max); - changed = 0; - rmin = rmax = 0; - list_for_each_entry(fp, &subs->fmt_list, list) { - if (!hw_check_valid_format(subs, params, fp)) - continue; - if (changed++) { - if (rmin > fp->rate_min) - rmin = fp->rate_min; - if (rmax < fp->rate_max) - rmax = fp->rate_max; - } else { - rmin = fp->rate_min; - rmax = fp->rate_max; - } - } - - if (!changed) { + if (rmin > rmax) { hwc_debug(" --> get empty\n"); it->empty = 1; return -EINVAL; @@ -1035,55 +826,123 @@ static int hw_rule_rate(struct snd_pcm_hw_params *params, return changed; } +/* get the specified endpoint object that is being used by other streams + * (i.e. the parameter is locked) + */ +static const struct snd_usb_endpoint * +get_endpoint_in_use(struct snd_usb_audio *chip, int endpoint, + const struct snd_usb_endpoint *ref_ep) +{ + const struct snd_usb_endpoint *ep; + + ep = snd_usb_get_endpoint(chip, endpoint); + if (ep && ep->cur_audiofmt && (ep != ref_ep || ep->opened > 1)) + return ep; + return NULL; +} + +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_usb_substream *subs = rule->private; + struct snd_usb_audio *chip = subs->stream->chip; + const struct snd_usb_endpoint *ep; + const struct audioformat *fp; + struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + unsigned int rmin, rmax, r; + int i; + + hwc_debug("hw_rule_rate: (%d,%d)\n", it->min, it->max); + rmin = UINT_MAX; + rmax = 0; + list_for_each_entry(fp, &subs->fmt_list, list) { + if (!hw_check_valid_format(subs, params, fp)) + continue; + + ep = get_endpoint_in_use(chip, fp->endpoint, + subs->data_endpoint); + if (ep) { + hwc_debug("rate limit %d for ep#%x\n", + ep->cur_rate, fp->endpoint); + rmin = min(rmin, ep->cur_rate); + rmax = max(rmax, ep->cur_rate); + continue; + } + + if (fp->implicit_fb) { + ep = get_endpoint_in_use(chip, fp->sync_ep, + subs->sync_endpoint); + if (ep) { + hwc_debug("rate limit %d for sync_ep#%x\n", + ep->cur_rate, fp->sync_ep); + rmin = min(rmin, ep->cur_rate); + rmax = max(rmax, ep->cur_rate); + continue; + } + } + + r = snd_usb_endpoint_get_clock_rate(chip, fp->clock); + if (r > 0) { + if (!snd_interval_test(it, r)) + continue; + rmin = min(rmin, r); + rmax = max(rmax, r); + continue; + } + if (fp->rate_table && fp->nr_rates) { + for (i = 0; i < fp->nr_rates; i++) { + r = fp->rate_table[i]; + if (!snd_interval_test(it, r)) + continue; + rmin = min(rmin, r); + rmax = max(rmax, r); + } + } else { + rmin = min(rmin, fp->rate_min); + rmax = max(rmax, fp->rate_max); + } + } + + return apply_hw_params_minmax(it, rmin, rmax); +} + static int hw_rule_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { struct snd_usb_substream *subs = rule->private; - struct audioformat *fp; + const struct audioformat *fp; struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); unsigned int rmin, rmax; - int changed; hwc_debug("hw_rule_channels: (%d,%d)\n", it->min, it->max); - changed = 0; - rmin = rmax = 0; + rmin = UINT_MAX; + rmax = 0; list_for_each_entry(fp, &subs->fmt_list, list) { if (!hw_check_valid_format(subs, params, fp)) continue; - if (changed++) { - if (rmin > fp->channels) - rmin = fp->channels; - if (rmax < fp->channels) - rmax = fp->channels; - } else { - rmin = fp->channels; - rmax = fp->channels; - } + rmin = min(rmin, fp->channels); + rmax = max(rmax, fp->channels); } - if (!changed) { - hwc_debug(" --> get empty\n"); - it->empty = 1; - return -EINVAL; - } + return apply_hw_params_minmax(it, rmin, rmax); +} - changed = 0; - if (it->min < rmin) { - it->min = rmin; - it->openmin = 0; - changed = 1; - } - if (it->max > rmax) { - it->max = rmax; - it->openmax = 0; - changed = 1; - } - if (snd_interval_checkempty(it)) { - it->empty = 1; +static int apply_hw_params_format_bits(struct snd_mask *fmt, u64 fbits) +{ + u32 oldbits[2]; + int changed; + + oldbits[0] = fmt->bits[0]; + oldbits[1] = fmt->bits[1]; + fmt->bits[0] &= (u32)fbits; + fmt->bits[1] &= (u32)(fbits >> 32); + if (!fmt->bits[0] && !fmt->bits[1]) { + hwc_debug(" --> get empty\n"); return -EINVAL; } - hwc_debug(" --> (%d, %d) (changed = %d)\n", it->min, it->max, changed); + changed = (oldbits[0] != fmt->bits[0] || oldbits[1] != fmt->bits[1]); + hwc_debug(" --> %x:%x (changed = %d)\n", fmt->bits[0], fmt->bits[1], changed); return changed; } @@ -1091,42 +950,51 @@ static int hw_rule_format(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { struct snd_usb_substream *subs = rule->private; - struct audioformat *fp; + struct snd_usb_audio *chip = subs->stream->chip; + const struct snd_usb_endpoint *ep; + const struct audioformat *fp; struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); u64 fbits; - u32 oldbits[2]; - int changed; hwc_debug("hw_rule_format: %x:%x\n", fmt->bits[0], fmt->bits[1]); fbits = 0; list_for_each_entry(fp, &subs->fmt_list, list) { if (!hw_check_valid_format(subs, params, fp)) continue; - fbits |= fp->formats; - } - oldbits[0] = fmt->bits[0]; - oldbits[1] = fmt->bits[1]; - fmt->bits[0] &= (u32)fbits; - fmt->bits[1] &= (u32)(fbits >> 32); - if (!fmt->bits[0] && !fmt->bits[1]) { - hwc_debug(" --> get empty\n"); - return -EINVAL; + ep = get_endpoint_in_use(chip, fp->endpoint, + subs->data_endpoint); + if (ep) { + hwc_debug("format limit %d for ep#%x\n", + ep->cur_format, fp->endpoint); + fbits |= pcm_format_to_bits(ep->cur_format); + continue; + } + + if (fp->implicit_fb) { + ep = get_endpoint_in_use(chip, fp->sync_ep, + subs->sync_endpoint); + if (ep) { + hwc_debug("format limit %d for sync_ep#%x\n", + ep->cur_format, fp->sync_ep); + fbits |= pcm_format_to_bits(ep->cur_format); + continue; + } + } + + fbits |= fp->formats; } - changed = (oldbits[0] != fmt->bits[0] || oldbits[1] != fmt->bits[1]); - hwc_debug(" --> %x:%x (changed = %d)\n", fmt->bits[0], fmt->bits[1], changed); - return changed; + return apply_hw_params_format_bits(fmt, fbits); } static int hw_rule_period_time(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { struct snd_usb_substream *subs = rule->private; - struct audioformat *fp; + const struct audioformat *fp; struct snd_interval *it; unsigned char min_datainterval; unsigned int pmin; - int changed; it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME); hwc_debug("hw_rule_period_time: (%u,%u)\n", it->min, it->max); @@ -1142,74 +1010,110 @@ static int hw_rule_period_time(struct snd_pcm_hw_params *params, return -EINVAL; } pmin = 125 * (1 << min_datainterval); - changed = 0; - if (it->min < pmin) { - it->min = pmin; - it->openmin = 0; - changed = 1; - } - if (snd_interval_checkempty(it)) { - it->empty = 1; - return -EINVAL; - } - hwc_debug(" --> (%u,%u) (changed = %d)\n", it->min, it->max, changed); - return changed; + + return apply_hw_params_minmax(it, pmin, UINT_MAX); } -/* - * If the device supports unusual bit rates, does the request meet these? - */ -static int snd_usb_pcm_check_knot(struct snd_pcm_runtime *runtime, - struct snd_usb_substream *subs) +/* additional hw constraints for implicit feedback mode */ +static int hw_rule_period_size_implicit_fb(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) { - struct audioformat *fp; - int *rate_list; - int count = 0, needs_knot = 0; - int err; - - kfree(subs->rate_list.list); - subs->rate_list.list = NULL; + struct snd_usb_substream *subs = rule->private; + struct snd_usb_audio *chip = subs->stream->chip; + const struct audioformat *fp; + const struct snd_usb_endpoint *ep; + struct snd_interval *it; + unsigned int rmin, rmax; + it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + hwc_debug("hw_rule_period_size: (%u,%u)\n", it->min, it->max); + rmin = UINT_MAX; + rmax = 0; list_for_each_entry(fp, &subs->fmt_list, list) { - if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) - return 0; - count += fp->nr_rates; - if (fp->rates & SNDRV_PCM_RATE_KNOT) - needs_knot = 1; + if (!hw_check_valid_format(subs, params, fp)) + continue; + ep = get_endpoint_in_use(chip, fp->endpoint, + subs->data_endpoint); + if (ep) { + hwc_debug("period size limit %d for ep#%x\n", + ep->cur_period_frames, fp->endpoint); + rmin = min(rmin, ep->cur_period_frames); + rmax = max(rmax, ep->cur_period_frames); + continue; + } + + if (fp->implicit_fb) { + ep = get_endpoint_in_use(chip, fp->sync_ep, + subs->sync_endpoint); + if (ep) { + hwc_debug("period size limit %d for sync_ep#%x\n", + ep->cur_period_frames, fp->sync_ep); + rmin = min(rmin, ep->cur_period_frames); + rmax = max(rmax, ep->cur_period_frames); + continue; + } + } } - if (!needs_knot) - return 0; - subs->rate_list.list = rate_list = - kmalloc_array(count, sizeof(int), GFP_KERNEL); - if (!subs->rate_list.list) - return -ENOMEM; - subs->rate_list.count = count; - subs->rate_list.mask = 0; - count = 0; + if (!rmax) + return 0; /* no limit by implicit fb */ + return apply_hw_params_minmax(it, rmin, rmax); +} + +static int hw_rule_periods_implicit_fb(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_usb_substream *subs = rule->private; + struct snd_usb_audio *chip = subs->stream->chip; + const struct audioformat *fp; + const struct snd_usb_endpoint *ep; + struct snd_interval *it; + unsigned int rmin, rmax; + + it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIODS); + hwc_debug("hw_rule_periods: (%u,%u)\n", it->min, it->max); + rmin = UINT_MAX; + rmax = 0; list_for_each_entry(fp, &subs->fmt_list, list) { - int i; - for (i = 0; i < fp->nr_rates; i++) - rate_list[count++] = fp->rate_table[i]; + if (!hw_check_valid_format(subs, params, fp)) + continue; + ep = get_endpoint_in_use(chip, fp->endpoint, + subs->data_endpoint); + if (ep) { + hwc_debug("periods limit %d for ep#%x\n", + ep->cur_buffer_periods, fp->endpoint); + rmin = min(rmin, ep->cur_buffer_periods); + rmax = max(rmax, ep->cur_buffer_periods); + continue; + } + + if (fp->implicit_fb) { + ep = get_endpoint_in_use(chip, fp->sync_ep, + subs->sync_endpoint); + if (ep) { + hwc_debug("periods limit %d for sync_ep#%x\n", + ep->cur_buffer_periods, fp->sync_ep); + rmin = min(rmin, ep->cur_buffer_periods); + rmax = max(rmax, ep->cur_buffer_periods); + continue; + } + } } - err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, - &subs->rate_list); - if (err < 0) - return err; - return 0; + if (!rmax) + return 0; /* no limit by implicit fb */ + return apply_hw_params_minmax(it, rmin, rmax); } - /* * set up the runtime hardware information. */ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substream *subs) { - struct audioformat *fp; + const struct audioformat *fp; unsigned int pt, ptmin; - int param_period_time_if_needed; + int param_period_time_if_needed = -1; int err; runtime->hw.formats = subs->formats; @@ -1256,14 +1160,17 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, hw_rule_rate, subs, + SNDRV_PCM_HW_PARAM_RATE, SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_CHANNELS, param_period_time_if_needed, -1); if (err < 0) return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, hw_rule_channels, subs, + SNDRV_PCM_HW_PARAM_CHANNELS, SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_RATE, param_period_time_if_needed, @@ -1272,6 +1179,7 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre return err; err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, hw_rule_format, subs, + SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_RATE, SNDRV_PCM_HW_PARAM_CHANNELS, param_period_time_if_needed, @@ -1289,11 +1197,39 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre if (err < 0) return err; } - err = snd_usb_pcm_check_knot(runtime, subs); + + /* set max period and buffer sizes for 1 and 2 seconds, respectively */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 0, 1000000); + if (err < 0) + return err; + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_TIME, + 0, 2000000); + if (err < 0) + return err; + + /* additional hw constraints for implicit fb */ + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + hw_rule_period_size_implicit_fb, subs, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIODS, + hw_rule_periods_implicit_fb, subs, + SNDRV_PCM_HW_PARAM_PERIODS, -1); if (err < 0) return err; - return snd_usb_autoresume(subs->stream->chip); + list_for_each_entry(fp, &subs->fmt_list, list) { + if (fp->implicit_fb) { + runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; + break; + } + } + + return 0; } static int snd_usb_pcm_open(struct snd_pcm_substream *substream) @@ -1302,10 +1238,20 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream) struct snd_usb_stream *as = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_usb_substream *subs = &as->substream[direction]; + struct snd_usb_audio *chip = subs->stream->chip; + int ret; + + scoped_guard(mutex, &chip->mutex) { + if (subs->opened) + return -EBUSY; + subs->opened = 1; + } - subs->interface = -1; - subs->altset_idx = 0; runtime->hw = snd_usb_hardware; + /* need an explicit sync to catch applptr update in low-latency mode */ + if (direction == SNDRV_PCM_STREAM_PLAYBACK && + as->chip->lowlatency) + runtime->hw.info |= SNDRV_PCM_INFO_SYNC_APPLPTR; runtime->private_data = subs; subs->pcm_substream = substream; /* runtime PM is also done there */ @@ -1315,7 +1261,26 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream) subs->dsd_dop.channel = 0; subs->dsd_dop.marker = 1; - return setup_hw_info(runtime, subs); + ret = setup_hw_info(runtime, subs); + if (ret < 0) + goto err_open; + ret = snd_usb_autoresume(subs->stream->chip); + if (ret < 0) + goto err_open; + ret = snd_media_stream_init(subs, as->pcm, direction); + if (ret < 0) + goto err_resume; + + return 0; + +err_resume: + snd_usb_autosuspend(subs->stream->chip); +err_open: + scoped_guard(mutex, &chip->mutex) { + subs->opened = 0; + } + + return ret; } static int snd_usb_pcm_close(struct snd_pcm_substream *substream) @@ -1323,23 +1288,25 @@ static int snd_usb_pcm_close(struct snd_pcm_substream *substream) int direction = substream->stream; struct snd_usb_stream *as = snd_pcm_substream_chip(substream); struct snd_usb_substream *subs = &as->substream[direction]; + struct snd_usb_audio *chip = subs->stream->chip; int ret; - stop_endpoints(subs, true); + snd_media_stop_pipeline(subs); - if (!as->chip->keep_iface && - subs->interface >= 0 && - !snd_usb_lock_shutdown(subs->stream->chip)) { - usb_set_interface(subs->dev, subs->interface, 0); - subs->interface = -1; + { + CLASS(snd_usb_lock, pm)(subs->stream->chip); + if (pm.err) + return pm.err; ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D1); - snd_usb_unlock_shutdown(subs->stream->chip); if (ret < 0) return ret; } subs->pcm_substream = NULL; snd_usb_autosuspend(subs->stream->chip); + scoped_guard(mutex, &chip->mutex) { + subs->opened = 0; + } return 0; } @@ -1355,7 +1322,6 @@ static void retire_capture_urb(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; unsigned int stride, frames, bytes, oldptr; int i, period_elapsed = 0; - unsigned long flags; unsigned char *cp; int current_frame_number; @@ -1366,12 +1332,17 @@ static void retire_capture_urb(struct snd_usb_substream *subs, for (i = 0; i < urb->number_of_packets; i++) { cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset + subs->pkt_offset_adj; - if (urb->iso_frame_desc[i].status && printk_ratelimit()) { - dev_dbg(&subs->dev->dev, "frame %d active: %d\n", - i, urb->iso_frame_desc[i].status); - // continue; - } + if (urb->iso_frame_desc[i].status) + dev_dbg_ratelimited(&subs->dev->dev, + "frame %d active: %d\n", i, + urb->iso_frame_desc[i].status); bytes = urb->iso_frame_desc[i].actual_length; + if (subs->stream_offset_adj > 0) { + unsigned int adj = min(subs->stream_offset_adj, bytes); + cp += adj; + bytes -= adj; + subs->stream_offset_adj -= adj; + } frames = bytes / stride; if (!subs->txfr_quirk) bytes = frames * stride; @@ -1383,31 +1354,25 @@ static void retire_capture_urb(struct snd_usb_substream *subs, oldbytes, bytes); } /* update the current pointer */ - spin_lock_irqsave(&subs->lock, flags); - oldptr = subs->hwptr_done; - subs->hwptr_done += bytes; - if (subs->hwptr_done >= runtime->buffer_size * stride) - subs->hwptr_done -= runtime->buffer_size * stride; - frames = (bytes + (oldptr % stride)) / stride; - subs->transfer_done += frames; - if (subs->transfer_done >= runtime->period_size) { - subs->transfer_done -= runtime->period_size; - period_elapsed = 1; - } - /* capture delay is by construction limited to one URB, - * reset delays here - */ - runtime->delay = subs->last_delay = 0; - - /* realign last_frame_number */ - subs->last_frame_number = current_frame_number; - subs->last_frame_number &= 0xFF; /* keep 8 LSBs */ + scoped_guard(spinlock_irqsave, &subs->lock) { + oldptr = subs->hwptr_done; + subs->hwptr_done += bytes; + if (subs->hwptr_done >= subs->buffer_bytes) + subs->hwptr_done -= subs->buffer_bytes; + frames = (bytes + (oldptr % stride)) / stride; + subs->transfer_done += frames; + if (subs->transfer_done >= runtime->period_size) { + subs->transfer_done -= runtime->period_size; + period_elapsed = 1; + } - spin_unlock_irqrestore(&subs->lock, flags); + /* realign last_frame_number */ + subs->last_frame_number = current_frame_number; + } /* copy a data chunk */ - if (oldptr + bytes > runtime->buffer_size * stride) { - unsigned int bytes1 = - runtime->buffer_size * stride - oldptr; + if (oldptr + bytes > subs->buffer_bytes) { + unsigned int bytes1 = subs->buffer_bytes - oldptr; + memcpy(runtime->dma_area + oldptr, cp, bytes1); memcpy(runtime->dma_area, cp + bytes1, bytes - bytes1); } else { @@ -1419,17 +1384,29 @@ static void retire_capture_urb(struct snd_usb_substream *subs, snd_pcm_period_elapsed(subs->pcm_substream); } +static void urb_ctx_queue_advance(struct snd_usb_substream *subs, + struct urb *urb, unsigned int bytes) +{ + struct snd_urb_ctx *ctx = urb->context; + + ctx->queued += bytes; + subs->inflight_bytes += bytes; + subs->hwptr_done += bytes; + if (subs->hwptr_done >= subs->buffer_bytes) + subs->hwptr_done -= subs->buffer_bytes; +} + static inline void fill_playback_urb_dsd_dop(struct snd_usb_substream *subs, struct urb *urb, unsigned int bytes) { struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; - unsigned int stride = runtime->frame_bits >> 3; unsigned int dst_idx = 0; unsigned int src_idx = subs->hwptr_done; - unsigned int wrap = runtime->buffer_size * stride; + unsigned int wrap = subs->buffer_bytes; u8 *dst = urb->transfer_buffer; u8 *src = runtime->dma_area; - u8 marker[] = { 0x05, 0xfa }; + static const u8 marker[] = { 0x05, 0xfa }; + unsigned int queued = 0; /* * The DSP DOP format defines a way to transport DSD samples over @@ -1468,12 +1445,29 @@ static inline void fill_playback_urb_dsd_dop(struct snd_usb_substream *subs, dst[dst_idx++] = bitrev8(src[idx]); else dst[dst_idx++] = src[idx]; - - subs->hwptr_done++; + queued++; } } - if (subs->hwptr_done >= runtime->buffer_size * stride) - subs->hwptr_done -= runtime->buffer_size * stride; + + urb_ctx_queue_advance(subs, urb, queued); +} + +/* copy bit-reversed bytes onto transfer buffer */ +static void fill_playback_urb_dsd_bitrev(struct snd_usb_substream *subs, + struct urb *urb, unsigned int bytes) +{ + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + const u8 *src = runtime->dma_area; + u8 *buf = urb->transfer_buffer; + int i, ofs = subs->hwptr_done; + + for (i = 0; i < bytes; i++) { + *buf++ = bitrev8(src[ofs]); + if (++ofs >= subs->buffer_bytes) + ofs = 0; + } + + urb_ctx_queue_advance(subs, urb, bytes); } static void copy_to_urb(struct snd_usb_substream *subs, struct urb *urb, @@ -1481,10 +1475,10 @@ static void copy_to_urb(struct snd_usb_substream *subs, struct urb *urb, { struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; - if (subs->hwptr_done + bytes > runtime->buffer_size * stride) { + if (subs->hwptr_done + bytes > subs->buffer_bytes) { /* err, the transferred area goes over buffer boundary. */ - unsigned int bytes1 = - runtime->buffer_size * stride - subs->hwptr_done; + unsigned int bytes1 = subs->buffer_bytes - subs->hwptr_done; + memcpy(urb->transfer_buffer + offset, runtime->dma_area + subs->hwptr_done, bytes1); memcpy(urb->transfer_buffer + offset + bytes1, @@ -1493,9 +1487,8 @@ static void copy_to_urb(struct snd_usb_substream *subs, struct urb *urb, memcpy(urb->transfer_buffer + offset, runtime->dma_area + subs->hwptr_done, bytes); } - subs->hwptr_done += bytes; - if (subs->hwptr_done >= runtime->buffer_size * stride) - subs->hwptr_done -= runtime->buffer_size * stride; + + urb_ctx_queue_advance(subs, urb, bytes); } static unsigned int copy_to_urb_quirk(struct snd_usb_substream *subs, @@ -1524,113 +1517,127 @@ static unsigned int copy_to_urb_quirk(struct snd_usb_substream *subs, return bytes; } -static void prepare_playback_urb(struct snd_usb_substream *subs, - struct urb *urb) +static int prepare_playback_urb(struct snd_usb_substream *subs, + struct urb *urb, + bool in_stream_lock) { struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; struct snd_usb_endpoint *ep = subs->data_endpoint; struct snd_urb_ctx *ctx = urb->context; - unsigned int counts, frames, bytes; + unsigned int frames, bytes; + int counts; + unsigned int transfer_done, frame_limit, avail = 0; int i, stride, period_elapsed = 0; - unsigned long flags; - stride = runtime->frame_bits >> 3; + stride = ep->stride; frames = 0; + ctx->queued = 0; urb->number_of_packets = 0; - spin_lock_irqsave(&subs->lock, flags); - subs->frame_limit += ep->max_urb_frames; - for (i = 0; i < ctx->packets; i++) { - if (ctx->packet_size[i]) - counts = ctx->packet_size[i]; - else - counts = snd_usb_endpoint_next_packet_size(ep); - - /* set up descriptor */ - urb->iso_frame_desc[i].offset = frames * ep->stride; - urb->iso_frame_desc[i].length = counts * ep->stride; - frames += counts; - urb->number_of_packets++; - subs->transfer_done += counts; - if (subs->transfer_done >= runtime->period_size) { - subs->transfer_done -= runtime->period_size; - subs->frame_limit = 0; - period_elapsed = 1; - if (subs->fmt_type == UAC_FORMAT_TYPE_II) { - if (subs->transfer_done > 0) { - /* FIXME: fill-max mode is not - * supported yet */ - frames -= subs->transfer_done; - counts -= subs->transfer_done; - urb->iso_frame_desc[i].length = - counts * ep->stride; - subs->transfer_done = 0; - } - i++; - if (i < ctx->packets) { - /* add a transfer delimiter */ - urb->iso_frame_desc[i].offset = - frames * ep->stride; - urb->iso_frame_desc[i].length = 0; - urb->number_of_packets++; - } + + scoped_guard(spinlock_irqsave, &subs->lock) { + frame_limit = subs->frame_limit + ep->max_urb_frames; + transfer_done = subs->transfer_done; + + if (subs->lowlatency_playback && + runtime->state != SNDRV_PCM_STATE_DRAINING) { + unsigned int hwptr = subs->hwptr_done / stride; + + /* calculate the byte offset-in-buffer of the appl_ptr */ + avail = (runtime->control->appl_ptr - runtime->hw_ptr_base) + % runtime->buffer_size; + if (avail <= hwptr) + avail += runtime->buffer_size; + avail -= hwptr; + } + + for (i = 0; i < ctx->packets; i++) { + counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, avail); + if (counts < 0) break; + /* set up descriptor */ + urb->iso_frame_desc[i].offset = frames * stride; + urb->iso_frame_desc[i].length = counts * stride; + frames += counts; + avail -= counts; + urb->number_of_packets++; + transfer_done += counts; + if (transfer_done >= runtime->period_size) { + transfer_done -= runtime->period_size; + frame_limit = 0; + period_elapsed = 1; + if (subs->fmt_type == UAC_FORMAT_TYPE_II) { + if (transfer_done > 0) { + /* FIXME: fill-max mode is not + * supported yet */ + frames -= transfer_done; + counts -= transfer_done; + urb->iso_frame_desc[i].length = + counts * stride; + transfer_done = 0; + } + i++; + if (i < ctx->packets) { + /* add a transfer delimiter */ + urb->iso_frame_desc[i].offset = + frames * stride; + urb->iso_frame_desc[i].length = 0; + urb->number_of_packets++; + } + break; + } } - } - /* finish at the period boundary or after enough frames */ - if ((period_elapsed || - subs->transfer_done >= subs->frame_limit) && - !snd_usb_endpoint_implicit_feedback_sink(ep)) - break; - } - bytes = frames * ep->stride; - - if (unlikely(subs->pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE && - subs->cur_audiofmt->dsd_dop)) { - fill_playback_urb_dsd_dop(subs, urb, bytes); - } else if (unlikely(subs->pcm_format == SNDRV_PCM_FORMAT_DSD_U8 && - subs->cur_audiofmt->dsd_bitrev)) { - /* bit-reverse the bytes */ - u8 *buf = urb->transfer_buffer; - for (i = 0; i < bytes; i++) { - int idx = (subs->hwptr_done + i) - % (runtime->buffer_size * stride); - buf[i] = bitrev8(runtime->dma_area[idx]); + /* finish at the period boundary or after enough frames */ + if ((period_elapsed || transfer_done >= frame_limit) && + !snd_usb_endpoint_implicit_feedback_sink(ep)) + break; } - subs->hwptr_done += bytes; - if (subs->hwptr_done >= runtime->buffer_size * stride) - subs->hwptr_done -= runtime->buffer_size * stride; - } else { - /* usual PCM */ - if (!subs->tx_length_quirk) - copy_to_urb(subs, urb, 0, stride, bytes); - else - bytes = copy_to_urb_quirk(subs, urb, stride, bytes); + if (!frames) + return -EAGAIN; + + bytes = frames * stride; + subs->transfer_done = transfer_done; + subs->frame_limit = frame_limit; + if (unlikely(ep->cur_format == SNDRV_PCM_FORMAT_DSD_U16_LE && + subs->cur_audiofmt->dsd_dop)) { + fill_playback_urb_dsd_dop(subs, urb, bytes); + } else if (unlikely(ep->cur_format == SNDRV_PCM_FORMAT_DSD_U8 && + subs->cur_audiofmt->dsd_bitrev)) { + fill_playback_urb_dsd_bitrev(subs, urb, bytes); + } else { + /* usual PCM */ + if (!subs->tx_length_quirk) + copy_to_urb(subs, urb, 0, stride, bytes); + else + bytes = copy_to_urb_quirk(subs, urb, stride, bytes); /* bytes is now amount of outgoing data */ - } + } - /* update delay with exact number of samples queued */ - runtime->delay = subs->last_delay; - runtime->delay += frames; - subs->last_delay = runtime->delay; + subs->last_frame_number = usb_get_current_frame_number(subs->dev); - /* realign last_frame_number */ - subs->last_frame_number = usb_get_current_frame_number(subs->dev); - subs->last_frame_number &= 0xFF; /* keep 8 LSBs */ + if (subs->trigger_tstamp_pending_update) { + /* this is the first actual URB submitted, + * update trigger timestamp to reflect actual start time + */ + snd_pcm_gettime(runtime, &runtime->trigger_tstamp); + subs->trigger_tstamp_pending_update = false; + } - if (subs->trigger_tstamp_pending_update) { - /* this is the first actual URB submitted, - * update trigger timestamp to reflect actual start time - */ - snd_pcm_gettime(runtime, &runtime->trigger_tstamp); - subs->trigger_tstamp_pending_update = false; + if (period_elapsed && !subs->running && subs->lowlatency_playback) { + subs->period_elapsed_pending = 1; + period_elapsed = 0; + } } - spin_unlock_irqrestore(&subs->lock, flags); urb->transfer_buffer_length = bytes; - if (period_elapsed) - snd_pcm_period_elapsed(subs->pcm_substream); + if (period_elapsed) { + if (in_stream_lock) + snd_pcm_period_elapsed_under_stream_lock(subs->pcm_substream); + else + snd_pcm_period_elapsed(subs->pcm_substream); + } + return 0; } /* @@ -1640,75 +1647,99 @@ static void prepare_playback_urb(struct snd_usb_substream *subs, static void retire_playback_urb(struct snd_usb_substream *subs, struct urb *urb) { - unsigned long flags; - struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; - struct snd_usb_endpoint *ep = subs->data_endpoint; - int processed = urb->transfer_buffer_length / ep->stride; - int est_delay; + struct snd_urb_ctx *ctx = urb->context; + bool period_elapsed = false; - /* ignore the delay accounting when procssed=0 is given, i.e. - * silent payloads are procssed before handling the actual data - */ - if (!processed) - return; + scoped_guard(spinlock_irqsave, &subs->lock) { + if (ctx->queued) { + if (subs->inflight_bytes >= ctx->queued) + subs->inflight_bytes -= ctx->queued; + else + subs->inflight_bytes = 0; + } - spin_lock_irqsave(&subs->lock, flags); - if (!subs->last_delay) - goto out; /* short path */ + subs->last_frame_number = usb_get_current_frame_number(subs->dev); + if (subs->running) { + period_elapsed = subs->period_elapsed_pending; + subs->period_elapsed_pending = 0; + } + } + if (period_elapsed) + snd_pcm_period_elapsed(subs->pcm_substream); +} - est_delay = snd_usb_pcm_delay(subs, runtime->rate); - /* update delay with exact number of samples played */ - if (processed > subs->last_delay) - subs->last_delay = 0; - else - subs->last_delay -= processed; - runtime->delay = subs->last_delay; +/* PCM ack callback for the playback stream; + * this plays a role only when the stream is running in low-latency mode. + */ +static int snd_usb_pcm_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_usb_substream *subs = substream->runtime->private_data; + struct snd_usb_endpoint *ep; - /* - * Report when delay estimate is off by more than 2ms. - * The error should be lower than 2ms since the estimate relies - * on two reads of a counter updated every ms. + if (!subs->lowlatency_playback || !subs->running) + return 0; + ep = subs->data_endpoint; + if (!ep) + return 0; + /* When no more in-flight URBs available, try to process the pending + * outputs here */ - if (abs(est_delay - subs->last_delay) * 1000 > runtime->rate * 2) - dev_dbg_ratelimited(&subs->dev->dev, - "delay: estimated %d, actual %d\n", - est_delay, subs->last_delay); - - if (!subs->running) { - /* update last_frame_number for delay counting here since - * prepare_playback_urb won't be called during pause - */ - subs->last_frame_number = - usb_get_current_frame_number(subs->dev) & 0xff; - } - - out: - spin_unlock_irqrestore(&subs->lock, flags); + if (!ep->active_mask) + return snd_usb_queue_pending_output_urbs(ep, true); + return 0; } static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_usb_substream *subs = substream->runtime->private_data; + int err; switch (cmd) { case SNDRV_PCM_TRIGGER_START: subs->trigger_tstamp_pending_update = true; - /* fall through */ + fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - subs->data_endpoint->prepare_data_urb = prepare_playback_urb; - subs->data_endpoint->retire_data_urb = retire_playback_urb; + snd_usb_endpoint_set_callback(subs->data_endpoint, + prepare_playback_urb, + retire_playback_urb, + subs); + if (subs->lowlatency_playback && + cmd == SNDRV_PCM_TRIGGER_START) { + if (in_free_wheeling_mode(substream->runtime)) + subs->lowlatency_playback = false; + err = start_endpoints(subs); + if (err < 0) { + snd_usb_endpoint_set_callback(subs->data_endpoint, + NULL, NULL, NULL); + return err; + } + } subs->running = 1; + dev_dbg(&subs->dev->dev, "%d:%d Start Playback PCM\n", + subs->cur_audiofmt->iface, + subs->cur_audiofmt->altsetting); return 0; + case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: - stop_endpoints(subs, false); + stop_endpoints(subs, substream->runtime->state == SNDRV_PCM_STATE_DRAINING); + snd_usb_endpoint_set_callback(subs->data_endpoint, + NULL, NULL, NULL); subs->running = 0; + dev_dbg(&subs->dev->dev, "%d:%d Stop Playback PCM\n", + subs->cur_audiofmt->iface, + subs->cur_audiofmt->altsetting); return 0; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - subs->data_endpoint->prepare_data_urb = NULL; /* keep retire_data_urb for delay calculation */ - subs->data_endpoint->retire_data_urb = retire_playback_urb; + snd_usb_endpoint_set_callback(subs->data_endpoint, + NULL, + retire_playback_urb, + subs); subs->running = 0; + dev_dbg(&subs->dev->dev, "%d:%d Pause Playback PCM\n", + subs->cur_audiofmt->iface, + subs->cur_audiofmt->altsetting); return 0; } @@ -1726,21 +1757,28 @@ static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream err = start_endpoints(subs); if (err < 0) return err; - - subs->data_endpoint->retire_data_urb = retire_capture_urb; + fallthrough; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + snd_usb_endpoint_set_callback(subs->data_endpoint, + NULL, retire_capture_urb, + subs); + subs->last_frame_number = usb_get_current_frame_number(subs->dev); subs->running = 1; + dev_dbg(&subs->dev->dev, "%d:%d Start Capture PCM\n", + subs->cur_audiofmt->iface, + subs->cur_audiofmt->altsetting); return 0; + case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: stop_endpoints(subs, false); - subs->running = 0; - return 0; + fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - subs->data_endpoint->retire_data_urb = NULL; + snd_usb_endpoint_set_callback(subs->data_endpoint, + NULL, NULL, NULL); subs->running = 0; - return 0; - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - subs->data_endpoint->retire_data_urb = retire_capture_urb; - subs->running = 1; + dev_dbg(&subs->dev->dev, "%d:%d Stop Capture PCM\n", + subs->cur_audiofmt->iface, + subs->cur_audiofmt->altsetting); return 0; } @@ -1750,61 +1788,32 @@ static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream static const struct snd_pcm_ops snd_usb_playback_ops = { .open = snd_usb_pcm_open, .close = snd_usb_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_usb_hw_params, - .hw_free = snd_usb_hw_free, + .hw_params = snd_usb_pcm_hw_params, + .hw_free = snd_usb_pcm_hw_free, .prepare = snd_usb_pcm_prepare, .trigger = snd_usb_substream_playback_trigger, + .sync_stop = snd_usb_pcm_sync_stop, .pointer = snd_usb_pcm_pointer, - .page = snd_pcm_lib_get_vmalloc_page, + .ack = snd_usb_pcm_playback_ack, }; static const struct snd_pcm_ops snd_usb_capture_ops = { .open = snd_usb_pcm_open, .close = snd_usb_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_usb_hw_params, - .hw_free = snd_usb_hw_free, + .hw_params = snd_usb_pcm_hw_params, + .hw_free = snd_usb_pcm_hw_free, .prepare = snd_usb_pcm_prepare, .trigger = snd_usb_substream_capture_trigger, + .sync_stop = snd_usb_pcm_sync_stop, .pointer = snd_usb_pcm_pointer, - .page = snd_pcm_lib_get_vmalloc_page, -}; - -static const struct snd_pcm_ops snd_usb_playback_dev_ops = { - .open = snd_usb_pcm_open, - .close = snd_usb_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_usb_hw_params, - .hw_free = snd_usb_hw_free, - .prepare = snd_usb_pcm_prepare, - .trigger = snd_usb_substream_playback_trigger, - .pointer = snd_usb_pcm_pointer, - .page = snd_pcm_sgbuf_ops_page, -}; - -static const struct snd_pcm_ops snd_usb_capture_dev_ops = { - .open = snd_usb_pcm_open, - .close = snd_usb_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_usb_hw_params, - .hw_free = snd_usb_hw_free, - .prepare = snd_usb_pcm_prepare, - .trigger = snd_usb_substream_capture_trigger, - .pointer = snd_usb_pcm_pointer, - .page = snd_pcm_sgbuf_ops_page, }; void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream) { const struct snd_pcm_ops *ops; - if (snd_usb_use_vmalloc) - ops = stream == SNDRV_PCM_STREAM_PLAYBACK ? + ops = stream == SNDRV_PCM_STREAM_PLAYBACK ? &snd_usb_playback_ops : &snd_usb_capture_ops; - else - ops = stream == SNDRV_PCM_STREAM_PLAYBACK ? - &snd_usb_playback_dev_ops : &snd_usb_capture_dev_ops; snd_pcm_set_ops(pcm, stream, ops); } @@ -1812,9 +1821,12 @@ void snd_usb_preallocate_buffer(struct snd_usb_substream *subs) { struct snd_pcm *pcm = subs->stream->pcm; struct snd_pcm_substream *s = pcm->streams[subs->direction].substream; - struct device *dev = subs->dev->bus->controller; + struct device *dev = subs->dev->bus->sysdev; - if (!snd_usb_use_vmalloc) - snd_pcm_lib_preallocate_pages(s, SNDRV_DMA_TYPE_DEV_SG, - dev, 64*1024, 512*1024); + if (snd_usb_use_vmalloc) + snd_pcm_set_managed_buffer(s, SNDRV_DMA_TYPE_VMALLOC, + NULL, 0, 0); + else + snd_pcm_set_managed_buffer(s, SNDRV_DMA_TYPE_DEV_SG, + dev, 64*1024, 512*1024); } diff --git a/sound/usb/pcm.h b/sound/usb/pcm.h index 9833627c1eca..c096021adb2b 100644 --- a/sound/usb/pcm.h +++ b/sound/usb/pcm.h @@ -2,17 +2,28 @@ #ifndef __USBAUDIO_PCM_H #define __USBAUDIO_PCM_H -snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs, - unsigned int rate); - void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream); int snd_usb_pcm_suspend(struct snd_usb_stream *as); int snd_usb_pcm_resume(struct snd_usb_stream *as); -int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt); +bool snd_usb_pcm_has_fixed_rate(struct snd_usb_substream *as); + +int snd_usb_init_pitch(struct snd_usb_audio *chip, + const struct audioformat *fmt); void snd_usb_preallocate_buffer(struct snd_usb_substream *subs); +int snd_usb_audioformat_set_sync_ep(struct snd_usb_audio *chip, + struct audioformat *fmt); + +const struct audioformat * +snd_usb_find_format(struct list_head *fmt_list_head, snd_pcm_format_t format, + unsigned int rate, unsigned int channels, bool strict_match, + struct snd_usb_substream *subs); +const struct audioformat * +snd_usb_find_substream_format(struct snd_usb_substream *subs, + const struct snd_pcm_hw_params *params); +int snd_usb_hw_params(struct snd_usb_substream *subs, + struct snd_pcm_hw_params *hw_params); +int snd_usb_hw_free(struct snd_usb_substream *subs); #endif /* __USBAUDIO_PCM_H */ diff --git a/sound/usb/power.c b/sound/usb/power.c index bd303a1ba1b7..66bd4daa68fd 100644 --- a/sound/usb/power.c +++ b/sound/usb/power.c @@ -31,6 +31,8 @@ snd_usb_find_power_domain(struct usb_host_interface *ctrl_iface, struct uac3_power_domain_descriptor *pd_desc = p; int i; + if (!snd_usb_validate_audio_desc(p, UAC_VERSION_3)) + continue; for (i = 0; i < pd_desc->bNrEntities; i++) { if (pd_desc->baEntityID[i] == id) { pd->pd_id = pd_desc->bPowerDomainID; @@ -38,6 +40,7 @@ snd_usb_find_power_domain(struct usb_host_interface *ctrl_iface, le16_to_cpu(pd_desc->waRecoveryTime1); pd->pd_d2d0_rec = le16_to_cpu(pd_desc->waRecoveryTime2); + pd->ctrl_iface = ctrl_iface; return pd; } } @@ -55,7 +58,7 @@ int snd_usb_power_domain_set(struct snd_usb_audio *chip, unsigned char current_state; int err, idx; - idx = snd_usb_ctrl_intf(chip) | (pd->pd_id << 8); + idx = snd_usb_ctrl_intf(pd->ctrl_iface) | (pd->pd_id << 8); err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), UAC2_CS_CUR, diff --git a/sound/usb/power.h b/sound/usb/power.h index 6004231a7c75..1fa92ad0ca92 100644 --- a/sound/usb/power.h +++ b/sound/usb/power.h @@ -6,6 +6,7 @@ struct snd_usb_power_domain { int pd_id; /* UAC3 Power Domain ID */ int pd_d1d0_rec; /* D1 to D0 recovery time */ int pd_d2d0_rec; /* D2 to D0 recovery time */ + struct usb_host_interface *ctrl_iface; /* Control interface */ }; enum { @@ -21,17 +22,7 @@ struct snd_usb_power_domain * snd_usb_find_power_domain(struct usb_host_interface *ctrl_iface, unsigned char id); -#ifdef CONFIG_PM int snd_usb_autoresume(struct snd_usb_audio *chip); void snd_usb_autosuspend(struct snd_usb_audio *chip); -#else -static inline int snd_usb_autoresume(struct snd_usb_audio *chip) -{ - return 0; -} -static inline void snd_usb_autosuspend(struct snd_usb_audio *chip) -{ -} -#endif #endif /* __USBAUDIO_POWER_H */ diff --git a/sound/usb/proc.c b/sound/usb/proc.c index 0ac89e294d31..f4b7a7ff3203 100644 --- a/sound/usb/proc.c +++ b/sound/usb/proc.c @@ -1,18 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * */ #include <linux/init.h> @@ -61,20 +48,51 @@ static void proc_audio_usbid_read(struct snd_info_entry *entry, struct snd_info_ void snd_usb_audio_create_proc(struct snd_usb_audio *chip) { - struct snd_info_entry *entry; - if (!snd_card_proc_new(chip->card, "usbbus", &entry)) - snd_info_set_text_ops(entry, chip, proc_audio_usbbus_read); - if (!snd_card_proc_new(chip->card, "usbid", &entry)) - snd_info_set_text_ops(entry, chip, proc_audio_usbid_read); + snd_card_ro_proc_new(chip->card, "usbbus", chip, + proc_audio_usbbus_read); + snd_card_ro_proc_new(chip->card, "usbid", chip, + proc_audio_usbid_read); } +static const char * const channel_labels[] = { + [SNDRV_CHMAP_NA] = "N/A", + [SNDRV_CHMAP_MONO] = "MONO", + [SNDRV_CHMAP_FL] = "FL", + [SNDRV_CHMAP_FR] = "FR", + [SNDRV_CHMAP_FC] = "FC", + [SNDRV_CHMAP_LFE] = "LFE", + [SNDRV_CHMAP_RL] = "RL", + [SNDRV_CHMAP_RR] = "RR", + [SNDRV_CHMAP_FLC] = "FLC", + [SNDRV_CHMAP_FRC] = "FRC", + [SNDRV_CHMAP_RC] = "RC", + [SNDRV_CHMAP_SL] = "SL", + [SNDRV_CHMAP_SR] = "SR", + [SNDRV_CHMAP_TC] = "TC", + [SNDRV_CHMAP_TFL] = "TFL", + [SNDRV_CHMAP_TFC] = "TFC", + [SNDRV_CHMAP_TFR] = "TFR", + [SNDRV_CHMAP_TRL] = "TRL", + [SNDRV_CHMAP_TRC] = "TRC", + [SNDRV_CHMAP_TRR] = "TRR", + [SNDRV_CHMAP_TFLC] = "TFLC", + [SNDRV_CHMAP_TFRC] = "TFRC", + [SNDRV_CHMAP_LLFE] = "LLFE", + [SNDRV_CHMAP_RLFE] = "RLFE", + [SNDRV_CHMAP_TSL] = "TSL", + [SNDRV_CHMAP_TSR] = "TSR", + [SNDRV_CHMAP_BC] = "BC", + [SNDRV_CHMAP_RLC] = "RLC", + [SNDRV_CHMAP_RRC] = "RRC", +}; + /* * proc interface for list the supported pcm formats */ static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct snd_info_buffer *buffer) { struct audioformat *fp; - static char *sync_types[4] = { + static const char * const sync_types[4] = { "NONE", "ASYNC", "ADAPTIVE", "SYNC" }; @@ -84,13 +102,14 @@ static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct s snd_iprintf(buffer, " Interface %d\n", fp->iface); snd_iprintf(buffer, " Altset %d\n", fp->altsetting); snd_iprintf(buffer, " Format:"); - for (fmt = 0; fmt <= SNDRV_PCM_FORMAT_LAST; ++fmt) + pcm_for_each_format(fmt) if (fp->formats & pcm_format_to_bits(fmt)) snd_iprintf(buffer, " %s", snd_pcm_format_name(fmt)); snd_iprintf(buffer, "\n"); snd_iprintf(buffer, " Channels: %d\n", fp->channels); - snd_iprintf(buffer, " Endpoint: %d %s (%s)\n", + snd_iprintf(buffer, " Endpoint: 0x%02x (%d %s) (%s)\n", + fp->endpoint, fp->endpoint & USB_ENDPOINT_NUMBER_MASK, fp->endpoint & USB_DIR_IN ? "IN" : "OUT", sync_types[(fp->ep_attr & USB_ENDPOINT_SYNCTYPE) >> 2]); @@ -110,6 +129,41 @@ static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct s if (subs->speed != USB_SPEED_FULL) snd_iprintf(buffer, " Data packet interval: %d us\n", 125 * (1 << fp->datainterval)); + snd_iprintf(buffer, " Bits: %d\n", fp->fmt_bits); + + if (fp->dsd_raw) + snd_iprintf(buffer, " DSD raw: DOP=%d, bitrev=%d\n", + fp->dsd_dop, fp->dsd_bitrev); + + if (fp->chmap) { + const struct snd_pcm_chmap_elem *map = fp->chmap; + int c; + + snd_iprintf(buffer, " Channel map:"); + for (c = 0; c < map->channels; c++) { + if (map->map[c] >= ARRAY_SIZE(channel_labels) || + !channel_labels[map->map[c]]) + snd_iprintf(buffer, " --"); + else + snd_iprintf(buffer, " %s", + channel_labels[map->map[c]]); + } + snd_iprintf(buffer, "\n"); + } + + if (fp->sync_ep) { + snd_iprintf(buffer, " Sync Endpoint: 0x%02x (%d %s)\n", + fp->sync_ep, + fp->sync_ep & USB_ENDPOINT_NUMBER_MASK, + fp->sync_ep & USB_DIR_IN ? "IN" : "OUT"); + snd_iprintf(buffer, " Sync EP Interface: %d\n", + fp->sync_iface); + snd_iprintf(buffer, " Sync EP Altset: %d\n", + fp->sync_altsetting); + snd_iprintf(buffer, " Implicit Feedback Mode: %s\n", + fp->implicit_fb ? "Yes" : "No"); + } + // snd_iprintf(buffer, " Max Packet Size = %d\n", fp->maxpacksize); // snd_iprintf(buffer, " EP Attribute = %#x\n", fp->attributes); } @@ -135,12 +189,17 @@ static void proc_dump_ep_status(struct snd_usb_substream *subs, } } -static void proc_dump_substream_status(struct snd_usb_substream *subs, struct snd_info_buffer *buffer) +static void proc_dump_substream_status(struct snd_usb_audio *chip, + struct snd_usb_substream *subs, + struct snd_info_buffer *buffer) { + guard(mutex)(&chip->mutex); if (subs->running) { snd_iprintf(buffer, " Status: Running\n"); - snd_iprintf(buffer, " Interface = %d\n", subs->interface); - snd_iprintf(buffer, " Altset = %d\n", subs->altset_idx); + if (subs->cur_audiofmt) { + snd_iprintf(buffer, " Interface = %d\n", subs->cur_audiofmt->iface); + snd_iprintf(buffer, " Altset = %d\n", subs->cur_audiofmt->altsetting); + } proc_dump_ep_status(subs, subs->data_endpoint, subs->sync_endpoint, buffer); } else { snd_iprintf(buffer, " Status: Stop\n"); @@ -150,29 +209,28 @@ static void proc_dump_substream_status(struct snd_usb_substream *subs, struct sn static void proc_pcm_format_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct snd_usb_stream *stream = entry->private_data; + struct snd_usb_audio *chip = stream->chip; - snd_iprintf(buffer, "%s : %s\n", stream->chip->card->longname, stream->pcm->name); + snd_iprintf(buffer, "%s : %s\n", chip->card->longname, stream->pcm->name); if (stream->substream[SNDRV_PCM_STREAM_PLAYBACK].num_formats) { snd_iprintf(buffer, "\nPlayback:\n"); - proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer); + proc_dump_substream_status(chip, &stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer); proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer); } if (stream->substream[SNDRV_PCM_STREAM_CAPTURE].num_formats) { snd_iprintf(buffer, "\nCapture:\n"); - proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer); + proc_dump_substream_status(chip, &stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer); proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer); } } void snd_usb_proc_pcm_format_add(struct snd_usb_stream *stream) { - struct snd_info_entry *entry; char name[32]; struct snd_card *card = stream->chip->card; - sprintf(name, "stream%d", stream->pcm_index); - if (!snd_card_proc_new(card, name, &entry)) - snd_info_set_text_ops(entry, stream, proc_pcm_format_read); + scnprintf(name, sizeof(name), "stream%d", stream->pcm_index); + snd_card_ro_proc_new(card, name, stream, proc_pcm_format_read); } diff --git a/sound/usb/qcom/Makefile b/sound/usb/qcom/Makefile new file mode 100644 index 000000000000..6567727b66f0 --- /dev/null +++ b/sound/usb/qcom/Makefile @@ -0,0 +1,4 @@ +snd-usb-audio-qmi-y := usb_audio_qmi_v01.o qc_audio_offload.o +snd-usb-audio-qmi-y += mixer_usb_offload.o +obj-$(CONFIG_SND_USB_AUDIO_QMI) += snd-usb-audio-qmi.o + diff --git a/sound/usb/qcom/mixer_usb_offload.c b/sound/usb/qcom/mixer_usb_offload.c new file mode 100644 index 000000000000..2adeb64f4d33 --- /dev/null +++ b/sound/usb/qcom/mixer_usb_offload.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include <linux/usb.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/soc-usb.h> + +#include "../usbaudio.h" +#include "../card.h" +#include "../helper.h" +#include "../mixer.h" + +#include "mixer_usb_offload.h" + +#define PCM_IDX(n) ((n) & 0xffff) +#define CARD_IDX(n) ((n) >> 16) + +static int +snd_usb_offload_card_route_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct device *sysdev = snd_kcontrol_chip(kcontrol); + int ret; + + ret = snd_soc_usb_update_offload_route(sysdev, + CARD_IDX(kcontrol->private_value), + PCM_IDX(kcontrol->private_value), + SNDRV_PCM_STREAM_PLAYBACK, + SND_SOC_USB_KCTL_CARD_ROUTE, + ucontrol->value.integer.value); + if (ret < 0) { + ucontrol->value.integer.value[0] = -1; + ucontrol->value.integer.value[1] = -1; + } + + return 0; +} + +static int snd_usb_offload_card_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = -1; + uinfo->value.integer.max = SNDRV_CARDS; + + return 0; +} + +static struct snd_kcontrol_new snd_usb_offload_mapped_card_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_usb_offload_card_route_info, + .get = snd_usb_offload_card_route_get, +}; + +static int +snd_usb_offload_pcm_route_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct device *sysdev = snd_kcontrol_chip(kcontrol); + int ret; + + ret = snd_soc_usb_update_offload_route(sysdev, + CARD_IDX(kcontrol->private_value), + PCM_IDX(kcontrol->private_value), + SNDRV_PCM_STREAM_PLAYBACK, + SND_SOC_USB_KCTL_PCM_ROUTE, + ucontrol->value.integer.value); + if (ret < 0) { + ucontrol->value.integer.value[0] = -1; + ucontrol->value.integer.value[1] = -1; + } + + return 0; +} + +static int snd_usb_offload_pcm_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = -1; + /* Arbitrary max value, as there is no 'limit' on number of PCM devices */ + uinfo->value.integer.max = 0xff; + + return 0; +} + +static struct snd_kcontrol_new snd_usb_offload_mapped_pcm_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_usb_offload_pcm_route_info, + .get = snd_usb_offload_pcm_route_get, +}; + +/** + * snd_usb_offload_create_ctl() - Add USB offload bounded mixer + * @chip: USB SND chip device + * @bedev: Reference to USB backend DAI device + * + * Creates a sound control for a USB audio device, so that applications can + * query for if there is an available USB audio offload path, and which + * card is managing it. + */ +int snd_usb_offload_create_ctl(struct snd_usb_audio *chip, struct device *bedev) +{ + struct snd_kcontrol_new *chip_kctl; + struct snd_usb_substream *subs; + struct snd_usb_stream *as; + char ctl_name[48]; + int ret; + + list_for_each_entry(as, &chip->pcm_list, list) { + subs = &as->substream[SNDRV_PCM_STREAM_PLAYBACK]; + if (!subs->ep_num || as->pcm_index > 0xff) + continue; + + chip_kctl = &snd_usb_offload_mapped_card_ctl; + chip_kctl->count = 1; + /* + * Store the associated USB SND card number and PCM index for + * the kctl. + */ + chip_kctl->private_value = as->pcm_index | + chip->card->number << 16; + sprintf(ctl_name, "USB Offload Playback Card Route PCM#%d", + as->pcm_index); + chip_kctl->name = ctl_name; + ret = snd_ctl_add(chip->card, snd_ctl_new1(chip_kctl, bedev)); + if (ret < 0) + break; + + chip_kctl = &snd_usb_offload_mapped_pcm_ctl; + chip_kctl->count = 1; + /* + * Store the associated USB SND card number and PCM index for + * the kctl. + */ + chip_kctl->private_value = as->pcm_index | + chip->card->number << 16; + sprintf(ctl_name, "USB Offload Playback PCM Route PCM#%d", + as->pcm_index); + chip_kctl->name = ctl_name; + ret = snd_ctl_add(chip->card, snd_ctl_new1(chip_kctl, bedev)); + if (ret < 0) + break; + } + + return ret; +} diff --git a/sound/usb/qcom/mixer_usb_offload.h b/sound/usb/qcom/mixer_usb_offload.h new file mode 100644 index 000000000000..cf20673f4dc9 --- /dev/null +++ b/sound/usb/qcom/mixer_usb_offload.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __USB_OFFLOAD_MIXER_H +#define __USB_OFFLOAD_MIXER_H + +int snd_usb_offload_create_ctl(struct snd_usb_audio *chip, struct device *bedev); + +#endif /* __USB_OFFLOAD_MIXER_H */ diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c new file mode 100644 index 000000000000..cfb30a195364 --- /dev/null +++ b/sound/usb/qcom/qc_audio_offload.c @@ -0,0 +1,1993 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include <linux/auxiliary_bus.h> +#include <linux/ctype.h> +#include <linux/dma-mapping.h> +#include <linux/dma-map-ops.h> +#include <linux/init.h> +#include <linux/iommu.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/soc/qcom/qmi.h> +#include <linux/usb.h> +#include <linux/usb/audio.h> +#include <linux/usb/audio-v2.h> +#include <linux/usb/audio-v3.h> +#include <linux/usb/hcd.h> +#include <linux/usb/quirks.h> +#include <linux/usb/xhci-sideband.h> + +#include <sound/control.h> +#include <sound/core.h> +#include <sound/info.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/q6usboffload.h> +#include <sound/soc.h> +#include <sound/soc-usb.h> + +#include "../usbaudio.h" +#include "../card.h" +#include "../endpoint.h" +#include "../format.h" +#include "../helper.h" +#include "../pcm.h" +#include "../power.h" + +#include "mixer_usb_offload.h" +#include "usb_audio_qmi_v01.h" + +/* Stream disable request timeout during USB device disconnect */ +#define DEV_RELEASE_WAIT_TIMEOUT 10000 /* in ms */ + +/* Data interval calculation parameters */ +#define BUS_INTERVAL_FULL_SPEED 1000 /* in us */ +#define BUS_INTERVAL_HIGHSPEED_AND_ABOVE 125 /* in us */ +#define MAX_BINTERVAL_ISOC_EP 16 + +#define QMI_STREAM_REQ_CARD_NUM_MASK 0xffff0000 +#define QMI_STREAM_REQ_DEV_NUM_MASK 0xff00 +#define QMI_STREAM_REQ_DIRECTION 0xff + +/* iommu resource parameters and management */ +#define PREPEND_SID_TO_IOVA(iova, sid) ((u64)(((u64)(iova)) | \ + (((u64)sid) << 32))) +#define IOVA_MASK(iova) (((u64)(iova)) & 0xFFFFFFFF) +#define IOVA_BASE 0x1000 +#define IOVA_XFER_RING_BASE (IOVA_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 { + /* IOMMU ring/buffer mapping information */ + unsigned long data_xfer_ring_va; + size_t data_xfer_ring_size; + unsigned long sync_xfer_ring_va; + size_t sync_xfer_ring_size; + dma_addr_t xfer_buf_iova; + size_t xfer_buf_size; + dma_addr_t xfer_buf_dma; + u8 *xfer_buf_cpu; + + /* USB endpoint information */ + unsigned int data_ep_pipe; + unsigned int sync_ep_pipe; + unsigned int data_ep_idx; + unsigned int sync_ep_idx; + + u8 intf_num; + u8 pcm_card_num; + u8 pcm_dev_num; + u8 direction; + bool in_use; +}; + +struct uaudio_qmi_dev { + struct device *dev; + struct q6usb_offload *data; + struct auxiliary_device *auxdev; + + /* list to keep track of available 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; + /* indicate event ring mapped or not */ + bool er_mapped; +}; + +struct uaudio_dev { + struct usb_device *udev; + /* audio control interface */ + struct usb_host_interface *ctrl_intf; + unsigned int usb_core_id; + atomic_t in_use; + struct kref kref; + wait_queue_head_t disconnect_wq; + + /* interface specific */ + int num_intf; + struct intf_info *info; + struct snd_usb_audio *chip; + + /* xhci sideband */ + struct xhci_sideband *sb; + + /* SoC USB device */ + struct snd_soc_usb_device *sdev; +}; + +static struct uaudio_dev uadev[SNDRV_CARDS]; +static struct uaudio_qmi_dev *uaudio_qdev; +static struct uaudio_qmi_svc *uaudio_svc; +static DEFINE_MUTEX(qdev_mutex); + +struct uaudio_qmi_svc { + struct qmi_handle *uaudio_svc_hdl; + struct sockaddr_qrtr client_sq; + bool client_connected; +}; + +enum mem_type { + MEM_EVENT_RING, + MEM_XFER_RING, + MEM_XFER_BUF, +}; + +/* Supported audio formats */ +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 int usb_qmi_get_pcm_num(struct snd_usb_audio *chip, int direction) +{ + struct snd_usb_substream *subs = NULL; + struct snd_usb_stream *as; + int count = 0; + + list_for_each_entry(as, &chip->pcm_list, list) { + subs = &as->substream[direction]; + if (subs->ep_num) + count++; + } + + return count; +} + +static enum usb_qmi_audio_device_speed_enum_v01 +get_speed_info(enum usb_device_speed udev_speed) +{ + switch (udev_speed) { + case USB_SPEED_LOW: + return USB_QMI_DEVICE_SPEED_LOW_V01; + case USB_SPEED_FULL: + return USB_QMI_DEVICE_SPEED_FULL_V01; + case USB_SPEED_HIGH: + return USB_QMI_DEVICE_SPEED_HIGH_V01; + case USB_SPEED_SUPER: + return USB_QMI_DEVICE_SPEED_SUPER_V01; + case USB_SPEED_SUPER_PLUS: + return USB_QMI_DEVICE_SPEED_SUPER_PLUS_V01; + default: + return USB_QMI_DEVICE_SPEED_INVALID_V01; + } +} + +static struct snd_usb_substream *find_substream(unsigned int card_num, + unsigned int pcm_idx, + unsigned int direction) +{ + struct snd_usb_substream *subs = NULL; + struct snd_usb_audio *chip; + struct snd_usb_stream *as; + + chip = uadev[card_num].chip; + if (!chip || atomic_read(&chip->shutdown)) + goto done; + + if (pcm_idx >= chip->pcm_devs) + goto done; + + if (direction > SNDRV_PCM_STREAM_CAPTURE) + goto done; + + list_for_each_entry(as, &chip->pcm_list, list) { + if (as->pcm_index == pcm_idx) { + subs = &as->substream[direction]; + goto done; + } + } + +done: + return subs; +} + +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 get_data_interval_from_si(struct snd_usb_substream *subs, + u32 service_interval) +{ + unsigned int bus_intval_mult; + unsigned int bus_intval; + unsigned int binterval; + + if (subs->dev->speed >= USB_SPEED_HIGH) + bus_intval = BUS_INTERVAL_HIGHSPEED_AND_ABOVE; + else + bus_intval = BUS_INTERVAL_FULL_SPEED; + + if (service_interval % bus_intval) + return -EINVAL; + + bus_intval_mult = service_interval / bus_intval; + binterval = ffs(bus_intval_mult); + if (!binterval || binterval > MAX_BINTERVAL_ISOC_EP) + return -EINVAL; + + /* check if another bit is set then bail out */ + bus_intval_mult = bus_intval_mult >> binterval; + if (bus_intval_mult) + return -EINVAL; + + return (binterval - 1); +} + +/* maps audio format received over QMI to asound.h based pcm format */ +static snd_pcm_format_t map_pcm_format(enum usb_qmi_audio_format 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: + /* + * We expect the caller to do input validation so we should + * never hit this. But we do have to return a proper + * snd_pcm_format_t value due to the __bitwise attribute; so + * just return the equivalent of 0 in case of bad input. + */ + return SNDRV_PCM_FORMAT_S8; + } +} + +/* + * Sends QMI disconnect indication message, assumes chip->mutex and qdev_mutex + * lock held by caller. + */ +static int uaudio_send_disconnect_ind(struct snd_usb_audio *chip) +{ + struct qmi_uaudio_stream_ind_msg_v01 disconnect_ind = {0}; + struct uaudio_qmi_svc *svc = uaudio_svc; + struct uaudio_dev *dev; + int ret = 0; + + dev = &uadev[chip->card->number]; + + if (atomic_read(&dev->in_use)) { + mutex_unlock(&chip->mutex); + mutex_unlock(&qdev_mutex); + dev_dbg(uaudio_qdev->data->dev, "sending qmi indication suspend\n"); + disconnect_ind.dev_event = USB_QMI_DEV_DISCONNECT_V01; + disconnect_ind.slot_id = dev->udev->slot_id; + disconnect_ind.controller_num = dev->usb_core_id; + disconnect_ind.controller_num_valid = 1; + ret = qmi_send_indication(svc->uaudio_svc_hdl, &svc->client_sq, + QMI_UAUDIO_STREAM_IND_V01, + QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN, + qmi_uaudio_stream_ind_msg_v01_ei, + &disconnect_ind); + if (ret < 0) + dev_err(uaudio_qdev->data->dev, + "qmi send failed with err: %d\n", ret); + + ret = wait_event_interruptible_timeout(dev->disconnect_wq, + !atomic_read(&dev->in_use), + msecs_to_jiffies(DEV_RELEASE_WAIT_TIMEOUT)); + if (!ret) { + dev_err(uaudio_qdev->data->dev, + "timeout while waiting for dev_release\n"); + atomic_set(&dev->in_use, 0); + } else if (ret < 0) { + dev_err(uaudio_qdev->data->dev, + "failed with ret %d\n", ret); + atomic_set(&dev->in_use, 0); + } + mutex_lock(&qdev_mutex); + mutex_lock(&chip->mutex); + } + + return ret; +} + +/* Offloading IOMMU management */ +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; + size_t tmp_size = size; + unsigned long iova = 0; + + if (size % PAGE_SIZE) + goto done; + + if (size > *curr_iova_size) + goto done; + + if (*curr_iova_size == 0) + goto done; + + list_for_each_entry(info, head, list) { + /* exact size iova_info */ + if (!info->in_use && info->size == size) { + info->in_use = true; + iova = info->start_iova; + *curr_iova_size -= size; + goto done; + } else if (!info->in_use && tmp_size >= info->size) { + if (!new_info) + new_info = info; + tmp_size -= info->size; + if (tmp_size) + continue; + + iova = 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; + goto done; + } else { + /* iova region in use */ + new_info = NULL; + tmp_size = size; + } + } + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + iova = 0; + goto done; + } + + iova = *curr_iova; + info->start_iova = *curr_iova; + info->size = size; + info->in_use = true; + *curr_iova += size; + *curr_iova_size -= size; + list_add_tail(&info->list, head); + +done: + return iova; +} + +static void uaudio_put_iova(unsigned long iova, 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 == iova) { + if (!info->in_use) + 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) + return; + +done: + *curr_iova_size += size; +} + +/** + * uaudio_iommu_unmap() - unmaps iommu memory for adsp + * @mtype: ring type + * @iova: virtual address to unmap + * @iova_size: region size + * @mapped_iova_size: mapped region size + * + * Unmaps the memory region that was previously assigned to the adsp. + * + */ +static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long iova, + size_t iova_size, size_t mapped_iova_size) +{ + size_t umap_size; + bool unmap = true; + + if (!iova || !iova_size) + return; + + switch (mtype) { + case MEM_EVENT_RING: + if (uaudio_qdev->er_mapped) + uaudio_qdev->er_mapped = false; + else + unmap = false; + break; + + case MEM_XFER_RING: + uaudio_put_iova(iova, iova_size, &uaudio_qdev->xfer_ring_list, + &uaudio_qdev->xfer_ring_iova_size); + break; + case MEM_XFER_BUF: + uaudio_put_iova(iova, iova_size, &uaudio_qdev->xfer_buf_list, + &uaudio_qdev->xfer_buf_iova_size); + break; + default: + unmap = false; + } + + if (!unmap || !mapped_iova_size) + return; + + umap_size = iommu_unmap(uaudio_qdev->data->domain, iova, mapped_iova_size); + if (umap_size != mapped_iova_size) + dev_err(uaudio_qdev->data->dev, + "unmapped size %zu for iova 0x%08lx of mapped size %zu\n", + umap_size, iova, mapped_iova_size); +} + +static int uaudio_iommu_map_prot(bool dma_coherent) +{ + int prot = IOMMU_READ | IOMMU_WRITE; + + if (dma_coherent) + prot |= IOMMU_CACHE; + return prot; +} + +/** + * uaudio_iommu_map_pa() - maps iommu memory for adsp + * @mtype: ring type + * @dma_coherent: dma coherent + * @pa: physical address for ring/buffer + * @size: size of memory region + * + * Maps the XHCI related resources to a memory region that is assigned to be + * used by the adsp. This will be mapped to the domain, which is created by + * the ASoC USB backend driver. + * + */ +static unsigned long uaudio_iommu_map_pa(enum mem_type mtype, bool dma_coherent, + phys_addr_t pa, size_t size) +{ + unsigned long iova = 0; + bool map = true; + int prot = uaudio_iommu_map_prot(dma_coherent); + + switch (mtype) { + case MEM_EVENT_RING: + iova = IOVA_BASE; + /* er already mapped */ + if (uaudio_qdev->er_mapped) + map = false; + break; + case MEM_XFER_RING: + iova = uaudio_get_iova(&uaudio_qdev->curr_xfer_ring_iova, + &uaudio_qdev->xfer_ring_iova_size, + &uaudio_qdev->xfer_ring_list, size); + break; + default: + dev_err(uaudio_qdev->data->dev, "unknown mem type %d\n", mtype); + } + + if (!iova || !map) + return 0; + + iommu_map(uaudio_qdev->data->domain, iova, pa, size, prot, GFP_KERNEL); + + return iova; +} + +static unsigned long uaudio_iommu_map_xfer_buf(bool dma_coherent, size_t size, + struct sg_table *sgt) +{ + struct scatterlist *sg; + unsigned long iova = 0; + size_t total_len = 0; + unsigned long iova_sg; + phys_addr_t pa_sg; + size_t sg_len; + int prot = uaudio_iommu_map_prot(dma_coherent); + int ret; + int i; + + prot = IOMMU_READ | IOMMU_WRITE; + + if (dma_coherent) + prot |= IOMMU_CACHE; + + iova = uaudio_get_iova(&uaudio_qdev->curr_xfer_buf_iova, + &uaudio_qdev->xfer_buf_iova_size, + &uaudio_qdev->xfer_buf_list, size); + if (!iova) + goto done; + + iova_sg = iova; + for_each_sg(sgt->sgl, sg, sgt->nents, i) { + sg_len = PAGE_ALIGN(sg->offset + sg->length); + pa_sg = page_to_phys(sg_page(sg)); + ret = iommu_map(uaudio_qdev->data->domain, iova_sg, pa_sg, sg_len, + prot, GFP_KERNEL); + if (ret) { + uaudio_iommu_unmap(MEM_XFER_BUF, iova, size, total_len); + iova = 0; + goto done; + } + + iova_sg += sg_len; + total_len += sg_len; + } + + if (size != total_len) { + uaudio_iommu_unmap(MEM_XFER_BUF, iova, size, total_len); + iova = 0; + } +done: + return iova; +} + +/* looks up alias, if any, for controller DT node and returns the index */ +static int usb_get_controller_id(struct usb_device *udev) +{ + if (udev->bus->sysdev && udev->bus->sysdev->of_node) + return of_alias_get_id(udev->bus->sysdev->of_node, "usb"); + + return -ENODEV; +} + +/** + * uaudio_dev_intf_cleanup() - cleanup transfer resources + * @udev: usb device + * @info: usb offloading interface + * + * Cleans up the transfer ring related resources which are assigned per + * endpoint from XHCI. This is invoked when the USB endpoints are no + * longer in use by the adsp. + * + */ +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_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_size); + info->sync_xfer_ring_va = 0; + info->sync_xfer_ring_size = 0; + + uaudio_iommu_unmap(MEM_XFER_BUF, info->xfer_buf_iova, info->xfer_buf_size, + info->xfer_buf_size); + info->xfer_buf_iova = 0; + + usb_free_coherent(udev, info->xfer_buf_size, info->xfer_buf_cpu, + info->xfer_buf_dma); + info->xfer_buf_size = 0; + info->xfer_buf_cpu = NULL; + info->xfer_buf_dma = 0; + + info->in_use = false; +} + +/** + * uaudio_event_ring_cleanup_free() - cleanup secondary event ring + * @dev: usb offload device + * + * Cleans up the secondary event ring that was requested. This will + * occur when the adsp is no longer transferring data on the USB bus + * across all endpoints. + * + */ +static void uaudio_event_ring_cleanup_free(struct uaudio_dev *dev) +{ + clear_bit(dev->chip->card->number, &uaudio_qdev->card_slot); + /* all audio devices are disconnected */ + if (!uaudio_qdev->card_slot) { + uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE, + PAGE_SIZE); + xhci_sideband_remove_interrupter(uadev[dev->chip->card->number].sb); + } +} + +static void uaudio_dev_cleanup(struct uaudio_dev *dev) +{ + int if_idx; + + if (!dev->udev) + return; + + /* 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]); + dev_dbg(uaudio_qdev->data->dev, + "release resources: intf# %d card# %d\n", + dev->info[if_idx].intf_num, dev->chip->card->number); + } + + dev->num_intf = 0; + + /* free interface info */ + kfree(dev->info); + dev->info = NULL; + uaudio_event_ring_cleanup_free(dev); + dev->udev = NULL; +} + +/** + * disable_audio_stream() - disable usb snd endpoints + * @subs: usb substream + * + * Closes the USB SND endpoints associated with the current audio stream + * used. This will decrement the USB SND endpoint opened reference count. + * + */ +static void disable_audio_stream(struct snd_usb_substream *subs) +{ + struct snd_usb_audio *chip = subs->stream->chip; + + snd_usb_hw_free(subs); + snd_usb_autosuspend(chip); +} + +/* QMI service disconnect handlers */ +static void qmi_stop_session(void) +{ + struct snd_usb_substream *subs; + struct usb_host_endpoint *ep; + struct snd_usb_audio *chip; + struct intf_info *info; + int pcm_card_num; + int if_idx; + int idx; + + guard(mutex)(&qdev_mutex); + /* 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; + + chip = uadev[idx].chip; + 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]; + pcm_card_num = info->pcm_card_num; + subs = find_substream(pcm_card_num, info->pcm_dev_num, + info->direction); + if (!subs || !chip || atomic_read(&chip->shutdown)) { + dev_err(&uadev[idx].udev->dev, + "no sub for c#%u dev#%u dir%u\n", + info->pcm_card_num, + info->pcm_dev_num, + info->direction); + continue; + } + /* Release XHCI endpoints */ + if (info->data_ep_pipe) + ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, + info->data_ep_pipe); + xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, ep); + + if (info->sync_ep_pipe) + ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, + info->sync_ep_pipe); + xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, ep); + + disable_audio_stream(subs); + } + atomic_set(&uadev[idx].in_use, 0); + guard(mutex)(&chip->mutex); + uaudio_dev_cleanup(&uadev[idx]); + } +} + +/** + * uaudio_sideband_notifier() - xHCI sideband event handler + * @intf: USB interface handle + * @evt: xHCI sideband event type + * + * This callback is executed when the xHCI sideband encounters a sequence + * that requires the sideband clients to take action. An example, is when + * xHCI frees the transfer ring, so the client has to ensure that the + * offload path is halted. + * + */ +static int uaudio_sideband_notifier(struct usb_interface *intf, + struct xhci_sideband_event *evt) +{ + struct snd_usb_audio *chip; + struct uaudio_dev *dev; + int if_idx; + + if (!intf || !evt) + return 0; + + chip = usb_get_intfdata(intf); + + guard(mutex)(&qdev_mutex); + guard(mutex)(&chip->mutex); + + dev = &uadev[chip->card->number]; + + if (evt->type == XHCI_SIDEBAND_XFER_RING_FREE) { + unsigned int *ep = (unsigned int *) evt->evt_data; + + for (if_idx = 0; if_idx < dev->num_intf; if_idx++) { + if (dev->info[if_idx].data_ep_idx == *ep || + dev->info[if_idx].sync_ep_idx == *ep) + uaudio_send_disconnect_ind(chip); + } + } + + return 0; +} + +/** + * qmi_bye_cb() - qmi bye message callback + * @handle: QMI handle + * @node: id of the dying node + * + * This callback is invoked when the QMI bye control message is received + * from the QMI client. Handle the message accordingly by ensuring that + * the USB offload path is disabled and cleaned up. At this point, ADSP + * is not utilizing the USB bus. + * + */ +static void qmi_bye_cb(struct qmi_handle *handle, unsigned int node) +{ + struct uaudio_qmi_svc *svc = uaudio_svc; + + if (svc->uaudio_svc_hdl != handle) + return; + + if (svc->client_connected && svc->client_sq.sq_node == node) { + qmi_stop_session(); + + /* clear QMI client parameters to block further QMI messages */ + svc->client_sq.sq_node = 0; + svc->client_sq.sq_port = 0; + svc->client_sq.sq_family = 0; + svc->client_connected = false; + } +} + +/** + * qmi_svc_disconnect_cb() - qmi client disconnected + * @handle: QMI handle + * @node: id of the dying node + * @port: port of the dying client + * + * Invoked when the remote QMI client is disconnected. Handle this event + * the same way as when the QMI bye message is received. This will ensure + * the USB offloading path is disabled and cleaned up. + * + */ +static void qmi_svc_disconnect_cb(struct qmi_handle *handle, + unsigned int node, unsigned int port) +{ + struct uaudio_qmi_svc *svc; + + if (!uaudio_svc) + return; + + svc = uaudio_svc; + if (svc->uaudio_svc_hdl != handle) + return; + + if (svc->client_connected && svc->client_sq.sq_node == node && + svc->client_sq.sq_port == port) { + qmi_stop_session(); + + /* clear QMI client parameters to block further QMI messages */ + svc->client_sq.sq_node = 0; + svc->client_sq.sq_port = 0; + svc->client_sq.sq_family = 0; + svc->client_connected = false; + } +} + +/* QMI client callback handlers from QMI interface */ +static struct qmi_ops uaudio_svc_ops_options = { + .bye = qmi_bye_cb, + .del_client = qmi_svc_disconnect_cb, +}; + +/* kref release callback when all streams are disabled */ +static void uaudio_dev_release(struct kref *kref) +{ + struct uaudio_dev *dev = container_of(kref, struct uaudio_dev, kref); + + uaudio_event_ring_cleanup_free(dev); + atomic_set(&dev->in_use, 0); + wake_up(&dev->disconnect_wq); +} + +/** + * enable_audio_stream() - enable usb snd endpoints + * @subs: usb substream + * @pcm_format: pcm format requested + * @channels: number of channels + * @cur_rate: sample rate + * @datainterval: interval + * + * Opens all USB SND endpoints used for the data interface. This will increment + * the USB SND endpoint's opened count. Requests to keep the interface resumed + * until the audio stream is stopped. Will issue the USB set interface control + * message to enable the data interface. + * + */ +static int enable_audio_stream(struct snd_usb_substream *subs, + snd_pcm_format_t pcm_format, + unsigned int channels, unsigned int cur_rate, + int datainterval) +{ + struct snd_pcm_hw_params params; + struct snd_usb_audio *chip; + struct snd_interval *i; + struct snd_mask *m; + int ret; + + chip = subs->stream->chip; + + _snd_pcm_hw_params_any(¶ms); + + m = hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_leave(m, pcm_format); + + i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS); + snd_interval_setinteger(i); + i->min = channels; + i->max = channels; + + i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_RATE); + snd_interval_setinteger(i); + i->min = cur_rate; + i->max = cur_rate; + + pm_runtime_barrier(&chip->intf[0]->dev); + snd_usb_autoresume(chip); + + ret = snd_usb_hw_params(subs, ¶ms); + if (ret < 0) + goto put_suspend; + + if (!atomic_read(&chip->shutdown)) { + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) { + ret = pm.err; + goto detach_ep; + } + + if (subs->sync_endpoint) { + ret = snd_usb_endpoint_prepare(chip, subs->sync_endpoint); + if (ret < 0) + goto detach_ep; + } + + ret = snd_usb_endpoint_prepare(chip, subs->data_endpoint); + if (ret < 0) + goto detach_ep; + + dev_dbg(uaudio_qdev->data->dev, + "selected %s iface:%d altsetting:%d datainterval:%dus\n", + subs->direction ? "capture" : "playback", + subs->cur_audiofmt->iface, subs->cur_audiofmt->altsetting, + (1 << subs->cur_audiofmt->datainterval) * + (subs->dev->speed >= USB_SPEED_HIGH ? + BUS_INTERVAL_HIGHSPEED_AND_ABOVE : + BUS_INTERVAL_FULL_SPEED)); + } + + return 0; + +detach_ep: + snd_usb_hw_free(subs); + +put_suspend: + snd_usb_autosuspend(chip); + + return ret; +} + +/** + * uaudio_transfer_buffer_setup() - fetch and populate xfer buffer params + * @subs: usb substream + * @xfer_buf: xfer buf to be allocated + * @xfer_buf_len: size of allocation + * @mem_info: QMI response info + * + * Allocates and maps the transfer buffers that will be utilized by the + * audio DSP. Will populate the information in the QMI response that is + * sent back to the stream enable request. + * + */ +static int uaudio_transfer_buffer_setup(struct snd_usb_substream *subs, + void **xfer_buf_cpu, u32 xfer_buf_len, + struct mem_info_v01 *mem_info) +{ + struct sg_table xfer_buf_sgt; + dma_addr_t xfer_buf_dma; + void *xfer_buf; + u32 len = xfer_buf_len; + bool dma_coherent; + dma_addr_t xfer_buf_dma_sysdev; + u32 remainder; + u32 mult; + int ret; + + dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev); + + /* xfer buffer, multiple of 4K only */ + if (!len) + len = PAGE_SIZE; + + mult = len / PAGE_SIZE; + remainder = len % PAGE_SIZE; + len = mult * PAGE_SIZE; + len += remainder ? PAGE_SIZE : 0; + + if (len > MAX_XFER_BUFF_LEN) { + dev_err(uaudio_qdev->data->dev, + "req buf len %d > max buf len %lu, setting %lu\n", + len, MAX_XFER_BUFF_LEN, MAX_XFER_BUFF_LEN); + len = MAX_XFER_BUFF_LEN; + } + + /* get buffer mapped into subs->dev */ + xfer_buf = usb_alloc_coherent(subs->dev, len, GFP_KERNEL, &xfer_buf_dma); + if (!xfer_buf) + return -ENOMEM; + + dma_get_sgtable(subs->dev->bus->sysdev, &xfer_buf_sgt, xfer_buf, + xfer_buf_dma, len); + + /* map the physical buffer into sysdev as well */ + xfer_buf_dma_sysdev = uaudio_iommu_map_xfer_buf(dma_coherent, + len, &xfer_buf_sgt); + if (!xfer_buf_dma_sysdev) { + ret = -ENOMEM; + goto unmap_sync; + } + + mem_info->dma = xfer_buf_dma; + mem_info->size = len; + mem_info->iova = PREPEND_SID_TO_IOVA(xfer_buf_dma_sysdev, uaudio_qdev->data->sid); + *xfer_buf_cpu = xfer_buf; + sg_free_table(&xfer_buf_sgt); + + return 0; + +unmap_sync: + usb_free_coherent(subs->dev, len, xfer_buf, xfer_buf_dma); + + return ret; +} + +/** + * uaudio_endpoint_setup() - fetch and populate endpoint params + * @subs: usb substream + * @endpoint: usb endpoint to add + * @card_num: uadev index + * @mem_info: QMI response info + * @ep_desc: QMI ep desc response field + * + * Initialize the USB endpoint being used for a particular USB + * stream. Will request XHCI sec intr to reserve the EP for + * offloading as well as populating the QMI response with the + * transfer ring parameters. + * + */ +static phys_addr_t +uaudio_endpoint_setup(struct snd_usb_substream *subs, + struct snd_usb_endpoint *endpoint, int card_num, + struct mem_info_v01 *mem_info, + struct usb_endpoint_descriptor_v01 *ep_desc) +{ + struct usb_host_endpoint *ep; + phys_addr_t tr_pa = 0; + struct sg_table *sgt; + bool dma_coherent; + unsigned long iova; + struct page *pg; + int ret = -ENODEV; + + dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev); + + ep = usb_pipe_endpoint(subs->dev, endpoint->pipe); + if (!ep) { + dev_err(uaudio_qdev->data->dev, "data ep # %d context is null\n", + subs->data_endpoint->ep_num); + goto exit; + } + + memcpy(ep_desc, &ep->desc, sizeof(ep->desc)); + + ret = xhci_sideband_add_endpoint(uadev[card_num].sb, ep); + if (ret < 0) { + dev_err(&subs->dev->dev, + "failed to add data ep to sec intr\n"); + ret = -ENODEV; + goto exit; + } + + sgt = xhci_sideband_get_endpoint_buffer(uadev[card_num].sb, ep); + if (!sgt) { + dev_err(&subs->dev->dev, + "failed to get data ep ring address\n"); + ret = -ENODEV; + goto remove_ep; + } + + pg = sg_page(sgt->sgl); + tr_pa = page_to_phys(pg); + mem_info->dma = sg_dma_address(sgt->sgl); + sg_free_table(sgt); + + /* data transfer ring */ + iova = uaudio_iommu_map_pa(MEM_XFER_RING, dma_coherent, tr_pa, + PAGE_SIZE); + if (!iova) { + ret = -ENOMEM; + goto clear_pa; + } + + mem_info->iova = PREPEND_SID_TO_IOVA(iova, uaudio_qdev->data->sid); + mem_info->size = PAGE_SIZE; + + return 0; + +clear_pa: + mem_info->dma = 0; +remove_ep: + xhci_sideband_remove_endpoint(uadev[card_num].sb, ep); +exit: + return ret; +} + +/** + * uaudio_event_ring_setup() - fetch and populate event ring params + * @subs: usb substream + * @card_num: uadev index + * @mem_info: QMI response info + * + * Register secondary interrupter to XHCI and fetch the event buffer info + * and populate the information into the QMI response. + * + */ +static int uaudio_event_ring_setup(struct snd_usb_substream *subs, + int card_num, struct mem_info_v01 *mem_info) +{ + struct sg_table *sgt; + phys_addr_t er_pa; + bool dma_coherent; + unsigned long iova; + struct page *pg; + int ret; + + dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev); + er_pa = 0; + + /* event ring */ + ret = xhci_sideband_create_interrupter(uadev[card_num].sb, 1, false, + 0, uaudio_qdev->data->intr_num); + if (ret < 0) { + dev_err(&subs->dev->dev, "failed to fetch interrupter\n"); + goto exit; + } + + sgt = xhci_sideband_get_event_buffer(uadev[card_num].sb); + if (!sgt) { + dev_err(&subs->dev->dev, + "failed to get event ring address\n"); + ret = -ENODEV; + goto remove_interrupter; + } + + pg = sg_page(sgt->sgl); + er_pa = page_to_phys(pg); + mem_info->dma = sg_dma_address(sgt->sgl); + sg_free_table(sgt); + + iova = uaudio_iommu_map_pa(MEM_EVENT_RING, dma_coherent, er_pa, + PAGE_SIZE); + if (!iova) { + ret = -ENOMEM; + goto clear_pa; + } + + mem_info->iova = PREPEND_SID_TO_IOVA(iova, uaudio_qdev->data->sid); + mem_info->size = PAGE_SIZE; + + return 0; + +clear_pa: + mem_info->dma = 0; +remove_interrupter: + xhci_sideband_remove_interrupter(uadev[card_num].sb); +exit: + return ret; +} + +/** + * uaudio_populate_uac_desc() - parse UAC parameters and populate QMI resp + * @subs: usb substream + * @resp: QMI response buffer + * + * Parses information specified within UAC descriptors which explain the + * sample parameters that the device expects. This information is populated + * to the QMI response sent back to the audio DSP. + * + */ +static int uaudio_populate_uac_desc(struct snd_usb_substream *subs, + struct qmi_uaudio_stream_resp_msg_v01 *resp) +{ + struct usb_interface_descriptor *altsd; + struct usb_host_interface *alts; + struct usb_interface *iface; + int protocol; + + iface = usb_ifnum_to_if(subs->dev, subs->cur_audiofmt->iface); + if (!iface) { + dev_err(&subs->dev->dev, "interface # %d does not exist\n", + subs->cur_audiofmt->iface); + return -ENODEV; + } + + alts = &iface->altsetting[subs->cur_audiofmt->altset_idx]; + altsd = get_iface_desc(alts); + protocol = altsd->bInterfaceProtocol; + + if (protocol == UAC_VERSION_1) { + struct uac1_as_header_descriptor *as; + + as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, + UAC_AS_GENERAL); + if (!as) { + dev_err(&subs->dev->dev, + "%u:%d : no UAC_AS_GENERAL desc\n", + subs->cur_audiofmt->iface, + subs->cur_audiofmt->altset_idx); + return -ENODEV; + } + + resp->data_path_delay = as->bDelay; + resp->data_path_delay_valid = 1; + + resp->usb_audio_subslot_size = subs->cur_audiofmt->fmt_sz; + resp->usb_audio_subslot_size_valid = 1; + + resp->usb_audio_spec_revision = le16_to_cpu((__force __le16)0x0100); + resp->usb_audio_spec_revision_valid = 1; + } else if (protocol == UAC_VERSION_2) { + resp->usb_audio_subslot_size = subs->cur_audiofmt->fmt_sz; + resp->usb_audio_subslot_size_valid = 1; + + resp->usb_audio_spec_revision = le16_to_cpu((__force __le16)0x0200); + resp->usb_audio_spec_revision_valid = 1; + } else if (protocol == UAC_VERSION_3) { + if (iface->intf_assoc->bFunctionSubClass == + UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0) { + dev_err(&subs->dev->dev, + "full adc is not supported\n"); + return -EINVAL; + } + + switch (le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize)) { + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16: + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16: { + resp->usb_audio_subslot_size = 0x2; + break; + } + + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24: + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24: { + resp->usb_audio_subslot_size = 0x3; + break; + } + + default: + dev_err(&subs->dev->dev, + "%d: %u: Invalid wMaxPacketSize\n", + subs->cur_audiofmt->iface, + subs->cur_audiofmt->altset_idx); + return -EINVAL; + } + resp->usb_audio_subslot_size_valid = 1; + } else { + dev_err(&subs->dev->dev, "unknown protocol version %x\n", + protocol); + return -ENODEV; + } + + memcpy(&resp->std_as_opr_intf_desc, &alts->desc, sizeof(alts->desc)); + + return 0; +} + +/** + * prepare_qmi_response() - prepare stream enable response + * @subs: usb substream + * @req_msg: QMI request message + * @resp: QMI response buffer + * @info_idx: usb interface array index + * + * Prepares the QMI response for a USB QMI stream enable request. Will parse + * out the parameters within the stream enable request, in order to match + * requested audio profile to the ones exposed by the USB device connected. + * + * In addition, will fetch the XHCI transfer resources needed for the handoff to + * happen. This includes, transfer ring and buffer addresses and secondary event + * ring address. These parameters will be communicated as part of the USB QMI + * stream enable response. + * + */ +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 q6usb_offload *data; + int pcm_dev_num; + int card_num; + void *xfer_buf_cpu; + int ret; + + pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8; + card_num = (req_msg->usb_token & QMI_STREAM_REQ_CARD_NUM_MASK) >> 16; + + if (!uadev[card_num].ctrl_intf) { + dev_err(&subs->dev->dev, "audio ctrl intf info not cached\n"); + return -ENODEV; + } + + ret = uaudio_populate_uac_desc(subs, resp); + if (ret < 0) + return ret; + + resp->slot_id = subs->dev->slot_id; + resp->slot_id_valid = 1; + + data = snd_soc_usb_find_priv_data(uaudio_qdev->auxdev->dev.parent); + if (!data) { + dev_err(&subs->dev->dev, "No private data found\n"); + return -ENODEV; + } + + uaudio_qdev->data = data; + + resp->std_as_opr_intf_desc_valid = 1; + ret = uaudio_endpoint_setup(subs, subs->data_endpoint, card_num, + &resp->xhci_mem_info.tr_data, + &resp->std_as_data_ep_desc); + if (ret < 0) + return ret; + + resp->std_as_data_ep_desc_valid = 1; + + if (subs->sync_endpoint) { + ret = uaudio_endpoint_setup(subs, subs->sync_endpoint, card_num, + &resp->xhci_mem_info.tr_sync, + &resp->std_as_sync_ep_desc); + if (ret < 0) + goto drop_data_ep; + + resp->std_as_sync_ep_desc_valid = 1; + } + + resp->interrupter_num_valid = 1; + resp->controller_num_valid = 0; + ret = usb_get_controller_id(subs->dev); + if (ret >= 0) { + resp->controller_num = ret; + resp->controller_num_valid = 1; + } + + /* event ring */ + ret = uaudio_event_ring_setup(subs, card_num, + &resp->xhci_mem_info.evt_ring); + if (ret < 0) + goto drop_sync_ep; + + uaudio_qdev->er_mapped = true; + resp->interrupter_num = xhci_sideband_interrupter_id(uadev[card_num].sb); + + resp->speed_info = get_speed_info(subs->dev->speed); + if (resp->speed_info == USB_QMI_DEVICE_SPEED_INVALID_V01) { + ret = -ENODEV; + goto free_sec_ring; + } + + resp->speed_info_valid = 1; + + ret = uaudio_transfer_buffer_setup(subs, &xfer_buf_cpu, req_msg->xfer_buff_size, + &resp->xhci_mem_info.xfer_buff); + if (ret < 0) { + ret = -ENOMEM; + goto free_sec_ring; + } + + 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 = kcalloc(uadev[card_num].num_intf, + sizeof(struct intf_info), + GFP_KERNEL); + if (!uadev[card_num].info) { + ret = -ENOMEM; + goto unmap_er; + } + uadev[card_num].udev = subs->dev; + atomic_set(&uadev[card_num].in_use, 1); + } else { + kref_get(&uadev[card_num].kref); + } + + uadev[card_num].usb_core_id = resp->controller_num; + + /* cache intf specific info to use it for unmap and free xfer buf */ + uadev[card_num].info[info_idx].data_xfer_ring_va = + IOVA_MASK(resp->xhci_mem_info.tr_data.iova); + uadev[card_num].info[info_idx].data_xfer_ring_size = PAGE_SIZE; + uadev[card_num].info[info_idx].sync_xfer_ring_va = + IOVA_MASK(resp->xhci_mem_info.tr_sync.iova); + uadev[card_num].info[info_idx].sync_xfer_ring_size = PAGE_SIZE; + uadev[card_num].info[info_idx].xfer_buf_iova = + IOVA_MASK(resp->xhci_mem_info.xfer_buff.iova); + uadev[card_num].info[info_idx].xfer_buf_dma = + resp->xhci_mem_info.xfer_buff.dma; + uadev[card_num].info[info_idx].xfer_buf_size = + resp->xhci_mem_info.xfer_buff.size; + uadev[card_num].info[info_idx].data_ep_pipe = subs->data_endpoint ? + subs->data_endpoint->pipe : 0; + uadev[card_num].info[info_idx].sync_ep_pipe = subs->sync_endpoint ? + subs->sync_endpoint->pipe : 0; + uadev[card_num].info[info_idx].data_ep_idx = subs->data_endpoint ? + subs->data_endpoint->ep_num : 0; + uadev[card_num].info[info_idx].sync_ep_idx = subs->sync_endpoint ? + subs->sync_endpoint->ep_num : 0; + uadev[card_num].info[info_idx].xfer_buf_cpu = xfer_buf_cpu; + 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->cur_audiofmt->iface; + uadev[card_num].info[info_idx].in_use = true; + + set_bit(card_num, &uaudio_qdev->card_slot); + + return 0; + +unmap_er: + uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE, PAGE_SIZE); +free_sec_ring: + xhci_sideband_remove_interrupter(uadev[card_num].sb); +drop_sync_ep: + if (subs->sync_endpoint) { + uaudio_iommu_unmap(MEM_XFER_RING, + IOVA_MASK(resp->xhci_mem_info.tr_sync.iova), + PAGE_SIZE, PAGE_SIZE); + xhci_sideband_remove_endpoint(uadev[card_num].sb, + usb_pipe_endpoint(subs->dev, subs->sync_endpoint->pipe)); + } +drop_data_ep: + uaudio_iommu_unmap(MEM_XFER_RING, IOVA_MASK(resp->xhci_mem_info.tr_data.iova), + PAGE_SIZE, PAGE_SIZE); + xhci_sideband_remove_endpoint(uadev[card_num].sb, + usb_pipe_endpoint(subs->dev, subs->data_endpoint->pipe)); + + return ret; +} + +/** + * handle_uaudio_stream_req() - handle stream enable/disable request + * @handle: QMI client handle + * @sq: qrtr socket + * @txn: QMI transaction context + * @decoded_msg: decoded QMI message + * + * Main handler for the QMI stream enable/disable requests. This executes the + * corresponding enable/disable stream apis, respectively. + * + */ +static void handle_uaudio_stream_req(struct qmi_handle *handle, + struct sockaddr_qrtr *sq, + struct qmi_txn *txn, + const void *decoded_msg) +{ + struct qmi_uaudio_stream_req_msg_v01 *req_msg; + struct qmi_uaudio_stream_resp_msg_v01 resp = {{0}, 0}; + struct uaudio_qmi_svc *svc = uaudio_svc; + struct snd_usb_audio *chip = NULL; + struct snd_usb_substream *subs; + struct usb_host_endpoint *ep; + int datainterval = -EINVAL; + int info_idx = -EINVAL; + struct intf_info *info; + u8 pcm_card_num; + u8 pcm_dev_num; + u8 direction; + int ret = 0; + + if (!svc->client_connected) { + svc->client_sq = *sq; + svc->client_connected = true; + } + + mutex_lock(&qdev_mutex); + req_msg = (struct qmi_uaudio_stream_req_msg_v01 *)decoded_msg; + if (!req_msg->audio_format_valid || !req_msg->bit_rate_valid || + !req_msg->number_of_ch_valid || !req_msg->xfer_buff_size_valid) { + ret = -EINVAL; + goto response; + } + + if (!uaudio_qdev) { + ret = -EINVAL; + goto response; + } + + direction = (req_msg->usb_token & QMI_STREAM_REQ_DIRECTION); + pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8; + pcm_card_num = (req_msg->usb_token & QMI_STREAM_REQ_CARD_NUM_MASK) >> 16; + if (pcm_card_num >= SNDRV_CARDS) { + ret = -EINVAL; + goto response; + } + + if (req_msg->audio_format > USB_QMI_PCM_FORMAT_U32_BE) { + ret = -EINVAL; + goto response; + } + + subs = find_substream(pcm_card_num, pcm_dev_num, direction); + chip = uadev[pcm_card_num].chip; + if (!subs || !chip || atomic_read(&chip->shutdown)) { + ret = -ENODEV; + goto response; + } + + info_idx = info_idx_from_ifnum(pcm_card_num, subs->cur_audiofmt ? + subs->cur_audiofmt->iface : -1, req_msg->enable); + if (atomic_read(&chip->shutdown) || !subs->stream || !subs->stream->pcm || + !subs->stream->chip) { + ret = -ENODEV; + goto response; + } + + scoped_guard(mutex, &chip->mutex) { + if (req_msg->enable) { + if (info_idx < 0 || chip->system_suspend || subs->opened) { + ret = -EBUSY; + goto response; + } + subs->opened = 1; + } + } + + if (req_msg->service_interval_valid) { + ret = get_data_interval_from_si(subs, + req_msg->service_interval); + if (ret == -EINVAL) + goto response; + + datainterval = ret; + } + + uadev[pcm_card_num].ctrl_intf = chip->ctrl_intf; + + if (req_msg->enable) { + ret = enable_audio_stream(subs, + map_pcm_format(req_msg->audio_format), + req_msg->number_of_ch, req_msg->bit_rate, + datainterval); + + if (!ret) + ret = prepare_qmi_response(subs, req_msg, &resp, + info_idx); + if (ret < 0) { + guard(mutex)(&chip->mutex); + subs->opened = 0; + } + } else { + info = &uadev[pcm_card_num].info[info_idx]; + if (info->data_ep_pipe) { + ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, + info->data_ep_pipe); + if (ep) { + xhci_sideband_stop_endpoint(uadev[pcm_card_num].sb, + ep); + xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, + ep); + } + + info->data_ep_pipe = 0; + } + + if (info->sync_ep_pipe) { + ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, + info->sync_ep_pipe); + if (ep) { + xhci_sideband_stop_endpoint(uadev[pcm_card_num].sb, + ep); + xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, + ep); + } + + info->sync_ep_pipe = 0; + } + + disable_audio_stream(subs); + guard(mutex)(&chip->mutex); + subs->opened = 0; + } + +response: + if (!req_msg->enable && ret != -EINVAL && ret != -ENODEV) { + guard(mutex)(&chip->mutex); + if (info_idx >= 0) { + info = &uadev[pcm_card_num].info[info_idx]; + uaudio_dev_intf_cleanup(uadev[pcm_card_num].udev, + info); + } + if (atomic_read(&uadev[pcm_card_num].in_use)) + kref_put(&uadev[pcm_card_num].kref, + uaudio_dev_release); + } + mutex_unlock(&qdev_mutex); + + 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_QMI_STREAM_REQ_FAILURE_V01 : ret; + resp.status_valid = 1; + ret = qmi_send_response(svc->uaudio_svc_hdl, sq, txn, + QMI_UAUDIO_STREAM_RESP_V01, + QMI_UAUDIO_STREAM_RESP_MSG_V01_MAX_MSG_LEN, + qmi_uaudio_stream_resp_msg_v01_ei, &resp); +} + +static struct qmi_msg_handler uaudio_stream_req_handlers = { + .type = QMI_REQUEST, + .msg_id = QMI_UAUDIO_STREAM_REQ_V01, + .ei = qmi_uaudio_stream_req_msg_v01_ei, + .decoded_size = QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN, + .fn = handle_uaudio_stream_req, +}; + +/** + * qc_usb_audio_offload_init_qmi_dev() - initializes qmi dev + * + * Initializes the USB qdev, which is used to carry information pertaining to + * the offloading resources. This device is freed only when there are no longer + * any offloading candidates. (i.e, when all audio devices are disconnected) + * + */ +static int qc_usb_audio_offload_init_qmi_dev(void) +{ + uaudio_qdev = kzalloc(sizeof(*uaudio_qdev), GFP_KERNEL); + if (!uaudio_qdev) + return -ENOMEM; + + /* initialize xfer ring and xfer buf iova list */ + 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; +} + +/* Populates ppcm_idx array with supported PCM indexes */ +static int qc_usb_audio_offload_fill_avail_pcms(struct snd_usb_audio *chip, + struct snd_soc_usb_device *sdev) +{ + struct snd_usb_stream *as; + struct snd_usb_substream *subs; + int idx = 0; + + list_for_each_entry(as, &chip->pcm_list, list) { + subs = &as->substream[SNDRV_PCM_STREAM_PLAYBACK]; + if (subs->ep_num) { + sdev->ppcm_idx[idx] = as->pcm->device; + idx++; + } + /* + * Break if the current index exceeds the number of possible + * playback streams counted from the UAC descriptors. + */ + if (idx >= sdev->num_playback) + break; + } + + return -1; +} + +/** + * qc_usb_audio_offload_probe() - platform op connect handler + * @chip: USB SND device + * + * Platform connect handler when a USB SND device is detected. Will + * notify SOC USB about the connection to enable the USB ASoC backend + * and populate internal USB chip array. + * + */ +static void qc_usb_audio_offload_probe(struct snd_usb_audio *chip) +{ + struct usb_interface *intf = chip->intf[chip->num_interfaces - 1]; + struct usb_interface_descriptor *altsd; + struct usb_host_interface *alts; + struct snd_soc_usb_device *sdev; + struct xhci_sideband *sb; + + /* + * If there is no priv_data, or no playback paths, the connected + * device doesn't support offloading. Avoid populating entries for + * this device. + */ + if (!snd_soc_usb_find_priv_data(uaudio_qdev->auxdev->dev.parent) || + !usb_qmi_get_pcm_num(chip, 0)) + return; + + guard(mutex)(&qdev_mutex); + guard(mutex)(&chip->mutex); + if (!uadev[chip->card->number].chip) { + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); + if (!sdev) + return; + + sb = xhci_sideband_register(intf, XHCI_SIDEBAND_VENDOR, + uaudio_sideband_notifier); + if (!sb) + goto free_sdev; + } else { + sb = uadev[chip->card->number].sb; + sdev = uadev[chip->card->number].sdev; + } + + uadev[chip->card->number].sb = sb; + uadev[chip->card->number].chip = chip; + uadev[chip->card->number].sdev = sdev; + + alts = &intf->altsetting[0]; + altsd = get_iface_desc(alts); + + /* Wait until all PCM devices are populated before notifying soc-usb */ + if (altsd->bInterfaceNumber == chip->last_iface) { + sdev->num_playback = usb_qmi_get_pcm_num(chip, 0); + + /* + * Allocate playback pcm index array based on number of possible + * playback paths within the UAC descriptors. + */ + sdev->ppcm_idx = kcalloc(sdev->num_playback, sizeof(unsigned int), + GFP_KERNEL); + if (!sdev->ppcm_idx) + goto unreg_xhci; + + qc_usb_audio_offload_fill_avail_pcms(chip, sdev); + sdev->card_idx = chip->card->number; + sdev->chip_idx = chip->index; + + snd_usb_offload_create_ctl(chip, uaudio_qdev->auxdev->dev.parent); + snd_soc_usb_connect(uaudio_qdev->auxdev->dev.parent, sdev); + } + + return; + +unreg_xhci: + xhci_sideband_unregister(sb); + uadev[chip->card->number].sb = NULL; +free_sdev: + kfree(sdev); + uadev[chip->card->number].sdev = NULL; + uadev[chip->card->number].chip = NULL; +} + +/** + * qc_usb_audio_cleanup_qmi_dev() - release qmi device + * + * Frees the USB qdev. Only occurs when there are no longer any potential + * devices that can utilize USB audio offloading. + * + */ +static void qc_usb_audio_cleanup_qmi_dev(void) +{ + kfree(uaudio_qdev); + uaudio_qdev = NULL; +} + +/** + * qc_usb_audio_offload_disconnect() - platform op disconnect handler + * @chip: USB SND device + * + * Platform disconnect handler. Will ensure that any pending stream is + * halted by issuing a QMI disconnect indication packet to the adsp. + * + */ +static void qc_usb_audio_offload_disconnect(struct snd_usb_audio *chip) +{ + struct uaudio_dev *dev; + int card_num; + + if (!chip) + return; + + card_num = chip->card->number; + if (card_num >= SNDRV_CARDS) + return; + + guard(mutex)(&qdev_mutex); + guard(mutex)(&chip->mutex); + dev = &uadev[card_num]; + + /* Device has already been cleaned up, or never populated */ + if (!dev->chip) + return; + + /* cleaned up already */ + if (!dev->udev) + goto done; + + uaudio_send_disconnect_ind(chip); + uaudio_dev_cleanup(dev); +done: + /* + * If num_interfaces == 1, the last USB SND interface is being removed. + * This is to accommodate for devices w/ multiple UAC functions. + */ + if (chip->num_interfaces == 1) { + snd_soc_usb_disconnect(uaudio_qdev->auxdev->dev.parent, dev->sdev); + xhci_sideband_unregister(dev->sb); + dev->chip = NULL; + kfree(dev->sdev->ppcm_idx); + kfree(dev->sdev); + dev->sdev = NULL; + } +} + +/** + * qc_usb_audio_offload_suspend() - USB offload PM suspend handler + * @intf: USB interface + * @message: suspend type + * + * PM suspend handler to ensure that the USB offloading driver is able to stop + * any pending traffic, so that the bus can be suspended. + * + */ +static void qc_usb_audio_offload_suspend(struct usb_interface *intf, + pm_message_t message) +{ + struct snd_usb_audio *chip = usb_get_intfdata(intf); + int card_num; + + if (!chip) + return; + + card_num = chip->card->number; + if (card_num >= SNDRV_CARDS) + return; + + guard(mutex)(&qdev_mutex); + guard(mutex)(&chip->mutex); + + uaudio_send_disconnect_ind(chip); +} + +static struct snd_usb_platform_ops offload_ops = { + .connect_cb = qc_usb_audio_offload_probe, + .disconnect_cb = qc_usb_audio_offload_disconnect, + .suspend_cb = qc_usb_audio_offload_suspend, +}; + +static int qc_usb_audio_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) + +{ + struct uaudio_qmi_svc *svc; + int ret; + + svc = kzalloc(sizeof(*svc), GFP_KERNEL); + if (!svc) + return -ENOMEM; + + svc->uaudio_svc_hdl = kzalloc(sizeof(*svc->uaudio_svc_hdl), GFP_KERNEL); + if (!svc->uaudio_svc_hdl) { + ret = -ENOMEM; + goto free_svc; + } + + ret = qmi_handle_init(svc->uaudio_svc_hdl, + QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN, + &uaudio_svc_ops_options, + &uaudio_stream_req_handlers); + ret = qmi_add_server(svc->uaudio_svc_hdl, UAUDIO_STREAM_SERVICE_ID_V01, + UAUDIO_STREAM_SERVICE_VERS_V01, 0); + + uaudio_svc = svc; + + qc_usb_audio_offload_init_qmi_dev(); + uaudio_qdev->auxdev = auxdev; + + ret = snd_usb_register_platform_ops(&offload_ops); + if (ret < 0) + goto release_qmi; + + snd_usb_rediscover_devices(); + + return 0; + +release_qmi: + qc_usb_audio_cleanup_qmi_dev(); + qmi_handle_release(svc->uaudio_svc_hdl); +free_svc: + kfree(svc); + + return ret; +} + +static void qc_usb_audio_remove(struct auxiliary_device *auxdev) +{ + struct uaudio_qmi_svc *svc = uaudio_svc; + int idx; + + /* + * Remove all connected devices after unregistering ops, to ensure + * that no further connect events will occur. The disconnect routine + * will issue the QMI disconnect indication, which results in the + * external DSP to stop issuing transfers. + */ + snd_usb_unregister_platform_ops(); + for (idx = 0; idx < SNDRV_CARDS; idx++) + qc_usb_audio_offload_disconnect(uadev[idx].chip); + + qc_usb_audio_cleanup_qmi_dev(); + + qmi_handle_release(svc->uaudio_svc_hdl); + kfree(svc); + uaudio_svc = NULL; +} + +static const struct auxiliary_device_id qc_usb_audio_table[] = { + { .name = "q6usb.qc-usb-audio-offload" }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, qc_usb_audio_table); + +static struct auxiliary_driver qc_usb_audio_offload_drv = { + .name = "qc-usb-audio-offload", + .id_table = qc_usb_audio_table, + .probe = qc_usb_audio_probe, + .remove = qc_usb_audio_remove, +}; +module_auxiliary_driver(qc_usb_audio_offload_drv); + +MODULE_DESCRIPTION("QC USB Audio Offloading"); +MODULE_LICENSE("GPL"); diff --git a/sound/usb/qcom/usb_audio_qmi_v01.c b/sound/usb/qcom/usb_audio_qmi_v01.c new file mode 100644 index 000000000000..502857612277 --- /dev/null +++ b/sound/usb/qcom/usb_audio_qmi_v01.c @@ -0,0 +1,863 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include <linux/soc/qcom/qmi.h> + +#include "usb_audio_qmi_v01.h" + +static const struct qmi_elem_info mem_info_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct mem_info_v01, iova), + }, + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct mem_info_v01, dma), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct mem_info_v01, size), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static const struct qmi_elem_info apps_mem_info_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct mem_info_v01), + .array_type = 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), + .array_type = 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), + .array_type = 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), + .array_type = 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), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct apps_mem_info_v01, dcba), + .ei_array = mem_info_v01_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static const struct qmi_elem_info usb_endpoint_descriptor_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u16), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bSynchAddress), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static const struct qmi_elem_info usb_interface_descriptor_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + iInterface), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +const struct qmi_elem_info qmi_uaudio_stream_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = 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(u32), + .array_type = 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(u8), + .array_type = 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(u32), + .array_type = 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(u8), + .array_type = 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(u32), + .array_type = 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(u8), + .array_type = 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(u32), + .array_type = 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(u8), + .array_type = 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(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + xfer_buff_size), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + service_interval_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + service_interval), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +const struct qmi_elem_info qmi_uaudio_stream_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = 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_qmi_audio_stream_status_enum_v01), + .array_type = 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(u8), + .array_type = 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(u32), + .array_type = 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(u8), + .array_type = 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(u32), + .array_type = 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(u8), + .array_type = 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(u32), + .array_type = 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(u8), + .array_type = 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), + .array_type = 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(u8), + .array_type = 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), + .array_type = 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(u8), + .array_type = 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), + .array_type = 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(u8), + .array_type = 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(u16), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x1B, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + interrupter_num), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x1C, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + speed_info_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum usb_qmi_audio_device_speed_enum_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x1C, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + speed_info), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x1D, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + controller_num_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x1D, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + controller_num), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +const struct qmi_elem_info qmi_uaudio_stream_ind_msg_v01_ei[] = { + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof( + enum usb_qmi_audio_device_indication_enum_v01), + .array_type = 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(u32), + .array_type = 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(u8), + .array_type = 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(u32), + .array_type = 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(u8), + .array_type = 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), + .array_type = 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(u8), + .array_type = 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), + .array_type = 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(u8), + .array_type = 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), + .array_type = 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(u8), + .array_type = 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(u16), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = 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), + .array_type = 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(u8), + .array_type = 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(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + interrupter_num), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x19, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + controller_num_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x19, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + controller_num), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; diff --git a/sound/usb/qcom/usb_audio_qmi_v01.h b/sound/usb/qcom/usb_audio_qmi_v01.h new file mode 100644 index 000000000000..a1298d75d9f8 --- /dev/null +++ b/sound/usb/qcom/usb_audio_qmi_v01.h @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#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_UAUDIO_STREAM_IND_V01 0x0001 + +struct mem_info_v01 { + u64 iova; /* mapped into sysdev */ + u64 dma; /* mapped into usb host */ + u32 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 { + u8 bLength; + u8 bDescriptorType; + u8 bEndpointAddress; + u8 bmAttributes; + u16 wMaxPacketSize; + u8 bInterval; + u8 bRefresh; + u8 bSynchAddress; +}; + +struct usb_interface_descriptor_v01 { + u8 bLength; + u8 bDescriptorType; + u8 bInterfaceNumber; + u8 bAlternateSetting; + u8 bNumEndpoints; + u8 bInterfaceClass; + u8 bInterfaceSubClass; + u8 bInterfaceProtocol; + u8 iInterface; +}; + +enum usb_qmi_audio_stream_status_enum_v01 { + USB_QMI_STREAM_STATUS_ENUM_MIN_VAL_V01 = INT_MIN, + USB_QMI_STREAM_REQ_SUCCESS_V01 = 0, + USB_QMI_STREAM_REQ_FAILURE_V01 = 1, + USB_QMI_STREAM_REQ_FAILURE_NOT_FOUND_V01 = 2, + USB_QMI_STREAM_REQ_FAILURE_INVALID_PARAM_V01 = 3, + USB_QMI_STREAM_REQ_FAILURE_MEMALLOC_V01 = 4, + USB_QMI_STREAM_STATUS_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +enum usb_qmi_audio_device_indication_enum_v01 { + USB_QMI_DEVICE_INDICATION_ENUM_MIN_VAL_V01 = INT_MIN, + USB_QMI_DEV_CONNECT_V01 = 0, + USB_QMI_DEV_DISCONNECT_V01 = 1, + USB_QMI_DEV_SUSPEND_V01 = 2, + USB_QMI_DEV_RESUME_V01 = 3, + USB_QMI_DEVICE_INDICATION_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +enum usb_qmi_audio_device_speed_enum_v01 { + USB_QMI_DEVICE_SPEED_ENUM_MIN_VAL_V01 = INT_MIN, + USB_QMI_DEVICE_SPEED_INVALID_V01 = 0, + USB_QMI_DEVICE_SPEED_LOW_V01 = 1, + USB_QMI_DEVICE_SPEED_FULL_V01 = 2, + USB_QMI_DEVICE_SPEED_HIGH_V01 = 3, + USB_QMI_DEVICE_SPEED_SUPER_V01 = 4, + USB_QMI_DEVICE_SPEED_SUPER_PLUS_V01 = 5, + USB_QMI_DEVICE_SPEED_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +struct qmi_uaudio_stream_req_msg_v01 { + u8 enable; + u32 usb_token; + u8 audio_format_valid; + u32 audio_format; + u8 number_of_ch_valid; + u32 number_of_ch; + u8 bit_rate_valid; + u32 bit_rate; + u8 xfer_buff_size_valid; + u32 xfer_buff_size; + u8 service_interval_valid; + u32 service_interval; +}; + +#define QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN 46 +extern const struct qmi_elem_info qmi_uaudio_stream_req_msg_v01_ei[]; + +struct qmi_uaudio_stream_resp_msg_v01 { + struct qmi_response_type_v01 resp; + u8 status_valid; + enum usb_qmi_audio_stream_status_enum_v01 status; + u8 internal_status_valid; + u32 internal_status; + u8 slot_id_valid; + u32 slot_id; + u8 usb_token_valid; + u32 usb_token; + u8 std_as_opr_intf_desc_valid; + struct usb_interface_descriptor_v01 std_as_opr_intf_desc; + u8 std_as_data_ep_desc_valid; + struct usb_endpoint_descriptor_v01 std_as_data_ep_desc; + u8 std_as_sync_ep_desc_valid; + struct usb_endpoint_descriptor_v01 std_as_sync_ep_desc; + u8 usb_audio_spec_revision_valid; + u16 usb_audio_spec_revision; + u8 data_path_delay_valid; + u8 data_path_delay; + u8 usb_audio_subslot_size_valid; + u8 usb_audio_subslot_size; + u8 xhci_mem_info_valid; + struct apps_mem_info_v01 xhci_mem_info; + u8 interrupter_num_valid; + u8 interrupter_num; + u8 speed_info_valid; + enum usb_qmi_audio_device_speed_enum_v01 speed_info; + u8 controller_num_valid; + u8 controller_num; +}; + +#define QMI_UAUDIO_STREAM_RESP_MSG_V01_MAX_MSG_LEN 202 +extern const struct qmi_elem_info qmi_uaudio_stream_resp_msg_v01_ei[]; + +struct qmi_uaudio_stream_ind_msg_v01 { + enum usb_qmi_audio_device_indication_enum_v01 dev_event; + u32 slot_id; + u8 usb_token_valid; + u32 usb_token; + u8 std_as_opr_intf_desc_valid; + struct usb_interface_descriptor_v01 std_as_opr_intf_desc; + u8 std_as_data_ep_desc_valid; + struct usb_endpoint_descriptor_v01 std_as_data_ep_desc; + u8 std_as_sync_ep_desc_valid; + struct usb_endpoint_descriptor_v01 std_as_sync_ep_desc; + u8 usb_audio_spec_revision_valid; + u16 usb_audio_spec_revision; + u8 data_path_delay_valid; + u8 data_path_delay; + u8 usb_audio_subslot_size_valid; + u8 usb_audio_subslot_size; + u8 xhci_mem_info_valid; + struct apps_mem_info_v01 xhci_mem_info; + u8 interrupter_num_valid; + u8 interrupter_num; + u8 controller_num_valid; + u8 controller_num; +}; + +#define QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN 181 +extern const struct qmi_elem_info qmi_uaudio_stream_ind_msg_v01_ei[]; + +#endif diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index b345beb447bd..eafc0d73cca1 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -1,23 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * ALSA USB Audio Driver * * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>, * Clemens Ladisch <clemens@ladisch.de> - * - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* @@ -39,10 +25,97 @@ .idProduct = prod, \ .bInterfaceClass = USB_CLASS_VENDOR_SPEC +/* A standard entry matching with vid/pid and the audio class/subclass */ +#define USB_AUDIO_DEVICE(vend, prod) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \ + USB_DEVICE_ID_MATCH_INT_CLASS | \ + USB_DEVICE_ID_MATCH_INT_SUBCLASS, \ + .idVendor = vend, \ + .idProduct = prod, \ + .bInterfaceClass = USB_CLASS_AUDIO, \ + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL + +/* Quirk .driver_info, followed by the definition of the quirk entry; + * put like QUIRK_DRIVER_INFO { ... } in each entry of the quirk table + */ +#define QUIRK_DRIVER_INFO \ + .driver_info = (unsigned long)&(const struct snd_usb_audio_quirk) + +/* + * Macros for quirk data entries + */ + +/* Quirk data entry for ignoring the interface */ +#define QUIRK_DATA_IGNORE(_ifno) \ + .ifnum = (_ifno), .type = QUIRK_IGNORE_INTERFACE +/* Quirk data entry for a standard audio interface */ +#define QUIRK_DATA_STANDARD_AUDIO(_ifno) \ + .ifnum = (_ifno), .type = QUIRK_AUDIO_STANDARD_INTERFACE +/* Quirk data entry for a standard MIDI interface */ +#define QUIRK_DATA_STANDARD_MIDI(_ifno) \ + .ifnum = (_ifno), .type = QUIRK_MIDI_STANDARD_INTERFACE +/* Quirk data entry for a standard mixer interface */ +#define QUIRK_DATA_STANDARD_MIXER(_ifno) \ + .ifnum = (_ifno), .type = QUIRK_AUDIO_STANDARD_MIXER + +/* Quirk data entry for Yamaha MIDI */ +#define QUIRK_DATA_MIDI_YAMAHA(_ifno) \ + .ifnum = (_ifno), .type = QUIRK_MIDI_YAMAHA +/* Quirk data entry for Edirol UAxx */ +#define QUIRK_DATA_EDIROL_UAXX(_ifno) \ + .ifnum = (_ifno), .type = QUIRK_AUDIO_EDIROL_UAXX +/* Quirk data entry for raw bytes interface */ +#define QUIRK_DATA_RAW_BYTES(_ifno) \ + .ifnum = (_ifno), .type = QUIRK_MIDI_RAW_BYTES + +/* Quirk composite array terminator */ +#define QUIRK_COMPOSITE_END { .ifnum = -1 } + +/* Quirk data entry for composite quirks; + * followed by the quirk array that is terminated with QUIRK_COMPOSITE_END + * e.g. QUIRK_DATA_COMPOSITE { { quirk1 }, { quirk2 },..., QUIRK_COMPOSITE_END } + */ +#define QUIRK_DATA_COMPOSITE \ + .ifnum = QUIRK_ANY_INTERFACE, \ + .type = QUIRK_COMPOSITE, \ + .data = &(const struct snd_usb_audio_quirk[]) + +/* Quirk data entry for a fixed audio endpoint; + * followed by audioformat definition + * e.g. QUIRK_DATA_AUDIOFORMAT(n) { .formats = xxx, ... } + */ +#define QUIRK_DATA_AUDIOFORMAT(_ifno) \ + .ifnum = (_ifno), \ + .type = QUIRK_AUDIO_FIXED_ENDPOINT, \ + .data = &(const struct audioformat) + +/* Quirk data entry for a fixed MIDI endpoint; + * followed by snd_usb_midi_endpoint_info definition + * e.g. QUIRK_DATA_MIDI_FIXED_ENDPOINT(n) { .out_cables = x, .in_cables = y } + */ +#define QUIRK_DATA_MIDI_FIXED_ENDPOINT(_ifno) \ + .ifnum = (_ifno), \ + .type = QUIRK_MIDI_FIXED_ENDPOINT, \ + .data = &(const struct snd_usb_midi_endpoint_info) +/* Quirk data entry for a MIDIMAN MIDI endpoint */ +#define QUIRK_DATA_MIDI_MIDIMAN(_ifno) \ + .ifnum = (_ifno), \ + .type = QUIRK_MIDI_MIDIMAN, \ + .data = &(const struct snd_usb_midi_endpoint_info) +/* Quirk data entry for a EMAGIC MIDI endpoint */ +#define QUIRK_DATA_MIDI_EMAGIC(_ifno) \ + .ifnum = (_ifno), \ + .type = QUIRK_MIDI_EMAGIC, \ + .data = &(const struct snd_usb_midi_endpoint_info) + +/* + * Here we go... the quirk table definition begins: + */ + /* FTDI devices */ { USB_DEVICE(0x0403, 0xb8d8), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "STARR LABS", */ /* .product_name = "Starr Labs MIDI USB device", */ .ifnum = 0, @@ -53,10 +126,8 @@ { /* Creative BT-D1 */ USB_DEVICE(0x041e, 0x0005), - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DRIVER_INFO { + QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S16_LE, .channels = 2, .iface = 1, @@ -72,85 +143,69 @@ } }, -/* Creative/E-Mu devices */ -{ - USB_DEVICE(0x041e, 0x3010), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Creative Labs", - .product_name = "Sound Blaster MP3+", - .ifnum = QUIRK_NO_INTERFACE - } -}, -/* Creative/Toshiba Multimedia Center SB-0500 */ -{ - USB_DEVICE(0x041e, 0x3048), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Toshiba", - .product_name = "SB-0500", - .ifnum = QUIRK_NO_INTERFACE - } -}, -{ - /* E-Mu 0202 USB */ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE, - .idVendor = 0x041e, - .idProduct = 0x3f02, - .bInterfaceClass = USB_CLASS_AUDIO, -}, -{ - /* E-Mu 0404 USB */ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE, - .idVendor = 0x041e, - .idProduct = 0x3f04, - .bInterfaceClass = USB_CLASS_AUDIO, -}, -{ - /* E-Mu Tracker Pre */ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE, - .idVendor = 0x041e, - .idProduct = 0x3f0a, - .bInterfaceClass = USB_CLASS_AUDIO, -}, +/* E-Mu 0202 USB */ +{ USB_DEVICE_VENDOR_SPEC(0x041e, 0x3f02) }, +/* E-Mu 0404 USB */ +{ USB_DEVICE_VENDOR_SPEC(0x041e, 0x3f04) }, +/* E-Mu Tracker Pre */ +{ USB_DEVICE_VENDOR_SPEC(0x041e, 0x3f0a) }, +/* E-Mu 0204 USB */ +{ USB_DEVICE_VENDOR_SPEC(0x041e, 0x3f19) }, +/* Ktmicro Usb_audio device */ +{ USB_DEVICE_VENDOR_SPEC(0x31b2, 0x0011) }, + +/* + * Creative Technology, Ltd Live! Cam Sync HD [VF0770] + * The device advertises 8 formats, but only a rate of 48kHz is honored by the + * hardware and 24 bits give chopped audio, so only report the one working + * combination. + */ { - /* E-Mu 0204 USB */ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE, - .idVendor = 0x041e, - .idProduct = 0x3f19, - .bInterfaceClass = USB_CLASS_AUDIO, + USB_AUDIO_DEVICE(0x041e, 0x4095), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(2) }, + { + QUIRK_DATA_AUDIOFORMAT(3) { + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels = 2, + .fmt_bits = 16, + .iface = 3, + .altsetting = 4, + .altset_idx = 4, + .endpoint = 0x82, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 }, + }, + }, + QUIRK_COMPOSITE_END + }, + }, }, /* * HP Wireless Audio * When not ignored, causes instability issues for some users, forcing them to - * blacklist the entire module. + * skip the entire module. */ { USB_DEVICE(0x0424, 0xb832), - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Standard Microsystems Corp.", .product_name = "HP Wireless Audio", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { + QUIRK_DATA_COMPOSITE { /* Mixer */ - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE, - }, + { QUIRK_DATA_IGNORE(0) }, /* Playback */ - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE, - }, + { QUIRK_DATA_IGNORE(1) }, /* Capture */ - { - .ifnum = 2, - .type = QUIRK_IGNORE_INTERFACE, - }, + { QUIRK_DATA_IGNORE(2) }, /* HID Device, .ifnum = 3 */ - { - .ifnum = -1, - } + QUIRK_COMPOSITE_END } } }, @@ -159,74 +214,13 @@ * Logitech QuickCam: bDeviceClass is vendor-specific, so generic interface * class matches do not take effect without an explicit ID match. */ -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .idVendor = 0x046d, - .idProduct = 0x0850, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL -}, -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .idVendor = 0x046d, - .idProduct = 0x08ae, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL -}, -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .idVendor = 0x046d, - .idProduct = 0x08c6, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL -}, -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .idVendor = 0x046d, - .idProduct = 0x08f0, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL -}, -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .idVendor = 0x046d, - .idProduct = 0x08f5, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL -}, -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .idVendor = 0x046d, - .idProduct = 0x08f6, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL -}, -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .idVendor = 0x046d, - .idProduct = 0x0990, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Logitech, Inc.", - .product_name = "QuickCam Pro 9000", - .ifnum = QUIRK_NO_INTERFACE - } -}, +{ USB_AUDIO_DEVICE(0x046d, 0x0850) }, +{ USB_AUDIO_DEVICE(0x046d, 0x08ae) }, +{ USB_AUDIO_DEVICE(0x046d, 0x08c6) }, +{ USB_AUDIO_DEVICE(0x046d, 0x08f0) }, +{ USB_AUDIO_DEVICE(0x046d, 0x08f5) }, +{ USB_AUDIO_DEVICE(0x046d, 0x08f6) }, +{ USB_AUDIO_DEVICE(0x046d, 0x0990) }, /* * Yamaha devices @@ -234,20 +228,18 @@ #define YAMAHA_DEVICE(id, name) { \ USB_DEVICE(0x0499, id), \ - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \ + QUIRK_DRIVER_INFO { \ .vendor_name = "Yamaha", \ .product_name = name, \ - .ifnum = QUIRK_ANY_INTERFACE, \ - .type = QUIRK_MIDI_YAMAHA \ + QUIRK_DATA_MIDI_YAMAHA(QUIRK_ANY_INTERFACE) \ } \ } #define YAMAHA_INTERFACE(id, intf, name) { \ USB_DEVICE_VENDOR_SPEC(0x0499, id), \ - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \ + QUIRK_DRIVER_INFO { \ .vendor_name = "Yamaha", \ .product_name = name, \ - .ifnum = intf, \ - .type = QUIRK_MIDI_YAMAHA \ + QUIRK_DATA_MIDI_YAMAHA(intf) \ } \ } YAMAHA_DEVICE(0x1000, "UX256"), @@ -334,135 +326,80 @@ YAMAHA_DEVICE(0x105c, NULL), YAMAHA_DEVICE(0x105d, NULL), { USB_DEVICE(0x0499, 0x1503), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "Yamaha", */ /* .product_name = "MOX6/MOX8", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_MIDI_YAMAHA - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + { QUIRK_DATA_MIDI_YAMAHA(3) }, + QUIRK_COMPOSITE_END } } }, { USB_DEVICE(0x0499, 0x1507), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "Yamaha", */ /* .product_name = "THR10", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_MIDI_YAMAHA - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + { QUIRK_DATA_MIDI_YAMAHA(3) }, + QUIRK_COMPOSITE_END } } }, { USB_DEVICE(0x0499, 0x1509), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "Yamaha", */ /* .product_name = "Steinberg UR22", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_MIDI_YAMAHA - }, - { - .ifnum = 4, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + { QUIRK_DATA_MIDI_YAMAHA(3) }, + { QUIRK_DATA_IGNORE(4) }, + QUIRK_COMPOSITE_END } } }, { USB_DEVICE(0x0499, 0x150a), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "Yamaha", */ /* .product_name = "THR5A", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_MIDI_YAMAHA - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + { QUIRK_DATA_MIDI_YAMAHA(3) }, + QUIRK_COMPOSITE_END } } }, { USB_DEVICE(0x0499, 0x150c), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "Yamaha", */ /* .product_name = "THR10C", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_MIDI_YAMAHA - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + { QUIRK_DATA_MIDI_YAMAHA(3) }, + QUIRK_COMPOSITE_END + } + } +}, +{ + USB_DEVICE(0x0499, 0x1718), + QUIRK_DRIVER_INFO { + /* .vendor_name = "Yamaha", */ + /* .product_name = "P-125", */ + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + { QUIRK_DATA_MIDI_YAMAHA(3) }, + QUIRK_COMPOSITE_END } } }, @@ -496,7 +433,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), USB_DEVICE_ID_MATCH_INT_CLASS, .idVendor = 0x0499, .bInterfaceClass = USB_CLASS_VENDOR_SPEC, - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .ifnum = QUIRK_ANY_INTERFACE, .type = QUIRK_AUTODETECT } @@ -507,16 +444,12 @@ YAMAHA_DEVICE(0x7010, "UB99"), */ { USB_DEVICE(0x0582, 0x0000), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "UA-100", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { + QUIRK_DATA_COMPOSITE { { - .ifnum = 0, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(0) { .formats = SNDRV_PCM_FMTBIT_S16_LE, .channels = 4, .iface = 0, @@ -531,9 +464,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S16_LE, .channels = 2, .iface = 1, @@ -548,106 +479,66 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0007, .in_cables = 0x0007 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { USB_DEVICE(0x0582, 0x0002), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "UM-4", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_IGNORE(1) }, { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x000f, .in_cables = 0x000f } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { USB_DEVICE(0x0582, 0x0003), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "SC-8850", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_IGNORE(1) }, { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x003f, .in_cables = 0x003f } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { USB_DEVICE(0x0582, 0x0004), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "U-8", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_IGNORE(1) }, { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0005, .in_cables = 0x0005 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, @@ -655,152 +546,92 @@ YAMAHA_DEVICE(0x7010, "UB99"), /* Has ID 0x0099 when not in "Advanced Driver" mode. * The UM-2EX has only one input, but we cannot detect this. */ USB_DEVICE(0x0582, 0x0005), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "UM-2", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_IGNORE(1) }, { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0003, .in_cables = 0x0003 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { USB_DEVICE(0x0582, 0x0007), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "SC-8820", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_IGNORE(1) }, { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0013, .in_cables = 0x0013 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { USB_DEVICE(0x0582, 0x0008), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "PC-300", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_IGNORE(1) }, { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0001, .in_cables = 0x0001 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { /* has ID 0x009d when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0009), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "UM-1", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_IGNORE(1) }, { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0001, .in_cables = 0x0001 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { USB_DEVICE(0x0582, 0x000b), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "SK-500", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_IGNORE(1) }, { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0013, .in_cables = 0x0013 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, @@ -808,31 +639,19 @@ YAMAHA_DEVICE(0x7010, "UB99"), /* thanks to Emiliano Grilli <emillo@libero.it> * for helping researching this data */ USB_DEVICE(0x0582, 0x000c), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "SC-D70", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(0) }, + { QUIRK_DATA_STANDARD_AUDIO(1) }, { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0007, .in_cables = 0x0007 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, @@ -846,35 +665,23 @@ YAMAHA_DEVICE(0x7010, "UB99"), * the 96kHz sample rate. */ USB_DEVICE(0x0582, 0x0010), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "UA-5", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + QUIRK_COMPOSITE_END } } }, { /* has ID 0x0013 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0012), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "XV-5050", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -883,12 +690,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* has ID 0x0015 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0014), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "UM-880", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x01ff, .in_cables = 0x01ff } @@ -897,74 +702,48 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* has ID 0x0017 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0016), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "SD-90", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(0) }, + { QUIRK_DATA_STANDARD_AUDIO(1) }, { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x000f, .in_cables = 0x000f } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { /* has ID 0x001c when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x001b), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "MMP-2", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_IGNORE(1) }, { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0001, .in_cables = 0x0001 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { /* has ID 0x001e when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x001d), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "V-SYNTH", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -973,12 +752,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* has ID 0x0024 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0023), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "UM-550", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x003f, .in_cables = 0x003f } @@ -991,20 +768,13 @@ YAMAHA_DEVICE(0x7010, "UB99"), * and no MIDI. */ USB_DEVICE(0x0582, 0x0025), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "UA-20", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 2, .iface = 1, @@ -1019,9 +789,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - .ifnum = 2, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(2) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 2, .iface = 2, @@ -1036,28 +804,22 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - .ifnum = 3, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(3) { .out_cables = 0x0001, .in_cables = 0x0001 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { /* has ID 0x0028 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0027), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "SD-20", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0003, .in_cables = 0x0007 } @@ -1066,12 +828,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* has ID 0x002a when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0029), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "SD-80", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x000f, .in_cables = 0x000f } @@ -1084,39 +844,24 @@ YAMAHA_DEVICE(0x7010, "UB99"), * but offers only 16-bit PCM and no MIDI. */ USB_DEVICE_VENDOR_SPEC(0x0582, 0x002b), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "UA-700", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 3, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_EDIROL_UAXX(1) }, + { QUIRK_DATA_EDIROL_UAXX(2) }, + { QUIRK_DATA_EDIROL_UAXX(3) }, + QUIRK_COMPOSITE_END } } }, { /* has ID 0x002e when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x002d), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "XV-2020", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -1125,12 +870,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* has ID 0x0030 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x002f), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "VariOS", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0007, .in_cables = 0x0007 } @@ -1139,12 +882,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* has ID 0x0034 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0033), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "PCR", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0003, .in_cables = 0x0007 } @@ -1156,12 +897,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), * later revisions use IDs 0x0054 and 0x00a2. */ USB_DEVICE(0x0582, 0x0037), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "Digital Piano", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -1174,39 +913,24 @@ YAMAHA_DEVICE(0x7010, "UB99"), * and no MIDI. */ USB_DEVICE_VENDOR_SPEC(0x0582, 0x003b), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "BOSS", .product_name = "GS-10", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_MIDI_STANDARD_INTERFACE - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + { QUIRK_DATA_STANDARD_MIDI(3) }, + QUIRK_COMPOSITE_END } } }, { /* has ID 0x0041 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0040), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "GI-20", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -1215,12 +939,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* has ID 0x0043 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0042), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "RS-70", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -1229,36 +951,24 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* has ID 0x0049 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0047), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "EDIROL", */ /* .product_name = "UR-80", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { + QUIRK_DATA_COMPOSITE { /* in the 96 kHz modes, only interface 1 is there */ - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = -1 - } + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + QUIRK_COMPOSITE_END } } }, { /* has ID 0x004a when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0048), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "EDIROL", */ /* .product_name = "UR-80", */ - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0003, .in_cables = 0x0007 } @@ -1267,35 +977,23 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* has ID 0x004e when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x004c), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "PCR-A", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + QUIRK_COMPOSITE_END } } }, { /* has ID 0x004f when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x004d), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "PCR-A", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0003, .in_cables = 0x0007 } @@ -1307,76 +1005,52 @@ YAMAHA_DEVICE(0x7010, "UB99"), * is standard compliant, but has only 16-bit PCM. */ USB_DEVICE(0x0582, 0x0050), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "UA-3FX", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + QUIRK_COMPOSITE_END } } }, { USB_DEVICE(0x0582, 0x0052), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "UM-1SX", - .ifnum = 0, - .type = QUIRK_MIDI_STANDARD_INTERFACE + QUIRK_DATA_STANDARD_MIDI(0) } }, { USB_DEVICE(0x0582, 0x0060), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "EXR Series", - .ifnum = 0, - .type = QUIRK_MIDI_STANDARD_INTERFACE + QUIRK_DATA_STANDARD_MIDI(0) } }, { /* has ID 0x0066 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0064), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "EDIROL", */ /* .product_name = "PCR-1", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + QUIRK_COMPOSITE_END } } }, { /* has ID 0x0067 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0065), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "EDIROL", */ /* .product_name = "PCR-1", */ - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0001, .in_cables = 0x0003 } @@ -1385,12 +1059,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* has ID 0x006e when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x006d), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "FANTOM-X", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -1403,39 +1075,24 @@ YAMAHA_DEVICE(0x7010, "UB99"), * offers only 16-bit PCM at 44.1 kHz and no MIDI. */ USB_DEVICE_VENDOR_SPEC(0x0582, 0x0074), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "UA-25", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_EDIROL_UAXX(0) }, + { QUIRK_DATA_EDIROL_UAXX(1) }, + { QUIRK_DATA_EDIROL_UAXX(2) }, + QUIRK_COMPOSITE_END } } }, { /* has ID 0x0076 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0075), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "BOSS", .product_name = "DR-880", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -1444,12 +1101,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* has ID 0x007b when not in "Advanced Driver" mode */ USB_DEVICE_VENDOR_SPEC(0x0582, 0x007a), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", /* "RD" or "RD-700SX"? */ - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0003, .in_cables = 0x0003 } @@ -1458,12 +1113,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* has ID 0x0081 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0080), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Roland", .product_name = "G-70", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -1472,12 +1125,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* has ID 0x008c when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x008b), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "PC-50", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -1489,56 +1140,31 @@ YAMAHA_DEVICE(0x7010, "UB99"), * is standard compliant, but has only 16-bit PCM and no MIDI. */ USB_DEVICE(0x0582, 0x00a3), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "UA-4FX", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_EDIROL_UAXX(0) }, + { QUIRK_DATA_EDIROL_UAXX(1) }, + { QUIRK_DATA_EDIROL_UAXX(2) }, + QUIRK_COMPOSITE_END } } }, { /* Edirol M-16DX */ USB_DEVICE(0x0582, 0x00c4), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(0) }, + { QUIRK_DATA_STANDARD_AUDIO(1) }, { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0001, .in_cables = 0x0001 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, @@ -1548,37 +1174,22 @@ YAMAHA_DEVICE(0x7010, "UB99"), * offers only 16-bit PCM at 44.1 kHz and no MIDI. */ USB_DEVICE_VENDOR_SPEC(0x0582, 0x00e6), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "EDIROL", .product_name = "UA-25EX", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_EDIROL_UAXX(0) }, + { QUIRK_DATA_EDIROL_UAXX(1) }, + { QUIRK_DATA_EDIROL_UAXX(2) }, + QUIRK_COMPOSITE_END } } }, { /* Edirol UM-3G */ USB_DEVICE_VENDOR_SPEC(0x0582, 0x0108), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DRIVER_INFO { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(0) { .out_cables = 0x0007, .in_cables = 0x0007 } @@ -1587,45 +1198,29 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* BOSS ME-25 */ USB_DEVICE(0x0582, 0x0113), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(0) }, + { QUIRK_DATA_STANDARD_AUDIO(1) }, { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0001, .in_cables = 0x0001 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { /* only 44.1 kHz works at the moment */ USB_DEVICE(0x0582, 0x0120), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "Roland", */ /* .product_name = "OCTO-CAPTURE", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { + QUIRK_DATA_COMPOSITE { { - .ifnum = 0, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(0) { .formats = SNDRV_PCM_FMTBIT_S32_LE, .channels = 10, .iface = 0, @@ -1641,9 +1236,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S32_LE, .channels = 12, .iface = 1, @@ -1659,40 +1252,26 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0001, .in_cables = 0x0001 } }, - { - .ifnum = 3, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 4, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = -1 - } + { QUIRK_DATA_IGNORE(3) }, + { QUIRK_DATA_IGNORE(4) }, + QUIRK_COMPOSITE_END } } }, { /* only 44.1 kHz works at the moment */ USB_DEVICE(0x0582, 0x012f), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "Roland", */ /* .product_name = "QUAD-CAPTURE", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { + QUIRK_DATA_COMPOSITE { { - .ifnum = 0, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(0) { .formats = SNDRV_PCM_FMTBIT_S32_LE, .channels = 4, .iface = 0, @@ -1708,9 +1287,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S32_LE, .channels = 6, .iface = 1, @@ -1726,64 +1303,63 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0001, .in_cables = 0x0001 } }, - { - .ifnum = 3, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 4, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = -1 - } + { QUIRK_DATA_IGNORE(3) }, + { QUIRK_DATA_IGNORE(4) }, + QUIRK_COMPOSITE_END } } }, { USB_DEVICE(0x0582, 0x0159), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "Roland", */ /* .product_name = "UA-22", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(0) }, + { QUIRK_DATA_STANDARD_AUDIO(1) }, { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0001, .in_cables = 0x0001 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, + +/* UA101 and co are supported by another driver */ +{ + USB_DEVICE(0x0582, 0x0044), /* UA-1000 high speed */ + QUIRK_DRIVER_INFO { + .ifnum = QUIRK_NODEV_INTERFACE + }, +}, +{ + USB_DEVICE(0x0582, 0x007d), /* UA-101 high speed */ + QUIRK_DRIVER_INFO { + .ifnum = QUIRK_NODEV_INTERFACE + }, +}, +{ + USB_DEVICE(0x0582, 0x008d), /* UA-101 full speed */ + QUIRK_DRIVER_INFO { + .ifnum = QUIRK_NODEV_INTERFACE + }, +}, + /* this catches most recent vendor-specific Roland devices */ { .match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_CLASS, .idVendor = 0x0582, .bInterfaceClass = USB_CLASS_VENDOR_SPEC, - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .ifnum = QUIRK_ANY_INTERFACE, .type = QUIRK_AUTODETECT } @@ -1798,12 +1374,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), * compliant USB MIDI ports for external MIDI and controls. */ USB_DEVICE_VENDOR_SPEC(0x06f8, 0xb000), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Hercules", .product_name = "DJ Console (WE)", - .ifnum = 4, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(4) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -1813,12 +1387,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), /* Midiman/M-Audio devices */ { USB_DEVICE_VENDOR_SPEC(0x0763, 0x1002), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "M-Audio", .product_name = "MidiSport 2x2", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_MIDIMAN(QUIRK_ANY_INTERFACE) { .out_cables = 0x0003, .in_cables = 0x0003 } @@ -1826,12 +1398,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { USB_DEVICE_VENDOR_SPEC(0x0763, 0x1011), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "M-Audio", .product_name = "MidiSport 1x1", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_MIDIMAN(QUIRK_ANY_INTERFACE) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -1839,12 +1409,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { USB_DEVICE_VENDOR_SPEC(0x0763, 0x1015), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "M-Audio", .product_name = "Keystation", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_MIDIMAN(QUIRK_ANY_INTERFACE) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -1852,12 +1420,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { USB_DEVICE_VENDOR_SPEC(0x0763, 0x1021), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "M-Audio", .product_name = "MidiSport 4x4", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_MIDIMAN(QUIRK_ANY_INTERFACE) { .out_cables = 0x000f, .in_cables = 0x000f } @@ -1870,12 +1436,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), * Thanks to Olaf Giesbrecht <Olaf_Giesbrecht@yahoo.de> */ USB_DEVICE_VER(0x0763, 0x1031, 0x0100, 0x0109), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "M-Audio", .product_name = "MidiSport 8x8", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_MIDIMAN(QUIRK_ANY_INTERFACE) { .out_cables = 0x01ff, .in_cables = 0x01ff } @@ -1883,12 +1447,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { USB_DEVICE_VENDOR_SPEC(0x0763, 0x1033), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "M-Audio", .product_name = "MidiSport 8x8", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_MIDIMAN(QUIRK_ANY_INTERFACE) { .out_cables = 0x01ff, .in_cables = 0x01ff } @@ -1896,12 +1458,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { USB_DEVICE_VENDOR_SPEC(0x0763, 0x1041), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "M-Audio", .product_name = "MidiSport 2x4", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_MIDIMAN(QUIRK_ANY_INTERFACE) { .out_cables = 0x000f, .in_cables = 0x0003 } @@ -1909,76 +1469,41 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { USB_DEVICE_VENDOR_SPEC(0x0763, 0x2001), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "M-Audio", .product_name = "Quattro", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { + QUIRK_DATA_COMPOSITE { /* * Interfaces 0-2 are "Windows-compatible", 16-bit only, * and share endpoints with the other interfaces. * Ignore them. The other interfaces can do 24 bits, * but captured samples are big-endian (see usbaudio.c). */ - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 4, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 5, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 6, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 7, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 8, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 9, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_IGNORE(1) }, + { QUIRK_DATA_IGNORE(2) }, + { QUIRK_DATA_IGNORE(3) }, + { QUIRK_DATA_STANDARD_AUDIO(4) }, + { QUIRK_DATA_STANDARD_AUDIO(5) }, + { QUIRK_DATA_IGNORE(6) }, + { QUIRK_DATA_STANDARD_AUDIO(7) }, + { QUIRK_DATA_STANDARD_AUDIO(8) }, + { + QUIRK_DATA_MIDI_MIDIMAN(9) { .out_cables = 0x0001, .in_cables = 0x0001 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { USB_DEVICE_VENDOR_SPEC(0x0763, 0x2003), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "M-Audio", .product_name = "AudioPhile", - .ifnum = 6, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_MIDIMAN(6) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -1986,12 +1511,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { USB_DEVICE_VENDOR_SPEC(0x0763, 0x2008), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "M-Audio", .product_name = "Ozone", - .ifnum = 3, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_MIDIMAN(3) { .out_cables = 0x0001, .in_cables = 0x0001 } @@ -1999,113 +1522,62 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { USB_DEVICE_VENDOR_SPEC(0x0763, 0x200d), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "M-Audio", .product_name = "OmniStudio", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 4, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 5, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 6, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 7, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 8, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 9, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_IGNORE(1) }, + { QUIRK_DATA_IGNORE(2) }, + { QUIRK_DATA_IGNORE(3) }, + { QUIRK_DATA_STANDARD_AUDIO(4) }, + { QUIRK_DATA_STANDARD_AUDIO(5) }, + { QUIRK_DATA_IGNORE(6) }, + { QUIRK_DATA_STANDARD_AUDIO(7) }, + { QUIRK_DATA_STANDARD_AUDIO(8) }, + { + QUIRK_DATA_MIDI_MIDIMAN(9) { .out_cables = 0x0001, .in_cables = 0x0001 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { USB_DEVICE(0x0763, 0x2019), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "M-Audio", */ /* .product_name = "Ozone Academic", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(0) }, + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_MIDIMAN(3) { .out_cables = 0x0001, .in_cables = 0x0001 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { + /* M-Audio Micro */ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x201a), +}, +{ USB_DEVICE_VENDOR_SPEC(0x0763, 0x2030), - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "M-Audio", */ /* .product_name = "Fast Track C400", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = &(const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_MIXER, - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(1) }, /* Playback */ { - .ifnum = 2, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(2) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 6, .iface = 2, @@ -2129,9 +1601,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, /* Capture */ { - .ifnum = 3, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(3) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 4, .iface = 3, @@ -2153,30 +1623,21 @@ YAMAHA_DEVICE(0x7010, "UB99"), .clock = 0x80, } }, - /* MIDI */ - { - .ifnum = -1 /* Interface = 4 */ - } + /* MIDI: Interface = 4*/ + QUIRK_COMPOSITE_END } } }, { USB_DEVICE_VENDOR_SPEC(0x0763, 0x2031), - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "M-Audio", */ /* .product_name = "Fast Track C600", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = &(const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_MIXER, - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(1) }, /* Playback */ { - .ifnum = 2, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(2) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 8, .iface = 2, @@ -2200,9 +1661,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, /* Capture */ { - .ifnum = 3, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(3) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 6, .iface = 3, @@ -2224,29 +1683,20 @@ YAMAHA_DEVICE(0x7010, "UB99"), .clock = 0x80, } }, - /* MIDI */ - { - .ifnum = -1 /* Interface = 4 */ - } + /* MIDI: Interface = 4 */ + QUIRK_COMPOSITE_END } } }, { USB_DEVICE_VENDOR_SPEC(0x0763, 0x2080), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "M-Audio", */ /* .product_name = "Fast Track Ultra", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_MIXER, - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(0) }, { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 8, .iface = 1, @@ -2268,9 +1718,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - .ifnum = 2, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(2) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 8, .iface = 2, @@ -2292,28 +1740,19 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, /* interface 3 (MIDI) is standard compliant */ - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { USB_DEVICE_VENDOR_SPEC(0x0763, 0x2081), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "M-Audio", */ /* .product_name = "Fast Track Ultra 8R", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_MIXER, - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(0) }, { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 8, .iface = 1, @@ -2335,9 +1774,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - .ifnum = 2, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(2) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 8, .iface = 2, @@ -2359,9 +1796,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, /* interface 3 (MIDI) is standard compliant */ - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, @@ -2369,21 +1804,19 @@ YAMAHA_DEVICE(0x7010, "UB99"), /* Casio devices */ { USB_DEVICE(0x07cf, 0x6801), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Casio", .product_name = "PL-40R", - .ifnum = 0, - .type = QUIRK_MIDI_YAMAHA + QUIRK_DATA_MIDI_YAMAHA(0) } }, { /* this ID is used by several devices without a product ID */ USB_DEVICE(0x07cf, 0x6802), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Casio", .product_name = "Keyboard", - .ifnum = 0, - .type = QUIRK_MIDI_YAMAHA + QUIRK_DATA_MIDI_YAMAHA(0) } }, @@ -2396,23 +1829,13 @@ YAMAHA_DEVICE(0x7010, "UB99"), .idVendor = 0x07fd, .idProduct = 0x0001, .bDeviceSubClass = 2, - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "MOTU", .product_name = "Fastlane", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_MIDI_RAW_BYTES - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = -1 - } + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_RAW_BYTES(0) }, + { QUIRK_DATA_IGNORE(1) }, + QUIRK_COMPOSITE_END } } }, @@ -2420,12 +1843,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), /* Emagic devices */ { USB_DEVICE(0x086a, 0x0001), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Emagic", - /* .product_name = "Unitor8", */ - .ifnum = 2, - .type = QUIRK_MIDI_EMAGIC, - .data = & (const struct snd_usb_midi_endpoint_info) { + .product_name = "Unitor8", + QUIRK_DATA_MIDI_EMAGIC(2) { .out_cables = 0x80ff, .in_cables = 0x80ff } @@ -2433,12 +1854,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { USB_DEVICE(0x086a, 0x0002), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Emagic", /* .product_name = "AMT8", */ - .ifnum = 2, - .type = QUIRK_MIDI_EMAGIC, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_EMAGIC(2) { .out_cables = 0x80ff, .in_cables = 0x80ff } @@ -2446,12 +1865,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { USB_DEVICE(0x086a, 0x0003), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Emagic", /* .product_name = "MT4", */ - .ifnum = 2, - .type = QUIRK_MIDI_EMAGIC, - .data = & (const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_EMAGIC(2) { .out_cables = 0x800f, .in_cables = 0x8003 } @@ -2461,28 +1878,35 @@ YAMAHA_DEVICE(0x7010, "UB99"), /* KORG devices */ { USB_DEVICE_VENDOR_SPEC(0x0944, 0x0200), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "KORG, Inc.", /* .product_name = "PANDORA PX5D", */ - .ifnum = 3, - .type = QUIRK_MIDI_STANDARD_INTERFACE, + QUIRK_DATA_STANDARD_MIDI(3) } }, { USB_DEVICE_VENDOR_SPEC(0x0944, 0x0201), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "KORG, Inc.", /* .product_name = "ToneLab ST", */ - .ifnum = 3, - .type = QUIRK_MIDI_STANDARD_INTERFACE, + QUIRK_DATA_STANDARD_MIDI(3) + } +}, + +{ + USB_DEVICE_VENDOR_SPEC(0x0944, 0x0204), + QUIRK_DRIVER_INFO { + .vendor_name = "KORG, Inc.", + /* .product_name = "ToneLab EX", */ + QUIRK_DATA_STANDARD_MIDI(3) } }, /* AKAI devices */ { USB_DEVICE(0x09e8, 0x0062), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "AKAI", .product_name = "MPD16", .ifnum = 0, @@ -2493,21 +1917,11 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* Akai MPC Element */ USB_DEVICE(0x09e8, 0x0021), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_MIDI_STANDARD_INTERFACE - }, - { - .ifnum = -1 - } + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_STANDARD_MIDI(1) }, + QUIRK_COMPOSITE_END } } }, @@ -2516,66 +1930,36 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* Steinberg MI2 */ USB_DEVICE_VENDOR_SPEC(0x0a4e, 0x2040), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(0) }, + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = &(const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(3) { .out_cables = 0x0001, .in_cables = 0x0001 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, { /* Steinberg MI4 */ USB_DEVICE_VENDOR_SPEC(0x0a4e, 0x4040), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(0) }, + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = &(const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(3) { .out_cables = 0x0001, .in_cables = 0x0001 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, @@ -2583,42 +1967,31 @@ YAMAHA_DEVICE(0x7010, "UB99"), /* TerraTec devices */ { USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0012), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "TerraTec", .product_name = "PHASE 26", - .ifnum = 3, - .type = QUIRK_MIDI_STANDARD_INTERFACE + QUIRK_DATA_STANDARD_MIDI(3) } }, { USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0013), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "TerraTec", .product_name = "PHASE 26", - .ifnum = 3, - .type = QUIRK_MIDI_STANDARD_INTERFACE + QUIRK_DATA_STANDARD_MIDI(3) } }, { USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0014), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "TerraTec", .product_name = "PHASE 26", - .ifnum = 3, - .type = QUIRK_MIDI_STANDARD_INTERFACE - } -}, -{ - USB_DEVICE(0x0ccd, 0x0028), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "TerraTec", - .product_name = "Aureon5.1MkII", - .ifnum = QUIRK_NO_INTERFACE + QUIRK_DATA_STANDARD_MIDI(3) } }, { USB_DEVICE(0x0ccd, 0x0035), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Miditech", .product_name = "Play'n Roll", .ifnum = 0, @@ -2626,28 +1999,14 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, -/* Stanton/N2IT Final Scratch v1 device ('Scratchamp') */ -{ - USB_DEVICE(0x103d, 0x0100), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Stanton", - .product_name = "ScratchAmp", - .ifnum = QUIRK_NO_INTERFACE - } -}, -{ - USB_DEVICE(0x103d, 0x0101), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Stanton", - .product_name = "ScratchAmp", - .ifnum = QUIRK_NO_INTERFACE - } -}, +/* Stanton ScratchAmp */ +{ USB_DEVICE(0x103d, 0x0100) }, +{ USB_DEVICE(0x103d, 0x0101) }, /* Novation EMS devices */ { USB_DEVICE_VENDOR_SPEC(0x1235, 0x0001), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Novation", .product_name = "ReMOTE Audio/XStation", .ifnum = 4, @@ -2656,7 +2015,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { USB_DEVICE_VENDOR_SPEC(0x1235, 0x0002), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Novation", .product_name = "Speedio", .ifnum = 3, @@ -2665,34 +2024,29 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { USB_DEVICE(0x1235, 0x000a), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "Novation", */ /* .product_name = "Nocturn", */ - .ifnum = 0, - .type = QUIRK_MIDI_RAW_BYTES + QUIRK_DATA_RAW_BYTES(0) } }, { USB_DEVICE(0x1235, 0x000e), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { /* .vendor_name = "Novation", */ /* .product_name = "Launchpad", */ - .ifnum = 0, - .type = QUIRK_MIDI_RAW_BYTES + QUIRK_DATA_RAW_BYTES(0) } }, { USB_DEVICE(0x1235, 0x0010), - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Focusrite", .product_name = "Saffire 6 USB", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(0) }, { - .ifnum = 0, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(0) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 4, .iface = 0, @@ -2701,6 +2055,8 @@ YAMAHA_DEVICE(0x7010, "UB99"), .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, .endpoint = 0x01, .ep_attr = USB_ENDPOINT_XFER_ISOC, + .datainterval = 1, + .maxpacksize = 0x024c, .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, .rate_min = 44100, @@ -2708,31 +2064,50 @@ YAMAHA_DEVICE(0x7010, "UB99"), .nr_rates = 2, .rate_table = (unsigned int[]) { 44100, 48000 - } + }, + .sync_ep = 0x82, + .sync_iface = 0, + .sync_altsetting = 1, + .sync_ep_idx = 1, + .implicit_fb = 1, } }, { - .ifnum = 1, - .type = QUIRK_MIDI_RAW_BYTES + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 2, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC, + .datainterval = 1, + .maxpacksize = 0x0126, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { + 44100, 48000 + } + } }, - { - .ifnum = -1 - } + { QUIRK_DATA_RAW_BYTES(1) }, + QUIRK_COMPOSITE_END } } }, { USB_DEVICE(0x1235, 0x0018), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Novation", .product_name = "Twitch", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { + QUIRK_DATA_COMPOSITE { { - .ifnum = 0, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(0) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 4, .iface = 0, @@ -2751,19 +2126,14 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, - { - .ifnum = 1, - .type = QUIRK_MIDI_RAW_BYTES - }, - { - .ifnum = -1 - } + { QUIRK_DATA_RAW_BYTES(1) }, + QUIRK_COMPOSITE_END } } }, { USB_DEVICE_VENDOR_SPEC(0x1235, 0x4661), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Novation", .product_name = "ReMOTE25", .ifnum = 0, @@ -2775,40 +2145,20 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* VirusTI Desktop */ USB_DEVICE_VENDOR_SPEC(0x133e, 0x0815), - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = &(const struct snd_usb_audio_quirk[]) { + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { { - .ifnum = 3, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = &(const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(3) { .out_cables = 0x0003, .in_cables = 0x0003 } }, - { - .ifnum = 4, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = -1 - } + { QUIRK_DATA_IGNORE(4) }, + QUIRK_COMPOSITE_END } } }, -/* */ -{ - /* aka. Serato Scratch Live DJ Box */ - USB_DEVICE(0x13e5, 0x0001), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Rane", - .product_name = "SL-1", - .ifnum = QUIRK_NO_INTERFACE - } -}, - /* Native Instruments MK2 series */ { /* Komplete Audio 6 */ @@ -2832,7 +2182,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), /* QinHeng devices */ { USB_DEVICE(0x1a86, 0x752d), - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "QinHeng", .product_name = "CH345", .ifnum = 1, @@ -2841,15 +2191,12 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, /* KeithMcMillen Stringport */ -{ - USB_DEVICE(0x1f38, 0x0001), - .bInterfaceClass = USB_CLASS_AUDIO, -}, +{ USB_DEVICE(0x1f38, 0x0001) }, /* FIXME: should be more restrictive matching */ /* Miditech devices */ { USB_DEVICE(0x4752, 0x0011), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Miditech", .product_name = "Midistart-2", .ifnum = 0, @@ -2861,88 +2208,23 @@ YAMAHA_DEVICE(0x7010, "UB99"), { /* this ID used by both Miditech MidiStudio-2 and CME UF-x */ USB_DEVICE(0x7104, 0x2202), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .ifnum = 0, .type = QUIRK_MIDI_CME } }, -/* - * Auvitek au0828 devices with audio interface. - * This should be kept in sync with drivers/media/usb/au0828/au0828-cards.c - * Please notice that some drivers are DVB only, and don't need to be - * here. That's the case, for example, of DVICO_FUSIONHDTV7. - */ - -#define AU0828_DEVICE(vid, pid, vname, pname) { \ - .idVendor = vid, \ - .idProduct = pid, \ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \ - USB_DEVICE_ID_MATCH_INT_CLASS | \ - USB_DEVICE_ID_MATCH_INT_SUBCLASS, \ - .bInterfaceClass = USB_CLASS_AUDIO, \ - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, \ - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { \ - .vendor_name = vname, \ - .product_name = pname, \ - .ifnum = QUIRK_ANY_INTERFACE, \ - .type = QUIRK_AUDIO_ALIGN_TRANSFER, \ - } \ -} - -AU0828_DEVICE(0x2040, 0x7200, "Hauppauge", "HVR-950Q"), -AU0828_DEVICE(0x2040, 0x7240, "Hauppauge", "HVR-850"), -AU0828_DEVICE(0x2040, 0x7210, "Hauppauge", "HVR-950Q"), -AU0828_DEVICE(0x2040, 0x7217, "Hauppauge", "HVR-950Q"), -AU0828_DEVICE(0x2040, 0x721b, "Hauppauge", "HVR-950Q"), -AU0828_DEVICE(0x2040, 0x721e, "Hauppauge", "HVR-950Q"), -AU0828_DEVICE(0x2040, 0x721f, "Hauppauge", "HVR-950Q"), -AU0828_DEVICE(0x2040, 0x7280, "Hauppauge", "HVR-950Q"), -AU0828_DEVICE(0x0fd9, 0x0008, "Hauppauge", "HVR-950Q"), -AU0828_DEVICE(0x2040, 0x7201, "Hauppauge", "HVR-950Q-MXL"), -AU0828_DEVICE(0x2040, 0x7211, "Hauppauge", "HVR-950Q-MXL"), -AU0828_DEVICE(0x2040, 0x7281, "Hauppauge", "HVR-950Q-MXL"), -AU0828_DEVICE(0x05e1, 0x0480, "Hauppauge", "Woodbury"), -AU0828_DEVICE(0x2040, 0x8200, "Hauppauge", "Woodbury"), -AU0828_DEVICE(0x2040, 0x7260, "Hauppauge", "HVR-950Q"), -AU0828_DEVICE(0x2040, 0x7213, "Hauppauge", "HVR-950Q"), -AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), - -/* Syntek STK1160 */ -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .idVendor = 0x05e1, - .idProduct = 0x0408, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .vendor_name = "Syntek", - .product_name = "STK1160", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_AUDIO_ALIGN_TRANSFER - } -}, - /* Digidesign Mbox */ { /* Thanks to Clemens Ladisch <clemens@ladisch.de> */ USB_DEVICE(0x0dba, 0x1000), - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Digidesign", .product_name = "MBox", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]){ - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_MIXER, - }, + QUIRK_DATA_COMPOSITE{ + { QUIRK_DATA_STANDARD_MIXER(0) }, { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S24_3BE, .channels = 2, .iface = 1, @@ -2963,9 +2245,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), } }, { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S24_3BE, .channels = 2, .iface = 1, @@ -2973,6 +2253,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), .altset_idx = 1, .attributes = 0x4, .endpoint = 0x81, + .ep_idx = 1, .ep_attr = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, .maxpacksize = 0x130, @@ -2985,9 +2266,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), } } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, @@ -2995,24 +2274,14 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), /* DIGIDESIGN MBOX 2 */ { USB_DEVICE(0x0dba, 0x3000), - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Digidesign", .product_name = "Mbox 2", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_IGNORE(1) }, { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(2) { .formats = SNDRV_PCM_FMTBIT_S24_3BE, .channels = 2, .iface = 2, @@ -3030,15 +2299,10 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), } } }, + { QUIRK_DATA_IGNORE(3) }, { - .ifnum = 3, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 4, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { - .formats = SNDRV_PCM_FMTBIT_S24_3BE, + QUIRK_DATA_AUDIOFORMAT(4) { + .formats = SNDRV_PCM_FMTBIT_S24_3BE, .channels = 2, .iface = 4, .altsetting = 2, @@ -3055,14 +2319,9 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), } } }, + { QUIRK_DATA_IGNORE(5) }, { - .ifnum = 5, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 6, - .type = QUIRK_MIDI_MIDIMAN, - .data = &(const struct snd_usb_midi_endpoint_info) { + QUIRK_DATA_MIDI_MIDIMAN(6) { .out_ep = 0x02, .out_cables = 0x0001, .in_ep = 0x81, @@ -3070,32 +2329,90 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), .in_cables = 0x0001 } }, + QUIRK_COMPOSITE_END + } + } +}, +/* DIGIDESIGN MBOX 3 */ +{ + USB_DEVICE(0x0dba, 0x5000), + QUIRK_DRIVER_INFO { + .vendor_name = "Digidesign", + .product_name = "Mbox 3", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_IGNORE(1) }, { - .ifnum = -1 - } + QUIRK_DATA_AUDIOFORMAT(2) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .fmt_bits = 24, + .channels = 4, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0x00, + .endpoint = USB_RECIP_INTERFACE | USB_DIR_OUT, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 4, + .rate_table = (unsigned int[]) { + 44100, 48000, 88200, 96000 + }, + .sync_ep = USB_RECIP_INTERFACE | USB_DIR_IN, + .sync_iface = 3, + .sync_altsetting = 1, + .sync_ep_idx = 1, + .implicit_fb = 1, + } + }, + { + QUIRK_DATA_AUDIOFORMAT(3) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .fmt_bits = 24, + .channels = 4, + .iface = 3, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0x00, + .endpoint = USB_RECIP_INTERFACE | USB_DIR_IN, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .maxpacksize = 0x009c, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 4, + .rate_table = (unsigned int[]) { + 44100, 48000, 88200, 96000 + }, + .implicit_fb = 0, + } + }, + { + QUIRK_DATA_MIDI_FIXED_ENDPOINT(4) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + QUIRK_COMPOSITE_END } } }, { /* Tascam US122 MKII - playback-only support */ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE, - .idVendor = 0x0644, - .idProduct = 0x8021, - .bInterfaceClass = USB_CLASS_AUDIO, - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + USB_DEVICE_VENDOR_SPEC(0x0644, 0x8021), + QUIRK_DRIVER_INFO { .vendor_name = "TASCAM", .product_name = "US122 MKII", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 2, .iface = 1, @@ -3116,9 +2433,63 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), } } }, + QUIRK_COMPOSITE_END + } + } +}, + +/* Denon DN-X1600 */ +{ + USB_AUDIO_DEVICE(0x154e, 0x500e), + QUIRK_DRIVER_INFO { + .vendor_name = "Denon", + .product_name = "DN-X1600", + QUIRK_DATA_COMPOSITE{ + { QUIRK_DATA_IGNORE(0) }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0x0, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ADAPTIVE, + .maxpacksize = 0x138, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { + 48000 + } + } + }, { - .ifnum = -1 - } + QUIRK_DATA_AUDIOFORMAT(2) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0x0, + .endpoint = 0x85, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ADAPTIVE, + .maxpacksize = 0x138, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { + 48000 + } + } + }, + { QUIRK_DATA_STANDARD_MIDI(4) }, + QUIRK_COMPOSITE_END } } }, @@ -3127,17 +2498,13 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), { USB_DEVICE(0x045e, 0x0283), .bInterfaceClass = USB_CLASS_PER_INTERFACE, - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + QUIRK_DRIVER_INFO { .vendor_name = "Microsoft", .product_name = "XboxLive Headset/Xbox Communicator", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = &(const struct snd_usb_audio_quirk[]) { + QUIRK_DATA_COMPOSITE { { /* playback */ - .ifnum = 0, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(0) { .formats = SNDRV_PCM_FMTBIT_S16_LE, .channels = 1, .iface = 0, @@ -3153,9 +2520,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), }, { /* capture */ - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S16_LE, .channels = 1, .iface = 1, @@ -3169,9 +2534,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), .rate_max = 16000 } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, @@ -3180,18 +2543,11 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), { USB_DEVICE(0x200c, 0x100b), .bInterfaceClass = USB_CLASS_PER_INTERFACE, - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = &(const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_MIXER, - }, + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(0) }, { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 4, .iface = 1, @@ -3210,9 +2566,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), } } }, - { - .ifnum = -1 - } + QUIRK_COMPOSITE_END } } }, @@ -3225,28 +2579,12 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), * enabled in create_standard_audio_quirk(). */ USB_DEVICE(0x1686, 0x00dd), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - /* Playback */ - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE, - }, - { - /* Capture */ - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE, - }, - { - /* Midi */ - .ifnum = 3, - .type = QUIRK_MIDI_STANDARD_INTERFACE - }, - { - .ifnum = -1 - }, + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, /* Playback */ + { QUIRK_DATA_STANDARD_AUDIO(2) }, /* Capture */ + { QUIRK_DATA_STANDARD_MIDI(3) }, /* Midi */ + QUIRK_COMPOSITE_END } } }, @@ -3260,22 +2598,17 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), USB_DEVICE_ID_MATCH_INT_SUBCLASS, .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING, - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_STANDARD_INTERFACE + QUIRK_DRIVER_INFO { + QUIRK_DATA_STANDARD_MIDI(QUIRK_ANY_INTERFACE) } }, +/* Rane SL-1 */ { - /* - * The original product_name is "USB Sound Device", however this name - * is also used by the CM106 based cards, so make it unique. - */ - USB_DEVICE(0x0d8c, 0x0103), - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .product_name = "Audio Advantage MicroII", - .ifnum = QUIRK_NO_INTERFACE - } + USB_DEVICE(0x13e5, 0x0001), + QUIRK_DRIVER_INFO { + QUIRK_DATA_STANDARD_AUDIO(QUIRK_ANY_INTERFACE) + } }, /* disabled due to regression for other devices; @@ -3290,24 +2623,13 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), * and only the 48 kHz sample rate works for the playback interface. */ USB_DEVICE(0x0a12, 0x1243), - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_MIXER, - }, - /* Capture */ - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE, - }, + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(0) }, + { QUIRK_DATA_IGNORE(1) }, /* Capture */ /* Playback */ { - .ifnum = 2, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(2) { .formats = SNDRV_PCM_FMTBIT_S16_LE, .channels = 2, .iface = 2, @@ -3326,9 +2648,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), } } }, - { - .ifnum = -1 - }, + QUIRK_COMPOSITE_END } } }, @@ -3341,19 +2661,12 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), * even on windows. */ USB_DEVICE(0x19b5, 0x0021), - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_MIXER, - }, + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(0) }, /* Playback */ { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { + QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S16_LE, .channels = 2, .iface = 1, @@ -3372,31 +2685,1220 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), } } }, + QUIRK_COMPOSITE_END + } + } +}, +/* MOTU Microbook II */ +{ + USB_DEVICE_VENDOR_SPEC(0x07fd, 0x0004), + QUIRK_DRIVER_INFO { + .vendor_name = "MOTU", + .product_name = "MicroBookII", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(0) }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3BE, + .channels = 6, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x84, + .rates = SNDRV_PCM_RATE_96000, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .maxpacksize = 0x00d8, + .rate_table = (unsigned int[]) { + 96000 + } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3BE, + .channels = 8, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x03, + .ep_idx = 1, + .rates = SNDRV_PCM_RATE_96000, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .maxpacksize = 0x0120, + .rate_table = (unsigned int[]) { + 96000 + } + } + }, + QUIRK_COMPOSITE_END + } + } +}, +{ + /* + * PIONEER DJ DDJ-SX3 + * PCM is 12 channels out, 10 channels in @ 44.1 fixed + * interface 0, vendor class alt setting 1 for endpoints 5 and 0x86 + * The feedback for the output is the input. + */ + USB_DEVICE_VENDOR_SPEC(0x2b73, 0x0023), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 12, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x05, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 10, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x86, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + QUIRK_COMPOSITE_END + } + } +}, +{ + /* + * Pioneer DJ DJM-250MK2 + * PCM is 8 channels out @ 48 fixed (endpoint 0x01) + * and 8 channels in @ 48 fixed (endpoint 0x82). + * + * Both playback and recording is working, even simultaneously. + * + * Playback channels could be mapped to: + * - CH1 + * - CH2 + * - AUX + * + * Recording channels could be mapped to: + * - Post CH1 Fader + * - Post CH2 Fader + * - Cross Fader A + * - Cross Fader B + * - MIC + * - AUX + * - REC OUT + * + * There is remaining problem with recording directly from PHONO/LINE. + * If we map a channel to: + * - CH1 Control Tone PHONO + * - CH1 Control Tone LINE + * - CH2 Control Tone PHONO + * - CH2 Control Tone LINE + * it is silent. + * There is no signal even on other operating systems with official drivers. + * The signal appears only when a supported application is started. + * This needs to be investigated yet... + * (there is quite a lot communication on the USB in both directions) + * + * In current version this mixer could be used for playback + * and for recording from vinyls (through Post CH* Fader) + * but not for DVS (Digital Vinyl Systems) like in Mixxx. + */ + USB_DEVICE_VENDOR_SPEC(0x2b73, 0x0017), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, // outputs + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, // inputs + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + QUIRK_COMPOSITE_END + } + } +}, +{ + /* + * PIONEER DJ DDJ-RB + * PCM is 4 channels out, 2 dummy channels in @ 44.1 fixed + * The feedback for the output is the dummy input. + */ + USB_DEVICE_VENDOR_SPEC(0x2b73, 0x000e), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 4, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 2, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + QUIRK_COMPOSITE_END + } + } +}, + +{ + /* + * PIONEER DJ DDJ-RR + * PCM is 6 channels out & 4 channels in @ 44.1 fixed + */ + USB_DEVICE_VENDOR_SPEC(0x2b73, 0x000d), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { { - .ifnum = -1 + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 6, //Master, Headphones & Booth + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 4, //2x RCA inputs (CH1 & CH2) + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + QUIRK_COMPOSITE_END } } }, -/* Dell WD15 Dock */ + { - USB_DEVICE(0x0bda, 0x4014), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Dell", - .product_name = "WD15 Dock", - .profile_name = "Dell-WD15-Dock", - .ifnum = QUIRK_NO_INTERFACE + /* + * PIONEER DJ DDJ-SR2 + * PCM is 4 channels out, 6 channels in @ 44.1 fixed + * The Feedback for the output is the input + */ + USB_DEVICE_VENDOR_SPEC(0x2b73, 0x001e), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 4, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 6, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + QUIRK_COMPOSITE_END + } } }, -/* Dell WD19 Dock */ + { - USB_DEVICE(0x0bda, 0x402e), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Dell", - .product_name = "WD19 Dock", - .profile_name = "Dell-WD15-Dock", - .ifnum = QUIRK_NO_INTERFACE + /* + * Pioneer DJ DJM-900NXS2 + * 10 channels playback & 12 channels capture @ 44.1/48/96kHz S24LE + */ + USB_DEVICE_VENDOR_SPEC(0x2b73, 0x000a), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000| + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 3, + .rate_table = (unsigned int[]) { + 44100, 48000, 96000 + } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 12, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000| + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 3, + .rate_table = (unsigned int[]) { + 44100, 48000, 96000 + } + } + }, + QUIRK_COMPOSITE_END + } + } +}, + +{ + /* + * PIONEER DJ DDJ-800 + * PCM is 6 channels out, 6 channels in @ 44.1 fixed + * The Feedback for the output is the input + */ + USB_DEVICE_VENDOR_SPEC(0x2b73, 0x0029), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 6, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 6, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + QUIRK_COMPOSITE_END + } } }, +{ + /* + * Pioneer DJ / AlphaTheta DJM-A9 + * 10 channels playback & 12 channels capture @ 44.1/48/96kHz S24LE + */ + USB_DEVICE_VENDOR_SPEC(0x2b73, 0x003c), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000| + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 3, + .rate_table = (unsigned int[]) { + 44100, 48000, 96000 + } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 12, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000| + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 3, + .rate_table = (unsigned int[]) { + 44100, 48000, 96000 + } + } + }, + QUIRK_COMPOSITE_END + } + } +}, +{ + /* + * Pioneer DJ DJM-V10 + */ + USB_DEVICE_VENDOR_SPEC(0x2b73, 0x0034), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 12, // outputs + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000| + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 3, + .rate_table = (unsigned int[]) { 44100, 48000, 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 12, // inputs + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000| + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 3, + .rate_table = (unsigned int[]) { 44100, 48000, 96000 } + } + }, + QUIRK_COMPOSITE_END + } + } +}, + +/* + * MacroSilicon MS2100/MS2106 based AV capture cards + * + * These claim 96kHz 1ch in the descriptors, but are actually 48kHz 2ch. + * They also need QUIRK_FLAG_ALIGN_TRANSFER, which makes one wonder if + * they pretend to be 96kHz mono as a workaround for stereo being broken + * by that... + * + * They also have an issue with initial stream alignment that causes the + * channels to be swapped and out of phase, which is dealt with in quirks.c. + */ +{ + USB_AUDIO_DEVICE(0x534d, 0x0021), + QUIRK_DRIVER_INFO { + .vendor_name = "MacroSilicon", + .product_name = "MS210x", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(2) }, + { + QUIRK_DATA_AUDIOFORMAT(3) { + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels = 2, + .iface = 3, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x82, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 48000, + .rate_max = 48000, + } + }, + QUIRK_COMPOSITE_END + } + } +}, + +/* + * MacroSilicon MS2109 based HDMI capture cards + * + * These claim 96kHz 1ch in the descriptors, but are actually 48kHz 2ch. + * They also need QUIRK_FLAG_ALIGN_TRANSFER, which makes one wonder if + * they pretend to be 96kHz mono as a workaround for stereo being broken + * by that... + * + * They also have an issue with initial stream alignment that causes the + * channels to be swapped and out of phase, which is dealt with in quirks.c. + */ +{ + USB_AUDIO_DEVICE(0x534d, 0x2109), + QUIRK_DRIVER_INFO { + .vendor_name = "MacroSilicon", + .product_name = "MS2109", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(2) }, + { + QUIRK_DATA_AUDIOFORMAT(3) { + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels = 2, + .iface = 3, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x82, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 48000, + .rate_max = 48000, + } + }, + QUIRK_COMPOSITE_END + } + } +}, +{ + /* + * Pioneer DJ DJM-750 + * 8 channels playback & 8 channels capture @ 44.1/48/96kHz S24LE + */ + USB_DEVICE_VENDOR_SPEC(0x08e4, 0x017f), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x05, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000| + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 3, + .rate_table = (unsigned int[]) { 44100, 48000, 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x86, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000| + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 3, + .rate_table = (unsigned int[]) { 44100, 48000, 96000 } + } + }, + QUIRK_COMPOSITE_END + } + } +}, +{ + /* + * Pioneer DJ DJM-750MK2 + * 10 channels playback & 12 channels capture @ 48kHz S24LE + */ + USB_DEVICE_VENDOR_SPEC(0x2b73, 0x001b), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { + 48000 + } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 12, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + QUIRK_COMPOSITE_END + } + } +}, +{ + /* + * Pioneer DJ DJM-850 + * 8 channels playback and 8 channels capture @ 44.1/48/96kHz S24LE + * Playback on EP 0x05 + * Capture on EP 0x86 + */ + USB_DEVICE_VENDOR_SPEC(0x08e4, 0x0163), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x05, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_DATA, + .rates = SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000| + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 3, + .rate_table = (unsigned int[]) { 44100, 48000, 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x86, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_DATA, + .rates = SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000| + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 3, + .rate_table = (unsigned int[]) { 44100, 48000, 96000 } + } + }, + QUIRK_COMPOSITE_END + } + } +}, +{ + /* + * Pioneer DJ DJM-450 + * PCM is 8 channels out @ 48 fixed (endpoint 0x01) + * and 8 channels in @ 48 fixed (endpoint 0x82). + */ + USB_DEVICE_VENDOR_SPEC(0x2b73, 0x0013), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, // outputs + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, // inputs + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + QUIRK_COMPOSITE_END + } + } +}, +{ + /* + * Sennheiser GSP670 + * Change order of interfaces loaded + */ + USB_DEVICE(0x1395, 0x0300), + .bInterfaceClass = USB_CLASS_PER_INTERFACE, + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + // Communication + { QUIRK_DATA_STANDARD_AUDIO(3) }, + // Recording + { QUIRK_DATA_STANDARD_AUDIO(4) }, + // Main + { QUIRK_DATA_STANDARD_AUDIO(1) }, + QUIRK_COMPOSITE_END + } + } +}, +{ + /* + * Fiero SC-01 (firmware v1.0.0 @ 48 kHz) + */ + USB_DEVICE(0x2b53, 0x0023), + QUIRK_DRIVER_INFO { + .vendor_name = "Fiero", + .product_name = "SC-01", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(0) }, + /* Playback */ + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .fmt_bits = 24, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 }, + .clock = 0x29 + } + }, + /* Capture */ + { + QUIRK_DATA_AUDIOFORMAT(2) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .fmt_bits = 24, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC | + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 }, + .clock = 0x29 + } + }, + QUIRK_COMPOSITE_END + } + } +}, +{ + /* + * Fiero SC-01 (firmware v1.0.0 @ 96 kHz) + */ + USB_DEVICE(0x2b53, 0x0024), + QUIRK_DRIVER_INFO { + .vendor_name = "Fiero", + .product_name = "SC-01", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(0) }, + /* Playback */ + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .fmt_bits = 24, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 }, + .clock = 0x29 + } + }, + /* Capture */ + { + QUIRK_DATA_AUDIOFORMAT(2) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .fmt_bits = 24, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC | + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 }, + .clock = 0x29 + } + }, + QUIRK_COMPOSITE_END + } + } +}, +{ + /* + * Fiero SC-01 (firmware v1.1.0) + */ + USB_DEVICE(0x2b53, 0x0031), + QUIRK_DRIVER_INFO { + .vendor_name = "Fiero", + .product_name = "SC-01", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(0) }, + /* Playback */ + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .fmt_bits = 24, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 48000, + .rate_max = 96000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 48000, 96000 }, + .clock = 0x29 + } + }, + /* Capture */ + { + QUIRK_DATA_AUDIOFORMAT(2) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .fmt_bits = 24, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC | + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 48000, + .rate_max = 96000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 48000, 96000 }, + .clock = 0x29 + } + }, + QUIRK_COMPOSITE_END + } + } +}, +{ + /* Advanced modes of the Mythware XA001AU. + * For the standard mode, Mythware XA001AU has ID ffad:a001 + */ + USB_DEVICE_VENDOR_SPEC(0xffad, 0xa001), + QUIRK_DRIVER_INFO { + .vendor_name = "Mythware", + .product_name = "XA001AU", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + QUIRK_COMPOSITE_END + } + } +}, +#define QUIRK_RME_DIGIFACE(pid) \ +{ \ + /* Only claim interface 0 */ \ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR | \ + USB_DEVICE_ID_MATCH_PRODUCT | \ + USB_DEVICE_ID_MATCH_INT_CLASS | \ + USB_DEVICE_ID_MATCH_INT_NUMBER, \ + .idVendor = 0x2a39, \ + .idProduct = pid, \ + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, \ + .bInterfaceNumber = 0, \ + QUIRK_DRIVER_INFO { \ + QUIRK_DATA_COMPOSITE { \ + /* + * Three modes depending on sample rate band, + * with different channel counts for in/out + */ \ + { QUIRK_DATA_STANDARD_MIXER(0) }, \ + { \ + QUIRK_DATA_AUDIOFORMAT(0) { \ + .formats = SNDRV_PCM_FMTBIT_S32_LE, \ + .channels = 34, /* outputs */ \ + .fmt_bits = 24, \ + .iface = 0, \ + .altsetting = 1, \ + .altset_idx = 1, \ + .endpoint = 0x02, \ + .ep_idx = 1, \ + .ep_attr = USB_ENDPOINT_XFER_ISOC | \ + USB_ENDPOINT_SYNC_ASYNC, \ + .rates = SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000, \ + .rate_min = 32000, \ + .rate_max = 48000, \ + .nr_rates = 3, \ + .rate_table = (unsigned int[]) { \ + 32000, 44100, 48000, \ + }, \ + .sync_ep = 0x81, \ + .sync_iface = 0, \ + .sync_altsetting = 1, \ + .sync_ep_idx = 0, \ + .implicit_fb = 1, \ + }, \ + }, \ + { \ + QUIRK_DATA_AUDIOFORMAT(0) { \ + .formats = SNDRV_PCM_FMTBIT_S32_LE, \ + .channels = 18, /* outputs */ \ + .fmt_bits = 24, \ + .iface = 0, \ + .altsetting = 1, \ + .altset_idx = 1, \ + .endpoint = 0x02, \ + .ep_idx = 1, \ + .ep_attr = USB_ENDPOINT_XFER_ISOC | \ + USB_ENDPOINT_SYNC_ASYNC, \ + .rates = SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000, \ + .rate_min = 64000, \ + .rate_max = 96000, \ + .nr_rates = 3, \ + .rate_table = (unsigned int[]) { \ + 64000, 88200, 96000, \ + }, \ + .sync_ep = 0x81, \ + .sync_iface = 0, \ + .sync_altsetting = 1, \ + .sync_ep_idx = 0, \ + .implicit_fb = 1, \ + }, \ + }, \ + { \ + QUIRK_DATA_AUDIOFORMAT(0) { \ + .formats = SNDRV_PCM_FMTBIT_S32_LE, \ + .channels = 10, /* outputs */ \ + .fmt_bits = 24, \ + .iface = 0, \ + .altsetting = 1, \ + .altset_idx = 1, \ + .endpoint = 0x02, \ + .ep_idx = 1, \ + .ep_attr = USB_ENDPOINT_XFER_ISOC | \ + USB_ENDPOINT_SYNC_ASYNC, \ + .rates = SNDRV_PCM_RATE_KNOT | \ + SNDRV_PCM_RATE_176400 | \ + SNDRV_PCM_RATE_192000, \ + .rate_min = 128000, \ + .rate_max = 192000, \ + .nr_rates = 3, \ + .rate_table = (unsigned int[]) { \ + 128000, 176400, 192000, \ + }, \ + .sync_ep = 0x81, \ + .sync_iface = 0, \ + .sync_altsetting = 1, \ + .sync_ep_idx = 0, \ + .implicit_fb = 1, \ + }, \ + }, \ + { \ + QUIRK_DATA_AUDIOFORMAT(0) { \ + .formats = SNDRV_PCM_FMTBIT_S32_LE, \ + .channels = 32, /* inputs */ \ + .fmt_bits = 24, \ + .iface = 0, \ + .altsetting = 1, \ + .altset_idx = 1, \ + .endpoint = 0x81, \ + .ep_attr = USB_ENDPOINT_XFER_ISOC | \ + USB_ENDPOINT_SYNC_ASYNC, \ + .rates = SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000, \ + .rate_min = 32000, \ + .rate_max = 48000, \ + .nr_rates = 3, \ + .rate_table = (unsigned int[]) { \ + 32000, 44100, 48000, \ + } \ + } \ + }, \ + { \ + QUIRK_DATA_AUDIOFORMAT(0) { \ + .formats = SNDRV_PCM_FMTBIT_S32_LE, \ + .channels = 16, /* inputs */ \ + .fmt_bits = 24, \ + .iface = 0, \ + .altsetting = 1, \ + .altset_idx = 1, \ + .endpoint = 0x81, \ + .ep_attr = USB_ENDPOINT_XFER_ISOC | \ + USB_ENDPOINT_SYNC_ASYNC, \ + .rates = SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000, \ + .rate_min = 64000, \ + .rate_max = 96000, \ + .nr_rates = 3, \ + .rate_table = (unsigned int[]) { \ + 64000, 88200, 96000, \ + } \ + } \ + }, \ + { \ + QUIRK_DATA_AUDIOFORMAT(0) { \ + .formats = SNDRV_PCM_FMTBIT_S32_LE, \ + .channels = 8, /* inputs */ \ + .fmt_bits = 24, \ + .iface = 0, \ + .altsetting = 1, \ + .altset_idx = 1, \ + .endpoint = 0x81, \ + .ep_attr = USB_ENDPOINT_XFER_ISOC | \ + USB_ENDPOINT_SYNC_ASYNC, \ + .rates = SNDRV_PCM_RATE_KNOT | \ + SNDRV_PCM_RATE_176400 | \ + SNDRV_PCM_RATE_192000, \ + .rate_min = 128000, \ + .rate_max = 192000, \ + .nr_rates = 3, \ + .rate_table = (unsigned int[]) { \ + 128000, 176400, 192000, \ + } \ + } \ + }, \ + QUIRK_COMPOSITE_END \ + } \ + } \ +} + +QUIRK_RME_DIGIFACE(0x3f8c), +QUIRK_RME_DIGIFACE(0x3fa0), + #undef USB_DEVICE_VENDOR_SPEC +#undef USB_AUDIO_DEVICE diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index ebbadb3a7094..61bd61ffb1b2 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -1,17 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/init.h> @@ -31,6 +19,7 @@ #include "mixer.h" #include "mixer_quirks.h" #include "midi.h" +#include "midi2.h" #include "quirks.h" #include "helper.h" #include "endpoint.h" @@ -67,8 +56,12 @@ static int create_composite_quirk(struct snd_usb_audio *chip, if (!iface) continue; if (quirk->ifnum != probed_ifnum && - !usb_interface_claimed(iface)) - usb_driver_claim_interface(driver, iface, (void *)-1L); + !usb_interface_claimed(iface)) { + err = usb_driver_claim_interface(driver, iface, + USB_AUDIO_IFACE_UNUSED); + if (err < 0) + return err; + } } return 0; @@ -83,25 +76,12 @@ static int ignore_interface_quirk(struct snd_usb_audio *chip, } -/* - * Allow alignment on audio sub-slot (channel samples) rather than - * on audio slots (audio frames) - */ -static int create_align_transfer_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - struct usb_driver *driver, - const struct snd_usb_audio_quirk *quirk) -{ - chip->txfr_quirk = 1; - return 1; /* Continue with creating streams and mixer */ -} - static int create_any_midi_quirk(struct snd_usb_audio *chip, struct usb_interface *intf, struct usb_driver *driver, const struct snd_usb_audio_quirk *quirk) { - return snd_usbmidi_create(chip->card, intf, &chip->midi_list, quirk); + return snd_usb_midi_v2_create(chip, intf, quirk, 0); } /* @@ -116,9 +96,6 @@ static int create_standard_audio_quirk(struct snd_usb_audio *chip, struct usb_interface_descriptor *altsd; int err; - if (chip->usb_id == USB_ID(0x1686, 0x00dd)) /* Zoom R16/24 */ - chip->tx_length_quirk = 1; - alts = &iface->altsetting[0]; altsd = get_iface_desc(alts); err = snd_usb_parse_audio_interface(chip, altsd->bInterfaceNumber); @@ -132,6 +109,40 @@ static int create_standard_audio_quirk(struct snd_usb_audio *chip, return 0; } +/* create the audio stream and the corresponding endpoints from the fixed + * audioformat object; this is used for quirks with the fixed EPs + */ +static int add_audio_stream_from_fixed_fmt(struct snd_usb_audio *chip, + struct audioformat *fp) +{ + int stream, err; + + stream = (fp->endpoint & USB_DIR_IN) ? + SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; + + snd_usb_audioformat_set_sync_ep(chip, fp); + + err = snd_usb_add_audio_stream(chip, stream, fp); + if (err < 0) + return err; + + err = snd_usb_add_endpoint(chip, fp->endpoint, + SND_USB_ENDPOINT_TYPE_DATA); + if (err < 0) + return err; + + if (fp->sync_ep) { + err = snd_usb_add_endpoint(chip, fp->sync_ep, + fp->implicit_fb ? + SND_USB_ENDPOINT_TYPE_DATA : + SND_USB_ENDPOINT_TYPE_SYNC); + if (err < 0) + return err; + } + + return 0; +} + /* * create a stream for an endpoint/altsetting without proper descriptors */ @@ -143,8 +154,8 @@ static int create_fixed_stream_quirk(struct snd_usb_audio *chip, struct audioformat *fp; struct usb_host_interface *alts; struct usb_interface_descriptor *altsd; - int stream, err; unsigned *rate_table = NULL; + int err; fp = kmemdup(quirk->data, sizeof(*fp), GFP_KERNEL); if (!fp) @@ -156,8 +167,8 @@ static int create_fixed_stream_quirk(struct snd_usb_audio *chip, return -EINVAL; } if (fp->nr_rates > 0) { - rate_table = kmemdup(fp->rate_table, - sizeof(int) * fp->nr_rates, GFP_KERNEL); + rate_table = kmemdup_array(fp->rate_table, fp->nr_rates, sizeof(int), + GFP_KERNEL); if (!rate_table) { kfree(fp); return -ENOMEM; @@ -165,11 +176,6 @@ static int create_fixed_stream_quirk(struct snd_usb_audio *chip, fp->rate_table = rate_table; } - stream = (fp->endpoint & USB_DIR_IN) - ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; - err = snd_usb_add_audio_stream(chip, stream, fp); - if (err < 0) - goto error; if (fp->iface != get_iface_desc(&iface->altsetting[0])->bInterfaceNumber || fp->altset_idx >= iface->num_altsetting) { err = -EINVAL; @@ -177,7 +183,7 @@ static int create_fixed_stream_quirk(struct snd_usb_audio *chip, } alts = &iface->altsetting[fp->altset_idx]; altsd = get_iface_desc(alts); - if (altsd->bNumEndpoints < 1) { + if (altsd->bNumEndpoints <= fp->ep_idx) { err = -EINVAL; goto error; } @@ -187,10 +193,17 @@ static int create_fixed_stream_quirk(struct snd_usb_audio *chip, if (fp->datainterval == 0) fp->datainterval = snd_usb_parse_datainterval(chip, alts); if (fp->maxpacksize == 0) - fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + fp->maxpacksize = le16_to_cpu(get_endpoint(alts, fp->ep_idx)->wMaxPacketSize); + if (!fp->fmt_type) + fp->fmt_type = UAC_FORMAT_TYPE_I; + + err = add_audio_stream_from_fixed_fmt(chip, fp); + if (err < 0) + goto error; + usb_set_interface(chip->dev, fp->iface, 0); - snd_usb_init_pitch(chip, fp->iface, alts, fp); - snd_usb_init_sample_rate(chip, fp->iface, alts, fp, fp->rate_max); + snd_usb_init_pitch(chip, fp); + snd_usb_init_sample_rate(chip, fp, fp->rate_max); return 0; error: @@ -260,6 +273,9 @@ static int create_yamaha_midi_quirk(struct snd_usb_audio *chip, NULL, USB_MS_MIDI_OUT_JACK); if (!injd && !outjd) return -ENODEV; + if ((injd && !snd_usb_validate_midi_desc(injd)) || + (outjd && !snd_usb_validate_midi_desc(outjd))) + return -ENODEV; if (injd && (injd->bLength < 5 || (injd->bJackType != USB_MS_EMBEDDED && injd->bJackType != USB_MS_EXTERNAL))) @@ -361,7 +377,8 @@ static int create_auto_midi_quirk(struct snd_usb_audio *chip, static int create_autodetect_quirk(struct snd_usb_audio *chip, struct usb_interface *iface, - struct usb_driver *driver) + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) { int err; @@ -371,41 +388,6 @@ static int create_autodetect_quirk(struct snd_usb_audio *chip, return err; } -static int create_autodetect_quirks(struct snd_usb_audio *chip, - struct usb_interface *iface, - struct usb_driver *driver, - const struct snd_usb_audio_quirk *quirk) -{ - int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber; - int ifcount, ifnum, err; - - err = create_autodetect_quirk(chip, iface, driver); - if (err < 0) - return err; - - /* - * ALSA PCM playback/capture devices cannot be registered in two steps, - * so we have to claim the other corresponding interface here. - */ - ifcount = chip->dev->actconfig->desc.bNumInterfaces; - for (ifnum = 0; ifnum < ifcount; ifnum++) { - if (ifnum == probed_ifnum || quirk->ifnum >= 0) - continue; - iface = usb_ifnum_to_if(chip->dev, ifnum); - if (!iface || - usb_interface_claimed(iface) || - get_iface_desc(iface->altsetting)->bInterfaceClass != - USB_CLASS_VENDOR_SPEC) - continue; - - err = create_autodetect_quirk(chip, iface, driver); - if (err >= 0) - usb_driver_claim_interface(driver, iface, (void *)-1L); - } - - return 0; -} - /* * Create a stream for an Edirol UA-700/UA-25/UA-4FX interface. * The only way to detect the sample rate is by looking at wMaxPacketSize. @@ -426,7 +408,7 @@ static int create_uaxx_quirk(struct snd_usb_audio *chip, struct usb_host_interface *alts; struct usb_interface_descriptor *altsd; struct audioformat *fp; - int stream, err; + int err; /* both PCM and MIDI interfaces have 2 or more altsettings */ if (iface->num_altsetting < 2) @@ -455,8 +437,9 @@ static int create_uaxx_quirk(struct snd_usb_audio *chip, chip->usb_id == USB_ID(0x0582, 0x002b) ? &ua700_quirk : &uaxx_quirk; return __snd_usbmidi_create(chip->card, iface, - &chip->midi_list, quirk, - chip->usb_id); + &chip->midi_list, quirk, + chip->usb_id, + &chip->num_rawmidis); } if (altsd->bNumEndpoints != 1) @@ -491,9 +474,7 @@ static int create_uaxx_quirk(struct snd_usb_audio *chip, return -ENXIO; } - stream = (fp->endpoint & USB_DIR_IN) - ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; - err = snd_usb_add_audio_stream(chip, stream, fp); + err = add_audio_stream_from_fixed_fmt(chip, fp); if (err < 0) { list_del(&fp->list); /* unlink for avoiding double-free */ kfree(fp); @@ -514,7 +495,7 @@ static int create_standard_mixer_quirk(struct snd_usb_audio *chip, if (quirk->ifnum < 0) return 0; - return snd_usb_create_mixer(chip, quirk->ifnum, 0); + return snd_usb_create_mixer(chip, quirk->ifnum); } /* @@ -537,7 +518,7 @@ int snd_usb_create_quirk(struct snd_usb_audio *chip, static const quirk_func_t quirk_funcs[] = { [QUIRK_IGNORE_INTERFACE] = ignore_interface_quirk, [QUIRK_COMPOSITE] = create_composite_quirk, - [QUIRK_AUTODETECT] = create_autodetect_quirks, + [QUIRK_AUTODETECT] = create_autodetect_quirk, [QUIRK_MIDI_STANDARD_INTERFACE] = create_any_midi_quirk, [QUIRK_MIDI_FIXED_ENDPOINT] = create_any_midi_quirk, [QUIRK_MIDI_YAMAHA] = create_any_midi_quirk, @@ -553,7 +534,6 @@ int snd_usb_create_quirk(struct snd_usb_audio *chip, [QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk, [QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk, [QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_quirk, - [QUIRK_AUDIO_ALIGN_TRANSFER] = create_align_transfer_quirk, [QUIRK_AUDIO_STANDARD_MIXER] = create_standard_mixer_quirk, }; @@ -575,6 +555,7 @@ int snd_usb_create_quirk(struct snd_usb_audio *chip, static int snd_usb_extigy_boot_quirk(struct usb_device *dev, struct usb_interface *intf) { struct usb_host_config *config = dev->actconfig; + struct usb_device_descriptor *new_device_descriptor __free(kfree) = NULL; int err; if (le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_OLD || @@ -585,11 +566,19 @@ static int snd_usb_extigy_boot_quirk(struct usb_device *dev, struct usb_interfac 0x10, 0x43, 0x0001, 0x000a, NULL, 0); if (err < 0) dev_dbg(&dev->dev, "error sending boot message: %d\n", err); + + new_device_descriptor = kmalloc(sizeof(*new_device_descriptor), GFP_KERNEL); + if (!new_device_descriptor) + return -ENOMEM; err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, - &dev->descriptor, sizeof(dev->descriptor)); - config = dev->actconfig; + new_device_descriptor, sizeof(*new_device_descriptor)); if (err < 0) dev_dbg(&dev->dev, "error usb_get_descriptor: %d\n", err); + if (new_device_descriptor->bNumConfigurations > dev->descriptor.bNumConfigurations) + dev_dbg(&dev->dev, "error too large bNumConfigurations: %d\n", + new_device_descriptor->bNumConfigurations); + else + memcpy(&dev->descriptor, new_device_descriptor, sizeof(dev->descriptor)); err = usb_reset_configuration(dev); if (err < 0) dev_dbg(&dev->dev, "error usb_reset_configuration: %d\n", err); @@ -840,11 +829,13 @@ static int snd_usb_novation_boot_quirk(struct usb_device *dev) static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev) { int err, actual_length; - /* "midi send" enable */ static const u8 seq[] = { 0x4e, 0x73, 0x52, 0x01 }; + void *buf; - void *buf = kmemdup(seq, ARRAY_SIZE(seq), GFP_KERNEL); + if (usb_pipe_type_check(dev, usb_sndintpipe(dev, 0x05))) + return -EINVAL; + buf = kmemdup(seq, ARRAY_SIZE(seq), GFP_KERNEL); if (!buf) return -ENOMEM; err = usb_interrupt_msg(dev, usb_sndintpipe(dev, 0x05), buf, @@ -869,7 +860,9 @@ static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev) static int snd_usb_nativeinstruments_boot_quirk(struct usb_device *dev) { - int ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + int ret; + + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), 0xaf, USB_TYPE_VENDOR | USB_RECIP_DEVICE, 1, 0, NULL, 0, 1000); @@ -917,6 +910,7 @@ static void mbox2_setup_48_24_magic(struct usb_device *dev) static int snd_usb_mbox2_boot_quirk(struct usb_device *dev) { struct usb_host_config *config = dev->actconfig; + struct usb_device_descriptor *new_device_descriptor __free(kfree) = NULL; int err; u8 bootresponse[0x12]; int fwsize; @@ -951,11 +945,19 @@ static int snd_usb_mbox2_boot_quirk(struct usb_device *dev) dev_dbg(&dev->dev, "device initialised!\n"); + new_device_descriptor = kmalloc(sizeof(*new_device_descriptor), GFP_KERNEL); + if (!new_device_descriptor) + return -ENOMEM; + err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, - &dev->descriptor, sizeof(dev->descriptor)); - config = dev->actconfig; + new_device_descriptor, sizeof(*new_device_descriptor)); if (err < 0) dev_dbg(&dev->dev, "error usb_get_descriptor: %d\n", err); + if (new_device_descriptor->bNumConfigurations > dev->descriptor.bNumConfigurations) + dev_dbg(&dev->dev, "error too large bNumConfigurations: %d\n", + new_device_descriptor->bNumConfigurations); + else + memcpy(&dev->descriptor, new_device_descriptor, sizeof(dev->descriptor)); err = usb_reset_configuration(dev); if (err < 0) @@ -1000,20 +1002,455 @@ static int snd_usb_axefx3_boot_quirk(struct usb_device *dev) return 0; } +static void mbox3_setup_defaults(struct usb_device *dev) +{ + /* The Mbox 3 is "little endian" */ + /* max volume is: 0x0000. */ + /* min volume is: 0x0080 (shown in little endian form) */ + + u8 com_buff[2]; + + /* Deactivate Tuner */ + /* on = 0x01*/ + /* off = 0x00*/ + com_buff[0] = 0x00; + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 0x01, 0x21, 0x0003, 0x2001, &com_buff, 1); + + /* Set clock source to Internal (as opposed to S/PDIF) */ + /* Internal = 0x01*/ + /* S/PDIF = 0x02*/ + com_buff[0] = 0x01; + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0100, 0x8001, &com_buff, 1); + + /* Mute the hardware loopbacks to start the device in a known state. */ + com_buff[0] = 0x00; + com_buff[1] = 0x80; + /* Analogue input 1 left channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0110, 0x4001, &com_buff, 2); + /* Analogue input 1 right channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0111, 0x4001, &com_buff, 2); + /* Analogue input 2 left channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0114, 0x4001, &com_buff, 2); + /* Analogue input 2 right channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0115, 0x4001, &com_buff, 2); + /* Analogue input 3 left channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0118, 0x4001, &com_buff, 2); + /* Analogue input 3 right channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0119, 0x4001, &com_buff, 2); + /* Analogue input 4 left channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x011c, 0x4001, &com_buff, 2); + /* Analogue input 4 right channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x011d, 0x4001, &com_buff, 2); + + /* Set software sends to output */ + com_buff[0] = 0x00; + com_buff[1] = 0x00; + /* Analogue software return 1 left channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0100, 0x4001, &com_buff, 2); + com_buff[0] = 0x00; + com_buff[1] = 0x80; + /* Analogue software return 1 right channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0101, 0x4001, &com_buff, 2); + com_buff[0] = 0x00; + com_buff[1] = 0x80; + /* Analogue software return 2 left channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0104, 0x4001, &com_buff, 2); + com_buff[0] = 0x00; + com_buff[1] = 0x00; + /* Analogue software return 2 right channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0105, 0x4001, &com_buff, 2); + + com_buff[0] = 0x00; + com_buff[1] = 0x80; + /* Analogue software return 3 left channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0108, 0x4001, &com_buff, 2); + /* Analogue software return 3 right channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0109, 0x4001, &com_buff, 2); + /* Analogue software return 4 left channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x010c, 0x4001, &com_buff, 2); + /* Analogue software return 4 right channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x010d, 0x4001, &com_buff, 2); + + /* Return to muting sends */ + com_buff[0] = 0x00; + com_buff[1] = 0x80; + /* Analogue fx return left channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0120, 0x4001, &com_buff, 2); + /* Analogue fx return right channel: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0121, 0x4001, &com_buff, 2); + + /* Analogue software input 1 fx send: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0100, 0x4201, &com_buff, 2); + /* Analogue software input 2 fx send: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0101, 0x4201, &com_buff, 2); + /* Analogue software input 3 fx send: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0102, 0x4201, &com_buff, 2); + /* Analogue software input 4 fx send: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0103, 0x4201, &com_buff, 2); + /* Analogue input 1 fx send: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0104, 0x4201, &com_buff, 2); + /* Analogue input 2 fx send: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0105, 0x4201, &com_buff, 2); + /* Analogue input 3 fx send: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0106, 0x4201, &com_buff, 2); + /* Analogue input 4 fx send: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0107, 0x4201, &com_buff, 2); + + /* Toggle allowing host control */ + /* Not needed + com_buff[0] = 0x02; + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 3, 0x21, 0x0000, 0x2001, &com_buff, 1); + */ + + /* Do not dim fx returns */ + com_buff[0] = 0x00; + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 3, 0x21, 0x0002, 0x2001, &com_buff, 1); + + /* Do not set fx returns to mono */ + com_buff[0] = 0x00; + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 3, 0x21, 0x0001, 0x2001, &com_buff, 1); + + /* Mute the S/PDIF hardware loopback + * same odd volume logic here as above + */ + com_buff[0] = 0x00; + com_buff[1] = 0x80; + /* S/PDIF hardware input 1 left channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0112, 0x4001, &com_buff, 2); + /* S/PDIF hardware input 1 right channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0113, 0x4001, &com_buff, 2); + /* S/PDIF hardware input 2 left channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0116, 0x4001, &com_buff, 2); + /* S/PDIF hardware input 2 right channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0117, 0x4001, &com_buff, 2); + /* S/PDIF hardware input 3 left channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x011a, 0x4001, &com_buff, 2); + /* S/PDIF hardware input 3 right channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x011b, 0x4001, &com_buff, 2); + /* S/PDIF hardware input 4 left channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x011e, 0x4001, &com_buff, 2); + /* S/PDIF hardware input 4 right channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x011f, 0x4001, &com_buff, 2); + /* S/PDIF software return 1 left channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0102, 0x4001, &com_buff, 2); + /* S/PDIF software return 1 right channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0103, 0x4001, &com_buff, 2); + /* S/PDIF software return 2 left channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0106, 0x4001, &com_buff, 2); + /* S/PDIF software return 2 right channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0107, 0x4001, &com_buff, 2); + + com_buff[0] = 0x00; + com_buff[1] = 0x00; + /* S/PDIF software return 3 left channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x010a, 0x4001, &com_buff, 2); + + com_buff[0] = 0x00; + com_buff[1] = 0x80; + /* S/PDIF software return 3 right channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x010b, 0x4001, &com_buff, 2); + /* S/PDIF software return 4 left channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x010e, 0x4001, &com_buff, 2); + + com_buff[0] = 0x00; + com_buff[1] = 0x00; + /* S/PDIF software return 4 right channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x010f, 0x4001, &com_buff, 2); + + com_buff[0] = 0x00; + com_buff[1] = 0x80; + /* S/PDIF fx returns left channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0122, 0x4001, &com_buff, 2); + /* S/PDIF fx returns right channel */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0123, 0x4001, &com_buff, 2); + + /* Set the dropdown "Effect" to the first option */ + /* Room1 = 0x00 */ + /* Room2 = 0x01 */ + /* Room3 = 0x02 */ + /* Hall 1 = 0x03 */ + /* Hall 2 = 0x04 */ + /* Plate = 0x05 */ + /* Delay = 0x06 */ + /* Echo = 0x07 */ + com_buff[0] = 0x00; + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0200, 0x4301, &com_buff, 1); /* max is 0xff */ + /* min is 0x00 */ + + + /* Set the effect duration to 0 */ + /* max is 0xffff */ + /* min is 0x0000 */ + com_buff[0] = 0x00; + com_buff[1] = 0x00; + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0400, 0x4301, &com_buff, 2); + + /* Set the effect volume and feedback to 0 */ + /* max is 0xff */ + /* min is 0x00 */ + com_buff[0] = 0x00; + /* feedback: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0500, 0x4301, &com_buff, 1); + /* volume: */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 1, 0x21, 0x0300, 0x4301, &com_buff, 1); + + /* Set soft button hold duration */ + /* 0x03 = 250ms */ + /* 0x05 = 500ms DEFAULT */ + /* 0x08 = 750ms */ + /* 0x0a = 1sec */ + com_buff[0] = 0x05; + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 3, 0x21, 0x0005, 0x2001, &com_buff, 1); + + /* Use dim LEDs for button of state */ + com_buff[0] = 0x00; + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 3, 0x21, 0x0004, 0x2001, &com_buff, 1); +} + +#define MBOX3_DESCRIPTOR_SIZE 464 + +static int snd_usb_mbox3_boot_quirk(struct usb_device *dev) +{ + struct usb_host_config *config = dev->actconfig; + struct usb_device_descriptor *new_device_descriptor __free(kfree) = NULL; + int err; + int descriptor_size; + + descriptor_size = le16_to_cpu(get_cfg_desc(config)->wTotalLength); + + if (descriptor_size != MBOX3_DESCRIPTOR_SIZE) { + dev_err(&dev->dev, "MBOX3: Invalid descriptor size=%d.\n", descriptor_size); + return -ENODEV; + } + + dev_dbg(&dev->dev, "MBOX3: device initialised!\n"); + + new_device_descriptor = kmalloc(sizeof(*new_device_descriptor), GFP_KERNEL); + if (!new_device_descriptor) + return -ENOMEM; + + err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, + new_device_descriptor, sizeof(*new_device_descriptor)); + if (err < 0) + dev_dbg(&dev->dev, "MBOX3: error usb_get_descriptor: %d\n", err); + if (new_device_descriptor->bNumConfigurations > dev->descriptor.bNumConfigurations) + dev_dbg(&dev->dev, "MBOX3: error too large bNumConfigurations: %d\n", + new_device_descriptor->bNumConfigurations); + else + memcpy(&dev->descriptor, new_device_descriptor, sizeof(dev->descriptor)); + + err = usb_reset_configuration(dev); + if (err < 0) + dev_dbg(&dev->dev, "MBOX3: error usb_reset_configuration: %d\n", err); + + dev_dbg(&dev->dev, "MBOX3: new boot length = %d\n", + le16_to_cpu(get_cfg_desc(config)->wTotalLength)); + + mbox3_setup_defaults(dev); + dev_info(&dev->dev, "MBOX3: Initialized."); + + return 0; /* Successful boot */ +} + +#define MICROBOOK_BUF_SIZE 128 + +static int snd_usb_motu_microbookii_communicate(struct usb_device *dev, u8 *buf, + int buf_size, int *length) +{ + int err, actual_length; + + if (usb_pipe_type_check(dev, usb_sndintpipe(dev, 0x01))) + return -EINVAL; + err = usb_interrupt_msg(dev, usb_sndintpipe(dev, 0x01), buf, *length, + &actual_length, 1000); + if (err < 0) + return err; + + print_hex_dump(KERN_DEBUG, "MicroBookII snd: ", DUMP_PREFIX_NONE, 16, 1, + buf, actual_length, false); + + memset(buf, 0, buf_size); + + if (usb_pipe_type_check(dev, usb_rcvintpipe(dev, 0x82))) + return -EINVAL; + err = usb_interrupt_msg(dev, usb_rcvintpipe(dev, 0x82), buf, buf_size, + &actual_length, 1000); + if (err < 0) + return err; + + print_hex_dump(KERN_DEBUG, "MicroBookII rcv: ", DUMP_PREFIX_NONE, 16, 1, + buf, actual_length, false); + + *length = actual_length; + return 0; +} + +static int snd_usb_motu_microbookii_boot_quirk(struct usb_device *dev) +{ + int err, actual_length, poll_attempts = 0; + static const u8 set_samplerate_seq[] = { 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0b, 0x14, + 0x00, 0x00, 0x00, 0x01 }; + static const u8 poll_ready_seq[] = { 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x0b, 0x18 }; + u8 *buf = kzalloc(MICROBOOK_BUF_SIZE, GFP_KERNEL); + + if (!buf) + return -ENOMEM; + + dev_info(&dev->dev, "Waiting for MOTU Microbook II to boot up...\n"); + + /* First we tell the device which sample rate to use. */ + memcpy(buf, set_samplerate_seq, sizeof(set_samplerate_seq)); + actual_length = sizeof(set_samplerate_seq); + err = snd_usb_motu_microbookii_communicate(dev, buf, MICROBOOK_BUF_SIZE, + &actual_length); + + if (err < 0) { + dev_err(&dev->dev, + "failed setting the sample rate for Motu MicroBook II: %d\n", + err); + goto free_buf; + } + + /* Then we poll every 100 ms until the device informs of its readiness. */ + while (true) { + if (++poll_attempts > 100) { + dev_err(&dev->dev, + "failed booting Motu MicroBook II: timeout\n"); + err = -ENODEV; + goto free_buf; + } + + memset(buf, 0, MICROBOOK_BUF_SIZE); + memcpy(buf, poll_ready_seq, sizeof(poll_ready_seq)); + + actual_length = sizeof(poll_ready_seq); + err = snd_usb_motu_microbookii_communicate( + dev, buf, MICROBOOK_BUF_SIZE, &actual_length); + if (err < 0) { + dev_err(&dev->dev, + "failed booting Motu MicroBook II: communication error %d\n", + err); + goto free_buf; + } + + /* the device signals its readiness through a message of the + * form + * XX 06 00 00 00 00 0b 18 00 00 00 01 + * If the device is not yet ready to accept audio data, the + * last byte of that sequence is 00. + */ + if (actual_length == 12 && buf[actual_length - 1] == 1) + break; + + msleep(100); + } + + dev_info(&dev->dev, "MOTU MicroBook II ready\n"); + +free_buf: + kfree(buf); + return err; +} + +static int snd_usb_motu_m_series_boot_quirk(struct usb_device *dev) +{ + msleep(4000); + + return 0; +} + +static int snd_usb_rme_digiface_boot_quirk(struct usb_device *dev) +{ + /* Disable mixer, internal clock, all outputs ADAT, 48kHz, TMS off */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 16, 0x40, 0x2410, 0x7fff, NULL, 0); + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 18, 0x40, 0x0104, 0xffff, NULL, 0); + + /* Disable loopback for all inputs */ + for (int ch = 0; ch < 32; ch++) + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 22, 0x40, 0x400, ch, NULL, 0); + + /* Unity gain for all outputs */ + for (int ch = 0; ch < 34; ch++) + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 21, 0x40, 0x9000, 0x100 + ch, NULL, 0); + + return 0; +} + /* * Setup quirks */ #define MAUDIO_SET 0x01 /* parse device_setup */ #define MAUDIO_SET_COMPATIBLE 0x80 /* use only "win-compatible" interfaces */ #define MAUDIO_SET_DTS 0x02 /* enable DTS Digital Output */ -#define MAUDIO_SET_96K 0x04 /* 48-96KHz rate if set, 8-48KHz otherwise */ +#define MAUDIO_SET_96K 0x04 /* 48-96kHz rate if set, 8-48kHz otherwise */ #define MAUDIO_SET_24B 0x08 /* 24bits sample if set, 16bits otherwise */ #define MAUDIO_SET_DI 0x10 /* enable Digital Input */ #define MAUDIO_SET_MASK 0x1f /* bit mask for setup value */ -#define MAUDIO_SET_24B_48K_DI 0x19 /* 24bits+48KHz+Digital Input */ -#define MAUDIO_SET_24B_48K_NOTDI 0x09 /* 24bits+48KHz+No Digital Input */ -#define MAUDIO_SET_16B_48K_DI 0x11 /* 16bits+48KHz+Digital Input */ -#define MAUDIO_SET_16B_48K_NOTDI 0x01 /* 16bits+48KHz+No Digital Input */ +#define MAUDIO_SET_24B_48K_DI 0x19 /* 24bits+48kHz+Digital Input */ +#define MAUDIO_SET_24B_48K_NOTDI 0x09 /* 24bits+48kHz+No Digital Input */ +#define MAUDIO_SET_16B_48K_DI 0x11 /* 16bits+48kHz+Digital Input */ +#define MAUDIO_SET_16B_48K_NOTDI 0x01 /* 16bits+48kHz+No Digital Input */ static int quattro_skip_setting_quirk(struct snd_usb_audio *chip, int iface, int altno) @@ -1114,6 +1551,38 @@ static int fasttrackpro_skip_setting_quirk(struct snd_usb_audio *chip, return 0; /* keep this altsetting */ } +static int s1810c_skip_setting_quirk(struct snd_usb_audio *chip, + int iface, int altno) +{ + /* + * Altno settings: + * + * Playback (Interface 1): + * 1: 6 Analog + 2 S/PDIF + * 2: 6 Analog + 2 S/PDIF + * 3: 6 Analog + * + * Capture (Interface 2): + * 1: 8 Analog + 2 S/PDIF + 8 ADAT + * 2: 8 Analog + 2 S/PDIF + 4 ADAT + * 3: 8 Analog + */ + + /* + * I'll leave 2 as the default one and + * use device_setup to switch to the + * other two. + */ + if ((chip->setup == 0 || chip->setup > 2) && altno != 2) + return 1; + else if (chip->setup == 1 && altno != 1) + return 1; + else if (chip->setup == 2 && altno != 3) + return 1; + + return 0; +} + int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip, int iface, int altno) @@ -1127,6 +1596,9 @@ int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip, /* fasttrackpro usb: skip altsets incompatible with device_setup */ if (chip->usb_id == USB_ID(0x0763, 0x2012)) return fasttrackpro_skip_setting_quirk(chip, iface, altno); + /* presonus studio 1810c: skip altsets incompatible with device_setup */ + if (chip->usb_id == USB_ID(0x194f, 0x010c)) + return s1810c_skip_setting_quirk(chip, iface, altno); return 0; } @@ -1158,6 +1630,10 @@ int snd_usb_apply_boot_quirk(struct usb_device *dev, case USB_ID(0x0dba, 0x3000): /* Digidesign Mbox 2 */ return snd_usb_mbox2_boot_quirk(dev); + case USB_ID(0x0dba, 0x5000): + /* Digidesign Mbox 3 */ + return snd_usb_mbox3_boot_quirk(dev); + case USB_ID(0x1235, 0x0010): /* Focusrite Novation Saffire 6 USB */ case USB_ID(0x1235, 0x0018): /* Focusrite Novation Twitch */ @@ -1177,6 +1653,32 @@ int snd_usb_apply_boot_quirk(struct usb_device *dev, return snd_usb_gamecon780_boot_quirk(dev); case USB_ID(0x2466, 0x8010): /* Fractal Audio Axe-Fx 3 */ return snd_usb_axefx3_boot_quirk(dev); + case USB_ID(0x07fd, 0x0004): /* MOTU MicroBook II */ + /* + * For some reason interface 3 with vendor-spec class is + * detected on MicroBook IIc. + */ + if (get_iface_desc(intf->altsetting)->bInterfaceClass == + USB_CLASS_VENDOR_SPEC && + get_iface_desc(intf->altsetting)->bInterfaceNumber < 3) + return snd_usb_motu_microbookii_boot_quirk(dev); + break; + case USB_ID(0x2a39, 0x3f8c): /* RME Digiface USB */ + case USB_ID(0x2a39, 0x3fa0): /* RME Digiface USB (alternate) */ + return snd_usb_rme_digiface_boot_quirk(dev); + } + + return 0; +} + +int snd_usb_apply_boot_quirk_once(struct usb_device *dev, + struct usb_interface *intf, + const struct snd_usb_audio_quirk *quirk, + unsigned int id) +{ + switch (id) { + case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ + return snd_usb_motu_m_series_boot_quirk(dev); } return 0; @@ -1185,7 +1687,8 @@ int snd_usb_apply_boot_quirk(struct usb_device *dev, /* * check if the device uses big-endian samples */ -int snd_usb_is_big_endian_format(struct snd_usb_audio *chip, struct audioformat *fp) +int snd_usb_is_big_endian_format(struct snd_usb_audio *chip, + const struct audioformat *fp) { /* it depends on altsetting whether the device is big-endian or not */ switch (chip->usb_id) { @@ -1224,7 +1727,7 @@ enum { }; static void set_format_emu_quirk(struct snd_usb_substream *subs, - struct audioformat *fmt) + const struct audioformat *fmt) { unsigned char emu_samplerate_id = 0; @@ -1233,7 +1736,7 @@ static void set_format_emu_quirk(struct snd_usb_substream *subs, * by playback substream */ if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) { - if (subs->stream->substream[SNDRV_PCM_STREAM_CAPTURE].interface != -1) + if (subs->stream->substream[SNDRV_PCM_STREAM_CAPTURE].cur_audiofmt) return; } @@ -1261,70 +1764,135 @@ static void set_format_emu_quirk(struct snd_usb_substream *subs, subs->pkt_offset_adj = (emu_samplerate_id >= EMU_QUIRK_SR_176400HZ) ? 4 : 0; } -void snd_usb_set_format_quirk(struct snd_usb_substream *subs, - struct audioformat *fmt) +static int pioneer_djm_set_format_quirk(struct snd_usb_substream *subs, + u16 windex) { - switch (subs->stream->chip->usb_id) { - case USB_ID(0x041e, 0x3f02): /* E-Mu 0202 USB */ - case USB_ID(0x041e, 0x3f04): /* E-Mu 0404 USB */ - case USB_ID(0x041e, 0x3f0a): /* E-Mu Tracker Pre */ - case USB_ID(0x041e, 0x3f19): /* E-Mu 0204 USB */ - set_format_emu_quirk(subs, fmt); - break; - } + unsigned int cur_rate = subs->data_endpoint->cur_rate; + u8 sr[3]; + // Convert to little endian + sr[0] = cur_rate & 0xff; + sr[1] = (cur_rate >> 8) & 0xff; + sr[2] = (cur_rate >> 16) & 0xff; + usb_set_interface(subs->dev, 0, 1); + // we should derive windex from fmt-sync_ep but it's not set + snd_usb_ctl_msg(subs->stream->chip->dev, + usb_sndctrlpipe(subs->stream->chip->dev, 0), + 0x01, 0x22, 0x0100, windex, &sr, 0x0003); + return 0; } -bool snd_usb_get_sample_rate_quirk(struct snd_usb_audio *chip) +static void mbox3_set_format_quirk(struct snd_usb_substream *subs, + const struct audioformat *fmt) { - /* devices which do not support reading the sample rate. */ - switch (chip->usb_id) { - case USB_ID(0x041E, 0x4080): /* Creative Live Cam VF0610 */ - case USB_ID(0x04D8, 0xFEEA): /* Benchmark DAC1 Pre */ - case USB_ID(0x0556, 0x0014): /* Phoenix Audio TMX320VC */ - case USB_ID(0x05A3, 0x9420): /* ELP HD USB Camera */ - case USB_ID(0x074D, 0x3553): /* Outlaw RR2150 (Micronas UAC3553B) */ - case USB_ID(0x1395, 0x740a): /* Sennheiser DECT */ - case USB_ID(0x1901, 0x0191): /* GE B850V3 CP2114 audio interface */ - case USB_ID(0x21B4, 0x0081): /* AudioQuest DragonFly */ - return true; + __le32 buff4 = 0; + u8 buff1 = 0x01; + u32 new_rate = subs->data_endpoint->cur_rate; + u32 current_rate; + + // Get current rate from card and check if changing it is needed + snd_usb_ctl_msg(subs->dev, usb_rcvctrlpipe(subs->dev, 0), + 0x01, 0x21 | USB_DIR_IN, 0x0100, 0x8101, &buff4, 4); + current_rate = le32_to_cpu(buff4); + dev_dbg(&subs->dev->dev, + "MBOX3: Current configured sample rate: %d", current_rate); + if (current_rate == new_rate) { + dev_dbg(&subs->dev->dev, + "MBOX3: No change needed (current rate:%d == new rate:%d)", + current_rate, new_rate); + return; } - /* devices of these vendors don't support reading rate, either */ - switch (USB_ID_VENDOR(chip->usb_id)) { - case 0x045E: /* MS Lifecam */ - case 0x047F: /* Plantronics */ - case 0x1de7: /* Phoenix Audio */ - return true; + // Set new rate + dev_info(&subs->dev->dev, + "MBOX3: Changing sample rate to: %d", new_rate); + buff4 = cpu_to_le32(new_rate); + snd_usb_ctl_msg(subs->dev, usb_sndctrlpipe(subs->dev, 0), + 0x01, 0x21, 0x0100, 0x8101, &buff4, 4); + + // Set clock source to Internal + snd_usb_ctl_msg(subs->dev, usb_sndctrlpipe(subs->dev, 0), + 0x01, 0x21, 0x0100, 0x8001, &buff1, 1); + + // Check whether the change was successful + buff4 = 0; + snd_usb_ctl_msg(subs->dev, usb_rcvctrlpipe(subs->dev, 0), + 0x01, 0x21 | USB_DIR_IN, 0x0100, 0x8101, &buff4, 4); + if (new_rate != le32_to_cpu(buff4)) + dev_warn(&subs->dev->dev, "MBOX3: Couldn't set the sample rate"); +} + +static const int rme_digiface_rate_table[] = { + 32000, 44100, 48000, 0, + 64000, 88200, 96000, 0, + 128000, 176400, 192000, 0, +}; + +static int rme_digiface_set_format_quirk(struct snd_usb_substream *subs) +{ + unsigned int cur_rate = subs->data_endpoint->cur_rate; + u16 val; + int speed_mode; + int id; + + for (id = 0; id < ARRAY_SIZE(rme_digiface_rate_table); id++) { + if (rme_digiface_rate_table[id] == cur_rate) + break; } - return false; + if (id >= ARRAY_SIZE(rme_digiface_rate_table)) + return -EINVAL; + + /* 2, 3, 4 for 1x, 2x, 4x */ + speed_mode = (id >> 2) + 2; + val = (id << 3) | (speed_mode << 12); + + /* Set the sample rate */ + snd_usb_ctl_msg(subs->stream->chip->dev, + usb_sndctrlpipe(subs->stream->chip->dev, 0), + 16, 0x40, val, 0x7078, NULL, 0); + return 0; } -/* ITF-USB DSD based DACs need a vendor cmd to switch - * between PCM and native DSD mode - */ -static bool is_itf_usb_dsd_dac(unsigned int id) +void snd_usb_set_format_quirk(struct snd_usb_substream *subs, + const struct audioformat *fmt) { - switch (id) { - case USB_ID(0x154e, 0x1003): /* Denon DA-300USB */ - case USB_ID(0x154e, 0x3005): /* Marantz HD-DAC1 */ - case USB_ID(0x154e, 0x3006): /* Marantz SA-14S1 */ - case USB_ID(0x1852, 0x5065): /* Luxman DA-06 */ - case USB_ID(0x0644, 0x8043): /* TEAC UD-501/UD-501V2/UD-503/NT-503 */ - case USB_ID(0x0644, 0x8044): /* Esoteric D-05X */ - case USB_ID(0x0644, 0x804a): /* TEAC UD-301 */ - return true; + switch (subs->stream->chip->usb_id) { + case USB_ID(0x041e, 0x3f02): /* E-Mu 0202 USB */ + case USB_ID(0x041e, 0x3f04): /* E-Mu 0404 USB */ + case USB_ID(0x041e, 0x3f0a): /* E-Mu Tracker Pre */ + case USB_ID(0x041e, 0x3f19): /* E-Mu 0204 USB */ + set_format_emu_quirk(subs, fmt); + break; + case USB_ID(0x534d, 0x0021): /* MacroSilicon MS2100/MS2106 */ + case USB_ID(0x534d, 0x2109): /* MacroSilicon MS2109 */ + subs->stream_offset_adj = 2; + break; + case USB_ID(0x2b73, 0x000a): /* Pioneer DJM-900NXS2 */ + case USB_ID(0x2b73, 0x0013): /* Pioneer DJM-450 */ + case USB_ID(0x2b73, 0x0034): /* Pioneer DJM-V10 */ + pioneer_djm_set_format_quirk(subs, 0x0082); + break; + case USB_ID(0x08e4, 0x017f): /* Pioneer DJM-750 */ + case USB_ID(0x08e4, 0x0163): /* Pioneer DJM-850 */ + pioneer_djm_set_format_quirk(subs, 0x0086); + break; + case USB_ID(0x0dba, 0x5000): + mbox3_set_format_quirk(subs, fmt); /* Digidesign Mbox 3 */ + break; + case USB_ID(0x2a39, 0x3f8c): /* RME Digiface USB */ + case USB_ID(0x2a39, 0x3fa0): /* RME Digiface USB (alternate) */ + rme_digiface_set_format_quirk(subs); + break; } - return false; } -int snd_usb_select_mode_quirk(struct snd_usb_substream *subs, - struct audioformat *fmt) +int snd_usb_select_mode_quirk(struct snd_usb_audio *chip, + const struct audioformat *fmt) { - struct usb_device *dev = subs->dev; + struct usb_device *dev = chip->dev; int err; - if (is_itf_usb_dsd_dac(subs->stream->chip->usb_id)) { + if (chip->quirk_flags & QUIRK_FLAG_ITF_USB_DSD_DAC) { /* First switch to alt set 0, otherwise the mode switch cmd * will not be accepted by the DAC */ @@ -1370,7 +1938,7 @@ void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep) /* * M-Audio Fast Track C400/C600 - when packets are not skipped, real - * world latency varies by approx. +/- 50 frames (at 96KHz) each time + * world latency varies by approx. +/- 50 frames (at 96kHz) each time * the stream is (re)started. When skipping packets 16 at endpoint * start up, the real world latency is stable within +/- 1 frame (also * across power cycles). @@ -1387,24 +1955,6 @@ void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep) ep->tenor_fb_quirk = 1; } -void snd_usb_set_interface_quirk(struct usb_device *dev) -{ - struct snd_usb_audio *chip = dev_get_drvdata(&dev->dev); - - if (!chip) - return; - /* - * "Playback Design" products need a 50ms delay after setting the - * USB interface. - */ - switch (USB_ID_VENDOR(chip->usb_id)) { - case 0x23ba: /* Playback Design */ - case 0x0644: /* TEAC Corp. */ - msleep(50); - break; - } -} - /* quirk applied after snd_usb_ctl_msg(); not applied during boot quirks */ void snd_usb_ctl_msg_quirk(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, @@ -1412,40 +1962,15 @@ void snd_usb_ctl_msg_quirk(struct usb_device *dev, unsigned int pipe, { struct snd_usb_audio *chip = dev_get_drvdata(&dev->dev); - if (!chip) + if (!chip || (requesttype & USB_TYPE_MASK) != USB_TYPE_CLASS) return; - /* - * "Playback Design" products need a 20ms delay after each - * class compliant request - */ - if (USB_ID_VENDOR(chip->usb_id) == 0x23ba && - (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS) - msleep(20); - /* - * "TEAC Corp." products need a 20ms delay after each - * class compliant request - */ - if (USB_ID_VENDOR(chip->usb_id) == 0x0644 && - (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS) + if (chip->quirk_flags & QUIRK_FLAG_CTL_MSG_DELAY) msleep(20); - - /* ITF-USB DSD based DACs functionality need a delay - * after each class compliant request - */ - if (is_itf_usb_dsd_dac(chip->usb_id) - && (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS) - msleep(20); - - /* Zoom R16/24, Logitech H650e, Jabra 550a needs a tiny delay here, - * otherwise requests like get/set frequency return as failed despite - * actually succeeding. - */ - if ((chip->usb_id == USB_ID(0x1686, 0x00dd) || - chip->usb_id == USB_ID(0x046d, 0x0a46) || - chip->usb_id == USB_ID(0x0b0e, 0x0349)) && - (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS) + else if (chip->quirk_flags & QUIRK_FLAG_CTL_MSG_DELAY_1M) usleep_range(1000, 2000); + else if (chip->quirk_flags & QUIRK_FLAG_CTL_MSG_DELAY_5M) + usleep_range(5000, 6000); } /* @@ -1462,7 +1987,8 @@ u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip, struct usb_interface *iface; /* Playback Designs */ - if (USB_ID_VENDOR(chip->usb_id) == 0x23ba) { + if (USB_ID_VENDOR(chip->usb_id) == 0x23ba && + USB_ID_PRODUCT(chip->usb_id) < 0x0110) { switch (fp->altsetting) { case 1: fp->dsd_dop = true; @@ -1478,41 +2004,34 @@ u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip, /* XMOS based USB DACs */ switch (chip->usb_id) { - case USB_ID(0x1511, 0x0037): /* AURALiC VEGA */ - case USB_ID(0x20b1, 0x0002): /* Wyred 4 Sound DAC-2 DSD */ - case USB_ID(0x20b1, 0x2004): /* Matrix Audio X-SPDIF 2 */ - case USB_ID(0x20b1, 0x2008): /* Matrix Audio X-Sabre */ - case USB_ID(0x20b1, 0x300a): /* Matrix Audio Mini-i Pro */ - case USB_ID(0x22d9, 0x0416): /* OPPO HA-1 */ - case USB_ID(0x22d9, 0x0436): /* OPPO Sonica */ - case USB_ID(0x22d9, 0x0461): /* OPPO UDP-205 */ + case USB_ID(0x139f, 0x5504): /* Nagra DAC */ + case USB_ID(0x20b1, 0x3089): /* Mola-Mola DAC */ + case USB_ID(0x2522, 0x0007): /* LH Labs Geek Out 1V5 */ + case USB_ID(0x2522, 0x0009): /* LH Labs Geek Pulse X Inifinity 2V0 */ case USB_ID(0x2522, 0x0012): /* LH Labs VI DAC Infinity */ case USB_ID(0x2772, 0x0230): /* Pro-Ject Pre Box S2 Digital */ if (fp->altsetting == 2) return SNDRV_PCM_FMTBIT_DSD_U32_BE; break; - case USB_ID(0x152a, 0x85de): /* SMSL D1 DAC */ - case USB_ID(0x16d0, 0x09dd): /* Encore mDSD */ case USB_ID(0x0d8c, 0x0316): /* Hegel HD12 DSD */ - case USB_ID(0x16b0, 0x06b2): /* NuPrime DAC-10 */ + case USB_ID(0x10cb, 0x0103): /* The Bit Opus #3; with fp->dsd_raw */ + case USB_ID(0x16d0, 0x06b2): /* NuPrime DAC-10 */ + case USB_ID(0x16d0, 0x06b4): /* NuPrime Audio HD-AVP/AVA */ case USB_ID(0x16d0, 0x0733): /* Furutech ADL Stratos */ + case USB_ID(0x16d0, 0x09d8): /* NuPrime IDA-8 */ case USB_ID(0x16d0, 0x09db): /* NuPrime Audio DAC-9 */ + case USB_ID(0x16d0, 0x09dd): /* Encore mDSD */ + case USB_ID(0x16d0, 0x0ab1): /* PureAudio APA DAC */ + case USB_ID(0x16d0, 0xeca1): /* PureAudio Lotus DAC5, DAC5 SE, DAC5 Pro */ case USB_ID(0x1db5, 0x0003): /* Bryston BDA3 */ - case USB_ID(0x20b1, 0x000a): /* Gustard DAC-X20U */ - case USB_ID(0x20b1, 0x2005): /* Denafrips Ares DAC */ - case USB_ID(0x20b1, 0x2009): /* DIYINHK DSD DXD 384kHz USB to I2S/DSD */ - case USB_ID(0x20b1, 0x2023): /* JLsounds I2SoverUSB */ - case USB_ID(0x20b1, 0x3021): /* Eastern El. MiniMax Tube DAC Supreme */ - case USB_ID(0x20b1, 0x3023): /* Aune X1S 32BIT/384 DSD DAC */ - case USB_ID(0x20b1, 0x302d): /* Unison Research Unico CD Due */ - case USB_ID(0x20b1, 0x307b): /* CH Precision C1 DAC */ - case USB_ID(0x20b1, 0x3086): /* Singxer F-1 converter board */ - case USB_ID(0x22d9, 0x0426): /* OPPO HA-2 */ + case USB_ID(0x20a0, 0x4143): /* WaveIO USB Audio 2.0 */ case USB_ID(0x22e1, 0xca01): /* HDTA Serenade DSD */ case USB_ID(0x249c, 0x9326): /* M2Tech Young MkIII */ case USB_ID(0x2616, 0x0106): /* PS Audio NuWave DAC */ case USB_ID(0x2622, 0x0041): /* Audiolab M-DAC+ */ + case USB_ID(0x2622, 0x0061): /* LEAK Stereo 230 */ + case USB_ID(0x278b, 0x5100): /* Rotel RC-1590 */ case USB_ID(0x27f7, 0x3002): /* W4S DAC-2v2SE */ case USB_ID(0x29a2, 0x0086): /* Mutec MC3+ USB */ case USB_ID(0x6b42, 0x0042): /* MSB Technology */ @@ -1522,9 +2041,6 @@ u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip, /* Amanero Combo384 USB based DACs with native DSD support */ case USB_ID(0x16d0, 0x071a): /* Amanero - Combo384 */ - case USB_ID(0x2ab6, 0x0004): /* T+A DAC8DSD-V2.0, MP1000E-V2.0, MP2000R-V2.0, MP2500R-V2.0, MP3100HV-V2.0 */ - case USB_ID(0x2ab6, 0x0005): /* T+A USB HD Audio 1 */ - case USB_ID(0x2ab6, 0x0006): /* T+A USB HD Audio 2 */ if (fp->altsetting == 2) { switch (le16_to_cpu(chip->dev->descriptor.bcdDevice)) { case 0x199: @@ -1547,7 +2063,7 @@ u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip, } /* ITF-USB DSD based DACs */ - if (is_itf_usb_dsd_dac(chip->usb_id)) { + if (chip->quirk_flags & QUIRK_FLAG_ITF_USB_DSD_DAC) { iface = usb_ifnum_to_if(chip->dev, fp->iface); /* Altsetting 2 support native DSD if the num of altsets is @@ -1559,20 +2075,9 @@ u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip, return SNDRV_PCM_FMTBIT_DSD_U32_BE; } - /* Mostly generic method to detect many DSD-capable implementations - - * from XMOS/Thesycon - */ - switch (USB_ID_VENDOR(chip->usb_id)) { - case 0x20b1: /* XMOS based devices */ - case 0x152a: /* Thesycon devices */ - case 0x25ce: /* Mytek devices */ - if (fp->dsd_raw) - return SNDRV_PCM_FMTBIT_DSD_U32_BE; - break; - default: - break; - - } + /* Mostly generic method to detect many DSD-capable implementations */ + if ((chip->quirk_flags & QUIRK_FLAG_DSD_RAW) && fp->dsd_raw) + return SNDRV_PCM_FMTBIT_DSD_U32_BE; return 0; } @@ -1608,5 +2113,529 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, else fp->ep_attr |= USB_ENDPOINT_SYNC_SYNC; break; + case USB_ID(0x07fd, 0x0004): /* MOTU MicroBook IIc */ + /* + * MaxPacketsOnly attribute is erroneously set in endpoint + * descriptors. As a result this card produces noise with + * all sample rates other than 96 kHz. + */ + fp->attributes &= ~UAC_EP_CS_ATTR_FILL_MAX; + break; + case USB_ID(0x1224, 0x2a25): /* Jieli Technology USB PHY 2.0 */ + /* mic works only when ep packet size is set to wMaxPacketSize */ + fp->attributes |= UAC_EP_CS_ATTR_FILL_MAX; + break; + case USB_ID(0x3511, 0x2b1e): /* Opencomm2 UC USB Bluetooth dongle */ + /* mic works only when ep pitch control is not set */ + if (stream == SNDRV_PCM_STREAM_CAPTURE) + fp->attributes &= ~UAC_EP_CS_ATTR_PITCH_CONTROL; + break; + } +} + +/* + * driver behavior quirk flags + */ +struct usb_audio_quirk_flags_table { + u32 id; + u32 flags; +}; + +#define DEVICE_FLG(vid, pid, _flags) \ + { .id = USB_ID(vid, pid), .flags = (_flags) } +#define VENDOR_FLG(vid, _flags) DEVICE_FLG(vid, 0, _flags) + +static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + /* Device matches */ + DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x041e, 0x4080, /* Creative Live Cam VF0610 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x045e, 0x083c, /* MS USB Link headset */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_CTL_MSG_DELAY | + QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x045e, 0x070f, /* MS LifeChat LX-3000 Headset */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x046d, 0x0807, /* Logitech Webcam C500 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x0808, /* Logitech Webcam C600 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x0809, + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x0819, /* Logitech Webcam C210 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x081b, /* HD Webcam c310 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x081d, /* HD Webcam c510 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x0825, /* HD Webcam c270 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x0826, /* HD Webcam c525 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x084c, /* Logitech ConferenceCam Connect */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x046d, 0x08ca, /* Logitech Quickcam Fusion */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x0991, /* Logitech QuickCam Pro */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_IGNORE_CTL_ERROR | + QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x09a2, /* QuickCam Communicate Deluxe/S7500 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x09a4, /* Logitech QuickCam E 3500 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x046d, 0x0a8f, /* Logitech H390 headset */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x0499, 0x1506, /* Yamaha THR5 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0499, 0x1509, /* Steinberg UR22 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0499, 0x3108, /* Yamaha YIT-W12TX */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x04d8, 0xfeea, /* Benchmark DAC1 Pre */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x04e8, 0xa051, /* Samsung USBC Headset (AKG) */ + QUIRK_FLAG_SKIP_CLOCK_SELECTOR | QUIRK_FLAG_CTL_MSG_DELAY_5M), + DEVICE_FLG(0x0525, 0xa4ad, /* Hamedal C20 usb camero */ + QUIRK_FLAG_IFACE_SKIP_CLOSE), + DEVICE_FLG(0x054c, 0x0b8c, /* Sony WALKMAN NW-A45 DAC */ + QUIRK_FLAG_SET_IFACE_FIRST), + DEVICE_FLG(0x0556, 0x0014, /* Phoenix Audio TMX320VC */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x0572, 0x1b08, /* Conexant Systems (Rockwell), Inc. */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x0572, 0x1b09, /* Conexant Systems (Rockwell), Inc. */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x05a3, 0x9420, /* ELP HD USB Camera */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x05a7, 0x1020, /* Bose Companion 5 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x05e1, 0x0408, /* Syntek STK1160 */ + QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x05e1, 0x0480, /* Hauppauge Woodbury */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x0644, 0x8043, /* TEAC UD-501/UD-501V2/UD-503/NT-503 */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | + QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x0644, 0x8044, /* Esoteric D-05X */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | + QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x0644, 0x804a, /* TEAC UD-301 */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | + QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x0644, 0x805f, /* TEAC Model 12 */ + QUIRK_FLAG_FORCE_IFACE_RESET), + DEVICE_FLG(0x0644, 0x806b, /* TEAC UD-701 */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | + QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x06f8, 0xb000, /* Hercules DJ Console (Windows Edition) */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x06f8, 0xd002, /* Hercules DJ Console (Macintosh Edition) */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x0711, 0x5800, /* MCT Trigger 5 USB-to-HDMI */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x074d, 0x3553, /* Outlaw RR2150 (Micronas UAC3553B) */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x0763, 0x2030, /* M-Audio Fast Track C400 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2031, /* M-Audio Fast Track C600 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x07fd, 0x000b, /* MOTU M Series 2nd hardware revision */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x08bb, 0x2702, /* LineX FM Transmitter */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x0951, 0x16ad, /* Kingston HyperX */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x0b05, 0x18a6, /* ASUSTek Computer, Inc. */ + QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x0b0e, 0x0349, /* Jabra 550a */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x0bda, 0x498a, /* Realtek Semiconductor Corp. */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x0c45, 0x6340, /* Sonix HD USB Camera */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x0c45, 0x636b, /* Microdia JP001 USB Camera */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x0d8c, 0x000c, /* C-Media */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x0d8c, 0x0012, /* C-Media */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x0d8c, 0x0014, /* C-Media */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x0ecb, 0x205c, /* JBL Quantum610 Wireless */ + QUIRK_FLAG_FIXED_RATE), + DEVICE_FLG(0x0ecb, 0x2069, /* JBL Quantum810 Wireless */ + QUIRK_FLAG_FIXED_RATE), + DEVICE_FLG(0x0fd9, 0x0008, /* Hauppauge HVR-950Q */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x1038, 0x1294, /* SteelSeries Arctis Pro Wireless */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x12d1, 0x3a07, /* Huawei Technologies Co., Ltd. */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x1397, 0x0507, /* Behringer UMC202HD */ + QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x1397, 0x0508, /* Behringer UMC204HD */ + QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x1397, 0x0509, /* Behringer UMC404HD */ + QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x154e, 0x1002, /* Denon DCD-1500RE */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x154e, 0x1003, /* Denon DA-300USB */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x154e, 0x3005, /* Marantz HD-DAC1 */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x154e, 0x3006, /* Marantz SA-14S1 */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x154e, 0x300b, /* Marantz SA-KI RUBY / SA-12 */ + QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x154e, 0x500e, /* Denon DN-X1600 */ + QUIRK_FLAG_IGNORE_CLOCK_SOURCE), + DEVICE_FLG(0x1686, 0x00dd, /* Zoom R16/24 */ + QUIRK_FLAG_TX_LENGTH | QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x16d0, 0x0ab1, /* PureAudio APA DAC */ + QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x16d0, 0xeca1, /* PureAudio Lotus DAC5, DAC5 SE and DAC5 Pro */ + QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x17aa, 0x1046, /* Lenovo ThinkStation P620 Rear Line-in, Line-out and Microphone */ + QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x17aa, 0x104d, /* Lenovo ThinkStation P620 Internal Speaker + Front Headset */ + QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x17ef, 0x3083, /* Lenovo TBT3 dock */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x1852, 0x5062, /* Luxman D-08u */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x1852, 0x5065, /* Luxman DA-06 */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x1901, 0x0191, /* GE B850V3 CP2114 audio interface */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x19f7, 0x0003, /* RODE NT-USB */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x19f7, 0x0035, /* RODE NT-USB+ */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x1bcf, 0x2281, /* HD Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x1bcf, 0x2283, /* NexiGo N930AF FHD Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x2040, 0x7200, /* Hauppauge HVR-950Q */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x2040, 0x7201, /* Hauppauge HVR-950Q-MXL */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x2040, 0x7210, /* Hauppauge HVR-950Q */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x2040, 0x7211, /* Hauppauge HVR-950Q-MXL */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x2040, 0x7213, /* Hauppauge HVR-950Q */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x2040, 0x7217, /* Hauppauge HVR-950Q */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x2040, 0x721b, /* Hauppauge HVR-950Q */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x2040, 0x721e, /* Hauppauge HVR-950Q */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x2040, 0x721f, /* Hauppauge HVR-950Q */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x2040, 0x7240, /* Hauppauge HVR-850 */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x2040, 0x7260, /* Hauppauge HVR-950Q */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x2040, 0x7270, /* Hauppauge HVR-950Q */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x2040, 0x7280, /* Hauppauge HVR-950Q */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x2040, 0x7281, /* Hauppauge HVR-950Q-MXL */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x2040, 0x8200, /* Hauppauge Woodbury */ + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x21b4, 0x0081, /* AudioQuest DragonFly */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x21b4, 0x0230, /* Ayre QB-9 Twenty */ + QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x21b4, 0x0232, /* Ayre QX-5 Twenty */ + QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x2522, 0x0007, /* LH Labs Geek Out HD Audio 1V5 */ + QUIRK_FLAG_SET_IFACE_FIRST), + DEVICE_FLG(0x262a, 0x9302, /* ddHiFi TC44C */ + QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x2708, 0x0002, /* Audient iD14 */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x2912, 0x30c8, /* Audioengine D1 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x2a70, 0x1881, /* OnePlus Technology (Shenzhen) Co., Ltd. BE02T */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x2b53, 0x0023, /* Fiero SC-01 (firmware v1.0.0 @ 48 kHz) */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x2b53, 0x0024, /* Fiero SC-01 (firmware v1.0.0 @ 96 kHz) */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x2b53, 0x0031, /* Fiero SC-01 (firmware v1.1.0) */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x2d95, 0x8011, /* VIVO USB-C HEADSET */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x2d95, 0x8021, /* VIVO USB-C-XE710 HEADSET */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x2d99, 0x0026, /* HECATE G2 GAMING HEADSET */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x2fc6, 0xf0b7, /* iBasso DC07 Pro */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x30be, 0x0101, /* Schiit Hel */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x339b, 0x3a07, /* Synaptics HONOR USB-C HEADSET */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x413c, 0xa506, /* Dell AE515 sound bar */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x534d, 0x0021, /* MacroSilicon MS2100/MS2106 */ + QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ + QUIRK_FLAG_ALIGN_TRANSFER), + + /* Vendor matches */ + VENDOR_FLG(0x045e, /* MS Lifecam */ + QUIRK_FLAG_GET_SAMPLE_RATE), + VENDOR_FLG(0x046d, /* Logitech */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), + VENDOR_FLG(0x047f, /* Plantronics */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_CTL_MSG_DELAY), + VENDOR_FLG(0x0644, /* TEAC Corp. */ + QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY), + VENDOR_FLG(0x07fd, /* MOTU */ + QUIRK_FLAG_VALIDATE_RATES), + VENDOR_FLG(0x1235, /* Focusrite Novation */ + QUIRK_FLAG_VALIDATE_RATES), + VENDOR_FLG(0x1511, /* AURALiC */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x152a, /* Thesycon devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x18d1, /* iBasso devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x1de7, /* Phoenix Audio */ + QUIRK_FLAG_GET_SAMPLE_RATE), + VENDOR_FLG(0x20b1, /* XMOS based devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x21ed, /* Accuphase Laboratory */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x22d9, /* Oppo */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x23ba, /* Playback Design */ + QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY | + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x25ce, /* Mytek devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x2622, /* IAG Limited devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x278b, /* Rotel? */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x292b, /* Gustard/Ess based devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x2972, /* FiiO devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x2ab6, /* T+A devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x2afd, /* McIntosh Laboratory, Inc. */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x2d87, /* Cayin device */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x2fc6, /* Comture-inc devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x3336, /* HEM devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x3353, /* Khadas devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x35f4, /* MSB Technology */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x3842, /* EVGA */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0xc502, /* HiBy devices */ + QUIRK_FLAG_DSD_RAW), + + {} /* terminator */ +}; + +#define QUIRK_STRING_ENTRY(x) \ + [QUIRK_TYPE_ ## x] = __stringify(x) + +static const char *const snd_usb_audio_quirk_flag_names[] = { + QUIRK_STRING_ENTRY(GET_SAMPLE_RATE), + QUIRK_STRING_ENTRY(SHARE_MEDIA_DEVICE), + QUIRK_STRING_ENTRY(ALIGN_TRANSFER), + QUIRK_STRING_ENTRY(TX_LENGTH), + QUIRK_STRING_ENTRY(PLAYBACK_FIRST), + QUIRK_STRING_ENTRY(SKIP_CLOCK_SELECTOR), + QUIRK_STRING_ENTRY(IGNORE_CLOCK_SOURCE), + QUIRK_STRING_ENTRY(ITF_USB_DSD_DAC), + QUIRK_STRING_ENTRY(CTL_MSG_DELAY), + QUIRK_STRING_ENTRY(CTL_MSG_DELAY_1M), + QUIRK_STRING_ENTRY(CTL_MSG_DELAY_5M), + QUIRK_STRING_ENTRY(IFACE_DELAY), + QUIRK_STRING_ENTRY(VALIDATE_RATES), + QUIRK_STRING_ENTRY(DISABLE_AUTOSUSPEND), + QUIRK_STRING_ENTRY(IGNORE_CTL_ERROR), + QUIRK_STRING_ENTRY(DSD_RAW), + QUIRK_STRING_ENTRY(SET_IFACE_FIRST), + QUIRK_STRING_ENTRY(GENERIC_IMPLICIT_FB), + QUIRK_STRING_ENTRY(SKIP_IMPLICIT_FB), + QUIRK_STRING_ENTRY(IFACE_SKIP_CLOSE), + QUIRK_STRING_ENTRY(FORCE_IFACE_RESET), + QUIRK_STRING_ENTRY(FIXED_RATE), + QUIRK_STRING_ENTRY(MIC_RES_16), + QUIRK_STRING_ENTRY(MIC_RES_384), + QUIRK_STRING_ENTRY(MIXER_PLAYBACK_MIN_MUTE), + QUIRK_STRING_ENTRY(MIXER_CAPTURE_MIN_MUTE), + NULL +}; + +const char *snd_usb_quirk_flag_find_name(unsigned long index) +{ + if (index >= ARRAY_SIZE(snd_usb_audio_quirk_flag_names)) + return NULL; + + return snd_usb_audio_quirk_flag_names[index]; +} + +u32 snd_usb_quirk_flags_from_name(const char *name) +{ + int i; + + if (!name || !*name) + return 0; + + for (i = 0; snd_usb_audio_quirk_flag_names[i]; i++) { + if (strcasecmp(name, snd_usb_audio_quirk_flag_names[i]) == 0) + return BIT_U32(i); + } + + return 0; +} + +void snd_usb_apply_flag_dbg(const char *reason, + struct snd_usb_audio *chip, + unsigned long flag) +{ + unsigned long bit; + + for_each_set_bit(bit, &flag, BYTES_TO_BITS(sizeof(flag))) { + const char *name = snd_usb_audio_quirk_flag_names[bit]; + + if (name) + usb_audio_dbg(chip, + "From %s apply quirk flag %s for device %04x:%04x\n", + reason, name, USB_ID_VENDOR(chip->usb_id), + USB_ID_PRODUCT(chip->usb_id)); + else + usb_audio_warn(chip, + "From %s apply unknown quirk flag 0x%lx for device %04x:%04x\n", + reason, bit, USB_ID_VENDOR(chip->usb_id), + USB_ID_PRODUCT(chip->usb_id)); + } +} + +void snd_usb_init_quirk_flags_table(struct snd_usb_audio *chip) +{ + const struct usb_audio_quirk_flags_table *p; + + for (p = quirk_flags_table; p->id; p++) { + if (chip->usb_id == p->id || + (!USB_ID_PRODUCT(p->id) && + USB_ID_VENDOR(chip->usb_id) == USB_ID_VENDOR(p->id))) { + snd_usb_apply_flag_dbg("builtin table", chip, p->flags); + chip->quirk_flags |= p->flags; + return; + } + } +} + +void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + const char *str) +{ + u16 chip_vid = USB_ID_VENDOR(chip->usb_id); + u16 chip_pid = USB_ID_PRODUCT(chip->usb_id); + u32 mask_flags, unmask_flags, bit; + char *p, *field, *flag; + bool is_unmask; + u16 vid, pid; + + char *val __free(kfree) = kstrdup(str, GFP_KERNEL); + + if (!val) + return; + + for (p = val; p && *p;) { + /* Each entry consists of VID:PID:flags */ + field = strsep(&p, ":"); + if (!field) + break; + + if (strcmp(field, "*") == 0) + vid = 0; + else if (kstrtou16(field, 16, &vid)) + break; + + field = strsep(&p, ":"); + if (!field) + break; + + if (strcmp(field, "*") == 0) + pid = 0; + else if (kstrtou16(field, 16, &pid)) + break; + + field = strsep(&p, ";"); + if (!field || !*field) + break; + + if ((vid != 0 && vid != chip_vid) || + (pid != 0 && pid != chip_pid)) + continue; + + /* Collect the flags */ + mask_flags = 0; + unmask_flags = 0; + while (field && *field) { + flag = strsep(&field, "|"); + + if (!flag) + break; + + if (*flag == '!') { + is_unmask = true; + flag++; + } else { + is_unmask = false; + } + + if (!kstrtou32(flag, 16, &bit)) { + if (is_unmask) + unmask_flags |= bit; + else + mask_flags |= bit; + + break; + } + + bit = snd_usb_quirk_flags_from_name(flag); + + if (bit) { + if (is_unmask) + unmask_flags |= bit; + else + mask_flags |= bit; + } else { + pr_warn("snd_usb_audio: unknown flag %s while parsing param quirk_flags\n", + flag); + } + } + + chip->quirk_flags &= ~unmask_flags; + chip->quirk_flags |= mask_flags; + snd_usb_apply_flag_dbg("module param", chip, + chip->quirk_flags); } } diff --git a/sound/usb/quirks.h b/sound/usb/quirks.h index a80e0ddd0736..f24d6a5a197a 100644 --- a/sound/usb/quirks.h +++ b/sound/usb/quirks.h @@ -20,23 +20,25 @@ int snd_usb_apply_boot_quirk(struct usb_device *dev, const struct snd_usb_audio_quirk *quirk, unsigned int usb_id); -void snd_usb_set_format_quirk(struct snd_usb_substream *subs, - struct audioformat *fmt); +int snd_usb_apply_boot_quirk_once(struct usb_device *dev, + struct usb_interface *intf, + const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id); -bool snd_usb_get_sample_rate_quirk(struct snd_usb_audio *chip); +void snd_usb_set_format_quirk(struct snd_usb_substream *subs, + const struct audioformat *fmt); int snd_usb_is_big_endian_format(struct snd_usb_audio *chip, - struct audioformat *fp); + const struct audioformat *fp); void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep); -void snd_usb_set_interface_quirk(struct usb_device *dev); void snd_usb_ctl_msg_quirk(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *data, __u16 size); -int snd_usb_select_mode_quirk(struct snd_usb_substream *subs, - struct audioformat *fmt); +int snd_usb_select_mode_quirk(struct snd_usb_audio *chip, + const struct audioformat *fmt); u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip, struct audioformat *fp, @@ -46,4 +48,15 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, struct audioformat *fp, int stream); +void snd_usb_apply_flag_dbg(const char *reason, + struct snd_usb_audio *chip, + unsigned long flag); + +void snd_usb_init_quirk_flags_table(struct snd_usb_audio *chip); +void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + const char *str); + +const char *snd_usb_quirk_flag_find_name(unsigned long flag); +u32 snd_usb_quirk_flags_from_name(const char *name); + #endif /* __USBAUDIO_QUIRKS_H */ diff --git a/sound/usb/stream.c b/sound/usb/stream.c index d9e3de495c16..ec7d756d78d1 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -1,17 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ @@ -38,6 +26,15 @@ #include "clock.h" #include "stream.h" #include "power.h" +#include "media.h" + +static void audioformat_free(struct audioformat *fp) +{ + list_del(&fp->list); /* unlink for avoiding double-free */ + kfree(fp->rate_table); + kfree(fp->chmap); + kfree(fp); +} /* * free a substream @@ -48,13 +45,10 @@ static void free_substream(struct snd_usb_substream *subs) if (!subs->num_formats) return; /* not initialized */ - list_for_each_entry_safe(fp, n, &subs->fmt_list, list) { - kfree(fp->rate_table); - kfree(fp->chmap); - kfree(fp); - } - kfree(subs->rate_list.list); + list_for_each_entry_safe(fp, n, &subs->fmt_list, list) + audioformat_free(fp); kfree(subs->str_pd); + snd_media_stream_delete(subs); } @@ -95,10 +89,11 @@ static void snd_usb_init_substream(struct snd_usb_stream *as, subs->stream = as; subs->direction = stream; subs->dev = as->chip->dev; - subs->txfr_quirk = as->chip->txfr_quirk; - subs->tx_length_quirk = as->chip->tx_length_quirk; + subs->txfr_quirk = !!(as->chip->quirk_flags & QUIRK_FLAG_ALIGN_TRANSFER); + subs->tx_length_quirk = !!(as->chip->quirk_flags & QUIRK_FLAG_TX_LENGTH); subs->speed = snd_usb_get_speed(subs->dev); subs->pkt_offset_adj = 0; + subs->stream_offset_adj = 0; snd_usb_set_pcm_ops(as->pcm, stream); @@ -197,16 +192,16 @@ static int usb_chmap_ctl_get(struct snd_kcontrol *kcontrol, struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); struct snd_usb_substream *subs = info->private_data; struct snd_pcm_chmap_elem *chmap = NULL; - int i; + int i = 0; - memset(ucontrol->value.integer.value, 0, - sizeof(ucontrol->value.integer.value)); if (subs->cur_audiofmt) chmap = subs->cur_audiofmt->chmap; if (chmap) { for (i = 0; i < chmap->channels; i++) ucontrol->value.integer.value[i] = chmap->map[i]; } + for (; i < subs->channels_max; i++) + ucontrol->value.integer.value[i] = 0; return 0; } @@ -244,13 +239,13 @@ static int add_chmap(struct snd_pcm *pcm, int stream, static struct snd_pcm_chmap_elem *convert_chmap(int channels, unsigned int bits, int protocol) { - static unsigned int uac1_maps[] = { + static const unsigned int uac1_maps[] = { SNDRV_CHMAP_FL, /* left front */ SNDRV_CHMAP_FR, /* right front */ SNDRV_CHMAP_FC, /* center front */ SNDRV_CHMAP_LFE, /* LFE */ - SNDRV_CHMAP_SL, /* left surround */ - SNDRV_CHMAP_SR, /* right surround */ + SNDRV_CHMAP_RL, /* left surround */ + SNDRV_CHMAP_RR, /* right surround */ SNDRV_CHMAP_FLC, /* left of center */ SNDRV_CHMAP_FRC, /* right of center */ SNDRV_CHMAP_RC, /* surround */ @@ -259,7 +254,7 @@ static struct snd_pcm_chmap_elem *convert_chmap(int channels, unsigned int bits, SNDRV_CHMAP_TC, /* top */ 0 /* terminator */ }; - static unsigned int uac2_maps[] = { + static const unsigned int uac2_maps[] = { SNDRV_CHMAP_FL, /* front left */ SNDRV_CHMAP_FR, /* front right */ SNDRV_CHMAP_FC, /* front center */ @@ -305,9 +300,12 @@ static struct snd_pcm_chmap_elem *convert_chmap(int channels, unsigned int bits, c = 0; if (bits) { - for (; bits && *maps; maps++, bits >>= 1) + for (; bits && *maps; maps++, bits >>= 1) { if (bits & 1) chmap->map[c++] = *maps; + if (c == chmap->channels) + break; + } } else { /* If we're missing wChannelConfig, then guess something to make sure the channel map is not skipped entirely */ @@ -343,20 +341,28 @@ snd_pcm_chmap_elem *convert_chmap_v3(struct uac3_cluster_header_descriptor len = le16_to_cpu(cluster->wLength); c = 0; - p += sizeof(struct uac3_cluster_header_descriptor); + p += sizeof(*cluster); + len -= sizeof(*cluster); - while (((p - (void *)cluster) < len) && (c < channels)) { + while (len > 0 && (c < channels)) { struct uac3_cluster_segment_descriptor *cs_desc = p; u16 cs_len; u8 cs_type; + if (len < sizeof(*cs_desc)) + break; cs_len = le16_to_cpu(cs_desc->wLength); + if (len < cs_len) + break; cs_type = cs_desc->bSegmentType; if (cs_type == UAC3_CHANNEL_INFORMATION) { struct uac3_cluster_information_segment_descriptor *is = p; unsigned char map; + if (cs_len < sizeof(*is)) + break; + /* * TODO: this conversion is not complete, update it * after adding UAC3 values to asound.h @@ -458,6 +464,7 @@ snd_pcm_chmap_elem *convert_chmap_v3(struct uac3_cluster_header_descriptor chmap->map[c++] = map; } p += cs_len; + len -= cs_len; } if (channels < c) @@ -500,6 +507,10 @@ static int __snd_usb_add_audio_stream(struct snd_usb_audio *chip, return 0; } } + + if (chip->card->registered) + chip->need_delayed_register = true; + /* look for an empty stream */ list_for_each_entry(as, &chip->pcm_list, list) { if (as->fmt_type != fp->fmt_type) @@ -534,9 +545,10 @@ static int __snd_usb_add_audio_stream(struct snd_usb_audio *chip, pcm->private_free = snd_usb_audio_pcm_free; pcm->info_flags = 0; if (chip->pcm_devs > 0) - sprintf(pcm->name, "USB Audio #%d", chip->pcm_devs); + scnprintf(pcm->name, sizeof(pcm->name), "USB Audio #%d", + chip->pcm_devs); else - strcpy(pcm->name, "USB Audio"); + strscpy(pcm->name, "USB Audio"); snd_usb_init_substream(as, stream, fp, pd); @@ -637,16 +649,14 @@ static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip, */ static void * snd_usb_find_input_terminal_descriptor(struct usb_host_interface *ctrl_iface, - int terminal_id, bool uac23) + int terminal_id, int protocol) { struct uac2_input_terminal_descriptor *term = NULL; - size_t minlen = uac23 ? sizeof(struct uac2_input_terminal_descriptor) : - sizeof(struct uac_input_terminal_descriptor); while ((term = snd_usb_find_csint_desc(ctrl_iface->extra, ctrl_iface->extralen, term, UAC_INPUT_TERMINAL))) { - if (term->bLength < minlen) + if (!snd_usb_validate_audio_desc(term, protocol)) continue; if (term->bTerminalID == terminal_id) return term; @@ -657,7 +667,7 @@ snd_usb_find_input_terminal_descriptor(struct usb_host_interface *ctrl_iface, static void * snd_usb_find_output_terminal_descriptor(struct usb_host_interface *ctrl_iface, - int terminal_id) + int terminal_id, int protocol) { /* OK to use with both UAC2 and UAC3 */ struct uac2_output_terminal_descriptor *term = NULL; @@ -665,8 +675,9 @@ snd_usb_find_output_terminal_descriptor(struct usb_host_interface *ctrl_iface, while ((term = snd_usb_find_csint_desc(ctrl_iface->extra, ctrl_iface->extralen, term, UAC_OUTPUT_TERMINAL))) { - if (term->bLength >= sizeof(*term) && - term->bTerminalID == terminal_id) + if (!snd_usb_validate_audio_desc(term, protocol)) + continue; + if (term->bTerminalID == terminal_id) return term; } @@ -679,6 +690,7 @@ audio_format_alloc_init(struct snd_usb_audio *chip, int protocol, int iface_no, int altset_idx, int altno, int num_channels, int clock) { + struct usb_host_endpoint *ep = &alts->endpoint[0]; struct audioformat *fp; fp = kzalloc(sizeof(*fp), GFP_KERNEL); @@ -692,11 +704,8 @@ audio_format_alloc_init(struct snd_usb_audio *chip, fp->ep_attr = get_endpoint(alts, 0)->bmAttributes; fp->datainterval = snd_usb_parse_datainterval(chip, alts); fp->protocol = protocol; - fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + fp->maxpacksize = usb_endpoint_max_periodic_payload(chip->dev, ep); fp->channels = num_channels; - if (snd_usb_get_speed(chip->dev) == USB_SPEED_HIGH) - fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1) - * (fp->maxpacksize & 0x7ff); fp->clock = clock; INIT_LIST_HEAD(&fp->list); @@ -712,10 +721,13 @@ snd_usb_get_audioformat_uac12(struct snd_usb_audio *chip, struct usb_device *dev = chip->dev; struct uac_format_type_i_continuous_descriptor *fmt; unsigned int num_channels = 0, chconfig = 0; + struct usb_host_interface *ctrl_intf; struct audioformat *fp; int clock = 0; u64 format; + ctrl_intf = snd_usb_find_ctrl_interface(chip, iface_no); + /* get audio formats */ if (protocol == UAC_VERSION_1) { struct uac1_as_header_descriptor *as = @@ -739,9 +751,9 @@ snd_usb_get_audioformat_uac12(struct snd_usb_audio *chip, format = le16_to_cpu(as->wFormatTag); /* remember the format value */ - iterm = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, + iterm = snd_usb_find_input_terminal_descriptor(ctrl_intf, as->bTerminalLink, - false); + protocol); if (iterm) { num_channels = iterm->bNrChannels; chconfig = le16_to_cpu(iterm->wChannelConfig); @@ -775,9 +787,9 @@ snd_usb_get_audioformat_uac12(struct snd_usb_audio *chip, * lookup the terminal associated to this interface * to extract the clock */ - input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, + input_term = snd_usb_find_input_terminal_descriptor(ctrl_intf, as->bTerminalLink, - true); + protocol); if (input_term) { clock = input_term->bCSourceID; if (!chconfig && (num_channels == input_term->bNrChannels)) @@ -785,8 +797,9 @@ snd_usb_get_audioformat_uac12(struct snd_usb_audio *chip, goto found_clock; } - output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, - as->bTerminalLink); + output_term = snd_usb_find_output_terminal_descriptor(ctrl_intf, + as->bTerminalLink, + protocol); if (output_term) { clock = output_term->bCSourceID; goto found_clock; @@ -842,8 +855,7 @@ found_clock: /* ok, let's parse further... */ if (snd_usb_parse_audio_format(chip, fp, format, fmt, stream) < 0) { - kfree(fp->rate_table); - kfree(fp); + audioformat_free(fp); return NULL; } @@ -869,17 +881,19 @@ snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip, struct uac3_cluster_header_descriptor *cluster; struct uac3_as_header_descriptor *as = NULL; struct uac3_hc_descriptor_header hc_header; + struct usb_host_interface *ctrl_intf; struct snd_pcm_chmap_elem *chmap; struct snd_usb_power_domain *pd; unsigned char badd_profile; u64 badd_formats = 0; unsigned int num_channels; struct audioformat *fp; - u16 cluster_id, wLength; + u16 cluster_id, wLength, cluster_wLength; int clock = 0; int err; badd_profile = chip->badd_profile; + ctrl_intf = snd_usb_find_ctrl_interface(chip, iface_no); if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { unsigned int maxpacksize = @@ -965,7 +979,7 @@ snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip, UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, cluster_id, - snd_usb_ctrl_intf(chip), + snd_usb_ctrl_intf(ctrl_intf), &hc_header, sizeof(hc_header)); if (err < 0) return ERR_PTR(err); @@ -981,6 +995,8 @@ snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip, * and request Cluster Descriptor */ wLength = le16_to_cpu(hc_header.wLength); + if (wLength < sizeof(cluster)) + return NULL; cluster = kzalloc(wLength, GFP_KERNEL); if (!cluster) return ERR_PTR(-ENOMEM); @@ -989,7 +1005,7 @@ snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip, UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, cluster_id, - snd_usb_ctrl_intf(chip), + snd_usb_ctrl_intf(ctrl_intf), cluster, wLength); if (err < 0) { kfree(cluster); @@ -1002,6 +1018,16 @@ snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip, return ERR_PTR(-EIO); } + cluster_wLength = le16_to_cpu(cluster->wLength); + if (cluster_wLength < sizeof(*cluster) || + cluster_wLength > wLength) { + dev_err(&dev->dev, + "%u:%d : invalid Cluster Descriptor size\n", + iface_no, altno); + kfree(cluster); + return ERR_PTR(-EIO); + } + num_channels = cluster->bNrChannels; chmap = convert_chmap_v3(cluster); kfree(cluster); @@ -1010,16 +1036,17 @@ snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip, * lookup the terminal associated to this interface * to extract the clock */ - input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, + input_term = snd_usb_find_input_terminal_descriptor(ctrl_intf, as->bTerminalLink, - true); + UAC_VERSION_3); if (input_term) { clock = input_term->bCSourceID; goto found_clock; } - output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, - as->bTerminalLink); + output_term = snd_usb_find_output_terminal_descriptor(ctrl_intf, + as->bTerminalLink, + UAC_VERSION_3); if (output_term) { clock = output_term->bCSourceID; goto found_clock; @@ -1053,29 +1080,27 @@ found_clock: pd = kzalloc(sizeof(*pd), GFP_KERNEL); if (!pd) { - kfree(fp->rate_table); - kfree(fp); + audioformat_free(fp); return NULL; } pd->pd_id = (stream == SNDRV_PCM_STREAM_PLAYBACK) ? UAC3_BADD_PD_ID10 : UAC3_BADD_PD_ID11; pd->pd_d1d0_rec = UAC3_BADD_PD_RECOVER_D1D0; pd->pd_d2d0_rec = UAC3_BADD_PD_RECOVER_D2D0; + pd->ctrl_iface = ctrl_intf; } else { fp->attributes = parse_uac_endpoint_attributes(chip, alts, UAC_VERSION_3, iface_no); - pd = snd_usb_find_power_domain(chip->ctrl_intf, + pd = snd_usb_find_power_domain(ctrl_intf, as->bTerminalLink); /* ok, let's parse further... */ if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) { kfree(pd); - kfree(fp->chmap); - kfree(fp->rate_table); - kfree(fp); + audioformat_free(fp); return NULL; } } @@ -1086,7 +1111,9 @@ found_clock: return fp; } -int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) +static int __snd_usb_parse_audio_interface(struct snd_usb_audio *chip, + int iface_no, + bool *has_non_pcm, bool non_pcm) { struct usb_device *dev; struct usb_interface *iface; @@ -1095,6 +1122,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) int i, altno, err, stream; struct audioformat *fp = NULL; struct snd_usb_power_domain *pd = NULL; + bool set_iface_first; int num, protocol; dev = chip->dev; @@ -1108,7 +1136,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) * Dallas DS4201 workaround: It presents 5 altsettings, but the last * one misses syncpipe, and does not produce any sound. */ - if (chip->usb_id == USB_ID(0x04fa, 0x4201)) + if (chip->usb_id == USB_ID(0x04fa, 0x4201) && num >= 4) num = 4; for (i = 0; i < num; i++) { @@ -1149,9 +1177,8 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) dev_dbg(&dev->dev, "%u:%d: unknown interface protocol %#02x, assuming v1\n", iface_no, altno, protocol); protocol = UAC_VERSION_1; - /* fall through */ + fallthrough; case UAC_VERSION_1: - /* fall through */ case UAC_VERSION_2: { int bm_quirk = 0; @@ -1187,6 +1214,18 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) else if (IS_ERR(fp)) return PTR_ERR(fp); + if (fp->fmt_type != UAC_FORMAT_TYPE_I) + *has_non_pcm = true; + if ((fp->fmt_type == UAC_FORMAT_TYPE_I) == non_pcm) { + audioformat_free(fp); + kfree(pd); + fp = NULL; + pd = NULL; + continue; + } + + snd_usb_audioformat_set_sync_ep(chip, fp); + dev_dbg(&dev->dev, "%u:%d: add audio endpoint %#x\n", iface_no, altno, fp->endpoint); if (protocol == UAC_VERSION_3) err = snd_usb_add_audio_stream_v3(chip, stream, fp, pd); @@ -1194,18 +1233,60 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) err = snd_usb_add_audio_stream(chip, stream, fp); if (err < 0) { - list_del(&fp->list); /* unlink for avoiding double-free */ + audioformat_free(fp); kfree(pd); - kfree(fp->rate_table); - kfree(fp->chmap); - kfree(fp); return err; } + + /* add endpoints */ + err = snd_usb_add_endpoint(chip, fp->endpoint, + SND_USB_ENDPOINT_TYPE_DATA); + if (err < 0) + return err; + + if (fp->sync_ep) { + err = snd_usb_add_endpoint(chip, fp->sync_ep, + fp->implicit_fb ? + SND_USB_ENDPOINT_TYPE_DATA : + SND_USB_ENDPOINT_TYPE_SYNC); + if (err < 0) + return err; + } + + set_iface_first = false; + if (protocol == UAC_VERSION_1 || + (chip->quirk_flags & QUIRK_FLAG_SET_IFACE_FIRST)) + set_iface_first = true; + /* try to set the interface... */ - usb_set_interface(chip->dev, iface_no, altno); - snd_usb_init_pitch(chip, iface_no, alts, fp); - snd_usb_init_sample_rate(chip, iface_no, alts, fp, fp->rate_max); + usb_set_interface(chip->dev, iface_no, 0); + if (set_iface_first) + usb_set_interface(chip->dev, iface_no, altno); + snd_usb_init_pitch(chip, fp); + snd_usb_init_sample_rate(chip, fp, fp->rate_max); + if (!set_iface_first) + usb_set_interface(chip->dev, iface_no, altno); + } + return 0; +} + +int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) +{ + int err; + bool has_non_pcm = false; + + /* parse PCM formats */ + err = __snd_usb_parse_audio_interface(chip, iface_no, &has_non_pcm, false); + if (err < 0) + return err; + + if (has_non_pcm) { + /* parse non-PCM formats */ + err = __snd_usb_parse_audio_interface(chip, iface_no, &has_non_pcm, true); + if (err < 0) + return err; } + return 0; } diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index b9faeca645fd..79978cae9799 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -1,28 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef __USBAUDIO_H #define __USBAUDIO_H /* * (Tentative) USB Audio Driver for ALSA * * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> - * - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* handling of USB vendor/product ID pairs as 32-bit numbers */ -#define USB_ID(vendor, product) (((vendor) << 16) | (product)) +#define USB_ID(vendor, product) (((unsigned int)(vendor) << 16) | (product)) #define USB_ID_VENDOR(id) ((id) >> 16) #define USB_ID_PRODUCT(id) ((u16)(id)) @@ -30,22 +16,37 @@ * */ +struct media_device; +struct media_intf_devnode; + +#define MAX_CARD_INTERFACES 16 + +/* + * Structure holding assosiation between Audio Control Interface + * and given Streaming or Midi Interface. + */ +struct snd_intf_to_ctrl { + u8 interface; + struct usb_host_interface *ctrl_intf; +}; + struct snd_usb_audio { int index; struct usb_device *dev; struct snd_card *card; - struct usb_interface *pm_intf; + struct usb_interface *intf[MAX_CARD_INTERFACES]; u32 usb_id; + uint16_t quirk_type; struct mutex mutex; - unsigned int autosuspended:1; + unsigned int system_suspend; atomic_t active; atomic_t shutdown; atomic_t usage_count; wait_queue_head_t shutdown_wait; - unsigned int txfr_quirk:1; /* Subframe boundaries on transfers */ - unsigned int tx_length_quirk:1; /* Put length specifier in transfers */ - + unsigned int quirk_flags; + unsigned int need_delayed_register:1; /* warn for delayed registration */ int num_interfaces; + int last_iface; int num_suspended_intf; int sample_rate_read_error; @@ -53,23 +54,35 @@ struct snd_usb_audio { struct list_head pcm_list; /* list of pcm streams */ struct list_head ep_list; /* list of audio-related endpoints */ + struct list_head iface_ref_list; /* list of interface refcounts */ + struct list_head clock_ref_list; /* list of clock refcounts */ int pcm_devs; + unsigned int num_rawmidis; /* number of created rawmidi devices */ struct list_head midi_list; /* list of midi interfaces */ + struct list_head midi_v2_list; /* list of MIDI 2 interfaces */ struct list_head mixer_list; /* list of mixer interfaces */ int setup; /* from the 'device_setup' module param */ + bool generic_implicit_fb; /* from the 'implicit_fb' module param */ bool autoclock; /* from the 'autoclock' module param */ - bool keep_iface; /* keep interface/altset after closing - * or parameter change - */ + bool lowlatency; /* from the 'lowlatency' module param */ struct usb_host_interface *ctrl_intf; /* the audio control interface */ + struct media_device *media_dev; + struct media_intf_devnode *ctl_intf_media_devnode; + + unsigned int num_intf_to_ctrl; + struct snd_intf_to_ctrl intf_to_ctrl[MAX_CARD_INTERFACES]; }; +#define USB_AUDIO_IFACE_UNUSED ((void *)-1L) + #define usb_audio_err(chip, fmt, args...) \ dev_err(&(chip)->dev->dev, fmt, ##args) +#define usb_audio_err_ratelimited(chip, fmt, args...) \ + dev_err_ratelimited(&(chip)->dev->dev, fmt, ##args) #define usb_audio_warn(chip, fmt, args...) \ dev_warn(&(chip)->dev->dev, fmt, ##args) #define usb_audio_info(chip, fmt, args...) \ @@ -82,6 +95,7 @@ struct snd_usb_audio { */ /* special values for .ifnum */ +#define QUIRK_NODEV_INTERFACE -3 /* return -ENODEV */ #define QUIRK_NO_INTERFACE -2 #define QUIRK_ANY_INTERFACE -1 @@ -105,7 +119,6 @@ enum quirk_type { QUIRK_AUDIO_STANDARD_INTERFACE, QUIRK_AUDIO_FIXED_ENDPOINT, QUIRK_AUDIO_EDIROL_UAXX, - QUIRK_AUDIO_ALIGN_TRANSFER, QUIRK_AUDIO_STANDARD_MIXER, QUIRK_TYPE_COUNT @@ -114,7 +127,6 @@ enum quirk_type { struct snd_usb_audio_quirk { const char *vendor_name; const char *product_name; - const char *profile_name; /* override the card->longname */ int16_t ifnum; uint16_t type; const void *data; @@ -127,6 +139,150 @@ struct snd_usb_audio_quirk { int snd_usb_lock_shutdown(struct snd_usb_audio *chip); void snd_usb_unlock_shutdown(struct snd_usb_audio *chip); +/* auto-cleanup */ +struct __snd_usb_lock { + struct snd_usb_audio *chip; + int err; +}; + +static inline struct __snd_usb_lock __snd_usb_lock_shutdown(struct snd_usb_audio *chip) +{ + struct __snd_usb_lock T = { .chip = chip }; + T.err = snd_usb_lock_shutdown(chip); + return T; +} + +static inline void __snd_usb_unlock_shutdown(struct __snd_usb_lock *lock) +{ + if (!lock->err) + snd_usb_unlock_shutdown(lock->chip); +} + +DEFINE_CLASS(snd_usb_lock, struct __snd_usb_lock, + __snd_usb_unlock_shutdown(&(_T)), __snd_usb_lock_shutdown(chip), + struct snd_usb_audio *chip) + extern bool snd_usb_use_vmalloc; +extern bool snd_usb_skip_validation; + +/* + * Driver behavior quirk flags, stored in chip->quirk_flags + * + * QUIRK_FLAG_GET_SAMPLE_RATE: + * Skip reading sample rate for devices, as some devices behave inconsistently + * or return error + * QUIRK_FLAG_SHARE_MEDIA_DEVICE: + * Create Media Controller API entries + * QUIRK_FLAG_ALIGN_TRANSFER: + * Allow alignment on audio sub-slot (channel samples) rather than on audio + * slots (audio frames) + * QUIRK_TX_LENGTH: + * Add length specifier to transfers + * QUIRK_FLAG_PLAYBACK_FIRST: + * Start playback stream at first even in implement feedback mode + * QUIRK_FLAG_SKIP_CLOCK_SELECTOR: + * Skip clock selector setup; the device may reset to invalid state + * QUIRK_FLAG_IGNORE_CLOCK_SOURCE: + * Ignore errors from clock source search; i.e. hardcoded clock + * QUIRK_FLAG_ITF_USB_DSD_DAC: + * Indicates the device is for ITF-USB DSD based DACs that need a vendor cmd + * to switch between PCM and native DSD mode + * QUIRK_FLAG_CTL_MSG_DELAY: + * Add a delay of 20ms at each control message handling + * QUIRK_FLAG_CTL_MSG_DELAY_1M: + * Add a delay of 1-2ms at each control message handling + * QUIRK_FLAG_CTL_MSG_DELAY_5M: + * Add a delay of 5-6ms at each control message handling + * QUIRK_FLAG_IFACE_DELAY: + * Add a delay of 50ms at each interface setup + * QUIRK_FLAG_VALIDATE_RATES: + * Perform sample rate validations at probe + * QUIRK_FLAG_DISABLE_AUTOSUSPEND: + * Disable runtime PM autosuspend + * QUIRK_FLAG_IGNORE_CTL_ERROR: + * Ignore errors for mixer access + * QUIRK_FLAG_DSD_RAW: + * Support generic DSD raw U32_BE format + * QUIRK_FLAG_SET_IFACE_FIRST: + * Set up the interface at first like UAC1 + * QUIRK_FLAG_GENERIC_IMPLICIT_FB + * Apply the generic implicit feedback sync mode (same as implicit_fb=1 option) + * QUIRK_FLAG_SKIP_IMPLICIT_FB + * Don't apply implicit feedback sync mode + * QUIRK_FLAG_IFACE_SKIP_CLOSE + * Don't closed interface during setting sample rate + * QUIRK_FLAG_FORCE_IFACE_RESET + * Force an interface reset whenever stopping & restarting a stream + * (e.g. after xrun) + * QUIRK_FLAG_FIXED_RATE + * Do not set PCM rate (frequency) when only one rate is available + * for the given endpoint. + * QUIRK_FLAG_MIC_RES_16 and QUIRK_FLAG_MIC_RES_384 + * Set the fixed resolution for Mic Capture Volume (mostly for webcams) + * QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE + * Set minimum volume control value as mute for devices where the lowest + * playback value represents muted state instead of minimum audible volume + * QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE + * Similar to QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE, but for capture streams + */ + +enum { + QUIRK_TYPE_GET_SAMPLE_RATE = 0, + QUIRK_TYPE_SHARE_MEDIA_DEVICE = 1, + QUIRK_TYPE_ALIGN_TRANSFER = 2, + QUIRK_TYPE_TX_LENGTH = 3, + QUIRK_TYPE_PLAYBACK_FIRST = 4, + QUIRK_TYPE_SKIP_CLOCK_SELECTOR = 5, + QUIRK_TYPE_IGNORE_CLOCK_SOURCE = 6, + QUIRK_TYPE_ITF_USB_DSD_DAC = 7, + QUIRK_TYPE_CTL_MSG_DELAY = 8, + QUIRK_TYPE_CTL_MSG_DELAY_1M = 9, + QUIRK_TYPE_CTL_MSG_DELAY_5M = 10, + QUIRK_TYPE_IFACE_DELAY = 11, + QUIRK_TYPE_VALIDATE_RATES = 12, + QUIRK_TYPE_DISABLE_AUTOSUSPEND = 13, + QUIRK_TYPE_IGNORE_CTL_ERROR = 14, + QUIRK_TYPE_DSD_RAW = 15, + QUIRK_TYPE_SET_IFACE_FIRST = 16, + QUIRK_TYPE_GENERIC_IMPLICIT_FB = 17, + QUIRK_TYPE_SKIP_IMPLICIT_FB = 18, + QUIRK_TYPE_IFACE_SKIP_CLOSE = 19, + QUIRK_TYPE_FORCE_IFACE_RESET = 20, + QUIRK_TYPE_FIXED_RATE = 21, + QUIRK_TYPE_MIC_RES_16 = 22, + QUIRK_TYPE_MIC_RES_384 = 23, + QUIRK_TYPE_MIXER_PLAYBACK_MIN_MUTE = 24, + QUIRK_TYPE_MIXER_CAPTURE_MIN_MUTE = 25, +/* Please also edit snd_usb_audio_quirk_flag_names */ +}; + +#define QUIRK_FLAG(x) BIT_U32(QUIRK_TYPE_ ## x) + +#define QUIRK_FLAG_GET_SAMPLE_RATE QUIRK_FLAG(GET_SAMPLE_RATE) +#define QUIRK_FLAG_SHARE_MEDIA_DEVICE QUIRK_FLAG(SHARE_MEDIA_DEVICE) +#define QUIRK_FLAG_ALIGN_TRANSFER QUIRK_FLAG(ALIGN_TRANSFER) +#define QUIRK_FLAG_TX_LENGTH QUIRK_FLAG(TX_LENGTH) +#define QUIRK_FLAG_PLAYBACK_FIRST QUIRK_FLAG(PLAYBACK_FIRST) +#define QUIRK_FLAG_SKIP_CLOCK_SELECTOR QUIRK_FLAG(SKIP_CLOCK_SELECTOR) +#define QUIRK_FLAG_IGNORE_CLOCK_SOURCE QUIRK_FLAG(IGNORE_CLOCK_SOURCE) +#define QUIRK_FLAG_ITF_USB_DSD_DAC QUIRK_FLAG(ITF_USB_DSD_DAC) +#define QUIRK_FLAG_CTL_MSG_DELAY QUIRK_FLAG(CTL_MSG_DELAY) +#define QUIRK_FLAG_CTL_MSG_DELAY_1M QUIRK_FLAG(CTL_MSG_DELAY_1M) +#define QUIRK_FLAG_CTL_MSG_DELAY_5M QUIRK_FLAG(CTL_MSG_DELAY_5M) +#define QUIRK_FLAG_IFACE_DELAY QUIRK_FLAG(IFACE_DELAY) +#define QUIRK_FLAG_VALIDATE_RATES QUIRK_FLAG(VALIDATE_RATES) +#define QUIRK_FLAG_DISABLE_AUTOSUSPEND QUIRK_FLAG(DISABLE_AUTOSUSPEND) +#define QUIRK_FLAG_IGNORE_CTL_ERROR QUIRK_FLAG(IGNORE_CTL_ERROR) +#define QUIRK_FLAG_DSD_RAW QUIRK_FLAG(DSD_RAW) +#define QUIRK_FLAG_SET_IFACE_FIRST QUIRK_FLAG(SET_IFACE_FIRST) +#define QUIRK_FLAG_GENERIC_IMPLICIT_FB QUIRK_FLAG(GENERIC_IMPLICIT_FB) +#define QUIRK_FLAG_SKIP_IMPLICIT_FB QUIRK_FLAG(SKIP_IMPLICIT_FB) +#define QUIRK_FLAG_IFACE_SKIP_CLOSE QUIRK_FLAG(IFACE_SKIP_CLOSE) +#define QUIRK_FLAG_FORCE_IFACE_RESET QUIRK_FLAG(FORCE_IFACE_RESET) +#define QUIRK_FLAG_FIXED_RATE QUIRK_FLAG(FIXED_RATE) +#define QUIRK_FLAG_MIC_RES_16 QUIRK_FLAG(MIC_RES_16) +#define QUIRK_FLAG_MIC_RES_384 QUIRK_FLAG(MIC_RES_384) +#define QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE QUIRK_FLAG(MIXER_PLAYBACK_MIN_MUTE) +#define QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE QUIRK_FLAG(MIXER_CAPTURE_MIN_MUTE) #endif /* __USBAUDIO_H */ diff --git a/sound/usb/usx2y/Makefile b/sound/usb/usx2y/Makefile index cc4c2f1efab2..9db87ae39ee9 100644 --- a/sound/usb/usx2y/Makefile +++ b/sound/usb/usx2y/Makefile @@ -1,6 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 -snd-usb-usx2y-objs := usbusx2y.o usX2Yhwdep.o usx2yhwdeppcm.o -snd-usb-us122l-objs := us122l.o +snd-usb-usx2y-y := usbusx2y.o usX2Yhwdep.o usx2yhwdeppcm.o +snd-usb-us122l-y := us122l.o +snd-usb-us144mkii-y := us144mkii.o us144mkii_pcm.o us144mkii_playback.o us144mkii_capture.o us144mkii_midi.o us144mkii_controls.o obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-usx2y.o obj-$(CONFIG_SND_USB_US122L) += snd-usb-us122l.o +obj-$(CONFIG_SND_USB_US144MKII) += snd-usb-us144mkii.o
\ No newline at end of file diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c index 8082f7b077f1..011ea96e9779 100644 --- a/sound/usb/usx2y/us122l.c +++ b/sound/usb/usx2y/us122l.c @@ -1,19 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.de> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include <linux/slab.h> @@ -53,16 +40,16 @@ static int snd_us122l_card_used[SNDRV_CARDS]; static int us122l_create_usbmidi(struct snd_card *card) { - static struct snd_usb_midi_endpoint_info quirk_data = { + static const struct snd_usb_midi_endpoint_info quirk_data = { .out_ep = 4, .in_ep = 3, .out_cables = 0x001, .in_cables = 0x001 }; - static struct snd_usb_audio_quirk quirk = { + static const struct snd_usb_audio_quirk quirk = { .vendor_name = "US122L", .product_name = NAME_ALLCAPS, - .ifnum = 1, + .ifnum = 1, .type = QUIRK_MIDI_US122L, .data = &quirk_data }; @@ -75,16 +62,16 @@ static int us122l_create_usbmidi(struct snd_card *card) static int us144_create_usbmidi(struct snd_card *card) { - static struct snd_usb_midi_endpoint_info quirk_data = { + static const struct snd_usb_midi_endpoint_info quirk_data = { .out_ep = 4, .in_ep = 3, .out_cables = 0x001, .in_cables = 0x001 }; - static struct snd_usb_audio_quirk quirk = { + static const struct snd_usb_audio_quirk quirk = { .vendor_name = "US144", .product_name = NAME_ALLCAPS, - .ifnum = 0, + .ifnum = 0, .type = QUIRK_MIDI_US122L, .data = &quirk_data }; @@ -95,48 +82,11 @@ static int us144_create_usbmidi(struct snd_card *card) &US122L(card)->midi_list, &quirk); } -/* - * Wrapper for usb_control_msg(). - * Allocates a temp buffer to prevent dmaing from/to the stack. - */ -static int us122l_ctl_msg(struct usb_device *dev, unsigned int pipe, - __u8 request, __u8 requesttype, - __u16 value, __u16 index, void *data, - __u16 size, int timeout) -{ - int err; - void *buf = NULL; - - if (size > 0) { - buf = kmemdup(data, size, GFP_KERNEL); - if (!buf) - return -ENOMEM; - } - err = usb_control_msg(dev, pipe, request, requesttype, - value, index, buf, size, timeout); - if (size > 0) { - memcpy(data, buf, size); - kfree(buf); - } - return err; -} - static void pt_info_set(struct usb_device *dev, u8 v) { - int ret; - - ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), - 'I', - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - v, 0, NULL, 0, 1000); - snd_printdd(KERN_DEBUG "%i\n", ret); -} - -static void usb_stream_hwdep_vm_open(struct vm_area_struct *area) -{ - struct us122l *us122l = area->vm_private_data; - atomic_inc(&us122l->mmap_count); - snd_printdd(KERN_DEBUG "%i\n", atomic_read(&us122l->mmap_count)); + usb_control_msg_send(dev, 0, 'I', + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + v, 0, NULL, 0, 1000, GFP_NOIO); } static vm_fault_t usb_stream_hwdep_vm_fault(struct vm_fault *vmf) @@ -147,53 +97,40 @@ static vm_fault_t usb_stream_hwdep_vm_fault(struct vm_fault *vmf) struct us122l *us122l = vmf->vma->vm_private_data; struct usb_stream *s; - mutex_lock(&us122l->mutex); + guard(mutex)(&us122l->mutex); s = us122l->sk.s; if (!s) - goto unlock; + return VM_FAULT_SIGBUS; offset = vmf->pgoff << PAGE_SHIFT; - if (offset < PAGE_ALIGN(s->read_size)) + if (offset < PAGE_ALIGN(s->read_size)) { vaddr = (char *)s + offset; - else { + } else { offset -= PAGE_ALIGN(s->read_size); if (offset >= PAGE_ALIGN(s->write_size)) - goto unlock; + return VM_FAULT_SIGBUS; vaddr = us122l->sk.write_page + offset; } page = virt_to_page(vaddr); get_page(page); - mutex_unlock(&us122l->mutex); vmf->page = page; return 0; -unlock: - mutex_unlock(&us122l->mutex); - return VM_FAULT_SIGBUS; } -static void usb_stream_hwdep_vm_close(struct vm_area_struct *area) -{ - struct us122l *us122l = area->vm_private_data; - atomic_dec(&us122l->mmap_count); - snd_printdd(KERN_DEBUG "%i\n", atomic_read(&us122l->mmap_count)); -} static const struct vm_operations_struct usb_stream_hwdep_vm_ops = { - .open = usb_stream_hwdep_vm_open, .fault = usb_stream_hwdep_vm_fault, - .close = usb_stream_hwdep_vm_close, }; - static int usb_stream_hwdep_open(struct snd_hwdep *hw, struct file *file) { struct us122l *us122l = hw->private_data; struct usb_interface *iface; - snd_printdd(KERN_DEBUG "%p %p\n", hw, file); + if (hw->used >= 2) return -EBUSY; @@ -213,7 +150,6 @@ static int usb_stream_hwdep_release(struct snd_hwdep *hw, struct file *file) { struct us122l *us122l = hw->private_data; struct usb_interface *iface; - snd_printdd(KERN_DEBUG "%p %p\n", hw, file); if (us122l->is_us144) { iface = usb_ifnum_to_if(us122l->dev, 0); @@ -223,12 +159,11 @@ static int usb_stream_hwdep_release(struct snd_hwdep *hw, struct file *file) usb_autopm_put_interface(iface); if (us122l->first == file) us122l->first = NULL; - mutex_lock(&us122l->mutex); + guard(mutex)(&us122l->mutex); if (us122l->master == file) us122l->master = us122l->slave; us122l->slave = NULL; - mutex_unlock(&us122l->mutex); return 0; } @@ -239,43 +174,34 @@ static int usb_stream_hwdep_mmap(struct snd_hwdep *hw, struct us122l *us122l = hw->private_data; unsigned long offset; struct usb_stream *s; - int err = 0; bool read; offset = area->vm_pgoff << PAGE_SHIFT; - mutex_lock(&us122l->mutex); + guard(mutex)(&us122l->mutex); s = us122l->sk.s; read = offset < s->read_size; - if (read && area->vm_flags & VM_WRITE) { - err = -EPERM; - goto out; - } - snd_printdd(KERN_DEBUG "%lu %u\n", size, - read ? s->read_size : s->write_size); + if (read && area->vm_flags & VM_WRITE) + return -EPERM; /* if userspace tries to mmap beyond end of our buffer, fail */ if (size > PAGE_ALIGN(read ? s->read_size : s->write_size)) { - snd_printk(KERN_WARNING "%lu > %u\n", size, - read ? s->read_size : s->write_size); - err = -EINVAL; - goto out; + dev_warn(hw->card->dev, "%s: size %lu > %u\n", __func__, + size, read ? s->read_size : s->write_size); + return -EINVAL; } area->vm_ops = &usb_stream_hwdep_vm_ops; - area->vm_flags |= VM_DONTDUMP; + vm_flags_set(area, VM_DONTDUMP); if (!read) - area->vm_flags |= VM_DONTEXPAND; + vm_flags_set(area, VM_DONTEXPAND); area->vm_private_data = us122l; - atomic_inc(&us122l->mmap_count); -out: - mutex_unlock(&us122l->mutex); - return err; + return 0; } static __poll_t usb_stream_hwdep_poll(struct snd_hwdep *hw, struct file *file, poll_table *wait) { struct us122l *us122l = hw->private_data; - unsigned *polled; + unsigned int *polled; __poll_t mask; poll_wait(file, &us122l->sk.sleep, wait); @@ -283,6 +209,7 @@ static __poll_t usb_stream_hwdep_poll(struct snd_hwdep *hw, mask = EPOLLIN | EPOLLOUT | EPOLLWRNORM | EPOLLERR; if (mutex_trylock(&us122l->mutex)) { struct usb_stream *s = us122l->sk.s; + if (s && s->state == usb_stream_ready) { if (us122l->first == file) polled = &s->periods_polled; @@ -291,8 +218,9 @@ static __poll_t usb_stream_hwdep_poll(struct snd_hwdep *hw, if (*polled != s->periods_done) { *polled = s->periods_done; mask = EPOLLIN | EPOLLOUT | EPOLLWRNORM; - } else + } else { mask = 0; + } } mutex_unlock(&us122l->mutex); } @@ -302,6 +230,7 @@ static __poll_t usb_stream_hwdep_poll(struct snd_hwdep *hw, static void us122l_stop(struct us122l *us122l) { struct list_head *p; + list_for_each(p, &us122l->midi_list) snd_usbmidi_input_stop(p); @@ -318,21 +247,22 @@ static int us122l_set_sample_rate(struct usb_device *dev, int rate) data[0] = rate; data[1] = rate >> 8; data[2] = rate >> 16; - err = us122l_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, - USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, - UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3, 1000); - if (err < 0) - snd_printk(KERN_ERR "%d: cannot set freq %d to ep 0x%x\n", - dev->devnum, rate, ep); + err = usb_control_msg_send(dev, 0, UAC_SET_CUR, + USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3, + 1000, GFP_NOIO); + if (err) + dev_err(&dev->dev, "%d: cannot set freq %d to ep 0x%x\n", + dev->devnum, rate, ep); return err; } static bool us122l_start(struct us122l *us122l, - unsigned rate, unsigned period_frames) + unsigned int rate, unsigned int period_frames) { struct list_head *p; int err; - unsigned use_packsize = 0; + unsigned int use_packsize = 0; bool success = false; if (us122l->dev->speed == USB_SPEED_HIGH) { @@ -359,13 +289,13 @@ static bool us122l_start(struct us122l *us122l, err = us122l_set_sample_rate(us122l->dev, rate); if (err < 0) { us122l_stop(us122l); - snd_printk(KERN_ERR "us122l_set_sample_rate error \n"); + dev_err(&us122l->dev->dev, "us122l_set_sample_rate error\n"); goto out; } err = usb_stream_start(&us122l->sk); if (err < 0) { us122l_stop(us122l); - snd_printk(KERN_ERR "us122l_start error %i \n", err); + dev_err(&us122l->dev->dev, "%s error %i\n", __func__, err); goto out; } list_for_each(p, &us122l->midi_list) @@ -376,12 +306,12 @@ out: } static int usb_stream_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, - unsigned cmd, unsigned long arg) + unsigned int cmd, unsigned long arg) { struct usb_stream_config cfg; struct us122l *us122l = hw->private_data; struct usb_stream *s; - unsigned min_period_frames; + unsigned int min_period_frames; int err = 0; bool high_speed; @@ -418,13 +348,13 @@ static int usb_stream_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, if (cfg.period_frames < min_period_frames) return -EINVAL; - snd_power_wait(hw->card, SNDRV_CTL_POWER_D0); + snd_power_wait(hw->card); - mutex_lock(&us122l->mutex); + guard(mutex)(&us122l->mutex); s = us122l->sk.s; - if (!us122l->master) + if (!us122l->master) { us122l->master = file; - else if (us122l->master != file) { + } else if (us122l->master != file) { if (!s || memcmp(&cfg, &s->cfg, sizeof(cfg))) { err = -EIO; goto unlock; @@ -440,7 +370,6 @@ static int usb_stream_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, err = 1; } unlock: - mutex_unlock(&us122l->mutex); wake_up_all(&us122l->sk.sleep); return err; } @@ -470,7 +399,6 @@ static int usb_stream_hwdep_new(struct snd_card *card) return 0; } - static bool us122l_create_card(struct snd_card *card) { int err; @@ -479,13 +407,13 @@ static bool us122l_create_card(struct snd_card *card) if (us122l->is_us144) { err = usb_set_interface(us122l->dev, 0, 1); if (err) { - snd_printk(KERN_ERR "usb_set_interface error \n"); + dev_err(card->dev, "usb_set_interface error\n"); return false; } } err = usb_set_interface(us122l->dev, 1, 1); if (err) { - snd_printk(KERN_ERR "usb_set_interface error \n"); + dev_err(card->dev, "usb_set_interface error\n"); return false; } @@ -500,13 +428,14 @@ static bool us122l_create_card(struct snd_card *card) else err = us122l_create_usbmidi(card); if (err < 0) { - snd_printk(KERN_ERR "us122l_create_usbmidi error %i \n", err); + dev_err(card->dev, "us122l_create_usbmidi error %i\n", err); goto stop; } err = usb_stream_hwdep_new(card); if (err < 0) { -/* release the midi resources */ + /* release the midi resources */ struct list_head *p; + list_for_each(p, &us122l->midi_list) snd_usbmidi_disconnect(p); @@ -523,7 +452,8 @@ static void snd_us122l_free(struct snd_card *card) { struct us122l *us122l = US122L(card); int index = us122l->card_index; - if (index >= 0 && index < SNDRV_CARDS) + + if (index >= 0 && index < SNDRV_CARDS) snd_us122l_card_used[index] = 0; } @@ -549,10 +479,11 @@ static int usx2y_create_card(struct usb_device *device, card->private_free = snd_us122l_free; US122L(card)->dev = device; mutex_init(&US122L(card)->mutex); + US122L(card)->sk.dev = device; init_waitqueue_head(&US122L(card)->sk.sleep); US122L(card)->is_us144 = flags & US122L_FLAG_US144; INIT_LIST_HEAD(&US122L(card)->midi_list); - strcpy(card->driver, "USB "NAME_ALLCAPS""); + strscpy(card->driver, "USB "NAME_ALLCAPS""); sprintf(card->shortname, "TASCAM "NAME_ALLCAPS""); sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)", card->shortname, @@ -604,12 +535,10 @@ static int snd_us122l_probe(struct usb_interface *intf, if (id->driver_info & US122L_FLAG_US144 && device->speed == USB_SPEED_HIGH) { - snd_printk(KERN_ERR "disable ehci-hcd to run US-144 \n"); + dev_err(&device->dev, "disable ehci-hcd to run US-144\n"); return -ENODEV; } - snd_printdd(KERN_DEBUG"%p:%i\n", - intf, intf->cur_altsetting->desc.bInterfaceNumber); if (intf->cur_altsetting->desc.bInterfaceNumber != 1) return 0; @@ -636,11 +565,11 @@ static void snd_us122l_disconnect(struct usb_interface *intf) snd_card_disconnect(card); us122l = US122L(card); - mutex_lock(&us122l->mutex); - us122l_stop(us122l); - mutex_unlock(&us122l->mutex); + scoped_guard(mutex, &us122l->mutex) { + us122l_stop(us122l); + } -/* release the midi resources */ + /* release the midi resources */ list_for_each(p, &us122l->midi_list) { snd_usbmidi_disconnect(p); } @@ -649,10 +578,7 @@ static void snd_us122l_disconnect(struct usb_interface *intf) usb_put_intf(usb_ifnum_to_if(us122l->dev, 1)); usb_put_dev(us122l->dev); - while (atomic_read(&us122l->mmap_count)) - msleep(500); - - snd_card_free(card); + snd_card_free_when_closed(card); } static int snd_us122l_suspend(struct usb_interface *intf, pm_message_t message) @@ -673,9 +599,8 @@ static int snd_us122l_suspend(struct usb_interface *intf, pm_message_t message) list_for_each(p, &us122l->midi_list) snd_usbmidi_input_stop(p); - mutex_lock(&us122l->mutex); + guard(mutex)(&us122l->mutex); usb_stream_stop(&us122l->sk); - mutex_unlock(&us122l->mutex); return 0; } @@ -695,18 +620,18 @@ static int snd_us122l_resume(struct usb_interface *intf) if (!us122l) return 0; - mutex_lock(&us122l->mutex); + guard(mutex)(&us122l->mutex); /* needed, doesn't restart without: */ if (us122l->is_us144) { err = usb_set_interface(us122l->dev, 0, 1); if (err) { - snd_printk(KERN_ERR "usb_set_interface error \n"); + dev_err(&us122l->dev->dev, "usb_set_interface error\n"); goto unlock; } } err = usb_set_interface(us122l->dev, 1, 1); if (err) { - snd_printk(KERN_ERR "usb_set_interface error \n"); + dev_err(&us122l->dev->dev, "usb_set_interface error\n"); goto unlock; } @@ -716,7 +641,7 @@ static int snd_us122l_resume(struct usb_interface *intf) err = us122l_set_sample_rate(us122l->dev, us122l->sk.s->cfg.sample_rate); if (err < 0) { - snd_printk(KERN_ERR "us122l_set_sample_rate error \n"); + dev_err(&us122l->dev->dev, "us122l_set_sample_rate error\n"); goto unlock; } err = usb_stream_start(&us122l->sk); @@ -726,7 +651,6 @@ static int snd_us122l_resume(struct usb_interface *intf) list_for_each(p, &us122l->midi_list) snd_usbmidi_input_start(p); unlock: - mutex_unlock(&us122l->mutex); snd_power_change_state(card, SNDRV_CTL_POWER_D0); return err; } @@ -748,16 +672,10 @@ static const struct usb_device_id snd_us122l_usb_id_table[] = { .idVendor = 0x0644, .idProduct = USB_ID_US122MKII }, - { - .match_flags = USB_DEVICE_ID_MATCH_DEVICE, - .idVendor = 0x0644, - .idProduct = USB_ID_US144MKII, - .driver_info = US122L_FLAG_US144 - }, { /* terminator */ } }; - MODULE_DEVICE_TABLE(usb, snd_us122l_usb_id_table); + static struct usb_driver snd_us122l_usb_driver = { .name = "snd-usb-us122l", .probe = snd_us122l_probe, diff --git a/sound/usb/usx2y/us122l.h b/sound/usb/usx2y/us122l.h index 34bea99d343c..8e59b78d3514 100644 --- a/sound/usb/usx2y/us122l.h +++ b/sound/usb/usx2y/us122l.h @@ -11,13 +11,11 @@ struct us122l { struct mutex mutex; struct file *first; - unsigned second_periods_polled; + unsigned int second_periods_polled; struct file *master; struct file *slave; struct list_head midi_list; - atomic_t mmap_count; - bool is_us144; }; diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c new file mode 100644 index 000000000000..f6572a576c15 --- /dev/null +++ b/sound/usb/usx2y/us144mkii.c @@ -0,0 +1,620 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 Å erif Rami <ramiserifpersia@gmail.com> +/* + * ALSA Driver for TASCAM US-144MKII Audio Interface + */ + +#include "us144mkii.h" + +MODULE_AUTHOR("Å erif Rami <ramiserifpersia@gmail.com>"); +MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII"); +MODULE_LICENSE("GPL"); + +/** + * @brief Module parameters for ALSA card instantiation. + * + * These parameters allow users to configure how the ALSA sound card + * for the TASCAM US-144MKII is instantiated. + * + * @param index: Array of integers specifying the ALSA card index for each + * device. Defaults to -1 (automatic). + * @param id: Array of strings specifying the ALSA card ID for each device. + * Defaults to "US144MKII". + * @param enable: Array of booleans to enable or disable each device. + * Defaults to {1, 0, ..., 0} (first device enabled). + * @param dev_idx: Internal counter for the number of TASCAM devices probed. + */ +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = { 1, [1 ...(SNDRV_CARDS - 1)] = 0 }; +static int dev_idx; + +static int tascam_probe(struct usb_interface *intf, + const struct usb_device_id *usb_id); +static void tascam_disconnect(struct usb_interface *intf); +static int tascam_suspend(struct usb_interface *intf, pm_message_t message); +static int tascam_resume(struct usb_interface *intf); + +void tascam_free_urbs(struct tascam_card *tascam) +{ + int i; + + usb_kill_anchored_urbs(&tascam->playback_anchor); + for (i = 0; i < NUM_PLAYBACK_URBS; i++) { + if (tascam->playback_urbs[i]) { + usb_free_coherent( + tascam->dev, tascam->playback_urb_alloc_size, + tascam->playback_urbs[i]->transfer_buffer, + tascam->playback_urbs[i]->transfer_dma); + usb_free_urb(tascam->playback_urbs[i]); + tascam->playback_urbs[i] = NULL; + } + } + + usb_kill_anchored_urbs(&tascam->feedback_anchor); + for (i = 0; i < NUM_FEEDBACK_URBS; i++) { + if (tascam->feedback_urbs[i]) { + usb_free_coherent( + tascam->dev, tascam->feedback_urb_alloc_size, + tascam->feedback_urbs[i]->transfer_buffer, + tascam->feedback_urbs[i]->transfer_dma); + usb_free_urb(tascam->feedback_urbs[i]); + tascam->feedback_urbs[i] = NULL; + } + } + + usb_kill_anchored_urbs(&tascam->capture_anchor); + for (i = 0; i < NUM_CAPTURE_URBS; i++) { + if (tascam->capture_urbs[i]) { + usb_free_coherent( + tascam->dev, tascam->capture_urb_alloc_size, + tascam->capture_urbs[i]->transfer_buffer, + tascam->capture_urbs[i]->transfer_dma); + usb_free_urb(tascam->capture_urbs[i]); + tascam->capture_urbs[i] = NULL; + } + } + + usb_kill_anchored_urbs(&tascam->midi_in_anchor); + for (i = 0; i < NUM_MIDI_IN_URBS; i++) { + if (tascam->midi_in_urbs[i]) { + usb_free_coherent( + tascam->dev, MIDI_IN_BUF_SIZE, + tascam->midi_in_urbs[i]->transfer_buffer, + tascam->midi_in_urbs[i]->transfer_dma); + usb_free_urb(tascam->midi_in_urbs[i]); + tascam->midi_in_urbs[i] = NULL; + } + } + + usb_kill_anchored_urbs(&tascam->midi_out_anchor); + for (i = 0; i < NUM_MIDI_OUT_URBS; i++) { + if (tascam->midi_out_urbs[i]) { + usb_free_coherent( + tascam->dev, MIDI_OUT_BUF_SIZE, + tascam->midi_out_urbs[i]->transfer_buffer, + tascam->midi_out_urbs[i]->transfer_dma); + usb_free_urb(tascam->midi_out_urbs[i]); + tascam->midi_out_urbs[i] = NULL; + } + } + + kfree(tascam->capture_routing_buffer); + tascam->capture_routing_buffer = NULL; + kfree(tascam->capture_decode_dst_block); + tascam->capture_decode_dst_block = NULL; + kfree(tascam->capture_decode_raw_block); + tascam->capture_decode_raw_block = NULL; + kfree(tascam->capture_ring_buffer); + tascam->capture_ring_buffer = NULL; +} + +int tascam_alloc_urbs(struct tascam_card *tascam) +{ + int i; + size_t max_packet_size; + + max_packet_size = ((96000 / 8000) + 2) * BYTES_PER_FRAME; + tascam->playback_urb_alloc_size = + max_packet_size * PLAYBACK_URB_PACKETS; + + for (i = 0; i < NUM_PLAYBACK_URBS; i++) { + struct urb *urb = + usb_alloc_urb(PLAYBACK_URB_PACKETS, GFP_KERNEL); + + if (!urb) + goto error; + tascam->playback_urbs[i] = urb; + + urb->transfer_buffer = usb_alloc_coherent( + tascam->dev, tascam->playback_urb_alloc_size, + GFP_KERNEL, &urb->transfer_dma); + if (!urb->transfer_buffer) + goto error; + + urb->dev = tascam->dev; + urb->pipe = usb_sndisocpipe(tascam->dev, EP_AUDIO_OUT); + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->interval = 1; + urb->context = tascam; + urb->complete = playback_urb_complete; + } + + tascam->feedback_urb_alloc_size = + FEEDBACK_PACKET_SIZE * FEEDBACK_URB_PACKETS; + + for (i = 0; i < NUM_FEEDBACK_URBS; i++) { + struct urb *f_urb = + usb_alloc_urb(FEEDBACK_URB_PACKETS, GFP_KERNEL); + + if (!f_urb) + goto error; + tascam->feedback_urbs[i] = f_urb; + + f_urb->transfer_buffer = usb_alloc_coherent( + tascam->dev, tascam->feedback_urb_alloc_size, + GFP_KERNEL, &f_urb->transfer_dma); + if (!f_urb->transfer_buffer) + goto error; + + f_urb->dev = tascam->dev; + f_urb->pipe = + usb_rcvisocpipe(tascam->dev, EP_PLAYBACK_FEEDBACK); + f_urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + f_urb->interval = 4; + f_urb->context = tascam; + f_urb->complete = feedback_urb_complete; + } + + tascam->capture_urb_alloc_size = CAPTURE_URB_SIZE; + for (i = 0; i < NUM_CAPTURE_URBS; i++) { + struct urb *c_urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!c_urb) + goto error; + tascam->capture_urbs[i] = c_urb; + + c_urb->transfer_buffer = usb_alloc_coherent( + tascam->dev, tascam->capture_urb_alloc_size, GFP_KERNEL, + &c_urb->transfer_dma); + if (!c_urb->transfer_buffer) + goto error; + + usb_fill_bulk_urb(c_urb, tascam->dev, + usb_rcvbulkpipe(tascam->dev, EP_AUDIO_IN), + c_urb->transfer_buffer, + tascam->capture_urb_alloc_size, + capture_urb_complete, tascam); + c_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + } + + /* MIDI URB and buffer allocation */ + for (i = 0; i < NUM_MIDI_IN_URBS; i++) { + struct urb *m_urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!m_urb) + goto error; + tascam->midi_in_urbs[i] = m_urb; + m_urb->transfer_buffer = + usb_alloc_coherent(tascam->dev, MIDI_IN_BUF_SIZE, + GFP_KERNEL, &m_urb->transfer_dma); + if (!m_urb->transfer_buffer) + goto error; + usb_fill_bulk_urb(m_urb, tascam->dev, + usb_rcvbulkpipe(tascam->dev, EP_MIDI_IN), + m_urb->transfer_buffer, MIDI_IN_BUF_SIZE, + tascam_midi_in_urb_complete, tascam); + m_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + } + + for (i = 0; i < NUM_MIDI_OUT_URBS; i++) { + struct urb *m_urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!m_urb) + goto error; + tascam->midi_out_urbs[i] = m_urb; + m_urb->transfer_buffer = + usb_alloc_coherent(tascam->dev, MIDI_OUT_BUF_SIZE, + GFP_KERNEL, &m_urb->transfer_dma); + if (!m_urb->transfer_buffer) + goto error; + usb_fill_bulk_urb(m_urb, tascam->dev, + usb_sndbulkpipe(tascam->dev, EP_MIDI_OUT), + m_urb->transfer_buffer, + 0, /* length set later */ + tascam_midi_out_urb_complete, tascam); + m_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + } + + tascam->capture_ring_buffer = + kmalloc(CAPTURE_RING_BUFFER_SIZE, GFP_KERNEL); + if (!tascam->capture_ring_buffer) + goto error; + + tascam->capture_decode_raw_block = + kmalloc(RAW_BYTES_PER_DECODE_BLOCK, GFP_KERNEL); + if (!tascam->capture_decode_raw_block) + goto error; + + tascam->capture_decode_dst_block = + kmalloc(FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME * + DECODED_SAMPLE_SIZE, + GFP_KERNEL); + if (!tascam->capture_decode_dst_block) + goto error; + + tascam->capture_routing_buffer = + kmalloc(FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME * + DECODED_SAMPLE_SIZE, + GFP_KERNEL); + if (!tascam->capture_routing_buffer) + goto error; + + return 0; + +error: + dev_err(tascam->card->dev, "Failed to allocate URBs\n"); + tascam_free_urbs(tascam); + return -ENOMEM; +} + +void tascam_stop_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam = + container_of(work, struct tascam_card, stop_work); + + usb_kill_anchored_urbs(&tascam->playback_anchor); + usb_kill_anchored_urbs(&tascam->feedback_anchor); + usb_kill_anchored_urbs(&tascam->capture_anchor); + atomic_set(&tascam->active_urbs, 0); +} + +/** + * tascam_card_private_free() - Frees private data associated with the sound + * card. + * @card: Pointer to the ALSA sound card instance. + * + * This function is called when the sound card is being freed. It releases + * resources allocated for the tascam_card structure, including the MIDI + * input FIFO and decrements the USB device reference count. + */ +static void tascam_card_private_free(struct snd_card *card) +{ + struct tascam_card *tascam = card->private_data; + + if (tascam) { + kfifo_free(&tascam->midi_in_fifo); + if (tascam->dev) { + usb_put_dev(tascam->dev); + tascam->dev = NULL; + } + } +} + +/** + * tascam_suspend() - Handles device suspension. + * @intf: The USB interface being suspended. + * @message: Power management message. + * + * This function is called when the device is suspended. It stops all active + * streams, kills all URBs, and sends a vendor-specific deep sleep command + * to the device to ensure a stable low-power state. + * + * Return: 0 on success. + */ +static int tascam_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct tascam_card *tascam = usb_get_intfdata(intf); + + if (!tascam) + return 0; + + snd_pcm_suspend_all(tascam->pcm); + + cancel_work_sync(&tascam->stop_work); + cancel_work_sync(&tascam->capture_work); + cancel_work_sync(&tascam->midi_in_work); + cancel_work_sync(&tascam->midi_out_work); + cancel_work_sync(&tascam->stop_pcm_work); + usb_kill_anchored_urbs(&tascam->playback_anchor); + usb_kill_anchored_urbs(&tascam->capture_anchor); + usb_kill_anchored_urbs(&tascam->feedback_anchor); + usb_kill_anchored_urbs(&tascam->midi_in_anchor); + usb_kill_anchored_urbs(&tascam->midi_out_anchor); + + dev_info(&intf->dev, "sending deep sleep command\n"); + int err = usb_control_msg(tascam->dev, usb_sndctrlpipe(tascam->dev, 0), + VENDOR_REQ_DEEP_SLEEP, RT_H2D_VENDOR_DEV, + 0x0000, 0x0000, NULL, 0, USB_CTRL_TIMEOUT_MS); + if (err < 0) + dev_err(&intf->dev, "deep sleep command failed: %d\n", err); + + return 0; +} + +/** + * tascam_resume() - Handles device resumption from suspend. + * @intf: The USB interface being resumed. + * + * This function is called when the device resumes from suspend. It + * re-establishes the active USB interface settings and re-configures the sample + * rate if it was previously active. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int tascam_resume(struct usb_interface *intf) +{ + struct tascam_card *tascam = usb_get_intfdata(intf); + int err; + + if (!tascam) + return 0; + + dev_info(&intf->dev, "resuming TASCAM US-144MKII\n"); + + /* + * The device requires a full re-initialization sequence upon resume. + * First, re-establish the active USB interface settings. + */ + err = usb_set_interface(tascam->dev, 0, 1); + if (err < 0) { + dev_err(&intf->dev, + "resume: failed to set alt setting on intf 0: %d\n", + err); + return err; + } + err = usb_set_interface(tascam->dev, 1, 1); + if (err < 0) { + dev_err(&intf->dev, + "resume: failed to set alt setting on intf 1: %d\n", + err); + return err; + } + + /* Re-configure the sample rate if one was previously active */ + if (tascam->current_rate > 0) + us144mkii_configure_device_for_rate(tascam, + tascam->current_rate); + + return 0; +} + +static void tascam_error_timer(struct timer_list *t) +{ + struct tascam_card *tascam = + container_of(t, struct tascam_card, error_timer); + + if (atomic_read(&tascam->midi_in_active)) + schedule_work(&tascam->midi_in_work); + if (atomic_read(&tascam->midi_out_active)) + schedule_work(&tascam->midi_out_work); +} + +/** + * tascam_probe() - Probes for the TASCAM US-144MKII device. + * @intf: The USB interface being probed. + * @usb_id: The USB device ID. + * + * This function is the entry point for the USB driver when a matching device + * is found. It performs initial device setup, including: + * - Checking for the second interface (MIDI) and associating it. + * - Performing a vendor-specific handshake with the device. + * - Setting alternate settings for USB interfaces. + * - Creating and registering the ALSA sound card, PCM device, and MIDI device. + * - Allocating and initializing URBs for audio and MIDI transfers. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int tascam_probe(struct usb_interface *intf, + const struct usb_device_id *usb_id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct snd_card *card; + struct tascam_card *tascam; + int err; + char *handshake_buf __free(kfree) = NULL; + + if (dev->speed != USB_SPEED_HIGH) + dev_info( + &dev->dev, + "Device is connected to a USB 1.1 port, this is not supported.\n"); + + /* The device has two interfaces; we drive both from this driver. */ + if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { + tascam = usb_get_intfdata(usb_ifnum_to_if(dev, 0)); + if (tascam) { + usb_set_intfdata(intf, tascam); + tascam->iface1 = intf; + } + return 0; /* Let the core handle this interface */ + } + + if (dev_idx >= SNDRV_CARDS) { + dev_err(&dev->dev, "Too many TASCAM devices present"); + return -ENODEV; + } + + if (!enable[dev_idx]) { + dev_info(&dev->dev, "TASCAM US-144MKII device disabled"); + return -ENOENT; + } + + handshake_buf = kmalloc(1, GFP_KERNEL); + if (!handshake_buf) + return -ENOMEM; + + /* Perform vendor-specific handshake */ + err = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + VENDOR_REQ_MODE_CONTROL, RT_D2H_VENDOR_DEV, + MODE_VAL_HANDSHAKE_READ, 0x0000, handshake_buf, 1, + USB_CTRL_TIMEOUT_MS); + if (err < 0) { + dev_err(&dev->dev, "Handshake read failed with %d\n", err); + return err; + } + + if (handshake_buf[0] != 0x12 && handshake_buf[0] != 0x16 && + handshake_buf[0] != 0x30 && handshake_buf[0] != 0x32) { + dev_err(&dev->dev, "Unexpected handshake value: 0x%x\n", + handshake_buf[0]); + return -ENODEV; + } + + /* Set alternate settings to enable audio/MIDI endpoints */ + err = usb_set_interface(dev, 0, 1); + if (err < 0) { + dev_err(&dev->dev, + "Failed to set alt setting 1 on interface 0: %d\n", + err); + return err; + } + + err = usb_set_interface(dev, 1, 1); + if (err < 0) { + dev_err(&dev->dev, + "Failed to set alt setting 1 on interface 1: %d\n", + err); + return err; + } + + err = snd_card_new(&dev->dev, index[dev_idx], id[dev_idx], THIS_MODULE, + sizeof(struct tascam_card), &card); + if (err < 0) { + dev_err(&dev->dev, "Failed to create sound card instance\n"); + return err; + } + + tascam = card->private_data; + card->private_free = tascam_card_private_free; + tascam->dev = usb_get_dev(dev); + tascam->card = card; + tascam->iface0 = intf; + tascam->digital_out_source = 1; + tascam->capture_34_source = 1; + + spin_lock_init(&tascam->lock); + spin_lock_init(&tascam->midi_in_lock); + spin_lock_init(&tascam->midi_out_lock); + init_usb_anchor(&tascam->playback_anchor); + init_usb_anchor(&tascam->capture_anchor); + init_usb_anchor(&tascam->feedback_anchor); + init_usb_anchor(&tascam->midi_in_anchor); + init_usb_anchor(&tascam->midi_out_anchor); + + timer_setup(&tascam->error_timer, tascam_error_timer, 0); + + INIT_WORK(&tascam->stop_work, tascam_stop_work_handler); + INIT_WORK(&tascam->stop_pcm_work, tascam_stop_pcm_work_handler); + INIT_WORK(&tascam->capture_work, tascam_capture_work_handler); + init_completion(&tascam->midi_out_drain_completion); + + if (kfifo_alloc(&tascam->midi_in_fifo, MIDI_IN_FIFO_SIZE, GFP_KERNEL)) { + snd_card_free(card); + return -ENOMEM; + } + + strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); + if (le16_to_cpu(dev->descriptor.idProduct) == USB_PID_TASCAM_US144) { + strscpy(card->shortname, "TASCAM US-144", + sizeof(card->shortname)); + } else if (le16_to_cpu(dev->descriptor.idProduct) == USB_PID_TASCAM_US144MKII) { + strscpy(card->shortname, "TASCAM US-144MKII", + sizeof(card->shortname)); + } else { + strscpy(card->shortname, "TASCAM Unknown", + sizeof(card->shortname)); + } + snprintf(card->longname, sizeof(card->longname), "%s (%04x:%04x) at %s", + card->shortname, USB_VID_TASCAM, dev->descriptor.idProduct, + dev_name(&dev->dev)); + + err = snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm); + if (err < 0) + goto free_card; + tascam->pcm->private_data = tascam; + strscpy(tascam->pcm->name, "US144MKII PCM", sizeof(tascam->pcm->name)); + + err = tascam_init_pcm(tascam->pcm); + if (err < 0) + goto free_card; + + err = tascam_create_midi(tascam); + if (err < 0) + goto free_card; + + err = tascam_create_controls(tascam); + if (err < 0) + goto free_card; + + err = tascam_alloc_urbs(tascam); + if (err < 0) + goto free_card; + + err = snd_card_register(card); + if (err < 0) + goto free_card; + + usb_set_intfdata(intf, tascam); + + dev_idx++; + return 0; + +free_card: + tascam_free_urbs(tascam); + snd_card_free(card); + return err; +} + +/** + * tascam_disconnect() - Disconnects the TASCAM US-144MKII device. + * @intf: The USB interface being disconnected. + * + * This function is called when the device is disconnected from the system. + * It cleans up all allocated resources, including killing URBs, freeing + * the sound card, and releasing memory. + */ +static void tascam_disconnect(struct usb_interface *intf) +{ + struct tascam_card *tascam = usb_get_intfdata(intf); + + if (!tascam) + return; + + if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { + /* Ensure all deferred work is complete before freeing resources */ + snd_card_disconnect(tascam->card); + cancel_work_sync(&tascam->stop_work); + cancel_work_sync(&tascam->capture_work); + cancel_work_sync(&tascam->midi_in_work); + cancel_work_sync(&tascam->midi_out_work); + cancel_work_sync(&tascam->stop_pcm_work); + + usb_kill_anchored_urbs(&tascam->playback_anchor); + usb_kill_anchored_urbs(&tascam->capture_anchor); + usb_kill_anchored_urbs(&tascam->feedback_anchor); + usb_kill_anchored_urbs(&tascam->midi_in_anchor); + usb_kill_anchored_urbs(&tascam->midi_out_anchor); + timer_delete_sync(&tascam->error_timer); + tascam_free_urbs(tascam); + snd_card_free(tascam->card); + dev_idx--; + } +} + +static const struct usb_device_id tascam_usb_ids[] = { + { USB_DEVICE(USB_VID_TASCAM, USB_PID_TASCAM_US144) }, + { USB_DEVICE(USB_VID_TASCAM, USB_PID_TASCAM_US144MKII) }, + { /* Terminating entry */ } +}; +MODULE_DEVICE_TABLE(usb, tascam_usb_ids); + +static struct usb_driver tascam_alsa_driver = { + .name = DRIVER_NAME, + .probe = tascam_probe, + .disconnect = tascam_disconnect, + .suspend = tascam_suspend, + .resume = tascam_resume, + .id_table = tascam_usb_ids, +}; + +module_usb_driver(tascam_alsa_driver); diff --git a/sound/usb/usx2y/us144mkii.h b/sound/usb/usx2y/us144mkii.h new file mode 100644 index 000000000000..95c4341f038a --- /dev/null +++ b/sound/usb/usx2y/us144mkii.h @@ -0,0 +1,367 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +// Copyright (c) 2025 Å erif Rami <ramiserifpersia@gmail.com> + +#ifndef __US144MKII_H +#define __US144MKII_H + +#include <linux/kfifo.h> +#include <linux/timer.h> +#include <linux/usb.h> +#include <linux/workqueue.h> +#include <sound/control.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/rawmidi.h> + +#define DRIVER_NAME "us144mkii" + +/* --- USB Device Identification --- */ +#define USB_VID_TASCAM 0x0644 +#define USB_PID_TASCAM_US144 0x800f +#define USB_PID_TASCAM_US144MKII 0x8020 + +/* --- USB Endpoints (Alternate Setting 1) --- */ +#define EP_PLAYBACK_FEEDBACK 0x81 +#define EP_AUDIO_OUT 0x02 +#define EP_MIDI_IN 0x83 +#define EP_MIDI_OUT 0x04 +#define EP_AUDIO_IN 0x86 + +/* --- USB Control Message Protocol --- */ +#define RT_H2D_CLASS_EP (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT) +#define RT_D2H_CLASS_EP (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT) +#define RT_H2D_VENDOR_DEV (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE) +#define RT_D2H_VENDOR_DEV (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE) + +enum uac_request { + UAC_SET_CUR = 0x01, + UAC_GET_CUR = 0x81, +}; + +enum uac_control_selector { + UAC_SAMPLING_FREQ_CONTROL = 0x0100, +}; + +enum tascam_vendor_request { + VENDOR_REQ_REGISTER_WRITE = 0x41, + VENDOR_REQ_DEEP_SLEEP = 0x44, + VENDOR_REQ_MODE_CONTROL = 0x49, +}; + +enum tascam_mode_value { + MODE_VAL_HANDSHAKE_READ = 0x0000, + MODE_VAL_CONFIG = 0x0010, + MODE_VAL_STREAM_START = 0x0030, +}; + +#define HANDSHAKE_SUCCESS_VAL 0x12 + +enum tascam_register { + REG_ADDR_UNKNOWN_0D = 0x0d04, + REG_ADDR_UNKNOWN_0E = 0x0e00, + REG_ADDR_UNKNOWN_0F = 0x0f00, + REG_ADDR_RATE_44100 = 0x1000, + REG_ADDR_RATE_48000 = 0x1002, + REG_ADDR_RATE_88200 = 0x1008, + REG_ADDR_RATE_96000 = 0x100a, + REG_ADDR_UNKNOWN_11 = 0x110b, +}; + +#define REG_VAL_ENABLE 0x0101 + +/* --- URB Configuration --- */ +#define NUM_PLAYBACK_URBS 4 +#define PLAYBACK_URB_PACKETS 8 +#define NUM_FEEDBACK_URBS 4 +#define FEEDBACK_URB_PACKETS 1 +#define FEEDBACK_PACKET_SIZE 3 +#define NUM_CAPTURE_URBS 8 +#define CAPTURE_URB_SIZE 512 +#define CAPTURE_RING_BUFFER_SIZE (CAPTURE_URB_SIZE * NUM_CAPTURE_URBS * 4) +#define NUM_MIDI_IN_URBS 4 +#define MIDI_IN_BUF_SIZE 64 +#define MIDI_IN_FIFO_SIZE (MIDI_IN_BUF_SIZE * NUM_MIDI_IN_URBS) +#define MIDI_OUT_BUF_SIZE 64 +#define NUM_MIDI_OUT_URBS 4 +#define USB_CTRL_TIMEOUT_MS 1000 +#define FEEDBACK_SYNC_LOSS_THRESHOLD 41 + +/* --- Audio Format Configuration --- */ +#define BYTES_PER_SAMPLE 3 +#define NUM_CHANNELS 4 +#define BYTES_PER_FRAME (NUM_CHANNELS * BYTES_PER_SAMPLE) +#define FEEDBACK_ACCUMULATOR_SIZE 128 + +/* --- Capture Decoding Defines --- */ +#define DECODED_CHANNELS_PER_FRAME 4 +#define DECODED_SAMPLE_SIZE 4 +#define FRAMES_PER_DECODE_BLOCK 8 +#define RAW_BYTES_PER_DECODE_BLOCK 512 + +/** + * struct us144mkii_frame_pattern_observer - State for dynamic feedback + * patterns. + * @sample_rate_khz: The current sample rate in kHz. + * @base_feedback_value: The nominal feedback value for the current rate. + * @feedback_offset: An offset to align the feedback value range. + * @full_frame_patterns: A 2D array of pre-calculated packet size patterns. + * @current_index: The current index into the pattern array. + * @previous_index: The previous index, used for state tracking. + * @sync_locked: A flag indicating if the pattern has locked to the stream. + */ +struct us144mkii_frame_pattern_observer { + unsigned int sample_rate_khz; + unsigned int base_feedback_value; + int feedback_offset; + unsigned int full_frame_patterns[5][8]; + unsigned int current_index; + unsigned int previous_index; + bool sync_locked; +}; + +/** + * struct tascam_card - Main driver data structure for the TASCAM US-144MKII. + * @dev: Pointer to the USB device. + * @iface0: Pointer to USB interface 0 (audio). + * @iface1: Pointer to USB interface 1 (MIDI). + * @card: Pointer to the ALSA sound card instance. + * @pcm: Pointer to the ALSA PCM device. + * @rmidi: Pointer to the ALSA rawmidi device. + * + * @playback_substream: Pointer to the active playback PCM substream. + * @playback_urbs: Array of URBs for playback. + * @playback_urb_alloc_size: Size of allocated buffer for each playback URB. + * @feedback_urbs: Array of URBs for feedback. + * @feedback_urb_alloc_size: Size of allocated buffer for each feedback URB. + * @playback_active: Atomic flag indicating if playback is active. + * @playback_frames_consumed: Total frames consumed by playback. + * @driver_playback_pos: Current position in the ALSA playback buffer (frames). + * @last_period_pos: Last reported period position for playback. + * + * @capture_substream: Pointer to the active capture PCM substream. + * @capture_urbs: Array of URBs for capture. + * @capture_urb_alloc_size: Size of allocated buffer for each capture URB. + * @capture_active: Atomic flag indicating if capture is active. + * @driver_capture_pos: Current position in the ALSA capture buffer (frames). + * @capture_frames_processed: Total frames processed for capture. + * @last_capture_period_pos: Last reported period position for capture. + * @capture_ring_buffer: Ring buffer for raw capture data from USB. + * @capture_ring_buffer_read_ptr: Read pointer for the capture ring buffer. + * @capture_ring_buffer_write_ptr: Write pointer for the capture ring buffer. + * @capture_decode_raw_block: Buffer for a raw 512-byte capture block. + * @capture_decode_dst_block: Buffer for decoded 32-bit capture samples. + * @capture_routing_buffer: Intermediate buffer for capture routing. + * @capture_work: Work struct for deferred capture processing. + * @stop_work: Work struct for deferred stream stopping. + * @stop_pcm_work: Work struct for stopping PCM due to a fatal error (e.g. + * xrun). + * + * @midi_in_substream: Pointer to the active MIDI input substream. + * @midi_out_substream: Pointer to the active MIDI output substream. + * @midi_in_urbs: Array of URBs for MIDI input. + * @midi_out_urbs: Array of URBs for MIDI output. + * @midi_in_active: Atomic flag indicating if MIDI input is active. + * @midi_out_active: Atomic flag indicating if MIDI output is active. + * @midi_in_fifo: FIFO for raw MIDI input data. + * @midi_in_work: Work struct for deferred MIDI input processing. + * @midi_out_work: Work struct for deferred MIDI output processing. + * @midi_in_lock: Spinlock for MIDI input FIFO. + * @midi_out_lock: Spinlock for MIDI output. + * @midi_out_urbs_in_flight: Bitmap of MIDI output URBs currently in flight. + * @midi_running_status: Stores the last MIDI status byte for running status. + * @error_timer: Timer for MIDI error retry logic. + * + * @lock: Main spinlock for protecting shared driver state. + * @active_urbs: Atomic counter for active URBs. + * @current_rate: Currently configured sample rate of the device. + * @line_out_source: Source for Line Outputs (0: Playback 1-2, 1: Playback 3-4). + * @digital_out_source: Source for Digital Outputs (0: Playback 1-2, 1: Playback + * 3-4). + * @capture_12_source: Source for Capture channels 1-2 (0: Analog In, 1: Digital + * In). + * @capture_34_source: Source for Capture channels 3-4 (0: Analog In, 1: Digital + * In). + * + * @feedback_accumulator_pattern: Stores the calculated frames per packet for + * feedback. + * @feedback_pattern_out_idx: Read index for feedback_accumulator_pattern. + * @feedback_pattern_in_idx: Write index for feedback_accumulator_pattern. + * @feedback_synced: Flag indicating if feedback is synced. + * @feedback_consecutive_errors: Counter for consecutive feedback errors. + * @feedback_urb_skip_count: Number of feedback URBs to skip initially for + * stabilization. + * @fpo: Holds the state for the dynamic feedback pattern generation. + * + * @playback_anchor: USB anchor for playback URBs. + * @capture_anchor: USB anchor for capture URBs. + * @feedback_anchor: USB anchor for feedback URBs. + * @midi_in_anchor: USB anchor for MIDI input URBs. + * @midi_out_anchor: USB anchor for MIDI output URBs. + */ +struct tascam_card { + /* --- Core device pointers --- */ + struct usb_device *dev; + struct usb_interface *iface0; + struct usb_interface *iface1; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_rawmidi *rmidi; + + /* --- PCM Substreams --- */ + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + /* --- URBs and Anchors --- */ + struct urb *playback_urbs[NUM_PLAYBACK_URBS]; + size_t playback_urb_alloc_size; + struct urb *feedback_urbs[NUM_FEEDBACK_URBS]; + size_t feedback_urb_alloc_size; + struct urb *capture_urbs[NUM_CAPTURE_URBS]; + size_t capture_urb_alloc_size; + struct urb *midi_in_urbs[NUM_MIDI_IN_URBS]; + struct urb *midi_out_urbs[NUM_MIDI_OUT_URBS]; + struct usb_anchor playback_anchor; + struct usb_anchor capture_anchor; + struct usb_anchor feedback_anchor; + struct usb_anchor midi_in_anchor; + struct usb_anchor midi_out_anchor; + + /* --- Stream State --- */ + spinlock_t lock; + atomic_t playback_active; + atomic_t capture_active; + atomic_t active_urbs; + int current_rate; + + /* --- Playback State --- */ + u64 playback_frames_consumed; + snd_pcm_uframes_t driver_playback_pos; + u64 last_period_pos; + + /* --- Capture State --- */ + u64 capture_frames_processed; + snd_pcm_uframes_t driver_capture_pos; + u64 last_capture_period_pos; + u8 *capture_ring_buffer; + size_t capture_ring_buffer_read_ptr; + size_t capture_ring_buffer_write_ptr; + u8 *capture_decode_raw_block; + s32 *capture_decode_dst_block; + s32 *capture_routing_buffer; + + /* --- MIDI State --- */ + struct snd_rawmidi_substream *midi_in_substream; + struct snd_rawmidi_substream *midi_out_substream; + atomic_t midi_in_active; + atomic_t midi_out_active; + struct kfifo midi_in_fifo; + spinlock_t midi_in_lock; + spinlock_t midi_out_lock; + unsigned long midi_out_urbs_in_flight; + u8 midi_running_status; + struct timer_list error_timer; + struct completion midi_out_drain_completion; + + /* --- Feedback Sync State --- */ + unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE]; + unsigned int feedback_pattern_out_idx; + unsigned int feedback_pattern_in_idx; + bool feedback_synced; + unsigned int feedback_consecutive_errors; + unsigned int feedback_urb_skip_count; + struct us144mkii_frame_pattern_observer fpo; + + /* --- Workqueues --- */ + struct work_struct stop_work; + struct work_struct stop_pcm_work; + struct work_struct capture_work; + struct work_struct midi_in_work; + struct work_struct midi_out_work; + + /* --- Mixer/Routing State --- */ + unsigned int line_out_source; + unsigned int digital_out_source; + unsigned int capture_12_source; + unsigned int capture_34_source; +}; + +/* main.c */ +/** + * tascam_free_urbs() - Free all allocated URBs and associated buffers. + * @tascam: the tascam_card instance + * + * This function kills, unlinks, and frees all playback, feedback, capture, + * and MIDI URBs, along with their transfer buffers and the capture + * ring/decode buffers. + */ +void tascam_free_urbs(struct tascam_card *tascam); + +/** + * tascam_alloc_urbs() - Allocate all URBs and associated buffers. + * @tascam: the tascam_card instance + * + * This function allocates and initializes all URBs for playback, feedback, + * capture, and MIDI, as well as the necessary buffers for data processing. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_alloc_urbs(struct tascam_card *tascam); + +/** + * tascam_stop_work_handler() - Work handler to stop all active streams. + * @work: Pointer to the work_struct. + * + * This function is scheduled to stop all active URBs (playback, feedback, + * capture) and reset the active_urbs counter. + */ +void tascam_stop_work_handler(struct work_struct *work); + +/* us144mkii_pcm.h */ +#include "us144mkii_pcm.h" + +/* us144mkii_midi.c */ +/** + * tascam_midi_in_urb_complete() - Completion handler for MIDI IN URBs + * @urb: The completed URB. + * + * This function runs in interrupt context. It places the raw data from the + * USB endpoint into a kfifo and schedules a work item to process it later, + * ensuring the interrupt handler remains fast. + */ +void tascam_midi_in_urb_complete(struct urb *urb); + +/** + * tascam_midi_out_urb_complete() - Completion handler for MIDI OUT bulk URB. + * @urb: The completed URB. + * + * This function runs in interrupt context. It marks the output URB as no + * longer in-flight. It then re-schedules the work handler to check for and + * send any more data waiting in the ALSA buffer. This is a safe, non-blocking + * way to continue the data transmission chain. + */ +void tascam_midi_out_urb_complete(struct urb *urb); + +/** + * tascam_create_midi() - Create and initialize the ALSA rawmidi device. + * @tascam: The driver instance. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_create_midi(struct tascam_card *tascam); + +/* us144mkii_controls.c */ +/** + * tascam_create_controls() - Creates and adds ALSA mixer controls for the + * device. + * @tascam: The driver instance. + * + * This function registers custom ALSA controls for managing audio routing + * (line out source, digital out source, capture 1-2 source, capture 3-4 source) + * and displaying the current sample rate. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_create_controls(struct tascam_card *tascam); + +#endif /* __US144MKII_H */ diff --git a/sound/usb/usx2y/us144mkii_capture.c b/sound/usb/usx2y/us144mkii_capture.c new file mode 100644 index 000000000000..00188ff6cd51 --- /dev/null +++ b/sound/usb/usx2y/us144mkii_capture.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 Å erif Rami <ramiserifpersia@gmail.com> + +#include "us144mkii.h" + +/** + * tascam_capture_open() - Opens the PCM capture substream. + * @substream: The ALSA PCM substream to open. + * + * This function sets the hardware parameters for the capture substream + * and stores a reference to the substream in the driver's private data. + * + * Return: 0 on success. + */ +static int tascam_capture_open(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam = snd_pcm_substream_chip(substream); + + substream->runtime->hw = tascam_pcm_hw; + tascam->capture_substream = substream; + atomic_set(&tascam->capture_active, 0); + + return 0; +} + +/** + * tascam_capture_close() - Closes the PCM capture substream. + * @substream: The ALSA PCM substream to close. + * + * This function clears the reference to the capture substream in the + * driver's private data. + * + * Return: 0 on success. + */ +static int tascam_capture_close(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam = snd_pcm_substream_chip(substream); + + tascam->capture_substream = NULL; + + return 0; +} + +/** + * tascam_capture_prepare() - Prepares the PCM capture substream for use. + * @substream: The ALSA PCM substream to prepare. + * + * This function initializes capture-related counters and ring buffer pointers. + * + * Return: 0 on success. + */ +static int tascam_capture_prepare(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam = snd_pcm_substream_chip(substream); + + tascam->driver_capture_pos = 0; + tascam->capture_frames_processed = 0; + tascam->last_capture_period_pos = 0; + tascam->capture_ring_buffer_read_ptr = 0; + tascam->capture_ring_buffer_write_ptr = 0; + + return 0; +} + +/** + * tascam_capture_pointer() - Returns the current capture pointer position. + * @substream: The ALSA PCM substream. + * + * This function returns the current position of the capture pointer within + * the ALSA ring buffer, in frames. + * + * Return: The current capture pointer position in frames. + */ +static snd_pcm_uframes_t +tascam_capture_pointer(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + u64 pos; + + if (!atomic_read(&tascam->capture_active)) + return 0; + + scoped_guard(spinlock_irqsave, &tascam->lock) { + pos = tascam->capture_frames_processed; + } + + if (runtime->buffer_size == 0) + return 0; + + return do_div(pos, runtime->buffer_size); +} + +/** + * tascam_capture_ops - ALSA PCM operations for capture. + * + * This structure defines the callback functions for capture stream operations, + * including open, close, ioctl, hardware parameters, hardware free, prepare, + * trigger, and pointer. + */ +const struct snd_pcm_ops tascam_capture_ops = { + .open = tascam_capture_open, + .close = tascam_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = tascam_pcm_hw_params, + .hw_free = tascam_pcm_hw_free, + .prepare = tascam_capture_prepare, + .trigger = tascam_pcm_trigger, + .pointer = tascam_capture_pointer, +}; + +/** + * decode_tascam_capture_block() - Decodes a raw 512-byte block from the device. + * @src_block: Pointer to the 512-byte raw source block. + * @dst_block: Pointer to the destination buffer for decoded audio frames. + * + * The device sends audio data in a complex, multiplexed format. This function + * demultiplexes the bits from the raw block into 8 frames of 4-channel, + * 24-bit audio (stored in 32-bit containers). + */ +static void decode_tascam_capture_block(const u8 *src_block, s32 *dst_block) +{ + int frame, bit; + + memset(dst_block, 0, + FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME * + DECODED_SAMPLE_SIZE); + + for (frame = 0; frame < FRAMES_PER_DECODE_BLOCK; ++frame) { + const u8 *p_src_frame_base = src_block + frame * 64; + s32 *p_dst_frame = dst_block + frame * 4; + + s32 ch[4] = { 0 }; + + for (bit = 0; bit < 24; ++bit) { + u8 byte1 = p_src_frame_base[bit]; + u8 byte2 = p_src_frame_base[bit + 32]; + + ch[0] = (ch[0] << 1) | (byte1 & 1); + ch[2] = (ch[2] << 1) | ((byte1 >> 1) & 1); + + ch[1] = (ch[1] << 1) | (byte2 & 1); + ch[3] = (ch[3] << 1) | ((byte2 >> 1) & 1); + } + + /* + * The result is a 24-bit sample. Shift left by 8 to align it to + * the most significant bits of a 32-bit integer (S32_LE format). + */ + p_dst_frame[0] = ch[0] << 8; + p_dst_frame[1] = ch[1] << 8; + p_dst_frame[2] = ch[2] << 8; + p_dst_frame[3] = ch[3] << 8; + } +} + +void tascam_capture_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam = + container_of(work, struct tascam_card, capture_work); + struct snd_pcm_substream *substream = tascam->capture_substream; + struct snd_pcm_runtime *runtime; + u8 *raw_block = tascam->capture_decode_raw_block; + s32 *decoded_block = tascam->capture_decode_dst_block; + s32 *routed_block = tascam->capture_routing_buffer; + + if (!substream || !substream->runtime) + return; + runtime = substream->runtime; + + if (!raw_block || !decoded_block || !routed_block) { + dev_err(tascam->card->dev, + "Capture decode/routing buffers not allocated!\n"); + return; + } + + while (atomic_read(&tascam->capture_active)) { + size_t write_ptr, read_ptr, available_data; + bool can_process; + + scoped_guard(spinlock_irqsave, &tascam->lock) { + write_ptr = tascam->capture_ring_buffer_write_ptr; + read_ptr = tascam->capture_ring_buffer_read_ptr; + available_data = (write_ptr >= read_ptr) ? + (write_ptr - read_ptr) : + (CAPTURE_RING_BUFFER_SIZE - + read_ptr + write_ptr); + can_process = + (available_data >= RAW_BYTES_PER_DECODE_BLOCK); + + if (can_process) { + size_t bytes_to_end = + CAPTURE_RING_BUFFER_SIZE - read_ptr; + if (bytes_to_end >= + RAW_BYTES_PER_DECODE_BLOCK) { + memcpy(raw_block, + tascam->capture_ring_buffer + + read_ptr, + RAW_BYTES_PER_DECODE_BLOCK); + } else { + memcpy(raw_block, + tascam->capture_ring_buffer + + read_ptr, + bytes_to_end); + memcpy(raw_block + bytes_to_end, + tascam->capture_ring_buffer, + RAW_BYTES_PER_DECODE_BLOCK - + bytes_to_end); + } + tascam->capture_ring_buffer_read_ptr = + (read_ptr + + RAW_BYTES_PER_DECODE_BLOCK) % + CAPTURE_RING_BUFFER_SIZE; + } + } + + if (!can_process) + break; + + decode_tascam_capture_block(raw_block, decoded_block); + process_capture_routing_us144mkii(tascam, decoded_block, + routed_block); + + scoped_guard(spinlock_irqsave, &tascam->lock) { + if (atomic_read(&tascam->capture_active)) { + int f; + + for (f = 0; f < FRAMES_PER_DECODE_BLOCK; ++f) { + u8 *dst_frame_start = + runtime->dma_area + + frames_to_bytes( + runtime, + tascam->driver_capture_pos); + s32 *routed_frame_start = + routed_block + + (f * NUM_CHANNELS); + int c; + + for (c = 0; c < NUM_CHANNELS; c++) { + u8 *dst_channel = + dst_frame_start + + (c * BYTES_PER_SAMPLE); + s32 *src_channel_s32 = + routed_frame_start + c; + + memcpy(dst_channel, + ((char *)src_channel_s32) + + 1, + 3); + } + + tascam->driver_capture_pos = + (tascam->driver_capture_pos + + 1) % + runtime->buffer_size; + } + } + } + } +} + +void capture_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam = urb->context; + int ret; + + if (urb->status) { + if (urb->status != -ENOENT && urb->status != -ECONNRESET && + urb->status != -ESHUTDOWN && urb->status != -ENODEV && + urb->status != -EPROTO) + dev_err_ratelimited(tascam->card->dev, + "Capture URB failed: %d\n", + urb->status); + goto out; + } + if (!tascam || !atomic_read(&tascam->capture_active)) + goto out; + + if (urb->actual_length > 0) { + scoped_guard(spinlock_irqsave, &tascam->lock) { + size_t write_ptr = tascam->capture_ring_buffer_write_ptr; + size_t bytes_to_end = CAPTURE_RING_BUFFER_SIZE - write_ptr; + + if (urb->actual_length > bytes_to_end) { + memcpy(tascam->capture_ring_buffer + write_ptr, + urb->transfer_buffer, bytes_to_end); + memcpy(tascam->capture_ring_buffer, + urb->transfer_buffer + bytes_to_end, + urb->actual_length - bytes_to_end); + } else { + memcpy(tascam->capture_ring_buffer + write_ptr, + urb->transfer_buffer, + urb->actual_length); + } + + tascam->capture_ring_buffer_write_ptr = + (write_ptr + urb->actual_length) % + CAPTURE_RING_BUFFER_SIZE; + } + + schedule_work(&tascam->capture_work); + } + + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->capture_anchor); + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err_ratelimited(tascam->card->dev, + "Failed to resubmit capture URB: %d\n", + ret); + usb_unanchor_urb(urb); + usb_put_urb(urb); + atomic_dec( + &tascam->active_urbs); /* Decrement on failed resubmission */ + } +out: + usb_put_urb(urb); +} + diff --git a/sound/usb/usx2y/us144mkii_controls.c b/sound/usb/usx2y/us144mkii_controls.c new file mode 100644 index 000000000000..5d69441ef414 --- /dev/null +++ b/sound/usb/usx2y/us144mkii_controls.c @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 Å erif Rami <ramiserifpersia@gmail.com> + +#include "us144mkii.h" + +/** + * @brief Text descriptions for playback output source options. + * + * Used by ALSA kcontrol elements to provide user-friendly names for + * the playback routing options (e.g., "Playback 1-2", "Playback 3-4"). + */ +static const char *const playback_source_texts[] = { "Playback 1-2", + "Playback 3-4" }; + +/** + * @brief Text descriptions for capture input source options. + * + * Used by ALSA kcontrol elements to provide user-friendly names for + * the capture routing options (e.g., "Analog In", "Digital In"). + */ +static const char *const capture_source_texts[] = { "Analog In", "Digital In" }; + +/** + * tascam_playback_source_info() - ALSA control info callback for playback + * source. + * @kcontrol: The ALSA kcontrol instance. + * @uinfo: The ALSA control element info structure to fill. + * + * This function provides information about the enumerated playback source + * control, including its type, count, and available items (Playback 1-2, + * Playback 3-4). + * + * Return: 0 on success. + */ +static int tascam_playback_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, 2, playback_source_texts); +} + +/** + * tascam_line_out_get() - ALSA control get callback for Line Outputs Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current selection for the Line Outputs source + * (Playback 1-2 or Playback 3-4) from the driver's private data and populates + * the ALSA control element value. + * + * Return: 0 on success. + */ +static int tascam_line_out_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam = snd_kcontrol_chip(kcontrol); + + scoped_guard(spinlock_irqsave, &tascam->lock) { + ucontrol->value.enumerated.item[0] = tascam->line_out_source; + } + return 0; +} + +/** + * tascam_line_out_put() - ALSA control put callback for Line Outputs Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure containing the new value. + * + * This function sets the Line Outputs source (Playback 1-2 or Playback 3-4) + * based on the user's selection from the ALSA control element. It validates + * the input and updates the driver's private data. + * + * Return: 1 if the value was changed, 0 if unchanged, or a negative error code. + */ +static int tascam_line_out_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam = snd_kcontrol_chip(kcontrol); + int changed = 0; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + + scoped_guard(spinlock_irqsave, &tascam->lock) { + if (tascam->line_out_source != ucontrol->value.enumerated.item[0]) { + tascam->line_out_source = ucontrol->value.enumerated.item[0]; + changed = 1; + } + } + return changed; +} + +/** + * tascam_line_out_control - ALSA kcontrol definition for Line Outputs Source. + * + * This defines a new ALSA mixer control named "Line OUTPUTS Source" that allows + * the user to select between "Playback 1-2" and "Playback 3-4" for the analog + * line outputs of the device. It uses the `tascam_playback_source_info` for + * information and `tascam_line_out_get`/`tascam_line_out_put` for value + * handling. + */ +static const struct snd_kcontrol_new tascam_line_out_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Playback Source", + .info = tascam_playback_source_info, + .get = tascam_line_out_get, + .put = tascam_line_out_put, +}; + +/** + * tascam_digital_out_get() - ALSA control get callback for Digital Outputs + * Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current selection for the Digital Outputs source + * (Playback 1-2 or Playback 3-4) from the driver's private data and populates + * the ALSA control element value. + * + * Return: 0 on success. + */ +static int tascam_digital_out_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam = snd_kcontrol_chip(kcontrol); + + scoped_guard(spinlock_irqsave, &tascam->lock) { + ucontrol->value.enumerated.item[0] = tascam->digital_out_source; + } + return 0; +} + +/** + * tascam_digital_out_put() - ALSA control put callback for Digital Outputs + * Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure containing the new value. + * + * This function sets the Digital Outputs source (Playback 1-2 or Playback 3-4) + * based on the user's selection from the ALSA control element. It validates + * the input and updates the driver's private data. + * + * Return: 1 if the value was changed, 0 if unchanged, or a negative error code. + */ +static int tascam_digital_out_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam = snd_kcontrol_chip(kcontrol); + int changed = 0; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + + scoped_guard(spinlock_irqsave, &tascam->lock) { + if (tascam->digital_out_source != ucontrol->value.enumerated.item[0]) { + tascam->digital_out_source = ucontrol->value.enumerated.item[0]; + changed = 1; + } + } + return changed; +} + +/** + * tascam_digital_out_control - ALSA kcontrol definition for Digital Outputs + * Source. + * + * This defines a new ALSA mixer control named "Digital OUTPUTS Source" that + * allows the user to select between "Playback 1-2" and "Playback 3-4" for the + * digital outputs of the device. It uses the `tascam_playback_source_info` for + * information and `tascam_digital_out_get`/`tascam_digital_out_put` for value + * handling. + */ +static const struct snd_kcontrol_new tascam_digital_out_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Digital Playback Source", + .info = tascam_playback_source_info, + .get = tascam_digital_out_get, + .put = tascam_digital_out_put, +}; + +/** + * tascam_capture_source_info() - ALSA control info callback for capture source. + * @kcontrol: The ALSA kcontrol instance. + * @uinfo: The ALSA control element info structure to fill. + * + * This function provides information about the enumerated capture source + * control, including its type, count, and available items (Analog In, Digital + * In). + * + * Return: 0 on success. + */ +static int tascam_capture_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, 2, capture_source_texts); +} + +/** + * tascam_capture_12_get() - ALSA control get callback for Capture channels 1 + * and 2 Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current selection for the Capture channels 1 and + * 2 source (Analog In or Digital In) from the driver's private data and + * populates the ALSA control element value. + * + * Return: 0 on success. + */ +static int tascam_capture_12_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam = snd_kcontrol_chip(kcontrol); + + scoped_guard(spinlock_irqsave, &tascam->lock) { + ucontrol->value.enumerated.item[0] = tascam->capture_12_source; + } + return 0; +} + +/** + * tascam_capture_12_put() - ALSA control put callback for Capture channels 1 + * and 2 Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure containing the new value. + * + * This function sets the Capture channels 1 and 2 source (Analog In or Digital + * In) based on the user's selection from the ALSA control element. It validates + * the input and updates the driver's private data. + * + * Return: 1 if the value was changed, 0 if unchanged, or a negative error code. + */ +static int tascam_capture_12_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam = snd_kcontrol_chip(kcontrol); + int changed = 0; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + + scoped_guard(spinlock_irqsave, &tascam->lock) { + if (tascam->capture_12_source != ucontrol->value.enumerated.item[0]) { + tascam->capture_12_source = ucontrol->value.enumerated.item[0]; + changed = 1; + } + } + return changed; +} + +/** + * tascam_capture_12_control - ALSA kcontrol definition for Capture channels 1 + * and 2 Source. + * + * This defines a new ALSA mixer control named "ch1 and ch2 Source" that allows + * the user to select between "Analog In" and "Digital In" for the first two + * capture channels of the device. It uses the `tascam_capture_source_info` for + * information and `tascam_capture_12_get`/`tascam_capture_12_put` for value + * handling. + */ +static const struct snd_kcontrol_new tascam_capture_12_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Ch1/2 Capture Source", + .info = tascam_capture_source_info, + .get = tascam_capture_12_get, + .put = tascam_capture_12_put, +}; + +/** + * tascam_capture_34_get() - ALSA control get callback for Capture channels 3 + * and 4 Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current selection for the Capture channels 3 and + * 4 source (Analog In or Digital In) from the driver's private data and + * populates the ALSA control element value. + * + * Return: 0 on success. + */ +static int tascam_capture_34_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam = snd_kcontrol_chip(kcontrol); + + scoped_guard(spinlock_irqsave, &tascam->lock) { + ucontrol->value.enumerated.item[0] = tascam->capture_34_source; + } + return 0; +} + +/** + * tascam_capture_34_put() - ALSA control put callback for Capture channels 3 + * and 4 Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure containing the new value. + * + * This function sets the Capture channels 3 and 4 source (Analog In or Digital + * In) based on the user's selection from the ALSA control element. It validates + * the input and updates the driver's private data. + * + * Return: 1 if the value was changed, 0 if unchanged, or a negative error code. + */ +static int tascam_capture_34_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam = snd_kcontrol_chip(kcontrol); + int changed = 0; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + + scoped_guard(spinlock_irqsave, &tascam->lock) { + if (tascam->capture_34_source != ucontrol->value.enumerated.item[0]) { + tascam->capture_34_source = ucontrol->value.enumerated.item[0]; + changed = 1; + } + } + return changed; +} + +/** + * tascam_capture_34_control - ALSA kcontrol definition for Capture channels 3 + * and 4 Source. + * + * This defines a new ALSA mixer control named "ch3 and ch4 Source" that allows + * the user to select between "Analog In" and "Digital In" for the third and + * fourth capture channels of the device. It uses the + * `tascam_capture_source_info` for information and + * `tascam_capture_34_get`/`tascam_capture_34_put` for value handling. + */ +static const struct snd_kcontrol_new tascam_capture_34_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Ch3/4 Capture Source", + .info = tascam_capture_source_info, + .get = tascam_capture_34_get, + .put = tascam_capture_34_put, +}; + +/** + * tascam_samplerate_info() - ALSA control info callback for Sample Rate. + * @kcontrol: The ALSA kcontrol instance. + * @uinfo: The ALSA control element info structure to fill. + * + * This function provides information about the Sample Rate control, defining + * it as an integer type with a minimum value of 0 and a maximum of 96000. + * + * Return: 0 on success. + */ +static int tascam_samplerate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 96000; + return 0; +} + +/** + * tascam_samplerate_get() - ALSA control get callback for Sample Rate. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current sample rate from the device via a USB + * control message and populates the ALSA control element value. If the rate + * is already known (i.e., `current_rate` is set), it returns that value + * directly. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int tascam_samplerate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam = + (struct tascam_card *)snd_kcontrol_chip(kcontrol); + u8 *buf __free(kfree) = NULL; + int err; + u32 rate = 0; + + scoped_guard(spinlock_irqsave, &tascam->lock) { + if (tascam->current_rate > 0) { + ucontrol->value.integer.value[0] = tascam->current_rate; + return 0; + } + } + + buf = kmalloc(3, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + err = usb_control_msg(tascam->dev, usb_rcvctrlpipe(tascam->dev, 0), + UAC_GET_CUR, RT_D2H_CLASS_EP, + UAC_SAMPLING_FREQ_CONTROL, EP_AUDIO_IN, buf, 3, + USB_CTRL_TIMEOUT_MS); + + if (err >= 3) + rate = buf[0] | (buf[1] << 8) | (buf[2] << 16); + + ucontrol->value.integer.value[0] = rate; + return 0; +} + +/** + * tascam_samplerate_control - ALSA kcontrol definition for Sample Rate. + * + * This defines a new ALSA mixer control named "Sample Rate" that displays + * the current sample rate of the device. It is a read-only control. + */ +static const struct snd_kcontrol_new tascam_samplerate_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Sample Rate", + .info = tascam_samplerate_info, + .get = tascam_samplerate_get, + .access = SNDRV_CTL_ELEM_ACCESS_READ, +}; + +int tascam_create_controls(struct tascam_card *tascam) +{ + int err; + + err = snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_line_out_control, tascam)); + if (err < 0) + return err; + err = snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_digital_out_control, tascam)); + if (err < 0) + return err; + err = snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_capture_12_control, tascam)); + if (err < 0) + return err; + err = snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_capture_34_control, tascam)); + if (err < 0) + return err; + + err = snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_samplerate_control, tascam)); + if (err < 0) + return err; + + return 0; +} diff --git a/sound/usb/usx2y/us144mkii_midi.c b/sound/usb/usx2y/us144mkii_midi.c new file mode 100644 index 000000000000..ed2afec2a89a --- /dev/null +++ b/sound/usb/usx2y/us144mkii_midi.c @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 Å erif Rami <ramiserifpersia@gmail.com> + +#include "us144mkii.h" + +/** + * tascam_midi_in_work_handler() - Deferred work for processing MIDI input. + * @work: The work_struct instance. + * + * This function runs in a thread context. It safely reads raw USB data from + * the kfifo, processes it by stripping protocol-specific padding bytes, and + * passes the clean MIDI data to the ALSA rawmidi subsystem. + */ +static void tascam_midi_in_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam = + container_of(work, struct tascam_card, midi_in_work); + u8 buf[9]; + u8 clean_buf[8]; + unsigned int count, clean_count; + + if (!tascam->midi_in_substream) + return; + + while (kfifo_out_spinlocked(&tascam->midi_in_fifo, buf, sizeof(buf), + &tascam->midi_in_lock) == sizeof(buf)) { + clean_count = 0; + for (count = 0; count < 8; ++count) { + if (buf[count] != 0xfd) + clean_buf[clean_count++] = buf[count]; + } + + if (clean_count > 0) + snd_rawmidi_receive(tascam->midi_in_substream, + clean_buf, clean_count); + } +} + +void tascam_midi_in_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam = urb->context; + int ret; + + if (!tascam) + goto out; + + if (urb->status) { + if (urb->status != -ENOENT && urb->status != -ECONNRESET && + urb->status != -ESHUTDOWN && urb->status != -EPROTO) { + dev_err_ratelimited(tascam->card->dev, + "MIDI IN URB failed: status %d\n", + urb->status); + } + goto out; + } + + if (atomic_read(&tascam->midi_in_active) && + urb->actual_length > 0) { + kfifo_in_spinlocked(&tascam->midi_in_fifo, urb->transfer_buffer, + urb->actual_length, &tascam->midi_in_lock); + schedule_work(&tascam->midi_in_work); + } + + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->midi_in_anchor); + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(tascam->card->dev, + "Failed to resubmit MIDI IN URB: error %d\n", ret); + usb_unanchor_urb(urb); + goto out; + } + +out: + usb_put_urb(urb); +} + +/** + * tascam_midi_in_open() - Opens the MIDI input substream. + * @substream: The ALSA rawmidi substream to open. + * + * This function stores a reference to the MIDI input substream in the + * driver's private data. + * + * Return: 0 on success. + */ +static int tascam_midi_in_open(struct snd_rawmidi_substream *substream) +{ + struct tascam_card *tascam = substream->rmidi->private_data; + + tascam->midi_in_substream = substream; + return 0; +} + +/** + * tascam_midi_in_close() - Closes the MIDI input substream. + * @substream: The ALSA rawmidi substream to close. + * + * Return: 0 on success. + */ +static int tascam_midi_in_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +/** + * tascam_midi_in_trigger() - Triggers MIDI input stream activity. + * @substream: The ALSA rawmidi substream. + * @up: Boolean indicating whether to start (1) or stop (0) the stream. + * + * This function starts or stops the MIDI input URBs based on the 'up' + * parameter. When starting, it resets the kfifo and submits all MIDI input + * URBs. When stopping, it kills all anchored MIDI input URBs and cancels the + * associated workqueue. + */ +static void tascam_midi_in_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct tascam_card *tascam = substream->rmidi->private_data; + int i, err; + + if (up) { + if (atomic_xchg(&tascam->midi_in_active, 1) == 0) { + scoped_guard(spinlock_irqsave, &tascam->midi_in_lock) + { + kfifo_reset(&tascam->midi_in_fifo); + } + + for (i = 0; i < NUM_MIDI_IN_URBS; i++) { + usb_get_urb(tascam->midi_in_urbs[i]); + usb_anchor_urb(tascam->midi_in_urbs[i], + &tascam->midi_in_anchor); + err = usb_submit_urb(tascam->midi_in_urbs[i], + GFP_KERNEL); + if (err < 0) { + dev_err(tascam->card->dev, + "Failed to submit MIDI IN URB %d: %d\n", + i, err); + usb_unanchor_urb( + tascam->midi_in_urbs[i]); + usb_put_urb(tascam->midi_in_urbs[i]); + } + } + } + } else { + if (atomic_xchg(&tascam->midi_in_active, 0) == 1) { + usb_kill_anchored_urbs(&tascam->midi_in_anchor); + cancel_work_sync(&tascam->midi_in_work); + } + } +} + +/** + * tascam_midi_in_ops - ALSA rawmidi operations for MIDI input. + * + * This structure defines the callback functions for MIDI input stream + * operations, including open, close, and trigger. + */ +static const struct snd_rawmidi_ops tascam_midi_in_ops = { + .open = tascam_midi_in_open, + .close = tascam_midi_in_close, + .trigger = tascam_midi_in_trigger, +}; + +void tascam_midi_out_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam = urb->context; + int i, urb_index = -1; + + if (urb->status) { + if (urb->status != -ENOENT && urb->status != -ECONNRESET && + urb->status != -ESHUTDOWN) { + dev_err_ratelimited(tascam->card->dev, + "MIDI OUT URB failed: %d\n", + urb->status); + } + goto out; + } + + if (!tascam) + goto out; + + for (i = 0; i < NUM_MIDI_OUT_URBS; i++) { + if (tascam->midi_out_urbs[i] == urb) { + urb_index = i; + break; + } + } + + if (urb_index < 0) { + dev_err_ratelimited(tascam->card->dev, + "Unknown MIDI OUT URB completed!\n"); + goto out; + } + + scoped_guard(spinlock_irqsave, &tascam->midi_out_lock) + { + clear_bit(urb_index, &tascam->midi_out_urbs_in_flight); + } + + if (atomic_read(&tascam->midi_out_active)) + schedule_work(&tascam->midi_out_work); + +out: + usb_put_urb(urb); +} + +/** + * tascam_midi_out_work_handler() - Deferred work for sending MIDI data + * @work: The work_struct instance. + * + * This function handles the proprietary output protocol: take the raw MIDI + * message bytes from the application, place them at the start of a 9-byte + * buffer, pad the rest with 0xFD, and add a terminator byte (0x00). + * This function pulls as many bytes as will fit into one packet from the + * ALSA buffer and sends them. + */ +static void tascam_midi_out_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam = + container_of(work, struct tascam_card, midi_out_work); + struct snd_rawmidi_substream *substream = tascam->midi_out_substream; + int i; + + if (!substream || !atomic_read(&tascam->midi_out_active)) + return; + + while (snd_rawmidi_transmit_peek(substream, (u8[]){ 0 }, 1) == 1) { + int urb_index; + struct urb *urb; + u8 *buf; + int bytes_to_send; + + scoped_guard(spinlock_irqsave, &tascam->midi_out_lock) { + urb_index = -1; + for (i = 0; i < NUM_MIDI_OUT_URBS; i++) { + if (!test_bit( + i, + &tascam->midi_out_urbs_in_flight)) { + urb_index = i; + break; + } + } + + if (urb_index < 0) + return; /* No free URBs, will be rescheduled by + * completion handler + */ + + urb = tascam->midi_out_urbs[urb_index]; + buf = urb->transfer_buffer; + bytes_to_send = snd_rawmidi_transmit(substream, buf, 8); + + if (bytes_to_send <= 0) + break; /* No more data */ + + if (bytes_to_send < 9) + memset(buf + bytes_to_send, 0xfd, + 9 - bytes_to_send); + buf[8] = 0xe0; + + set_bit(urb_index, &tascam->midi_out_urbs_in_flight); + urb->transfer_buffer_length = 9; + } + + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->midi_out_anchor); + if (usb_submit_urb(urb, GFP_KERNEL) < 0) { + dev_err_ratelimited( + tascam->card->dev, + "Failed to submit MIDI OUT URB %d\n", + urb_index); + scoped_guard(spinlock_irqsave, &tascam->midi_out_lock) + { + clear_bit(urb_index, + &tascam->midi_out_urbs_in_flight); + } + usb_unanchor_urb(urb); + usb_put_urb(urb); + break; /* Stop on error */ + } + } +} + +/** + * tascam_midi_out_open() - Opens the MIDI output substream. + * @substream: The ALSA rawmidi substream to open. + * + * This function stores a reference to the MIDI output substream in the + * driver's private data and initializes the MIDI running status. + * + * Return: 0 on success. + */ +static int tascam_midi_out_open(struct snd_rawmidi_substream *substream) +{ + struct tascam_card *tascam = substream->rmidi->private_data; + + tascam->midi_out_substream = substream; + /* Initialize the running status state for the packet packer. */ + tascam->midi_running_status = 0; + return 0; +} + +/** + * tascam_midi_out_close() - Closes the MIDI output substream. + * @substream: The ALSA rawmidi substream to close. + * + * Return: 0 on success. + */ +static int tascam_midi_out_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +/** + * tascam_midi_out_drain() - Drains the MIDI output stream. + * @substream: The ALSA rawmidi substream. + * + * This function cancels any pending MIDI output work and kills all + * anchored MIDI output URBs, ensuring all data is sent or discarded. + */ +static void tascam_midi_out_drain(struct snd_rawmidi_substream *substream) +{ + struct tascam_card *tascam = substream->rmidi->private_data; + bool in_flight = true; + + while (in_flight) { + in_flight = false; + for (int i = 0; i < NUM_MIDI_OUT_URBS; i++) { + if (test_bit(i, &tascam->midi_out_urbs_in_flight)) { + in_flight = true; + break; + } + } + if (in_flight) + schedule_timeout_uninterruptible(1); + } + + cancel_work_sync(&tascam->midi_out_work); + usb_kill_anchored_urbs(&tascam->midi_out_anchor); +} + +/** + * tascam_midi_out_trigger() - Triggers MIDI output stream activity. + * @substream: The ALSA rawmidi substream. + * @up: Boolean indicating whether to start (1) or stop (0) the stream. + * + * This function starts or stops the MIDI output workqueue based on the + * 'up' parameter. + */ +static void tascam_midi_out_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct tascam_card *tascam = substream->rmidi->private_data; + + if (up) { + atomic_set(&tascam->midi_out_active, 1); + schedule_work(&tascam->midi_out_work); + } else { + atomic_set(&tascam->midi_out_active, 0); + } +} + +/** + * tascam_midi_out_ops - ALSA rawmidi operations for MIDI output. + * + * This structure defines the callback functions for MIDI output stream + * operations, including open, close, trigger, and drain. + */ +static const struct snd_rawmidi_ops tascam_midi_out_ops = { + .open = tascam_midi_out_open, + .close = tascam_midi_out_close, + .trigger = tascam_midi_out_trigger, + .drain = tascam_midi_out_drain, +}; + +int tascam_create_midi(struct tascam_card *tascam) +{ + int err; + + err = snd_rawmidi_new(tascam->card, "US144MKII MIDI", 0, 1, 1, + &tascam->rmidi); + if (err < 0) + return err; + + strscpy(tascam->rmidi->name, "US144MKII MIDI", + sizeof(tascam->rmidi->name)); + tascam->rmidi->private_data = tascam; + + snd_rawmidi_set_ops(tascam->rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &tascam_midi_in_ops); + snd_rawmidi_set_ops(tascam->rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &tascam_midi_out_ops); + + tascam->rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + INIT_WORK(&tascam->midi_in_work, tascam_midi_in_work_handler); + INIT_WORK(&tascam->midi_out_work, tascam_midi_out_work_handler); + + return 0; +} diff --git a/sound/usb/usx2y/us144mkii_pcm.c b/sound/usb/usx2y/us144mkii_pcm.c new file mode 100644 index 000000000000..0c84304d4624 --- /dev/null +++ b/sound/usb/usx2y/us144mkii_pcm.c @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 Å erif Rami <ramiserifpersia@gmail.com> + +#include "us144mkii.h" + +/** + * fpo_init_pattern() - Generates a packet distribution pattern. + * @size: The number of elements in the pattern array (e.g., 8). + * @pattern_array: Pointer to the array to be populated. + * @initial_value: The base value to initialize each element with. + * @target_sum: The desired sum of all elements in the final array. + * + * This function initializes an array with a base value and then iteratively + * adjusts the elements to match a target sum, distributing the difference + * as evenly as possible. + */ +static void fpo_init_pattern(unsigned int size, unsigned int *pattern_array, + unsigned int initial_value, int target_sum) +{ + int diff, i; + + if (!size) + return; + + for (i = 0; i < size; ++i) + pattern_array[i] = initial_value; + + diff = target_sum - (size * initial_value); + for (i = 0; i < abs(diff); ++i) { + if (diff > 0) + pattern_array[i]++; + else + pattern_array[i]--; + } +} + +const struct snd_pcm_hardware tascam_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000), + .rate_min = 44100, + .rate_max = 96000, + .channels_min = NUM_CHANNELS, + .channels_max = NUM_CHANNELS, + .buffer_bytes_max = 1024 * 1024, + .period_bytes_min = 48 * BYTES_PER_FRAME, + .period_bytes_max = 1024 * BYTES_PER_FRAME, + .periods_min = 2, + .periods_max = 1024, +}; + +void process_playback_routing_us144mkii(struct tascam_card *tascam, + const u8 *src_buffer, u8 *dst_buffer, + size_t frames) +{ + size_t f; + const u8 *src_12, *src_34; + u8 *dst_line, *dst_digital; + + for (f = 0; f < frames; ++f) { + src_12 = src_buffer + f * BYTES_PER_FRAME; + src_34 = src_12 + (2 * BYTES_PER_SAMPLE); + dst_line = dst_buffer + f * BYTES_PER_FRAME; + dst_digital = dst_line + (2 * BYTES_PER_SAMPLE); + + /* LINE OUTPUTS (ch1/2 on device) */ + if (tascam->line_out_source == 0) /* "ch1 and ch2" */ + memcpy(dst_line, src_12, 2 * BYTES_PER_SAMPLE); + else /* "ch3 and ch4" */ + memcpy(dst_line, src_34, 2 * BYTES_PER_SAMPLE); + + /* DIGITAL OUTPUTS (ch3/4 on device) */ + if (tascam->digital_out_source == 0) /* "ch1 and ch2" */ + memcpy(dst_digital, src_12, 2 * BYTES_PER_SAMPLE); + else /* "ch3 and ch4" */ + memcpy(dst_digital, src_34, 2 * BYTES_PER_SAMPLE); + } +} + +void process_capture_routing_us144mkii(struct tascam_card *tascam, + const s32 *decoded_block, + s32 *routed_block) +{ + int f; + const s32 *src_frame; + s32 *dst_frame; + + for (f = 0; f < FRAMES_PER_DECODE_BLOCK; f++) { + src_frame = decoded_block + (f * DECODED_CHANNELS_PER_FRAME); + dst_frame = routed_block + (f * DECODED_CHANNELS_PER_FRAME); + + /* ch1 and ch2 Source */ + if (tascam->capture_12_source == 0) { /* analog inputs */ + dst_frame[0] = src_frame[0]; /* Analog L */ + dst_frame[1] = src_frame[1]; /* Analog R */ + } else { /* digital inputs */ + dst_frame[0] = src_frame[2]; /* Digital L */ + dst_frame[1] = src_frame[3]; /* Digital R */ + } + + /* ch3 and ch4 Source */ + if (tascam->capture_34_source == 0) { /* analog inputs */ + dst_frame[2] = src_frame[0]; /* Analog L (Duplicate) */ + dst_frame[3] = src_frame[1]; /* Analog R (Duplicate) */ + } else { /* digital inputs */ + dst_frame[2] = src_frame[2]; /* Digital L */ + dst_frame[3] = src_frame[3]; /* Digital R */ + } + } +} + +int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int rate) +{ + struct usb_device *dev = tascam->dev; + u8 *rate_payload_buf __free(kfree) = NULL; + u16 rate_vendor_wValue; + int err = 0; + const u8 *current_payload_src; + + static const u8 payload_44100[] = { 0x44, 0xac, 0x00 }; + static const u8 payload_48000[] = { 0x80, 0xbb, 0x00 }; + static const u8 payload_88200[] = { 0x88, 0x58, 0x01 }; + static const u8 payload_96000[] = { 0x00, 0x77, 0x01 }; + + switch (rate) { + case 44100: + current_payload_src = payload_44100; + rate_vendor_wValue = REG_ADDR_RATE_44100; + break; + case 48000: + current_payload_src = payload_48000; + rate_vendor_wValue = REG_ADDR_RATE_48000; + break; + case 88200: + current_payload_src = payload_88200; + rate_vendor_wValue = REG_ADDR_RATE_88200; + break; + case 96000: + current_payload_src = payload_96000; + rate_vendor_wValue = REG_ADDR_RATE_96000; + break; + default: + dev_err(&dev->dev, + "Unsupported sample rate %d for configuration\n", rate); + return -EINVAL; + } + + rate_payload_buf = kmemdup(current_payload_src, 3, GFP_KERNEL); + if (!rate_payload_buf) + return -ENOMEM; + + dev_info(&dev->dev, "Configuring device for %d Hz\n", rate); + + err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_MODE_CONTROL, RT_H2D_VENDOR_DEV, + MODE_VAL_CONFIG, 0x0000, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + RT_H2D_CLASS_EP, UAC_SAMPLING_FREQ_CONTROL, + EP_AUDIO_IN, rate_payload_buf, 3, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + RT_H2D_CLASS_EP, UAC_SAMPLING_FREQ_CONTROL, + EP_AUDIO_OUT, rate_payload_buf, 3, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + REG_ADDR_UNKNOWN_0D, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + REG_ADDR_UNKNOWN_0E, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + REG_ADDR_UNKNOWN_0F, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + rate_vendor_wValue, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + REG_ADDR_UNKNOWN_11, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_MODE_CONTROL, RT_H2D_VENDOR_DEV, + MODE_VAL_STREAM_START, 0x0000, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + return 0; + +fail: + dev_err(&dev->dev, + "Device configuration failed at rate %d with error %d\n", rate, + err); + return err; +} + +int tascam_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct tascam_card *tascam = snd_pcm_substream_chip(substream); + int err; + unsigned int rate = params_rate(params); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + tascam->fpo.sample_rate_khz = rate / 1000; + tascam->fpo.base_feedback_value = tascam->fpo.sample_rate_khz; + tascam->fpo.feedback_offset = 2; + tascam->fpo.current_index = 0; + tascam->fpo.previous_index = 0; + tascam->fpo.sync_locked = false; + + unsigned int initial_value = tascam->fpo.sample_rate_khz / 8; + + for (int i = 0; i < 5; i++) { + int target_sum = tascam->fpo.sample_rate_khz - + tascam->fpo.feedback_offset + i; + fpo_init_pattern(8, tascam->fpo.full_frame_patterns[i], + initial_value, target_sum); + } + } + + if (tascam->current_rate != rate) { + err = us144mkii_configure_device_for_rate(tascam, rate); + if (err < 0) { + tascam->current_rate = 0; + return err; + } + tascam->current_rate = rate; + } + + return 0; +} + +int tascam_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct tascam_card *tascam = snd_pcm_substream_chip(substream); + int err = 0; + int i; + bool do_start = false; + bool do_stop = false; + + scoped_guard(spinlock_irqsave, &tascam->lock) { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (!atomic_read(&tascam->playback_active)) { + atomic_set(&tascam->playback_active, 1); + atomic_set(&tascam->capture_active, 1); + do_start = true; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (atomic_read(&tascam->playback_active)) { + atomic_set(&tascam->playback_active, 0); + atomic_set(&tascam->capture_active, 0); + do_stop = true; + } + break; + default: + err = -EINVAL; + break; + } + } + + if (do_start) { + if (atomic_read(&tascam->active_urbs) > 0) { + dev_warn(tascam->card->dev, + "Cannot start, URBs still active.\n"); + return -EAGAIN; + } + + for (i = 0; i < NUM_FEEDBACK_URBS; i++) { + usb_get_urb(tascam->feedback_urbs[i]); + usb_anchor_urb(tascam->feedback_urbs[i], + &tascam->feedback_anchor); + err = usb_submit_urb(tascam->feedback_urbs[i], + GFP_ATOMIC); + if (err < 0) { + usb_unanchor_urb(tascam->feedback_urbs[i]); + usb_put_urb(tascam->feedback_urbs[i]); + atomic_dec(&tascam->active_urbs); + goto start_rollback; + } + atomic_inc(&tascam->active_urbs); + } + for (i = 0; i < NUM_PLAYBACK_URBS; i++) { + usb_get_urb(tascam->playback_urbs[i]); + usb_anchor_urb(tascam->playback_urbs[i], + &tascam->playback_anchor); + err = usb_submit_urb(tascam->playback_urbs[i], + GFP_ATOMIC); + if (err < 0) { + usb_unanchor_urb(tascam->playback_urbs[i]); + usb_put_urb(tascam->playback_urbs[i]); + atomic_dec(&tascam->active_urbs); + goto start_rollback; + } + atomic_inc(&tascam->active_urbs); + } + for (i = 0; i < NUM_CAPTURE_URBS; i++) { + usb_get_urb(tascam->capture_urbs[i]); + usb_anchor_urb(tascam->capture_urbs[i], + &tascam->capture_anchor); + err = usb_submit_urb(tascam->capture_urbs[i], + GFP_ATOMIC); + if (err < 0) { + usb_unanchor_urb(tascam->capture_urbs[i]); + usb_put_urb(tascam->capture_urbs[i]); + atomic_dec(&tascam->active_urbs); + goto start_rollback; + } + atomic_inc(&tascam->active_urbs); + } + + return 0; +start_rollback: + dev_err(tascam->card->dev, + "Failed to submit URBs to start stream: %d\n", err); + do_stop = true; + } + + if (do_stop) + schedule_work(&tascam->stop_work); + + return err; +} + +int tascam_init_pcm(struct snd_pcm *pcm) +{ + struct tascam_card *tascam = pcm->private_data; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &tascam_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &tascam_capture_ops); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + tascam->dev->dev.parent, 64 * 1024, + tascam_pcm_hw.buffer_bytes_max); + + return 0; +} diff --git a/sound/usb/usx2y/us144mkii_pcm.h b/sound/usb/usx2y/us144mkii_pcm.h new file mode 100644 index 000000000000..74da8564431b --- /dev/null +++ b/sound/usb/usx2y/us144mkii_pcm.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +// Copyright (c) 2025 Å erif Rami <ramiserifpersia@gmail.com> + +#ifndef __US144MKII_PCM_H +#define __US144MKII_PCM_H + +#include "us144mkii.h" + +/** + * tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM. + * + * Defines the supported PCM formats, rates, channels, and buffer/period sizes + * for the TASCAM US-144MKII audio interface. + */ +extern const struct snd_pcm_hardware tascam_pcm_hw; + +/** + * tascam_playback_ops - ALSA PCM operations for playback. + * + * This structure defines the callback functions for playback stream operations. + */ +extern const struct snd_pcm_ops tascam_playback_ops; + +/** + * tascam_capture_ops - ALSA PCM operations for capture. + * + * This structure defines the callback functions for capture stream operations. + */ +extern const struct snd_pcm_ops tascam_capture_ops; + +/** + * playback_urb_complete() - Completion handler for playback isochronous URBs. + * @urb: the completed URB + * + * This function runs in interrupt context. It calculates the number of bytes + * to send in the next set of packets based on the feedback-driven clock, + * copies the audio data from the ALSA ring buffer, and resubmits the URB. + */ +void playback_urb_complete(struct urb *urb); + +/** + * feedback_urb_complete() - Completion handler for feedback isochronous URBs. + * @urb: the completed URB + * + * This is the master clock for the driver. It runs in interrupt context. + * It reads the feedback value from the device, which indicates how many + * samples the device has consumed. This information is used to adjust the + * playback rate and to advance the capture stream pointer, keeping both + * streams in sync. It then calls snd_pcm_period_elapsed if necessary and + * resubmits itself. + */ +void feedback_urb_complete(struct urb *urb); + +/** + * capture_urb_complete() - Completion handler for capture bulk URBs. + * @urb: the completed URB + * + * This function runs in interrupt context. It copies the received raw data + * into an intermediate ring buffer and then schedules the workqueue to process + * it. It then resubmits the URB to receive more data. + */ +void capture_urb_complete(struct urb *urb); + +/** + * tascam_stop_pcm_work_handler() - Work handler to stop PCM streams. + * @work: Pointer to the work_struct. + * + * This function is scheduled to stop PCM streams (playback and capture) + * from a workqueue context, avoiding blocking operations in interrupt context. + */ +void tascam_stop_pcm_work_handler(struct work_struct *work); + +/** + * tascam_init_pcm() - Initializes the ALSA PCM device. + * @pcm: Pointer to the ALSA PCM device to initialize. + * + * This function sets up the PCM operations, adds ALSA controls for routing + * and sample rate, and preallocates pages for the PCM buffer. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_init_pcm(struct snd_pcm *pcm); + +/** + * us144mkii_configure_device_for_rate() - Set sample rate via USB control msgs + * @tascam: the tascam_card instance + * @rate: the target sample rate (e.g., 44100, 96000) + * + * This function sends a sequence of vendor-specific and UAC control messages + * to configure the device hardware for the specified sample rate. + * + * Return: 0 on success, or a negative error code on failure. + */ +int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int rate); + +/** + * process_playback_routing_us144mkii() - Apply playback routing matrix + * @tascam: The driver instance. + * @src_buffer: Buffer containing 4 channels of S24_3LE audio from ALSA. + * @dst_buffer: Buffer to be filled for the USB device. + * @frames: Number of frames to process. + */ +void process_playback_routing_us144mkii(struct tascam_card *tascam, + const u8 *src_buffer, u8 *dst_buffer, + size_t frames); + +/** + * process_capture_routing_us144mkii() - Apply capture routing matrix + * @tascam: The driver instance. + * @decoded_block: Buffer containing 4 channels of S32LE decoded audio. + * @routed_block: Buffer to be filled for ALSA. + */ +void process_capture_routing_us144mkii(struct tascam_card *tascam, + const s32 *decoded_block, + s32 *routed_block); + +/** + * tascam_pcm_hw_params() - Configures hardware parameters for PCM streams. + * @substream: The ALSA PCM substream. + * @params: The hardware parameters to apply. + * + * This function allocates pages for the PCM buffer and, for playback streams, + * selects the appropriate feedback patterns based on the requested sample rate. + * It also configures the device hardware for the selected sample rate if it + * has changed. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params); + +/** + * tascam_pcm_hw_free() - Frees hardware parameters for PCM streams. + * @substream: The ALSA PCM substream. + * + * This function is a stub for freeing hardware-related resources. + * + * Return: 0 on success. + */ +int tascam_pcm_hw_free(struct snd_pcm_substream *substream); + +/** + * tascam_pcm_trigger() - Triggers the start or stop of PCM streams. + * @substream: The ALSA PCM substream. + * @cmd: The trigger command (e.g., SNDRV_PCM_TRIGGER_START). + * + * This function handles starting and stopping of playback and capture streams + * by submitting or killing the associated URBs. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd); + +/** + * tascam_capture_work_handler() - Deferred work for processing capture data. + * @work: the work_struct instance + * + * This function runs in a kernel thread context, not an IRQ context. It reads + * raw data from the capture ring buffer, decodes it, applies routing, and + * copies the final audio data into the ALSA capture ring buffer. This offloads + * the CPU-intensive decoding from the time-sensitive URB completion handlers. + */ +void tascam_capture_work_handler(struct work_struct *work); + +#endif /* __US144MKII_PCM_H */ diff --git a/sound/usb/usx2y/us144mkii_playback.c b/sound/usb/usx2y/us144mkii_playback.c new file mode 100644 index 000000000000..0cb9699ec211 --- /dev/null +++ b/sound/usb/usx2y/us144mkii_playback.c @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 Å erif Rami <ramiserifpersia@gmail.com> + +#include "us144mkii.h" + +/** + * tascam_playback_open() - Opens the PCM playback substream. + * @substream: The ALSA PCM substream to open. + * + * This function sets the hardware parameters for the playback substream + * and stores a reference to the substream in the driver's private data. + * + * Return: 0 on success. + */ +static int tascam_playback_open(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam = snd_pcm_substream_chip(substream); + + substream->runtime->hw = tascam_pcm_hw; + tascam->playback_substream = substream; + atomic_set(&tascam->playback_active, 0); + + return 0; +} + +/** + * tascam_playback_close() - Closes the PCM playback substream. + * @substream: The ALSA PCM substream to close. + * + * This function clears the reference to the playback substream in the + * driver's private data. + * + * Return: 0 on success. + */ +static int tascam_playback_close(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam = snd_pcm_substream_chip(substream); + + tascam->playback_substream = NULL; + + return 0; +} + +/** + * tascam_playback_prepare() - Prepares the PCM playback substream for use. + * @substream: The ALSA PCM substream to prepare. + * + * This function initializes playback-related counters and flags, and configures + * the playback URBs with appropriate packet sizes based on the nominal frame + * rate. + * + * Return: 0 on success. + */ +static int tascam_playback_prepare(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int i, u; + size_t nominal_frames_per_packet, nominal_bytes_per_packet; + size_t total_bytes_in_urb; + + tascam->driver_playback_pos = 0; + tascam->playback_frames_consumed = 0; + tascam->last_period_pos = 0; + tascam->feedback_pattern_in_idx = 0; + tascam->feedback_pattern_out_idx = 0; + tascam->feedback_synced = false; + tascam->feedback_consecutive_errors = 0; + tascam->feedback_urb_skip_count = NUM_FEEDBACK_URBS; + + nominal_frames_per_packet = runtime->rate / 8000; + for (i = 0; i < FEEDBACK_ACCUMULATOR_SIZE; i++) + tascam->feedback_accumulator_pattern[i] = + nominal_frames_per_packet; + + for (i = 0; i < NUM_FEEDBACK_URBS; i++) { + struct urb *f_urb = tascam->feedback_urbs[i]; + int j; + + f_urb->number_of_packets = FEEDBACK_URB_PACKETS; + f_urb->transfer_buffer_length = + FEEDBACK_URB_PACKETS * FEEDBACK_PACKET_SIZE; + for (j = 0; j < FEEDBACK_URB_PACKETS; j++) { + f_urb->iso_frame_desc[j].offset = + j * FEEDBACK_PACKET_SIZE; + f_urb->iso_frame_desc[j].length = FEEDBACK_PACKET_SIZE; + } + } + + nominal_bytes_per_packet = nominal_frames_per_packet * BYTES_PER_FRAME; + total_bytes_in_urb = nominal_bytes_per_packet * PLAYBACK_URB_PACKETS; + + for (u = 0; u < NUM_PLAYBACK_URBS; u++) { + struct urb *urb = tascam->playback_urbs[u]; + + memset(urb->transfer_buffer, 0, + tascam->playback_urb_alloc_size); + urb->transfer_buffer_length = total_bytes_in_urb; + urb->number_of_packets = PLAYBACK_URB_PACKETS; + for (i = 0; i < PLAYBACK_URB_PACKETS; i++) { + urb->iso_frame_desc[i].offset = + i * nominal_bytes_per_packet; + urb->iso_frame_desc[i].length = + nominal_bytes_per_packet; + } + } + + return 0; +} + +/** + * tascam_playback_pointer() - Returns the current playback pointer position. + * @substream: The ALSA PCM substream. + * + * This function returns the current position of the playback pointer within + * the ALSA ring buffer, in frames. + * + * Return: The current playback pointer position in frames. + */ +static snd_pcm_uframes_t +tascam_playback_pointer(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + u64 pos; + + if (!atomic_read(&tascam->playback_active)) + return 0; + + scoped_guard(spinlock_irqsave, &tascam->lock) { + pos = tascam->playback_frames_consumed; + } + + if (runtime->buffer_size == 0) + return 0; + + return do_div(pos, runtime->buffer_size); +} + +/** + * tascam_playback_ops - ALSA PCM operations for playback. + * + * This structure defines the callback functions for playback stream operations, + * including open, close, ioctl, hardware parameters, hardware free, prepare, + * trigger, and pointer. + */ +const struct snd_pcm_ops tascam_playback_ops = { + .open = tascam_playback_open, + .close = tascam_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = tascam_pcm_hw_params, + .hw_free = tascam_pcm_hw_free, + .prepare = tascam_playback_prepare, + .trigger = tascam_pcm_trigger, + .pointer = tascam_playback_pointer, +}; + +void playback_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam = urb->context; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + size_t total_bytes_for_urb = 0; + snd_pcm_uframes_t offset_frames; + snd_pcm_uframes_t frames_to_copy; + int ret, i; + + if (urb->status) { + if (urb->status != -ENOENT && urb->status != -ECONNRESET && + urb->status != -ESHUTDOWN && urb->status != -ENODEV) + dev_err_ratelimited(tascam->card->dev, + "Playback URB failed: %d\n", + urb->status); + goto out; + } + if (!tascam || !atomic_read(&tascam->playback_active)) + goto out; + + substream = tascam->playback_substream; + if (!substream || !substream->runtime) + goto out; + runtime = substream->runtime; + + scoped_guard(spinlock_irqsave, &tascam->lock) { + for (i = 0; i < urb->number_of_packets; i++) { + unsigned int frames_for_packet; + size_t bytes_for_packet; + + if (tascam->feedback_synced) { + frames_for_packet = + tascam->feedback_accumulator_pattern + [tascam->feedback_pattern_out_idx]; + tascam->feedback_pattern_out_idx = + (tascam->feedback_pattern_out_idx + 1) % + FEEDBACK_ACCUMULATOR_SIZE; + } else { + frames_for_packet = runtime->rate / 8000; + } + bytes_for_packet = frames_for_packet * BYTES_PER_FRAME; + + urb->iso_frame_desc[i].offset = total_bytes_for_urb; + urb->iso_frame_desc[i].length = bytes_for_packet; + total_bytes_for_urb += bytes_for_packet; + } + urb->transfer_buffer_length = total_bytes_for_urb; + + offset_frames = tascam->driver_playback_pos; + frames_to_copy = bytes_to_frames(runtime, total_bytes_for_urb); + tascam->driver_playback_pos = + (offset_frames + frames_to_copy) % runtime->buffer_size; + } + + if (total_bytes_for_urb > 0) { + u8 *dst_buf = urb->transfer_buffer; + + /* Handle ring buffer wrap-around */ + if (offset_frames + frames_to_copy > runtime->buffer_size) { + size_t first_chunk_bytes = frames_to_bytes( + runtime, runtime->buffer_size - offset_frames); + size_t second_chunk_bytes = + total_bytes_for_urb - first_chunk_bytes; + + memcpy(dst_buf, + runtime->dma_area + + frames_to_bytes(runtime, offset_frames), + first_chunk_bytes); + memcpy(dst_buf + first_chunk_bytes, runtime->dma_area, + second_chunk_bytes); + } else { + memcpy(dst_buf, + runtime->dma_area + + frames_to_bytes(runtime, offset_frames), + total_bytes_for_urb); + } + + process_playback_routing_us144mkii(tascam, dst_buf, dst_buf, + frames_to_copy); + } + + urb->dev = tascam->dev; + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->playback_anchor); + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err_ratelimited(tascam->card->dev, + "Failed to resubmit playback URB: %d\n", + ret); + usb_unanchor_urb(urb); + usb_put_urb(urb); + atomic_dec( + &tascam->active_urbs); /* Decrement on failed resubmission */ + } +out: + usb_put_urb(urb); +} + +void feedback_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam = urb->context; + struct snd_pcm_substream *playback_ss, *capture_ss; + struct snd_pcm_runtime *playback_rt, *capture_rt; + u64 total_frames_in_urb = 0; + int ret, p; + unsigned int old_in_idx, new_in_idx; + bool playback_period_elapsed = false; + bool capture_period_elapsed = false; + + if (urb->status) { + if (urb->status != -ENOENT && urb->status != -ECONNRESET && + urb->status != -ESHUTDOWN && urb->status != -ENODEV) { + dev_err_ratelimited(tascam->card->dev, + "Feedback URB failed: %d\n", + urb->status); + atomic_dec( + &tascam->active_urbs); /* Decrement on failed resubmission */ + } + goto out; + } + if (!tascam || !atomic_read(&tascam->playback_active)) + goto out; + + playback_ss = tascam->playback_substream; + if (!playback_ss || !playback_ss->runtime) + goto out; + playback_rt = playback_ss->runtime; + + capture_ss = tascam->capture_substream; + capture_rt = capture_ss ? capture_ss->runtime : NULL; + + scoped_guard(spinlock_irqsave, &tascam->lock) { + if (tascam->feedback_urb_skip_count > 0) { + tascam->feedback_urb_skip_count--; + break; + } + + old_in_idx = tascam->feedback_pattern_in_idx; + + for (p = 0; p < urb->number_of_packets; p++) { + u8 feedback_value = 0; + const unsigned int *pattern; + bool packet_ok = + (urb->iso_frame_desc[p].status == 0 && + urb->iso_frame_desc[p].actual_length >= 1); + + if (packet_ok) + feedback_value = + *((u8 *)urb->transfer_buffer + + urb->iso_frame_desc[p].offset); + + if (packet_ok) { + int delta = feedback_value - + tascam->fpo.base_feedback_value + + tascam->fpo.feedback_offset; + int pattern_idx; + + if (delta < 0) { + pattern_idx = + 0; // Clamp to the lowest pattern + } else if (delta >= 5) { + pattern_idx = + 4; // Clamp to the highest pattern + } else { + pattern_idx = delta; + } + + pattern = + tascam->fpo + .full_frame_patterns[pattern_idx]; + tascam->feedback_consecutive_errors = 0; + int i; + + for (i = 0; i < 8; i++) { + unsigned int in_idx = + (tascam->feedback_pattern_in_idx + + i) % + FEEDBACK_ACCUMULATOR_SIZE; + + tascam->feedback_accumulator_pattern + [in_idx] = pattern[i]; + total_frames_in_urb += pattern[i]; + } + } else { + unsigned int nominal_frames = + playback_rt->rate / 8000; + int i; + + if (tascam->feedback_synced) { + tascam->feedback_consecutive_errors++; + if (tascam->feedback_consecutive_errors > + FEEDBACK_SYNC_LOSS_THRESHOLD) { + dev_err(tascam->card->dev, + "Fatal: Feedback sync lost. Stopping stream.\n"); + schedule_work( + &tascam->stop_pcm_work); + tascam->feedback_synced = false; + break; + } + } + for (i = 0; i < 8; i++) { + unsigned int in_idx = + (tascam->feedback_pattern_in_idx + + i) % + FEEDBACK_ACCUMULATOR_SIZE; + + tascam->feedback_accumulator_pattern + [in_idx] = nominal_frames; + total_frames_in_urb += nominal_frames; + } + } + tascam->feedback_pattern_in_idx = + (tascam->feedback_pattern_in_idx + 8) % + FEEDBACK_ACCUMULATOR_SIZE; + } + + new_in_idx = tascam->feedback_pattern_in_idx; + + if (!tascam->feedback_synced) { + unsigned int out_idx = tascam->feedback_pattern_out_idx; + bool is_ahead = (new_in_idx - out_idx) % + FEEDBACK_ACCUMULATOR_SIZE < + (FEEDBACK_ACCUMULATOR_SIZE / 2); + bool was_behind = (old_in_idx - out_idx) % + FEEDBACK_ACCUMULATOR_SIZE >= + (FEEDBACK_ACCUMULATOR_SIZE / 2); + + if (is_ahead && was_behind) { + dev_dbg(tascam->card->dev, + "Sync Acquired! (in: %u, out: %u)\n", + new_in_idx, out_idx); + tascam->feedback_synced = true; + tascam->feedback_consecutive_errors = 0; + } + } + + if (total_frames_in_urb > 0) { + tascam->playback_frames_consumed += total_frames_in_urb; + if (atomic_read(&tascam->capture_active)) + tascam->capture_frames_processed += + total_frames_in_urb; + } + + if (playback_rt->period_size > 0) { + u64 current_period = + div_u64(tascam->playback_frames_consumed, + playback_rt->period_size); + + if (current_period > tascam->last_period_pos) { + tascam->last_period_pos = current_period; + playback_period_elapsed = true; + } + } + + if (atomic_read(&tascam->capture_active) && capture_rt && + capture_rt->period_size > 0) { + u64 current_capture_period = + div_u64(tascam->capture_frames_processed, + capture_rt->period_size); + + if (current_capture_period > + tascam->last_capture_period_pos) { + tascam->last_capture_period_pos = + current_capture_period; + capture_period_elapsed = true; + } + } + } + if (playback_period_elapsed) + snd_pcm_period_elapsed(playback_ss); + if (capture_period_elapsed) + snd_pcm_period_elapsed(capture_ss); + + urb->dev = tascam->dev; + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->feedback_anchor); + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err_ratelimited(tascam->card->dev, + "Failed to resubmit feedback URB: %d\n", + ret); + usb_unanchor_urb(urb); + usb_put_urb(urb); + } +out: + usb_put_urb(urb); +} + +void tascam_stop_pcm_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam = + container_of(work, struct tascam_card, stop_pcm_work); + + if (tascam->playback_substream) + snd_pcm_stop(tascam->playback_substream, SNDRV_PCM_STATE_XRUN); + if (tascam->capture_substream) + snd_pcm_stop(tascam->capture_substream, SNDRV_PCM_STATE_XRUN); +} diff --git a/sound/usb/usx2y/usX2Yhwdep.c b/sound/usb/usx2y/usX2Yhwdep.c index c1dd9a7b48df..4d7925184826 100644 --- a/sound/usb/usx2y/usX2Yhwdep.c +++ b/sound/usb/usx2y/usX2Yhwdep.c @@ -1,23 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Driver for Tascam US-X2Y USB soundcards * * FPGA Loader + ALSA Startup * * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/interrupt.h> @@ -34,22 +21,15 @@ static vm_fault_t snd_us428ctls_vm_fault(struct vm_fault *vmf) { unsigned long offset; - struct page * page; + struct page *page; void *vaddr; - snd_printdd("ENTER, start %lXh, pgoff %ld\n", - vmf->vma->vm_start, - vmf->pgoff); - offset = vmf->pgoff << PAGE_SHIFT; - vaddr = (char *)((struct usX2Ydev *)vmf->vma->vm_private_data)->us428ctls_sharedmem + offset; + vaddr = (char *)((struct usx2ydev *)vmf->vma->vm_private_data)->us428ctls_sharedmem + offset; page = virt_to_page(vaddr); get_page(page); vmf->page = page; - snd_printdd("vaddr=%p made us428ctls_vm_fault() page %p\n", - vaddr, page); - return 0; } @@ -57,31 +37,25 @@ static const struct vm_operations_struct us428ctls_vm_ops = { .fault = snd_us428ctls_vm_fault, }; -static int snd_us428ctls_mmap(struct snd_hwdep * hw, struct file *filp, struct vm_area_struct *area) +static int snd_us428ctls_mmap(struct snd_hwdep *hw, struct file *filp, struct vm_area_struct *area) { unsigned long size = (unsigned long)(area->vm_end - area->vm_start); - struct usX2Ydev *us428 = hw->private_data; + struct usx2ydev *us428 = hw->private_data; // FIXME this hwdep interface is used twice: fpga download and mmap for controlling Lights etc. Maybe better using 2 hwdep devs? // so as long as the device isn't fully initialised yet we return -EBUSY here. - if (!(us428->chip_status & USX2Y_STAT_CHIP_INIT)) + if (!(us428->chip_status & USX2Y_STAT_CHIP_INIT)) return -EBUSY; - /* if userspace tries to mmap beyond end of our buffer, fail */ - if (size > PAGE_ALIGN(sizeof(struct us428ctls_sharedmem))) { - snd_printd( "%lu > %lu\n", size, (unsigned long)sizeof(struct us428ctls_sharedmem)); - return -EINVAL; + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > US428_SHAREDMEM_PAGES) { + dev_dbg(hw->card->dev, "%s: mmap size %lu > %lu\n", __func__, + size, (unsigned long)US428_SHAREDMEM_PAGES); + return -EINVAL; } - if (!us428->us428ctls_sharedmem) { - init_waitqueue_head(&us428->us428ctls_wait_queue_head); - if(!(us428->us428ctls_sharedmem = snd_malloc_pages(sizeof(struct us428ctls_sharedmem), GFP_KERNEL))) - return -ENOMEM; - memset(us428->us428ctls_sharedmem, -1, sizeof(struct us428ctls_sharedmem)); - us428->us428ctls_sharedmem->CtlSnapShotLast = -2; - } area->vm_ops = &us428ctls_vm_ops; - area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + vm_flags_set(area, VM_DONTEXPAND | VM_DONTDUMP); area->vm_private_data = hw->private_data; return 0; } @@ -89,29 +63,30 @@ static int snd_us428ctls_mmap(struct snd_hwdep * hw, struct file *filp, struct v static __poll_t snd_us428ctls_poll(struct snd_hwdep *hw, struct file *file, poll_table *wait) { __poll_t mask = 0; - struct usX2Ydev *us428 = hw->private_data; + struct usx2ydev *us428 = hw->private_data; struct us428ctls_sharedmem *shm = us428->us428ctls_sharedmem; + if (us428->chip_status & USX2Y_STAT_CHIP_HUP) return EPOLLHUP; poll_wait(file, &us428->us428ctls_wait_queue_head, wait); - if (shm != NULL && shm->CtlSnapShotLast != shm->CtlSnapShotRed) + if (shm && shm->ctl_snapshot_last != shm->ctl_snapshot_red) mask |= EPOLLIN; return mask; } -static int snd_usX2Y_hwdep_dsp_status(struct snd_hwdep *hw, +static int snd_usx2y_hwdep_dsp_status(struct snd_hwdep *hw, struct snd_hwdep_dsp_status *info) { - static char *type_ids[USX2Y_TYPE_NUMS] = { + static const char * const type_ids[USX2Y_TYPE_NUMS] = { [USX2Y_TYPE_122] = "us122", [USX2Y_TYPE_224] = "us224", [USX2Y_TYPE_428] = "us428", }; - struct usX2Ydev *us428 = hw->private_data; + struct usx2ydev *us428 = hw->private_data; int id = -1; switch (le16_to_cpu(us428->dev->descriptor.idProduct)) { @@ -125,92 +100,90 @@ static int snd_usX2Y_hwdep_dsp_status(struct snd_hwdep *hw, id = USX2Y_TYPE_428; break; } - if (0 > id) + if (id < 0) return -ENODEV; - strcpy(info->id, type_ids[id]); + strscpy(info->id, type_ids[id]); info->num_dsps = 2; // 0: Prepad Data, 1: FPGA Code if (us428->chip_status & USX2Y_STAT_CHIP_INIT) info->chip_ready = 1; - info->version = USX2Y_DRIVER_VERSION; + info->version = USX2Y_DRIVER_VERSION; return 0; } - -static int usX2Y_create_usbmidi(struct snd_card *card) +static int usx2y_create_usbmidi(struct snd_card *card) { - static struct snd_usb_midi_endpoint_info quirk_data_1 = { + static const struct snd_usb_midi_endpoint_info quirk_data_1 = { .out_ep = 0x06, .in_ep = 0x06, .out_cables = 0x001, .in_cables = 0x001 }; - static struct snd_usb_audio_quirk quirk_1 = { + static const struct snd_usb_audio_quirk quirk_1 = { .vendor_name = "TASCAM", .product_name = NAME_ALLCAPS, - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, .data = &quirk_data_1 }; - static struct snd_usb_midi_endpoint_info quirk_data_2 = { + static const struct snd_usb_midi_endpoint_info quirk_data_2 = { .out_ep = 0x06, .in_ep = 0x06, .out_cables = 0x003, .in_cables = 0x003 }; - static struct snd_usb_audio_quirk quirk_2 = { + static const struct snd_usb_audio_quirk quirk_2 = { .vendor_name = "TASCAM", .product_name = "US428", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, .data = &quirk_data_2 }; - struct usb_device *dev = usX2Y(card)->dev; + struct usb_device *dev = usx2y(card)->dev; struct usb_interface *iface = usb_ifnum_to_if(dev, 0); - struct snd_usb_audio_quirk *quirk = + const struct snd_usb_audio_quirk *quirk = le16_to_cpu(dev->descriptor.idProduct) == USB_ID_US428 ? &quirk_2 : &quirk_1; - snd_printdd("usX2Y_create_usbmidi \n"); - return snd_usbmidi_create(card, iface, &usX2Y(card)->midi_list, quirk); + return snd_usbmidi_create(card, iface, &usx2y(card)->midi_list, quirk); } -static int usX2Y_create_alsa_devices(struct snd_card *card) +static int usx2y_create_alsa_devices(struct snd_card *card) { int err; - do { - if ((err = usX2Y_create_usbmidi(card)) < 0) { - snd_printk(KERN_ERR "usX2Y_create_alsa_devices: usX2Y_create_usbmidi error %i \n", err); - break; - } - if ((err = usX2Y_audio_create(card)) < 0) - break; - if ((err = usX2Y_hwdep_pcm_new(card)) < 0) - break; - if ((err = snd_card_register(card)) < 0) - break; - } while (0); - - return err; -} + err = usx2y_create_usbmidi(card); + if (err < 0) { + dev_err(card->dev, "%s: usx2y_create_usbmidi error %i\n", + __func__, err); + return err; + } + err = usx2y_audio_create(card); + if (err < 0) + return err; + err = usx2y_hwdep_pcm_new(card); + if (err < 0) + return err; + err = snd_card_register(card); + if (err < 0) + return err; + return 0; +} -static int snd_usX2Y_hwdep_dsp_load(struct snd_hwdep *hw, +static int snd_usx2y_hwdep_dsp_load(struct snd_hwdep *hw, struct snd_hwdep_dsp_image *dsp) { - struct usX2Ydev *priv = hw->private_data; - struct usb_device* dev = priv->dev; + struct usx2ydev *priv = hw->private_data; + struct usb_device *dev = priv->dev; int lret, err; char *buf; - snd_printdd( "dsp_load %s\n", dsp->name); - buf = memdup_user(dsp->image, dsp->length); if (IS_ERR(buf)) return PTR_ERR(buf); err = usb_set_interface(dev, 0, 1); if (err) - snd_printk(KERN_ERR "usb_set_interface error \n"); + dev_err(&dev->dev, "usb_set_interface error\n"); else err = usb_bulk_msg(dev, usb_sndbulkpipe(dev, 2), buf, dsp->length, &lret, 6000); kfree(buf); @@ -218,45 +191,50 @@ static int snd_usX2Y_hwdep_dsp_load(struct snd_hwdep *hw, return err; if (dsp->index == 1) { msleep(250); // give the device some time - err = usX2Y_AsyncSeq04_init(priv); + err = usx2y_async_seq04_init(priv); if (err) { - snd_printk(KERN_ERR "usX2Y_AsyncSeq04_init error \n"); + dev_err(&dev->dev, "usx2y_async_seq04_init error\n"); return err; } - err = usX2Y_In04_init(priv); + err = usx2y_in04_init(priv); if (err) { - snd_printk(KERN_ERR "usX2Y_In04_init error \n"); + dev_err(&dev->dev, "usx2y_in04_init error\n"); return err; } - err = usX2Y_create_alsa_devices(hw->card); + err = usx2y_create_alsa_devices(hw->card); if (err) { - snd_printk(KERN_ERR "usX2Y_create_alsa_devices error %i \n", err); - snd_card_free(hw->card); + dev_err(&dev->dev, "usx2y_create_alsa_devices error %i\n", err); return err; } - priv->chip_status |= USX2Y_STAT_CHIP_INIT; - snd_printdd("%s: alsa all started\n", hw->name); + priv->chip_status |= USX2Y_STAT_CHIP_INIT; } return err; } - -int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device) +int usx2y_hwdep_new(struct snd_card *card, struct usb_device *device) { int err; struct snd_hwdep *hw; + struct usx2ydev *us428 = usx2y(card); - if ((err = snd_hwdep_new(card, SND_USX2Y_LOADER_ID, 0, &hw)) < 0) + err = snd_hwdep_new(card, SND_USX2Y_LOADER_ID, 0, &hw); + if (err < 0) return err; hw->iface = SNDRV_HWDEP_IFACE_USX2Y; - hw->private_data = usX2Y(card); - hw->ops.dsp_status = snd_usX2Y_hwdep_dsp_status; - hw->ops.dsp_load = snd_usX2Y_hwdep_dsp_load; + hw->private_data = us428; + hw->ops.dsp_status = snd_usx2y_hwdep_dsp_status; + hw->ops.dsp_load = snd_usx2y_hwdep_dsp_load; hw->ops.mmap = snd_us428ctls_mmap; hw->ops.poll = snd_us428ctls_poll; hw->exclusive = 1; sprintf(hw->name, "/dev/bus/usb/%03d/%03d", device->bus->busnum, device->devnum); + + us428->us428ctls_sharedmem = alloc_pages_exact(US428_SHAREDMEM_PAGES, GFP_KERNEL); + if (!us428->us428ctls_sharedmem) + return -ENOMEM; + memset(us428->us428ctls_sharedmem, -1, US428_SHAREDMEM_PAGES); + us428->us428ctls_sharedmem->ctl_snapshot_last = -2; + return 0; } - diff --git a/sound/usb/usx2y/usX2Yhwdep.h b/sound/usb/usx2y/usX2Yhwdep.h index 457199b5ed03..0c9946d9cd99 100644 --- a/sound/usb/usx2y/usX2Yhwdep.h +++ b/sound/usb/usx2y/usX2Yhwdep.h @@ -2,6 +2,6 @@ #ifndef USX2YHWDEP_H #define USX2YHWDEP_H -int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device); +int usx2y_hwdep_new(struct snd_card *card, struct usb_device *device); #endif diff --git a/sound/usb/usx2y/usb_stream.c b/sound/usb/usx2y/usb_stream.c index b0f8979ff2d2..3122cf653273 100644 --- a/sound/usb/usx2y/usb_stream.c +++ b/sound/usb/usx2y/usb_stream.c @@ -1,19 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.de> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include <linux/usb.h> @@ -21,12 +8,12 @@ #include "usb_stream.h" - /* setup */ -static unsigned usb_stream_next_packet_size(struct usb_stream_kernel *sk) +static unsigned int usb_stream_next_packet_size(struct usb_stream_kernel *sk) { struct usb_stream *s = sk->s; + sk->out_phase_peeked = (sk->out_phase & 0xffff) + sk->freqn; return (sk->out_phase_peeked >> 16) * s->cfg.frame_size; } @@ -38,6 +25,7 @@ static void playback_prep_freqn(struct usb_stream_kernel *sk, struct urb *urb) for (pack = 0; pack < sk->n_o_ps; pack++) { int l = usb_stream_next_packet_size(sk); + if (s->idle_outsize + lb + l > s->period_size) goto check; @@ -46,29 +34,28 @@ static void playback_prep_freqn(struct usb_stream_kernel *sk, struct urb *urb) urb->iso_frame_desc[pack].length = l; lb += l; } - snd_printdd(KERN_DEBUG "%i\n", lb); check: urb->number_of_packets = pack; urb->transfer_buffer_length = lb; s->idle_outsize += lb - s->period_size; - snd_printdd(KERN_DEBUG "idle=%i ul=%i ps=%i\n", s->idle_outsize, - lb, s->period_size); } -static int init_pipe_urbs(struct usb_stream_kernel *sk, unsigned use_packsize, - struct urb **urbs, char *transfer, - struct usb_device *dev, int pipe) +static int init_pipe_urbs(struct usb_stream_kernel *sk, + unsigned int use_packsize, + struct urb **urbs, char *transfer, + struct usb_device *dev, int pipe) { int u, p; int maxpacket = use_packsize ? - use_packsize : usb_maxpacket(dev, pipe, usb_pipeout(pipe)); + use_packsize : usb_maxpacket(dev, pipe); int transfer_length = maxpacket * sk->n_o_ps; for (u = 0; u < USB_STREAM_NURBS; ++u, transfer += transfer_length) { struct urb *urb = urbs[u]; struct usb_iso_packet_descriptor *desc; + urb->transfer_buffer = transfer; urb->dev = dev; urb->pipe = pipe; @@ -93,18 +80,22 @@ static int init_pipe_urbs(struct usb_stream_kernel *sk, unsigned use_packsize, return 0; } -static int init_urbs(struct usb_stream_kernel *sk, unsigned use_packsize, - struct usb_device *dev, int in_pipe, int out_pipe) +static int init_urbs(struct usb_stream_kernel *sk, unsigned int use_packsize, + struct usb_device *dev, int in_pipe, int out_pipe) { struct usb_stream *s = sk->s; - char *indata = (char *)s + sizeof(*s) + - sizeof(struct usb_stream_packet) * - s->inpackets; + char *indata = + (char *)s + sizeof(*s) + sizeof(struct usb_stream_packet) * s->inpackets; int u; for (u = 0; u < USB_STREAM_NURBS; ++u) { sk->inurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL); + if (!sk->inurb[u]) + return -ENOMEM; + sk->outurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL); + if (!sk->outurb[u]) + return -ENOMEM; } if (init_pipe_urbs(sk, use_packsize, sk->inurb, indata, dev, in_pipe) || @@ -115,12 +106,11 @@ static int init_urbs(struct usb_stream_kernel *sk, unsigned use_packsize, return 0; } - /* * convert a sampling rate into our full speed format (fs/1000 in Q16.16) * this will overflow at approx 524 kHz */ -static inline unsigned get_usb_full_speed_rate(unsigned rate) +static inline unsigned int get_usb_full_speed_rate(unsigned int rate) { return ((rate << 13) + 62) / 125; } @@ -129,7 +119,7 @@ static inline unsigned get_usb_full_speed_rate(unsigned rate) * convert a sampling rate into USB high speed format (fs/8000 in Q16.16) * this will overflow at approx 4 MHz */ -static inline unsigned get_usb_high_speed_rate(unsigned rate) +static inline unsigned int get_usb_high_speed_rate(unsigned int rate) { return ((rate << 10) + 62) / 125; } @@ -137,7 +127,7 @@ static inline unsigned get_usb_high_speed_rate(unsigned rate) void usb_stream_free(struct usb_stream_kernel *sk) { struct usb_stream *s; - unsigned u; + unsigned int u; for (u = 0; u < USB_STREAM_NURBS; ++u) { usb_free_urb(sk->inurb[u]); @@ -150,30 +140,35 @@ void usb_stream_free(struct usb_stream_kernel *sk) if (!s) return; - free_pages((unsigned long)sk->write_page, get_order(s->write_size)); - sk->write_page = NULL; - free_pages((unsigned long)s, get_order(s->read_size)); + if (sk->write_page) { + free_pages_exact(sk->write_page, s->write_size); + sk->write_page = NULL; + } + + free_pages_exact(s, s->read_size); sk->s = NULL; } struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk, struct usb_device *dev, - unsigned in_endpoint, unsigned out_endpoint, - unsigned sample_rate, unsigned use_packsize, - unsigned period_frames, unsigned frame_size) + unsigned int in_endpoint, + unsigned int out_endpoint, + unsigned int sample_rate, + unsigned int use_packsize, + unsigned int period_frames, + unsigned int frame_size) { int packets, max_packsize; int in_pipe, out_pipe; int read_size = sizeof(struct usb_stream); int write_size; int usb_frames = dev->speed == USB_SPEED_HIGH ? 8000 : 1000; - int pg; in_pipe = usb_rcvisocpipe(dev, in_endpoint); out_pipe = usb_sndisocpipe(dev, out_endpoint); max_packsize = use_packsize ? - use_packsize : usb_maxpacket(dev, in_pipe, 0); + use_packsize : usb_maxpacket(dev, in_pipe); /* t_period = period_frames / sample_rate @@ -189,19 +184,18 @@ struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk, read_size += packets * USB_STREAM_URBDEPTH * (max_packsize + sizeof(struct usb_stream_packet)); - max_packsize = usb_maxpacket(dev, out_pipe, 1); + max_packsize = usb_maxpacket(dev, out_pipe); write_size = max_packsize * packets * USB_STREAM_URBDEPTH; if (read_size >= 256*PAGE_SIZE || write_size >= 256*PAGE_SIZE) { - snd_printk(KERN_WARNING "a size exceeds 128*PAGE_SIZE\n"); + dev_warn(&dev->dev, "%s: a size exceeds 128*PAGE_SIZE\n", __func__); goto out; } - pg = get_order(read_size); - sk->s = (void *) __get_free_pages(GFP_KERNEL|__GFP_COMP|__GFP_ZERO| - __GFP_NOWARN, pg); + sk->s = alloc_pages_exact(read_size, + GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN); if (!sk->s) { - snd_printk(KERN_WARNING "couldn't __get_free_pages()\n"); + dev_warn(&dev->dev, "us122l: couldn't allocate read buffer\n"); goto out; } sk->s->cfg.version = USB_STREAM_INTERFACE_VERSION; @@ -216,13 +210,11 @@ struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk, sk->s->period_size = frame_size * period_frames; sk->s->write_size = write_size; - pg = get_order(write_size); - sk->write_page = - (void *)__get_free_pages(GFP_KERNEL|__GFP_COMP|__GFP_ZERO| - __GFP_NOWARN, pg); + sk->write_page = alloc_pages_exact(write_size, + GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN); if (!sk->write_page) { - snd_printk(KERN_WARNING "couldn't __get_free_pages()\n"); + dev_warn(&dev->dev, "us122l: couldn't allocate write buffer\n"); usb_stream_free(sk); return NULL; } @@ -243,15 +235,16 @@ out: return sk->s; } - /* start */ static bool balance_check(struct usb_stream_kernel *sk, struct urb *urb) { bool r; + if (unlikely(urb->status)) { if (urb->status != -ESHUTDOWN && urb->status != -ENOENT) - snd_printk(KERN_WARNING "status=%i\n", urb->status); + dev_warn(&sk->dev->dev, "%s: status=%i\n", __func__, + urb->status); sk->iso_frame_balance = 0x7FFFFFFF; return false; } @@ -279,6 +272,7 @@ static void subs_set_complete(struct urb **urbs, void (*complete)(struct urb *)) for (u = 0; u < USB_STREAM_NURBS; u++) { struct urb *urb = urbs[u]; + urb->complete = complete; } } @@ -296,6 +290,7 @@ static int usb_stream_prepare_playback(struct usb_stream_kernel *sk, for (; s->sync_packet < 0; ++p, ++s->sync_packet) { struct urb *ii = sk->completed_inurb; + id = ii->iso_frame_desc + ii->number_of_packets + s->sync_packet; l = id->actual_length; @@ -321,17 +316,18 @@ static int usb_stream_prepare_playback(struct usb_stream_kernel *sk, check_ok: s->sync_packet -= inurb->number_of_packets; if (unlikely(s->sync_packet < -2 || s->sync_packet > 0)) { - snd_printk(KERN_WARNING "invalid sync_packet = %i;" - " p=%i nop=%i %i %x %x %x > %x\n", - s->sync_packet, p, inurb->number_of_packets, - s->idle_outsize + lb + l, - s->idle_outsize, lb, l, - s->period_size); + dev_warn(&sk->dev->dev, + "%s: invalid sync_packet = %i; p=%i nop=%i %i %x %x %x > %x\n", + __func__, + s->sync_packet, p, inurb->number_of_packets, + s->idle_outsize + lb + l, + s->idle_outsize, lb, l, + s->period_size); return -1; } if (unlikely(lb % s->cfg.frame_size)) { - snd_printk(KERN_WARNING"invalid outsize = %i\n", - lb); + dev_warn(&sk->dev->dev, "%s: invalid outsize = %i\n", + __func__, lb); return -1; } s->idle_outsize += lb - s->period_size; @@ -340,7 +336,7 @@ check_ok: if (s->idle_outsize <= 0) return 0; - snd_printk(KERN_WARNING "idle=%i\n", s->idle_outsize); + dev_warn(&sk->dev->dev, "%s: idle=%i\n", __func__, s->idle_outsize); return -1; } @@ -363,6 +359,7 @@ static int submit_urbs(struct usb_stream_kernel *sk, struct urb *inurb, struct urb *outurb) { int err; + prepare_inurb(sk->idle_outurb->number_of_packets, sk->idle_inurb); err = usb_submit_urb(sk->idle_inurb, GFP_ATOMIC); if (err < 0) @@ -379,7 +376,7 @@ static int submit_urbs(struct usb_stream_kernel *sk, return 0; report_failure: - snd_printk(KERN_ERR "%i\n", err); + dev_err(&sk->dev->dev, "%s error: %i\n", __func__, err); return err; } @@ -426,8 +423,8 @@ loop: } if (iu == sk->completed_inurb) { if (l != s->period_size) - printk(KERN_DEBUG"%s:%i %i\n", __func__, __LINE__, - l/(int)s->cfg.frame_size); + dev_dbg(&sk->dev->dev, "%s:%i %i\n", __func__, __LINE__, + l/(int)s->cfg.frame_size); return; } @@ -459,10 +456,11 @@ static void stream_idle(struct usb_stream_kernel *sk, for (p = 0; p < inurb->number_of_packets; ++p) { struct usb_iso_packet_descriptor *id = inurb->iso_frame_desc; + l = id[p].actual_length; if (unlikely(l == 0 || id[p].status)) { - snd_printk(KERN_WARNING "underrun, status=%u\n", - id[p].status); + dev_warn(&sk->dev->dev, "%s: underrun, status=%u\n", + __func__, id[p].status); goto err_out; } s->inpacket_head++; @@ -483,8 +481,8 @@ static void stream_idle(struct usb_stream_kernel *sk, } s->idle_insize += urb_size - s->period_size; if (s->idle_insize < 0) { - snd_printk(KERN_WARNING "%i\n", - (s->idle_insize)/(int)s->cfg.frame_size); + dev_warn(&sk->dev->dev, "%s error: %i\n", __func__, + (s->idle_insize)/(int)s->cfg.frame_size); goto err_out; } s->insize_done += urb_size; @@ -515,6 +513,7 @@ err_out: static void i_capture_idle(struct urb *urb) { struct usb_stream_kernel *sk = urb->context; + if (balance_capture(sk, urb)) stream_idle(sk, urb, sk->i_urb); } @@ -522,6 +521,7 @@ static void i_capture_idle(struct urb *urb) static void i_playback_idle(struct urb *urb) { struct usb_stream_kernel *sk = urb->context; + if (balance_playback(sk, urb)) stream_idle(sk, sk->i_urb, urb); } @@ -530,10 +530,12 @@ static void stream_start(struct usb_stream_kernel *sk, struct urb *inurb, struct urb *outurb) { struct usb_stream *s = sk->s; + if (s->state >= usb_stream_sync1) { int l, p, max_diff, max_diff_0; int urb_size = 0; - unsigned frames_per_packet, min_frames = 0; + unsigned int frames_per_packet, min_frames = 0; + frames_per_packet = (s->period_size - s->idle_insize); frames_per_packet <<= 8; frames_per_packet /= @@ -548,32 +550,30 @@ static void stream_start(struct usb_stream_kernel *sk, max_diff = max_diff_0; for (p = 0; p < inurb->number_of_packets; ++p) { int diff; + l = inurb->iso_frame_desc[p].actual_length; urb_size += l; min_frames += frames_per_packet; diff = urb_size - (min_frames >> 8) * s->cfg.frame_size; - if (diff < max_diff) { - snd_printdd(KERN_DEBUG "%i %i %i %i\n", - s->insize_done, - urb_size / (int)s->cfg.frame_size, - inurb->number_of_packets, diff); + if (diff < max_diff) max_diff = diff; - } } s->idle_insize -= max_diff - max_diff_0; s->idle_insize += urb_size - s->period_size; if (s->idle_insize < 0) { - snd_printk(KERN_WARNING "%i %i %i\n", - s->idle_insize, urb_size, s->period_size); + dev_warn(&sk->dev->dev, "%s idle_insize: %i %i %i\n", + __func__, + s->idle_insize, urb_size, s->period_size); return; } else if (s->idle_insize == 0) { s->next_inpacket_split = (s->inpacket_head + 1) % s->inpackets; s->next_inpacket_split_at = 0; } else { - unsigned split = s->inpacket_head; + unsigned int split = s->inpacket_head; + l = s->idle_insize; while (l > s->inpacket[split].length) { l -= s->inpacket[split].length; @@ -615,16 +615,17 @@ static void i_capture_start(struct urb *urb) int empty = 0; if (urb->status) { - snd_printk(KERN_WARNING "status=%i\n", urb->status); + dev_warn(&sk->dev->dev, "%s: status=%i\n", __func__, urb->status); return; } for (p = 0; p < urb->number_of_packets; ++p) { int l = id[p].actual_length; + if (l < s->cfg.frame_size) { ++empty; if (s->state >= usb_stream_sync0) { - snd_printk(KERN_WARNING "%i\n", l); + dev_warn(&sk->dev->dev, "%s: length %i\n", __func__, l); return; } } @@ -636,10 +637,11 @@ static void i_capture_start(struct urb *urb) } #ifdef SHOW_EMPTY if (empty) { - printk(KERN_DEBUG"%s:%i: %i", __func__, __LINE__, - urb->iso_frame_desc[0].actual_length); + dev_dbg(&sk->dev->dev, "%s:%i: %i", __func__, __LINE__, + urb->iso_frame_desc[0].actual_length); for (pack = 1; pack < urb->number_of_packets; ++pack) { int l = urb->iso_frame_desc[pack].actual_length; + printk(KERN_CONT " %i", l); } printk(KERN_CONT "\n"); @@ -655,6 +657,7 @@ static void i_capture_start(struct urb *urb) static void i_playback_start(struct urb *urb) { struct usb_stream_kernel *sk = urb->context; + if (balance_playback(sk, urb)) stream_start(sk, sk->i_urb, urb); } @@ -662,7 +665,7 @@ static void i_playback_start(struct urb *urb) int usb_stream_start(struct usb_stream_kernel *sk) { struct usb_stream *s = sk->s; - int frame = 0, iters = 0; + int frame = 0; int u, err; int try = 0; @@ -683,6 +686,7 @@ dotry: for (u = 0; u < 2; u++) { struct urb *inurb = sk->inurb[u]; struct urb *outurb = sk->outurb[u]; + playback_prep_freqn(sk, outurb); inurb->number_of_packets = outurb->number_of_packets; inurb->transfer_buffer_length = @@ -692,44 +696,46 @@ dotry: if (u == 0) { int now; struct usb_device *dev = inurb->dev; + frame = usb_get_current_frame_number(dev); do { now = usb_get_current_frame_number(dev); - ++iters; } while (now > -1 && now == frame); } err = usb_submit_urb(inurb, GFP_ATOMIC); if (err < 0) { - snd_printk(KERN_ERR"usb_submit_urb(sk->inurb[%i])" - " returned %i\n", u, err); + dev_err(&sk->dev->dev, + "%s: usb_submit_urb(sk->inurb[%i]) returned %i\n", + __func__, u, err); return err; } err = usb_submit_urb(outurb, GFP_ATOMIC); if (err < 0) { - snd_printk(KERN_ERR"usb_submit_urb(sk->outurb[%i])" - " returned %i\n", u, err); + dev_err(&sk->dev->dev, + "%s: usb_submit_urb(sk->outurb[%i]) returned %i\n", + __func__, u, err); return err; } if (inurb->start_frame != outurb->start_frame) { - snd_printd(KERN_DEBUG - "u[%i] start_frames differ in:%u out:%u\n", - u, inurb->start_frame, outurb->start_frame); + dev_dbg(&sk->dev->dev, + "%s: u[%i] start_frames differ in:%u out:%u\n", + __func__, u, inurb->start_frame, outurb->start_frame); goto check_retry; } } - snd_printdd(KERN_DEBUG "%i %i\n", frame, iters); try = 0; check_retry: if (try) { usb_stream_stop(sk); if (try < 5) { msleep(1500); - snd_printd(KERN_DEBUG "goto dotry;\n"); + dev_dbg(&sk->dev->dev, "goto dotry;\n"); goto dotry; } - snd_printk(KERN_WARNING"couldn't start" - " all urbs on the same start_frame.\n"); + dev_warn(&sk->dev->dev, + "%s: couldn't start all urbs on the same start_frame.\n", + __func__); return -EFAULT; } @@ -741,8 +747,8 @@ check_retry: /* wait, check */ { int wait_ms = 3000; + while (s->state != usb_stream_ready && wait_ms > 0) { - snd_printdd(KERN_DEBUG "%i\n", s->state); msleep(200); wait_ms -= 200; } @@ -757,6 +763,7 @@ check_retry: void usb_stream_stop(struct usb_stream_kernel *sk) { int u; + if (!sk->s) return; for (u = 0; u < USB_STREAM_NURBS; ++u) { diff --git a/sound/usb/usx2y/usb_stream.h b/sound/usb/usx2y/usb_stream.h index 851358a8d709..2ed10add49f1 100644 --- a/sound/usb/usx2y/usb_stream.h +++ b/sound/usb/usx2y/usb_stream.h @@ -9,10 +9,11 @@ struct usb_stream_kernel { struct usb_stream *s; + struct usb_device *dev; void *write_page; - unsigned n_o_ps; + unsigned int n_o_ps; struct urb *inurb[USB_STREAM_NURBS]; struct urb *idle_inurb; @@ -26,18 +27,21 @@ struct usb_stream_kernel { wait_queue_head_t sleep; - unsigned out_phase; - unsigned out_phase_peeked; - unsigned freqn; + unsigned int out_phase; + unsigned int out_phase_peeked; + unsigned int freqn; }; struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk, struct usb_device *dev, - unsigned in_endpoint, unsigned out_endpoint, - unsigned sample_rate, unsigned use_packsize, - unsigned period_frames, unsigned frame_size); -void usb_stream_free(struct usb_stream_kernel *); -int usb_stream_start(struct usb_stream_kernel *); -void usb_stream_stop(struct usb_stream_kernel *); + unsigned int in_endpoint, + unsigned int out_endpoint, + unsigned int sample_rate, + unsigned int use_packsize, + unsigned int period_frames, + unsigned int frame_size); +void usb_stream_free(struct usb_stream_kernel *sk); +int usb_stream_start(struct usb_stream_kernel *sk); +void usb_stream_stop(struct usb_stream_kernel *sk); #endif /* __USB_STREAM_H */ diff --git a/sound/usb/usx2y/usbus428ctldefs.h b/sound/usb/usx2y/usbus428ctldefs.h index b864e7e262e5..9ba15d974e63 100644 --- a/sound/usb/usx2y/usbus428ctldefs.h +++ b/sound/usb/usx2y/usbus428ctldefs.h @@ -1,44 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -enum E_In84{ - eFader0 = 0, - eFader1, - eFader2, - eFader3, - eFader4, - eFader5, - eFader6, - eFader7, - eFaderM, - eTransport, - eModifier = 10, - eFilterSelect, - eSelect, - eMute, +enum E_IN84 { + E_FADER_0 = 0, + E_FADER_1, + E_FADER_2, + E_FADER_3, + E_FADER_4, + E_FADER_5, + E_FADER_6, + E_FADER_7, + E_FADER_M, + E_TRANSPORT, + E_MODIFIER = 10, + E_FILTER_SELECT, + E_SELECT, + E_MUTE, - eSwitch = 15, - eWheelGain, - eWheelFreq, - eWheelQ, - eWheelPan, - eWheel = 20 + E_SWITCH = 15, + E_WHEEL_GAIN, + E_WHEEL_FREQ, + E_WHEEL_Q, + E_WHEEL_PAN, + E_WHEEL = 20 }; #define T_RECORD 1 @@ -52,53 +39,55 @@ enum E_In84{ struct us428_ctls { - unsigned char Fader[9]; - unsigned char Transport; - unsigned char Modifier; - unsigned char FilterSelect; - unsigned char Select; - unsigned char Mute; - unsigned char UNKNOWN; - unsigned char Switch; - unsigned char Wheel[5]; + unsigned char fader[9]; + unsigned char transport; + unsigned char modifier; + unsigned char filters_elect; + unsigned char select; + unsigned char mute; + unsigned char unknown; + unsigned char wswitch; + unsigned char wheel[5]; }; -struct us428_setByte { - unsigned char Offset, - Value; +struct us428_set_byte { + unsigned char offset, + value; }; enum { - eLT_Volume = 0, - eLT_Light + ELT_VOLUME = 0, + ELT_LIGHT }; -struct usX2Y_volume { - unsigned char Channel, - LH, - LL, - RH, - RL; +struct usx2y_volume { + unsigned char channel, + lh, + ll, + rh, + rl; }; struct us428_lights { - struct us428_setByte Light[7]; + struct us428_set_byte light[7]; }; struct us428_p4out { char type; union { - struct usX2Y_volume vol; + struct usx2y_volume vol; struct us428_lights lights; } val; }; -#define N_us428_ctl_BUFS 16 -#define N_us428_p4out_BUFS 16 -struct us428ctls_sharedmem{ - struct us428_ctls CtlSnapShot[N_us428_ctl_BUFS]; - int CtlSnapShotDiffersAt[N_us428_ctl_BUFS]; - int CtlSnapShotLast, CtlSnapShotRed; - struct us428_p4out p4out[N_us428_p4out_BUFS]; - int p4outLast, p4outSent; +#define N_US428_CTL_BUFS 16 +#define N_US428_P4OUT_BUFS 16 +struct us428ctls_sharedmem { + struct us428_ctls ctl_snapshot[N_US428_CTL_BUFS]; + int ctl_snapshot_differs_at[N_US428_CTL_BUFS]; + int ctl_snapshot_last, ctl_snapshot_red; + struct us428_p4out p4out[N_US428_P4OUT_BUFS]; + int p4out_last, p4out_sent; }; + +#define US428_SHAREDMEM_PAGES PAGE_ALIGN(sizeof(struct us428ctls_sharedmem)) diff --git a/sound/usb/usx2y/usbusx2y.c b/sound/usb/usx2y/usbusx2y.c index da4a5a541512..f34e78910200 100644 --- a/sound/usb/usx2y/usbusx2y.c +++ b/sound/usb/usx2y/usbusx2y.c @@ -1,5 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* - * usbusy2y.c - ALSA USB US-428 Driver + * usbusx2y.c - ALSA USB US-428 Driver * 2005-04-14 Karsten Wiese Version 0.8.7.2: @@ -16,7 +17,7 @@ 2004-10-26 Karsten Wiese Version 0.8.6: - wake_up() process waiting in usX2Y_urbs_start() on error. + wake_up() process waiting in usx2y_urbs_start() on error. 2004-10-21 Karsten Wiese Version 0.8.5: @@ -47,7 +48,7 @@ 2004-06-12 Karsten Wiese Version 0.6.3: Made it thus the following rule is enforced: - "All pcm substreams of one usX2Y have to operate at the same rate & format." + "All pcm substreams of one usx2y have to operate at the same rate & format." 2004-04-06 Karsten Wiese Version 0.6.0: @@ -69,7 +70,7 @@ 2003-11-03 Karsten Wiese Version 0.3: - 24Bit support. + 24Bit support. "arecord -D hw:1 -c 2 -r 48000 -M -f S24_3LE|aplay -D hw:1 -c 2 -r 48000 -M -f S24_3LE" works. 2003-08-22 Karsten Wiese @@ -93,16 +94,15 @@ This helped me much on my slowish PII 400 & PIII 500. ACPI yet untested but might cause the same bad behaviour. Use a kernel with lowlatency and preemptiv patches applied. - To autoload snd-usb-midi append a line + To autoload snd-usb-midi append a line post-install snd-usb-us428 modprobe snd-usb-midi to /etc/modules.conf. known problems: sliders, knobs, lights not yet handled except MASTER Volume slider. - "pcm -c 2" doesn't work. "pcm -c 2 -m direct_interleaved" does. + "pcm -c 2" doesn't work. "pcm -c 2 -m direct_interleaved" does. KDE3: "Enable full duplex operation" deadlocks. - 2002-08-31 Karsten Wiese Version 0.0.3: audio also simplex; simplifying: iso urbs only 1 packet, melted structs. @@ -114,21 +114,7 @@ The firmware has been sniffed from win2k us-428 driver 3.09. * Copyright (c) 2002 - 2004 Karsten Wiese - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ + */ #include <linux/init.h> #include <linux/module.h> @@ -145,15 +131,12 @@ #include "usbusx2y.h" #include "usX2Yhwdep.h" - - MODULE_AUTHOR("Karsten Wiese <annabellesgarden@yahoo.de>"); MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.8.7.2"); MODULE_LICENSE("GPL"); -MODULE_SUPPORTED_DEVICE("{{TASCAM(0x1604),"NAME_ALLCAPS"(0x8001)(0x8005)(0x8007)}}"); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */ -static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */ static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ module_param_array(index, int, NULL, 0444); @@ -163,306 +146,331 @@ MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS"."); module_param_array(enable, bool, NULL, 0444); MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS"."); +static int snd_usx2y_card_used[SNDRV_CARDS]; -static int snd_usX2Y_card_used[SNDRV_CARDS]; +static void snd_usx2y_card_private_free(struct snd_card *card); +static void usx2y_unlinkseq(struct snd_usx2y_async_seq *s); -static void usX2Y_usb_disconnect(struct usb_device* usb_device, void* ptr); -static void snd_usX2Y_card_private_free(struct snd_card *card); +#ifdef USX2Y_NRPACKS_VARIABLE +int nrpacks = USX2Y_NRPACKS; /* number of packets per urb */ +module_param(nrpacks, int, 0444); +MODULE_PARM_DESC(nrpacks, "Number of packets per URB."); +#endif -/* - * pipe 4 is used for switching the lamps, setting samplerate, volumes .... +/* + * pipe 4 is used for switching the lamps, setting samplerate, volumes .... */ -static void i_usX2Y_Out04Int(struct urb *urb) +static void i_usx2y_out04_int(struct urb *urb) { #ifdef CONFIG_SND_DEBUG if (urb->status) { - int i; - struct usX2Ydev *usX2Y = urb->context; - for (i = 0; i < 10 && usX2Y->AS04.urb[i] != urb; i++); - snd_printdd("i_usX2Y_Out04Int() urb %i status=%i\n", i, urb->status); + int i; + struct usx2ydev *usx2y = urb->context; + + for (i = 0; i < 10 && usx2y->as04.urb[i] != urb; i++) + ; + dev_dbg(&urb->dev->dev, "%s urb %i status=%i\n", __func__, i, urb->status); } #endif } -static void i_usX2Y_In04Int(struct urb *urb) +static void i_usx2y_in04_int(struct urb *urb) { int err = 0; - struct usX2Ydev *usX2Y = urb->context; - struct us428ctls_sharedmem *us428ctls = usX2Y->us428ctls_sharedmem; + struct usx2ydev *usx2y = urb->context; + struct us428ctls_sharedmem *us428ctls = usx2y->us428ctls_sharedmem; + struct us428_p4out *p4out; + int i, j, n, diff, send; - usX2Y->In04IntCalls++; + usx2y->in04_int_calls++; if (urb->status) { - snd_printdd("Interrupt Pipe 4 came back with status=%i\n", urb->status); + dev_dbg(&urb->dev->dev, "Interrupt Pipe 4 came back with status=%i\n", urb->status); return; } - // printk("%i:0x%02X ", 8, (int)((unsigned char*)usX2Y->In04Buf)[8]); Master volume shows 0 here if fader is at max during boot ?!? if (us428ctls) { - int diff = -1; - if (-2 == us428ctls->CtlSnapShotLast) { + diff = -1; + if (us428ctls->ctl_snapshot_last == -2) { diff = 0; - memcpy(usX2Y->In04Last, usX2Y->In04Buf, sizeof(usX2Y->In04Last)); - us428ctls->CtlSnapShotLast = -1; + memcpy(usx2y->in04_last, usx2y->in04_buf, sizeof(usx2y->in04_last)); + us428ctls->ctl_snapshot_last = -1; } else { - int i; for (i = 0; i < 21; i++) { - if (usX2Y->In04Last[i] != ((char*)usX2Y->In04Buf)[i]) { + if (usx2y->in04_last[i] != ((char *)usx2y->in04_buf)[i]) { if (diff < 0) diff = i; - usX2Y->In04Last[i] = ((char*)usX2Y->In04Buf)[i]; + usx2y->in04_last[i] = ((char *)usx2y->in04_buf)[i]; } } } - if (0 <= diff) { - int n = us428ctls->CtlSnapShotLast + 1; - if (n >= N_us428_ctl_BUFS || n < 0) + if (diff >= 0) { + n = us428ctls->ctl_snapshot_last + 1; + if (n >= N_US428_CTL_BUFS || n < 0) n = 0; - memcpy(us428ctls->CtlSnapShot + n, usX2Y->In04Buf, sizeof(us428ctls->CtlSnapShot[0])); - us428ctls->CtlSnapShotDiffersAt[n] = diff; - us428ctls->CtlSnapShotLast = n; - wake_up(&usX2Y->us428ctls_wait_queue_head); + memcpy(us428ctls->ctl_snapshot + n, usx2y->in04_buf, sizeof(us428ctls->ctl_snapshot[0])); + us428ctls->ctl_snapshot_differs_at[n] = diff; + us428ctls->ctl_snapshot_last = n; + wake_up(&usx2y->us428ctls_wait_queue_head); } } - - - if (usX2Y->US04) { - if (0 == usX2Y->US04->submitted) + + if (usx2y->us04) { + if (!usx2y->us04->submitted) { do { - err = usb_submit_urb(usX2Y->US04->urb[usX2Y->US04->submitted++], GFP_ATOMIC); - } while (!err && usX2Y->US04->submitted < usX2Y->US04->len); - } else - if (us428ctls && us428ctls->p4outLast >= 0 && us428ctls->p4outLast < N_us428_p4out_BUFS) { - if (us428ctls->p4outLast != us428ctls->p4outSent) { - int j, send = us428ctls->p4outSent + 1; - if (send >= N_us428_p4out_BUFS) + err = usb_submit_urb(usx2y->us04->urb[usx2y->us04->submitted++], GFP_ATOMIC); + } while (!err && usx2y->us04->submitted < usx2y->us04->len); + } + } else { + if (us428ctls && us428ctls->p4out_last >= 0 && us428ctls->p4out_last < N_US428_P4OUT_BUFS) { + if (us428ctls->p4out_last != us428ctls->p4out_sent) { + send = us428ctls->p4out_sent + 1; + if (send >= N_US428_P4OUT_BUFS) send = 0; - for (j = 0; j < URBS_AsyncSeq && !err; ++j) - if (0 == usX2Y->AS04.urb[j]->status) { - struct us428_p4out *p4out = us428ctls->p4out + send; // FIXME if more than 1 p4out is new, 1 gets lost. - usb_fill_bulk_urb(usX2Y->AS04.urb[j], usX2Y->dev, - usb_sndbulkpipe(usX2Y->dev, 0x04), &p4out->val.vol, - p4out->type == eLT_Light ? sizeof(struct us428_lights) : 5, - i_usX2Y_Out04Int, usX2Y); - err = usb_submit_urb(usX2Y->AS04.urb[j], GFP_ATOMIC); - us428ctls->p4outSent = send; + for (j = 0; j < URBS_ASYNC_SEQ && !err; ++j) { + if (!usx2y->as04.urb[j]->status) { + p4out = us428ctls->p4out + send; // FIXME if more than 1 p4out is new, 1 gets lost. + usb_fill_bulk_urb(usx2y->as04.urb[j], usx2y->dev, + usb_sndbulkpipe(usx2y->dev, 0x04), &p4out->val.vol, + p4out->type == ELT_LIGHT ? sizeof(struct us428_lights) : 5, + i_usx2y_out04_int, usx2y); + err = usb_submit_urb(usx2y->as04.urb[j], GFP_ATOMIC); + us428ctls->p4out_sent = send; break; } + } } } + } if (err) - snd_printk(KERN_ERR "In04Int() usb_submit_urb err=%i\n", err); + dev_err(&urb->dev->dev, "in04_int() usb_submit_urb err=%i\n", err); - urb->dev = usX2Y->dev; + urb->dev = usx2y->dev; usb_submit_urb(urb, GFP_ATOMIC); } /* * Prepare some urbs */ -int usX2Y_AsyncSeq04_init(struct usX2Ydev *usX2Y) +int usx2y_async_seq04_init(struct usx2ydev *usx2y) { - int err = 0, - i; + int err = 0, i; - usX2Y->AS04.buffer = kmalloc_array(URBS_AsyncSeq, - URB_DataLen_AsyncSeq, GFP_KERNEL); - if (NULL == usX2Y->AS04.buffer) { + if (WARN_ON(usx2y->as04.buffer)) + return -EBUSY; + + usx2y->as04.buffer = kmalloc_array(URBS_ASYNC_SEQ, + URB_DATA_LEN_ASYNC_SEQ, GFP_KERNEL); + if (!usx2y->as04.buffer) { err = -ENOMEM; - } else - for (i = 0; i < URBS_AsyncSeq; ++i) { - if (NULL == (usX2Y->AS04.urb[i] = usb_alloc_urb(0, GFP_KERNEL))) { + } else { + for (i = 0; i < URBS_ASYNC_SEQ; ++i) { + usx2y->as04.urb[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!usx2y->as04.urb[i]) { err = -ENOMEM; break; } - usb_fill_bulk_urb( usX2Y->AS04.urb[i], usX2Y->dev, - usb_sndbulkpipe(usX2Y->dev, 0x04), - usX2Y->AS04.buffer + URB_DataLen_AsyncSeq*i, 0, - i_usX2Y_Out04Int, usX2Y - ); - err = usb_urb_ep_type_check(usX2Y->AS04.urb[i]); + usb_fill_bulk_urb(usx2y->as04.urb[i], usx2y->dev, + usb_sndbulkpipe(usx2y->dev, 0x04), + usx2y->as04.buffer + URB_DATA_LEN_ASYNC_SEQ * i, 0, + i_usx2y_out04_int, usx2y); + err = usb_urb_ep_type_check(usx2y->as04.urb[i]); if (err < 0) break; } + } + if (err) + usx2y_unlinkseq(&usx2y->as04); return err; } -int usX2Y_In04_init(struct usX2Ydev *usX2Y) +int usx2y_in04_init(struct usx2ydev *usx2y) { - if (! (usX2Y->In04urb = usb_alloc_urb(0, GFP_KERNEL))) - return -ENOMEM; + int err; - if (! (usX2Y->In04Buf = kmalloc(21, GFP_KERNEL))) { - usb_free_urb(usX2Y->In04urb); - return -ENOMEM; + if (WARN_ON(usx2y->in04_urb)) + return -EBUSY; + + usx2y->in04_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!usx2y->in04_urb) { + err = -ENOMEM; + goto error; } - - init_waitqueue_head(&usX2Y->In04WaitQueue); - usb_fill_int_urb(usX2Y->In04urb, usX2Y->dev, usb_rcvintpipe(usX2Y->dev, 0x4), - usX2Y->In04Buf, 21, - i_usX2Y_In04Int, usX2Y, + + usx2y->in04_buf = kmalloc(21, GFP_KERNEL); + if (!usx2y->in04_buf) { + err = -ENOMEM; + goto error; + } + + init_waitqueue_head(&usx2y->in04_wait_queue); + usb_fill_int_urb(usx2y->in04_urb, usx2y->dev, usb_rcvintpipe(usx2y->dev, 0x4), + usx2y->in04_buf, 21, + i_usx2y_in04_int, usx2y, 10); - if (usb_urb_ep_type_check(usX2Y->In04urb)) - return -EINVAL; - return usb_submit_urb(usX2Y->In04urb, GFP_KERNEL); + if (usb_urb_ep_type_check(usx2y->in04_urb)) { + err = -EINVAL; + goto error; + } + return usb_submit_urb(usx2y->in04_urb, GFP_KERNEL); + + error: + kfree(usx2y->in04_buf); + usb_free_urb(usx2y->in04_urb); + usx2y->in04_buf = NULL; + usx2y->in04_urb = NULL; + return err; } -static void usX2Y_unlinkSeq(struct snd_usX2Y_AsyncSeq *S) +static void usx2y_unlinkseq(struct snd_usx2y_async_seq *s) { int i; - for (i = 0; i < URBS_AsyncSeq; ++i) { - usb_kill_urb(S->urb[i]); - usb_free_urb(S->urb[i]); - S->urb[i] = NULL; + + for (i = 0; i < URBS_ASYNC_SEQ; ++i) { + if (!s->urb[i]) + continue; + usb_kill_urb(s->urb[i]); + usb_free_urb(s->urb[i]); + s->urb[i] = NULL; } - kfree(S->buffer); + kfree(s->buffer); + s->buffer = NULL; } - -static const struct usb_device_id snd_usX2Y_usb_id_table[] = { +static const struct usb_device_id snd_usx2y_usb_id_table[] = { { .match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = 0x1604, - .idProduct = USB_ID_US428 + .idProduct = USB_ID_US428 }, { .match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = 0x1604, - .idProduct = USB_ID_US122 + .idProduct = USB_ID_US122 }, - { + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = 0x1604, .idProduct = USB_ID_US224 }, { /* terminator */ } }; +MODULE_DEVICE_TABLE(usb, snd_usx2y_usb_id_table); -static int usX2Y_create_card(struct usb_device *device, +static int usx2y_create_card(struct usb_device *device, struct usb_interface *intf, struct snd_card **cardp) { int dev; - struct snd_card * card; + struct snd_card *card; int err; for (dev = 0; dev < SNDRV_CARDS; ++dev) - if (enable[dev] && !snd_usX2Y_card_used[dev]) + if (enable[dev] && !snd_usx2y_card_used[dev]) break; if (dev >= SNDRV_CARDS) return -ENODEV; err = snd_card_new(&intf->dev, index[dev], id[dev], THIS_MODULE, - sizeof(struct usX2Ydev), &card); + sizeof(struct usx2ydev), &card); if (err < 0) return err; - snd_usX2Y_card_used[usX2Y(card)->card_index = dev] = 1; - card->private_free = snd_usX2Y_card_private_free; - usX2Y(card)->dev = device; - init_waitqueue_head(&usX2Y(card)->prepare_wait_queue); - mutex_init(&usX2Y(card)->pcm_mutex); - INIT_LIST_HEAD(&usX2Y(card)->midi_list); - strcpy(card->driver, "USB "NAME_ALLCAPS""); + snd_usx2y_card_used[usx2y(card)->card_index = dev] = 1; + card->private_free = snd_usx2y_card_private_free; + usx2y(card)->dev = device; + init_waitqueue_head(&usx2y(card)->prepare_wait_queue); + init_waitqueue_head(&usx2y(card)->us428ctls_wait_queue_head); + mutex_init(&usx2y(card)->pcm_mutex); + INIT_LIST_HEAD(&usx2y(card)->midi_list); + strscpy(card->driver, "USB "NAME_ALLCAPS""); sprintf(card->shortname, "TASCAM "NAME_ALLCAPS""); sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)", - card->shortname, + card->shortname, le16_to_cpu(device->descriptor.idVendor), le16_to_cpu(device->descriptor.idProduct), 0,//us428(card)->usbmidi.ifnum, - usX2Y(card)->dev->bus->busnum, usX2Y(card)->dev->devnum - ); + usx2y(card)->dev->bus->busnum, usx2y(card)->dev->devnum); *cardp = card; return 0; } - -static int usX2Y_usb_probe(struct usb_device *device, - struct usb_interface *intf, - const struct usb_device_id *device_id, - struct snd_card **cardp) +static void snd_usx2y_card_private_free(struct snd_card *card) { - int err; - struct snd_card * card; + struct usx2ydev *usx2y = usx2y(card); + + kfree(usx2y->in04_buf); + usb_free_urb(usx2y->in04_urb); + if (usx2y->us428ctls_sharedmem) + free_pages_exact(usx2y->us428ctls_sharedmem, + US428_SHAREDMEM_PAGES); + if (usx2y->card_index >= 0 && usx2y->card_index < SNDRV_CARDS) + snd_usx2y_card_used[usx2y->card_index] = 0; +} - *cardp = NULL; - if (le16_to_cpu(device->descriptor.idVendor) != 0x1604 || - (le16_to_cpu(device->descriptor.idProduct) != USB_ID_US122 && - le16_to_cpu(device->descriptor.idProduct) != USB_ID_US224 && - le16_to_cpu(device->descriptor.idProduct) != USB_ID_US428)) - return -EINVAL; +static void snd_usx2y_disconnect(struct usb_interface *intf) +{ + struct snd_card *card; + struct usx2ydev *usx2y; + struct list_head *p; - err = usX2Y_create_card(device, intf, &card); - if (err < 0) - return err; - if ((err = usX2Y_hwdep_new(card, device)) < 0 || - (err = snd_card_register(card)) < 0) { - snd_card_free(card); - return err; + card = usb_get_intfdata(intf); + if (!card) + return; + usx2y = usx2y(card); + usx2y->chip_status = USX2Y_STAT_CHIP_HUP; + usx2y_unlinkseq(&usx2y->as04); + usb_kill_urb(usx2y->in04_urb); + snd_card_disconnect(card); + + /* release the midi resources */ + list_for_each(p, &usx2y->midi_list) { + snd_usbmidi_disconnect(p); } - *cardp = card; - return 0; + if (usx2y->us428ctls_sharedmem) + wake_up(&usx2y->us428ctls_wait_queue_head); + snd_card_free_when_closed(card); } -/* - * new 2.5 USB kernel API - */ -static int snd_usX2Y_probe(struct usb_interface *intf, const struct usb_device_id *id) +static int snd_usx2y_probe(struct usb_interface *intf, + const struct usb_device_id *id) { + struct usb_device *device = interface_to_usbdev(intf); struct snd_card *card; int err; - err = usX2Y_usb_probe(interface_to_usbdev(intf), intf, id, &card); +#ifdef USX2Y_NRPACKS_VARIABLE + if (nrpacks < 0 || nrpacks > USX2Y_NRPACKS_MAX) + return -EINVAL; +#endif + + if (le16_to_cpu(device->descriptor.idVendor) != 0x1604 || + (le16_to_cpu(device->descriptor.idProduct) != USB_ID_US122 && + le16_to_cpu(device->descriptor.idProduct) != USB_ID_US224 && + le16_to_cpu(device->descriptor.idProduct) != USB_ID_US428)) + return -EINVAL; + + err = usx2y_create_card(device, intf, &card); if (err < 0) return err; + err = usx2y_hwdep_new(card, device); + if (err < 0) + goto error; + err = snd_card_register(card); + if (err < 0) + goto error; + dev_set_drvdata(&intf->dev, card); return 0; -} -static void snd_usX2Y_disconnect(struct usb_interface *intf) -{ - usX2Y_usb_disconnect(interface_to_usbdev(intf), - usb_get_intfdata(intf)); + error: + snd_card_free(card); + return err; } -MODULE_DEVICE_TABLE(usb, snd_usX2Y_usb_id_table); -static struct usb_driver snd_usX2Y_usb_driver = { +static struct usb_driver snd_usx2y_usb_driver = { .name = "snd-usb-usx2y", - .probe = snd_usX2Y_probe, - .disconnect = snd_usX2Y_disconnect, - .id_table = snd_usX2Y_usb_id_table, + .probe = snd_usx2y_probe, + .disconnect = snd_usx2y_disconnect, + .id_table = snd_usx2y_usb_id_table, }; - -static void snd_usX2Y_card_private_free(struct snd_card *card) -{ - kfree(usX2Y(card)->In04Buf); - usb_free_urb(usX2Y(card)->In04urb); - if (usX2Y(card)->us428ctls_sharedmem) - snd_free_pages(usX2Y(card)->us428ctls_sharedmem, sizeof(*usX2Y(card)->us428ctls_sharedmem)); - if (usX2Y(card)->card_index >= 0 && usX2Y(card)->card_index < SNDRV_CARDS) - snd_usX2Y_card_used[usX2Y(card)->card_index] = 0; -} - -/* - * Frees the device. - */ -static void usX2Y_usb_disconnect(struct usb_device *device, void* ptr) -{ - if (ptr) { - struct snd_card *card = ptr; - struct usX2Ydev *usX2Y = usX2Y(card); - struct list_head *p; - usX2Y->chip_status = USX2Y_STAT_CHIP_HUP; - usX2Y_unlinkSeq(&usX2Y->AS04); - usb_kill_urb(usX2Y->In04urb); - snd_card_disconnect(card); - /* release the midi resources */ - list_for_each(p, &usX2Y->midi_list) { - snd_usbmidi_disconnect(p); - } - if (usX2Y->us428ctls_sharedmem) - wake_up(&usX2Y->us428ctls_wait_queue_head); - snd_card_free(card); - } -} - -module_usb_driver(snd_usX2Y_usb_driver); +module_usb_driver(snd_usx2y_usb_driver); diff --git a/sound/usb/usx2y/usbusx2y.h b/sound/usb/usx2y/usbusx2y.h index e0f77172ce8f..6a76d04bf1c7 100644 --- a/sound/usb/usx2y/usbusx2y.h +++ b/sound/usb/usx2y/usbusx2y.h @@ -3,37 +3,63 @@ #define USBUSX2Y_H #include "../usbaudio.h" #include "../midi.h" -#include "usbus428ctldefs.h" - -#define NRURBS 2 - +#include "usbus428ctldefs.h" + +#define NRURBS 2 + +/* Default value used for nr of packs per urb. + * 1 to 4 have been tested ok on uhci. + * To use 3 on ohci, you'd need a patch: + * look for "0000425-linux-2.6.9-rc4-mm1_ohci-hcd.patch.gz" on + * "https://bugtrack.alsa-project.org/alsa-bug/bug_view_page.php?bug_id=0000425" + * + * 1, 2 and 4 work out of the box on ohci, if I recall correctly. + * Bigger is safer operation, smaller gives lower latencies. + */ +#define USX2Y_NRPACKS 4 + +#define USX2Y_NRPACKS_MAX 1024 + +/* If your system works ok with this module's parameter + * nrpacks set to 1, you might as well comment + * this define out, and thereby produce smaller, faster code. + * You'd also set USX2Y_NRPACKS to 1 then. + */ +#define USX2Y_NRPACKS_VARIABLE 1 + +#ifdef USX2Y_NRPACKS_VARIABLE +extern int nrpacks; +#define nr_of_packs() nrpacks +#else +#define nr_of_packs() USX2Y_NRPACKS +#endif -#define URBS_AsyncSeq 10 -#define URB_DataLen_AsyncSeq 32 -struct snd_usX2Y_AsyncSeq { - struct urb *urb[URBS_AsyncSeq]; +#define URBS_ASYNC_SEQ 10 +#define URB_DATA_LEN_ASYNC_SEQ 32 +struct snd_usx2y_async_seq { + struct urb *urb[URBS_ASYNC_SEQ]; char *buffer; }; -struct snd_usX2Y_urbSeq { +struct snd_usx2y_urb_seq { int submitted; int len; - struct urb *urb[0]; + struct urb *urb[] __counted_by(len); }; #include "usx2yhwdeppcm.h" -struct usX2Ydev { +struct usx2ydev { struct usb_device *dev; int card_index; int stride; - struct urb *In04urb; - void *In04Buf; - char In04Last[24]; - unsigned In04IntCalls; - struct snd_usX2Y_urbSeq *US04; - wait_queue_head_t In04WaitQueue; - struct snd_usX2Y_AsyncSeq AS04; + struct urb *in04_urb; + void *in04_buf; + char in04_last[24]; + unsigned int in04_int_calls; + struct snd_usx2y_urb_seq *us04; + wait_queue_head_t in04_wait_queue; + struct snd_usx2y_async_seq as04; unsigned int rate, format; int chip_status; @@ -41,31 +67,30 @@ struct usX2Ydev { struct us428ctls_sharedmem *us428ctls_sharedmem; int wait_iso_frame; wait_queue_head_t us428ctls_wait_queue_head; - struct snd_usX2Y_hwdep_pcm_shm *hwdep_pcm_shm; - struct snd_usX2Y_substream *subs[4]; - struct snd_usX2Y_substream * volatile prepare_subs; + struct snd_usx2y_hwdep_pcm_shm *hwdep_pcm_shm; + struct snd_usx2y_substream *subs[4]; + struct snd_usx2y_substream * volatile prepare_subs; wait_queue_head_t prepare_wait_queue; struct list_head midi_list; - struct list_head pcm_list; int pcm_devs; }; -struct snd_usX2Y_substream { - struct usX2Ydev *usX2Y; +struct snd_usx2y_substream { + struct usx2ydev *usx2y; struct snd_pcm_substream *pcm_substream; - int endpoint; + int endpoint; unsigned int maxpacksize; /* max packet size in bytes */ atomic_t state; -#define state_STOPPED 0 -#define state_STARTING1 1 -#define state_STARTING2 2 -#define state_STARTING3 3 -#define state_PREPARED 4 -#define state_PRERUNNING 6 -#define state_RUNNING 8 +#define STATE_STOPPED 0 +#define STATE_STARTING1 1 +#define STATE_STARTING2 2 +#define STATE_STARTING3 3 +#define STATE_PREPARED 4 +#define STATE_PRERUNNING 6 +#define STATE_RUNNING 8 int hwptr; /* free frame position in the buffer (only for playback) */ int hwptr_done; /* processed frame position in the buffer */ @@ -77,12 +102,12 @@ struct snd_usX2Y_substream { }; -#define usX2Y(c) ((struct usX2Ydev *)(c)->private_data) +#define usx2y(c) ((struct usx2ydev *)(c)->private_data) -int usX2Y_audio_create(struct snd_card *card); +int usx2y_audio_create(struct snd_card *card); -int usX2Y_AsyncSeq04_init(struct usX2Ydev *usX2Y); -int usX2Y_In04_init(struct usX2Ydev *usX2Y); +int usx2y_async_seq04_init(struct usx2ydev *usx2y); +int usx2y_in04_init(struct usx2ydev *usx2y); #define NAME_ALLCAPS "US-X2Y" diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c index 2b833054e3b0..c7c7ec9c228b 100644 --- a/sound/usb/usx2y/usbusx2yaudio.c +++ b/sound/usb/usx2y/usbusx2yaudio.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * US-X2Y AUDIO * Copyright (c) 2002-2004 by Karsten Wiese @@ -10,24 +11,9 @@ * * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> * - * Many codes borrowed from audio.c by + * Many codes borrowed from audio.c by * Alan Cox (alan@lxorguk.ukuu.org.uk) * Thomas Sailer (sailer@ife.ee.ethz.ch) - * - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ @@ -42,66 +28,43 @@ #include "usx2y.h" #include "usbusx2y.h" -#define USX2Y_NRPACKS 4 /* Default value used for nr of packs per urb. - 1 to 4 have been tested ok on uhci. - To use 3 on ohci, you'd need a patch: - look for "0000425-linux-2.6.9-rc4-mm1_ohci-hcd.patch.gz" on - "https://bugtrack.alsa-project.org/alsa-bug/bug_view_page.php?bug_id=0000425" - . - 1, 2 and 4 work out of the box on ohci, if I recall correctly. - Bigger is safer operation, - smaller gives lower latencies. - */ -#define USX2Y_NRPACKS_VARIABLE y /* If your system works ok with this module's parameter - nrpacks set to 1, you might as well comment - this #define out, and thereby produce smaller, faster code. - You'd also set USX2Y_NRPACKS to 1 then. - */ - -#ifdef USX2Y_NRPACKS_VARIABLE - static int nrpacks = USX2Y_NRPACKS; /* number of packets per urb */ - #define nr_of_packs() nrpacks - module_param(nrpacks, int, 0444); - MODULE_PARM_DESC(nrpacks, "Number of packets per URB."); -#else - #define nr_of_packs() USX2Y_NRPACKS -#endif - - -static int usX2Y_urb_capt_retire(struct snd_usX2Y_substream *subs) +static int usx2y_urb_capt_retire(struct snd_usx2y_substream *subs) { struct urb *urb = subs->completed_urb; struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; unsigned char *cp; - int i, len, lens = 0, hwptr_done = subs->hwptr_done; - struct usX2Ydev *usX2Y = subs->usX2Y; + int i, len, lens = 0, hwptr_done = subs->hwptr_done; + int cnt, blen; + struct usx2ydev *usx2y = subs->usx2y; for (i = 0; i < nr_of_packs(); i++) { - cp = (unsigned char*)urb->transfer_buffer + urb->iso_frame_desc[i].offset; + cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset; if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */ - snd_printk(KERN_ERR "active frame status %i. " - "Most probably some hardware problem.\n", - urb->iso_frame_desc[i].status); + dev_err(&usx2y->dev->dev, + "%s: active frame status %i. Most probably some hardware problem.\n", + __func__, + urb->iso_frame_desc[i].status); return urb->iso_frame_desc[i].status; } - len = urb->iso_frame_desc[i].actual_length / usX2Y->stride; - if (! len) { - snd_printd("0 == len ERROR!\n"); + len = urb->iso_frame_desc[i].actual_length / usx2y->stride; + if (!len) { + dev_dbg(&usx2y->dev->dev, "%s: 0 == len ERROR!\n", __func__); continue; } /* copy a data chunk */ if ((hwptr_done + len) > runtime->buffer_size) { - int cnt = runtime->buffer_size - hwptr_done; - int blen = cnt * usX2Y->stride; - memcpy(runtime->dma_area + hwptr_done * usX2Y->stride, cp, blen); - memcpy(runtime->dma_area, cp + blen, len * usX2Y->stride - blen); + cnt = runtime->buffer_size - hwptr_done; + blen = cnt * usx2y->stride; + memcpy(runtime->dma_area + hwptr_done * usx2y->stride, cp, blen); + memcpy(runtime->dma_area, cp + blen, len * usx2y->stride - blen); } else { - memcpy(runtime->dma_area + hwptr_done * usX2Y->stride, cp, - len * usX2Y->stride); + memcpy(runtime->dma_area + hwptr_done * usx2y->stride, cp, + len * usx2y->stride); } lens += len; - if ((hwptr_done += len) >= runtime->buffer_size) + hwptr_done += len; + if (hwptr_done >= runtime->buffer_size) hwptr_done -= runtime->buffer_size; } @@ -114,6 +77,7 @@ static int usX2Y_urb_capt_retire(struct snd_usX2Y_substream *subs) } return 0; } + /* * prepare urb for playback data pipe * @@ -124,21 +88,22 @@ static int usX2Y_urb_capt_retire(struct snd_usX2Y_substream *subs) * it directly from the buffer. thus the data is once copied to * a temporary buffer and urb points to that. */ -static int usX2Y_urb_play_prepare(struct snd_usX2Y_substream *subs, +static int usx2y_urb_play_prepare(struct snd_usx2y_substream *subs, struct urb *cap_urb, struct urb *urb) { - int count, counts, pack; - struct usX2Ydev *usX2Y = subs->usX2Y; + struct usx2ydev *usx2y = subs->usx2y; struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + int count, counts, pack, len; count = 0; for (pack = 0; pack < nr_of_packs(); pack++) { /* calculate the size of a packet */ - counts = cap_urb->iso_frame_desc[pack].actual_length / usX2Y->stride; + counts = cap_urb->iso_frame_desc[pack].actual_length / usx2y->stride; count += counts; if (counts < 43 || counts > 50) { - snd_printk(KERN_ERR "should not be here with counts=%i\n", counts); + dev_err(&usx2y->dev->dev, "%s: should not be here with counts=%i\n", + __func__, counts); return -EPIPE; } /* set up descriptor */ @@ -148,29 +113,30 @@ static int usX2Y_urb_play_prepare(struct snd_usX2Y_substream *subs, 0; urb->iso_frame_desc[pack].length = cap_urb->iso_frame_desc[pack].actual_length; } - if (atomic_read(&subs->state) >= state_PRERUNNING) + if (atomic_read(&subs->state) >= STATE_PRERUNNING) { if (subs->hwptr + count > runtime->buffer_size) { /* err, the transferred area goes over buffer boundary. * copy the data to the temp buffer. */ - int len; len = runtime->buffer_size - subs->hwptr; urb->transfer_buffer = subs->tmpbuf; memcpy(subs->tmpbuf, runtime->dma_area + - subs->hwptr * usX2Y->stride, len * usX2Y->stride); - memcpy(subs->tmpbuf + len * usX2Y->stride, - runtime->dma_area, (count - len) * usX2Y->stride); + subs->hwptr * usx2y->stride, len * usx2y->stride); + memcpy(subs->tmpbuf + len * usx2y->stride, + runtime->dma_area, (count - len) * usx2y->stride); subs->hwptr += count; subs->hwptr -= runtime->buffer_size; } else { /* set the buffer pointer */ - urb->transfer_buffer = runtime->dma_area + subs->hwptr * usX2Y->stride; - if ((subs->hwptr += count) >= runtime->buffer_size) + urb->transfer_buffer = runtime->dma_area + subs->hwptr * usx2y->stride; + subs->hwptr += count; + if (subs->hwptr >= runtime->buffer_size) subs->hwptr -= runtime->buffer_size; } - else + } else { urb->transfer_buffer = subs->tmpbuf; - urb->transfer_buffer_length = count * usX2Y->stride; + } + urb->transfer_buffer_length = count * usx2y->stride; return 0; } @@ -179,10 +145,10 @@ static int usX2Y_urb_play_prepare(struct snd_usX2Y_substream *subs, * * update the current position and call callback if a period is processed. */ -static void usX2Y_urb_play_retire(struct snd_usX2Y_substream *subs, struct urb *urb) +static void usx2y_urb_play_retire(struct snd_usx2y_substream *subs, struct urb *urb) { struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; - int len = urb->actual_length / subs->usX2Y->stride; + int len = urb->actual_length / subs->usx2y->stride; subs->transfer_done += len; subs->hwptr_done += len; @@ -194,190 +160,209 @@ static void usX2Y_urb_play_retire(struct snd_usX2Y_substream *subs, struct urb * } } -static int usX2Y_urb_submit(struct snd_usX2Y_substream *subs, struct urb *urb, int frame) +static int usx2y_urb_submit(struct snd_usx2y_substream *subs, struct urb *urb, int frame) { int err; + if (!urb) return -ENODEV; - urb->start_frame = (frame + NRURBS * nr_of_packs()); // let hcd do rollover sanity checks + urb->start_frame = frame + NRURBS * nr_of_packs(); // let hcd do rollover sanity checks urb->hcpriv = NULL; - urb->dev = subs->usX2Y->dev; /* we need to set this at each time */ - if ((err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { - snd_printk(KERN_ERR "usb_submit_urb() returned %i\n", err); + urb->dev = subs->usx2y->dev; /* we need to set this at each time */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + dev_err(&urb->dev->dev, "%s: usb_submit_urb() returned %i\n", + __func__, err); return err; } return 0; } -static inline int usX2Y_usbframe_complete(struct snd_usX2Y_substream *capsubs, - struct snd_usX2Y_substream *playbacksubs, - int frame) +static int usx2y_usbframe_complete(struct snd_usx2y_substream *capsubs, + struct snd_usx2y_substream *playbacksubs, + int frame) { int err, state; struct urb *urb = playbacksubs->completed_urb; state = atomic_read(&playbacksubs->state); - if (NULL != urb) { - if (state == state_RUNNING) - usX2Y_urb_play_retire(playbacksubs, urb); - else if (state >= state_PRERUNNING) + if (urb) { + if (state == STATE_RUNNING) + usx2y_urb_play_retire(playbacksubs, urb); + else if (state >= STATE_PRERUNNING) atomic_inc(&playbacksubs->state); } else { switch (state) { - case state_STARTING1: + case STATE_STARTING1: urb = playbacksubs->urb[0]; atomic_inc(&playbacksubs->state); break; - case state_STARTING2: + case STATE_STARTING2: urb = playbacksubs->urb[1]; atomic_inc(&playbacksubs->state); break; } } if (urb) { - if ((err = usX2Y_urb_play_prepare(playbacksubs, capsubs->completed_urb, urb)) || - (err = usX2Y_urb_submit(playbacksubs, urb, frame))) { + err = usx2y_urb_play_prepare(playbacksubs, capsubs->completed_urb, urb); + if (err) + return err; + err = usx2y_urb_submit(playbacksubs, urb, frame); + if (err) return err; - } } playbacksubs->completed_urb = NULL; state = atomic_read(&capsubs->state); - if (state >= state_PREPARED) { - if (state == state_RUNNING) { - if ((err = usX2Y_urb_capt_retire(capsubs))) + if (state >= STATE_PREPARED) { + if (state == STATE_RUNNING) { + err = usx2y_urb_capt_retire(capsubs); + if (err) return err; - } else if (state >= state_PRERUNNING) + } else if (state >= STATE_PRERUNNING) { atomic_inc(&capsubs->state); - if ((err = usX2Y_urb_submit(capsubs, capsubs->completed_urb, frame))) + } + err = usx2y_urb_submit(capsubs, capsubs->completed_urb, frame); + if (err) return err; } capsubs->completed_urb = NULL; return 0; } - -static void usX2Y_clients_stop(struct usX2Ydev *usX2Y) +static void usx2y_clients_stop(struct usx2ydev *usx2y) { + struct snd_usx2y_substream *subs; + struct urb *urb; int s, u; for (s = 0; s < 4; s++) { - struct snd_usX2Y_substream *subs = usX2Y->subs[s]; + subs = usx2y->subs[s]; if (subs) { - snd_printdd("%i %p state=%i\n", s, subs, atomic_read(&subs->state)); - atomic_set(&subs->state, state_STOPPED); + dev_dbg(&usx2y->dev->dev, "%s: %i %p state=%i\n", + __func__, s, subs, atomic_read(&subs->state)); + atomic_set(&subs->state, STATE_STOPPED); } } for (s = 0; s < 4; s++) { - struct snd_usX2Y_substream *subs = usX2Y->subs[s]; + subs = usx2y->subs[s]; if (subs) { - if (atomic_read(&subs->state) >= state_PRERUNNING) + if (atomic_read(&subs->state) >= STATE_PRERUNNING) snd_pcm_stop_xrun(subs->pcm_substream); for (u = 0; u < NRURBS; u++) { - struct urb *urb = subs->urb[u]; - if (NULL != urb) - snd_printdd("%i status=%i start_frame=%i\n", - u, urb->status, urb->start_frame); + urb = subs->urb[u]; + if (urb) + dev_dbg(&usx2y->dev->dev, + "%s: %i status=%i start_frame=%i\n", + __func__, u, urb->status, urb->start_frame); } } } - usX2Y->prepare_subs = NULL; - wake_up(&usX2Y->prepare_wait_queue); + usx2y->prepare_subs = NULL; + wake_up(&usx2y->prepare_wait_queue); } -static void usX2Y_error_urb_status(struct usX2Ydev *usX2Y, - struct snd_usX2Y_substream *subs, struct urb *urb) +static void usx2y_error_urb_status(struct usx2ydev *usx2y, + struct snd_usx2y_substream *subs, struct urb *urb) { - snd_printk(KERN_ERR "ep=%i stalled with status=%i\n", subs->endpoint, urb->status); + dev_err(&usx2y->dev->dev, "%s: ep=%i stalled with status=%i\n", + __func__, subs->endpoint, urb->status); urb->status = 0; - usX2Y_clients_stop(usX2Y); + usx2y_clients_stop(usx2y); } -static void i_usX2Y_urb_complete(struct urb *urb) +static void i_usx2y_urb_complete(struct urb *urb) { - struct snd_usX2Y_substream *subs = urb->context; - struct usX2Ydev *usX2Y = subs->usX2Y; - - if (unlikely(atomic_read(&subs->state) < state_PREPARED)) { - snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n", - usb_get_current_frame_number(usX2Y->dev), - subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out", - urb->status, urb->start_frame); + struct snd_usx2y_substream *subs = urb->context; + struct usx2ydev *usx2y = subs->usx2y; + struct snd_usx2y_substream *capsubs, *playbacksubs; + + if (unlikely(atomic_read(&subs->state) < STATE_PREPARED)) { + dev_dbg(&usx2y->dev->dev, + "%s: hcd_frame=%i ep=%i%s status=%i start_frame=%i\n", + __func__, + usb_get_current_frame_number(usx2y->dev), + subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out", + urb->status, urb->start_frame); return; } if (unlikely(urb->status)) { - usX2Y_error_urb_status(usX2Y, subs, urb); + usx2y_error_urb_status(usx2y, subs, urb); return; } subs->completed_urb = urb; - { - struct snd_usX2Y_substream *capsubs = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE], - *playbacksubs = usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]; - if (capsubs->completed_urb && - atomic_read(&capsubs->state) >= state_PREPARED && - (playbacksubs->completed_urb || - atomic_read(&playbacksubs->state) < state_PREPARED)) { - if (!usX2Y_usbframe_complete(capsubs, playbacksubs, urb->start_frame)) - usX2Y->wait_iso_frame += nr_of_packs(); - else { - snd_printdd("\n"); - usX2Y_clients_stop(usX2Y); - } + capsubs = usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]; + playbacksubs = usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + + if (capsubs->completed_urb && + atomic_read(&capsubs->state) >= STATE_PREPARED && + (playbacksubs->completed_urb || + atomic_read(&playbacksubs->state) < STATE_PREPARED)) { + if (!usx2y_usbframe_complete(capsubs, playbacksubs, urb->start_frame)) { + usx2y->wait_iso_frame += nr_of_packs(); + } else { + usx2y_clients_stop(usx2y); } } } -static void usX2Y_urbs_set_complete(struct usX2Ydev * usX2Y, +static void usx2y_urbs_set_complete(struct usx2ydev *usx2y, void (*complete)(struct urb *)) { + struct snd_usx2y_substream *subs; + struct urb *urb; int s, u; + for (s = 0; s < 4; s++) { - struct snd_usX2Y_substream *subs = usX2Y->subs[s]; - if (NULL != subs) + subs = usx2y->subs[s]; + if (subs) { for (u = 0; u < NRURBS; u++) { - struct urb * urb = subs->urb[u]; - if (NULL != urb) + urb = subs->urb[u]; + if (urb) urb->complete = complete; } + } } } -static void usX2Y_subs_startup_finish(struct usX2Ydev * usX2Y) +static void usx2y_subs_startup_finish(struct usx2ydev *usx2y) { - usX2Y_urbs_set_complete(usX2Y, i_usX2Y_urb_complete); - usX2Y->prepare_subs = NULL; + usx2y_urbs_set_complete(usx2y, i_usx2y_urb_complete); + usx2y->prepare_subs = NULL; } -static void i_usX2Y_subs_startup(struct urb *urb) +static void i_usx2y_subs_startup(struct urb *urb) { - struct snd_usX2Y_substream *subs = urb->context; - struct usX2Ydev *usX2Y = subs->usX2Y; - struct snd_usX2Y_substream *prepare_subs = usX2Y->prepare_subs; - if (NULL != prepare_subs) + struct snd_usx2y_substream *subs = urb->context; + struct usx2ydev *usx2y = subs->usx2y; + struct snd_usx2y_substream *prepare_subs = usx2y->prepare_subs; + + if (prepare_subs) { if (urb->start_frame == prepare_subs->urb[0]->start_frame) { - usX2Y_subs_startup_finish(usX2Y); + usx2y_subs_startup_finish(usx2y); atomic_inc(&prepare_subs->state); - wake_up(&usX2Y->prepare_wait_queue); + wake_up(&usx2y->prepare_wait_queue); } + } - i_usX2Y_urb_complete(urb); + i_usx2y_urb_complete(urb); } -static void usX2Y_subs_prepare(struct snd_usX2Y_substream *subs) +static void usx2y_subs_prepare(struct snd_usx2y_substream *subs) { - snd_printdd("usX2Y_substream_prepare(%p) ep=%i urb0=%p urb1=%p\n", - subs, subs->endpoint, subs->urb[0], subs->urb[1]); + dev_dbg(&subs->usx2y->dev->dev, + "%s(%p) ep=%i urb0=%p urb1=%p\n", + __func__, subs, subs->endpoint, subs->urb[0], subs->urb[1]); /* reset the pointer */ subs->hwptr = 0; subs->hwptr_done = 0; subs->transfer_done = 0; } - -static void usX2Y_urb_release(struct urb **urb, int free_tb) +static void usx2y_urb_release(struct urb **urb, int free_tb) { if (*urb) { usb_kill_urb(*urb); @@ -387,51 +372,55 @@ static void usX2Y_urb_release(struct urb **urb, int free_tb) *urb = NULL; } } + /* * release a substreams urbs */ -static void usX2Y_urbs_release(struct snd_usX2Y_substream *subs) +static void usx2y_urbs_release(struct snd_usx2y_substream *subs) { int i; - snd_printdd("usX2Y_urbs_release() %i\n", subs->endpoint); + + dev_dbg(&subs->usx2y->dev->dev, "%s %i\n", __func__, subs->endpoint); for (i = 0; i < NRURBS; i++) - usX2Y_urb_release(subs->urb + i, - subs != subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]); + usx2y_urb_release(subs->urb + i, + subs != subs->usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK]); kfree(subs->tmpbuf); subs->tmpbuf = NULL; } + /* * initialize a substream's urbs */ -static int usX2Y_urbs_allocate(struct snd_usX2Y_substream *subs) +static int usx2y_urbs_allocate(struct snd_usx2y_substream *subs) { int i; unsigned int pipe; - int is_playback = subs == subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]; - struct usb_device *dev = subs->usX2Y->dev; + int is_playback = subs == subs->usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + struct usb_device *dev = subs->usx2y->dev; + struct urb **purb; pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) : usb_rcvisocpipe(dev, subs->endpoint); - subs->maxpacksize = usb_maxpacket(dev, pipe, is_playback); + subs->maxpacksize = usb_maxpacket(dev, pipe); if (!subs->maxpacksize) return -EINVAL; - if (is_playback && NULL == subs->tmpbuf) { /* allocate a temporary buffer for playback */ + if (is_playback && !subs->tmpbuf) { /* allocate a temporary buffer for playback */ subs->tmpbuf = kcalloc(nr_of_packs(), subs->maxpacksize, GFP_KERNEL); if (!subs->tmpbuf) return -ENOMEM; } /* allocate and initialize data urbs */ for (i = 0; i < NRURBS; i++) { - struct urb **purb = subs->urb + i; + purb = subs->urb + i; if (*purb) { usb_kill_urb(*purb); continue; } *purb = usb_alloc_urb(nr_of_packs(), GFP_KERNEL); - if (NULL == *purb) { - usX2Y_urbs_release(subs); + if (!*purb) { + usx2y_urbs_release(subs); return -ENOMEM; } if (!is_playback && !(*purb)->transfer_buffer) { @@ -439,8 +428,8 @@ static int usX2Y_urbs_allocate(struct snd_usX2Y_substream *subs) (*purb)->transfer_buffer = kmalloc_array(subs->maxpacksize, nr_of_packs(), GFP_KERNEL); - if (NULL == (*purb)->transfer_buffer) { - usX2Y_urbs_release(subs); + if (!(*purb)->transfer_buffer) { + usx2y_urbs_release(subs); return -ENOMEM; } } @@ -449,70 +438,77 @@ static int usX2Y_urbs_allocate(struct snd_usX2Y_substream *subs) (*purb)->number_of_packets = nr_of_packs(); (*purb)->context = subs; (*purb)->interval = 1; - (*purb)->complete = i_usX2Y_subs_startup; + (*purb)->complete = i_usx2y_subs_startup; } return 0; } -static void usX2Y_subs_startup(struct snd_usX2Y_substream *subs) +static void usx2y_subs_startup(struct snd_usx2y_substream *subs) { - struct usX2Ydev *usX2Y = subs->usX2Y; - usX2Y->prepare_subs = subs; + struct usx2ydev *usx2y = subs->usx2y; + + usx2y->prepare_subs = subs; subs->urb[0]->start_frame = -1; wmb(); - usX2Y_urbs_set_complete(usX2Y, i_usX2Y_subs_startup); + usx2y_urbs_set_complete(usx2y, i_usx2y_subs_startup); } -static int usX2Y_urbs_start(struct snd_usX2Y_substream *subs) +static int usx2y_urbs_start(struct snd_usx2y_substream *subs) { int i, err; - struct usX2Ydev *usX2Y = subs->usX2Y; + struct usx2ydev *usx2y = subs->usx2y; + struct urb *urb; + unsigned long pack; - if ((err = usX2Y_urbs_allocate(subs)) < 0) + err = usx2y_urbs_allocate(subs); + if (err < 0) return err; subs->completed_urb = NULL; for (i = 0; i < 4; i++) { - struct snd_usX2Y_substream *subs = usX2Y->subs[i]; - if (subs != NULL && atomic_read(&subs->state) >= state_PREPARED) + struct snd_usx2y_substream *subs = usx2y->subs[i]; + + if (subs && atomic_read(&subs->state) >= STATE_PREPARED) goto start; } start: - usX2Y_subs_startup(subs); + usx2y_subs_startup(subs); for (i = 0; i < NRURBS; i++) { - struct urb *urb = subs->urb[i]; + urb = subs->urb[i]; if (usb_pipein(urb->pipe)) { - unsigned long pack; - if (0 == i) - atomic_set(&subs->state, state_STARTING3); - urb->dev = usX2Y->dev; + if (!i) + atomic_set(&subs->state, STATE_STARTING3); + urb->dev = usx2y->dev; for (pack = 0; pack < nr_of_packs(); pack++) { urb->iso_frame_desc[pack].offset = subs->maxpacksize * pack; urb->iso_frame_desc[pack].length = subs->maxpacksize; } - urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs(); - if ((err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { - snd_printk (KERN_ERR "cannot submit datapipe for urb %d, err = %d\n", i, err); + urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs(); + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + dev_err(&urb->dev->dev, "%s: cannot submit datapipe for urb %d, err = %d\n", + __func__, i, err); err = -EPIPE; goto cleanup; - } else - if (i == 0) - usX2Y->wait_iso_frame = urb->start_frame; + } else { + if (!i) + usx2y->wait_iso_frame = urb->start_frame; + } urb->transfer_flags = 0; } else { - atomic_set(&subs->state, state_STARTING1); + atomic_set(&subs->state, STATE_STARTING1); break; } } err = 0; - wait_event(usX2Y->prepare_wait_queue, NULL == usX2Y->prepare_subs); - if (atomic_read(&subs->state) != state_PREPARED) + wait_event(usx2y->prepare_wait_queue, !usx2y->prepare_subs); + if (atomic_read(&subs->state) != STATE_PREPARED) err = -EPIPE; cleanup: if (err) { - usX2Y_subs_startup_finish(usX2Y); - usX2Y_clients_stop(usX2Y); // something is completely wroong > stop evrything + usx2y_subs_startup_finish(usx2y); + usx2y_clients_stop(usx2y); // something is completely wrong > stop everything } return err; } @@ -520,33 +516,34 @@ static int usX2Y_urbs_start(struct snd_usX2Y_substream *subs) /* * return the current pcm pointer. just return the hwptr_done value. */ -static snd_pcm_uframes_t snd_usX2Y_pcm_pointer(struct snd_pcm_substream *substream) +static snd_pcm_uframes_t snd_usx2y_pcm_pointer(struct snd_pcm_substream *substream) { - struct snd_usX2Y_substream *subs = substream->runtime->private_data; + struct snd_usx2y_substream *subs = substream->runtime->private_data; + return subs->hwptr_done; } + /* * start/stop substream */ -static int snd_usX2Y_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +static int snd_usx2y_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { - struct snd_usX2Y_substream *subs = substream->runtime->private_data; + struct snd_usx2y_substream *subs = substream->runtime->private_data; switch (cmd) { case SNDRV_PCM_TRIGGER_START: - snd_printdd("snd_usX2Y_pcm_trigger(START)\n"); - if (atomic_read(&subs->state) == state_PREPARED && - atomic_read(&subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]->state) >= state_PREPARED) { - atomic_set(&subs->state, state_PRERUNNING); + dev_dbg(&subs->usx2y->dev->dev, "%s(START)\n", __func__); + if (atomic_read(&subs->state) == STATE_PREPARED && + atomic_read(&subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]->state) >= STATE_PREPARED) { + atomic_set(&subs->state, STATE_PRERUNNING); } else { - snd_printdd("\n"); return -EPIPE; } break; case SNDRV_PCM_TRIGGER_STOP: - snd_printdd("snd_usX2Y_pcm_trigger(STOP)\n"); - if (atomic_read(&subs->state) >= state_PRERUNNING) - atomic_set(&subs->state, state_PREPARED); + dev_dbg(&subs->usx2y->dev->dev, "%s(STOP)\n", __func__); + if (atomic_read(&subs->state) >= STATE_PRERUNNING) + atomic_set(&subs->state, STATE_PREPARED); break; default: return -EINVAL; @@ -554,7 +551,6 @@ static int snd_usX2Y_pcm_trigger(struct snd_pcm_substream *substream, int cmd) return 0; } - /* * allocate a buffer, setup samplerate * @@ -563,12 +559,11 @@ static int snd_usX2Y_pcm_trigger(struct snd_pcm_substream *substream, int cmd) * if sg buffer is supported on the later version of alsa, we'll follow * that. */ -static struct s_c2 -{ +struct s_c2 { char c1, c2; -} - SetRate44100[] = -{ +}; + +static const struct s_c2 setrate_44100[] = { { 0x14, 0x08}, // this line sets 44100, well actually a little less { 0x18, 0x40}, // only tascam / frontier design knows the further lines ....... { 0x18, 0x42}, @@ -603,8 +598,8 @@ static struct s_c2 { 0x18, 0x7C}, { 0x18, 0x7E} }; -static struct s_c2 SetRate48000[] = -{ + +static const struct s_c2 setrate_48000[] = { { 0x14, 0x09}, // this line sets 48000, well actually a little less { 0x18, 0x40}, // only tascam / frontier design knows the further lines ....... { 0x18, 0x42}, @@ -639,62 +634,69 @@ static struct s_c2 SetRate48000[] = { 0x18, 0x7C}, { 0x18, 0x7E} }; -#define NOOF_SETRATE_URBS ARRAY_SIZE(SetRate48000) -static void i_usX2Y_04Int(struct urb *urb) +#define NOOF_SETRATE_URBS ARRAY_SIZE(setrate_48000) + +static void i_usx2y_04int(struct urb *urb) { - struct usX2Ydev *usX2Y = urb->context; - + struct usx2ydev *usx2y = urb->context; + if (urb->status) - snd_printk(KERN_ERR "snd_usX2Y_04Int() urb->status=%i\n", urb->status); - if (0 == --usX2Y->US04->len) - wake_up(&usX2Y->In04WaitQueue); + dev_err(&urb->dev->dev, "%s() urb->status=%i\n", + __func__, urb->status); + if (!--usx2y->us04->len) + wake_up(&usx2y->in04_wait_queue); } -static int usX2Y_rate_set(struct usX2Ydev *usX2Y, int rate) +static int usx2y_rate_set(struct usx2ydev *usx2y, int rate) { - int err = 0, i; - struct snd_usX2Y_urbSeq *us = NULL; - int *usbdata = NULL; - struct s_c2 *ra = rate == 48000 ? SetRate48000 : SetRate44100; - - if (usX2Y->rate != rate) { - us = kzalloc(sizeof(*us) + sizeof(struct urb*) * NOOF_SETRATE_URBS, GFP_KERNEL); - if (NULL == us) { + int err = 0, i; + struct snd_usx2y_urb_seq *us = NULL; + int *usbdata = NULL; + const struct s_c2 *ra = rate == 48000 ? setrate_48000 : setrate_44100; + struct urb *urb; + + if (usx2y->rate != rate) { + us = kzalloc(struct_size(us, urb, NOOF_SETRATE_URBS), + GFP_KERNEL); + if (!us) { err = -ENOMEM; goto cleanup; } + us->len = NOOF_SETRATE_URBS; usbdata = kmalloc_array(NOOF_SETRATE_URBS, sizeof(int), GFP_KERNEL); - if (NULL == usbdata) { + if (!usbdata) { err = -ENOMEM; goto cleanup; } for (i = 0; i < NOOF_SETRATE_URBS; ++i) { - if (NULL == (us->urb[i] = usb_alloc_urb(0, GFP_KERNEL))) { + us->urb[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!us->urb[i]) { err = -ENOMEM; goto cleanup; } - ((char*)(usbdata + i))[0] = ra[i].c1; - ((char*)(usbdata + i))[1] = ra[i].c2; - usb_fill_bulk_urb(us->urb[i], usX2Y->dev, usb_sndbulkpipe(usX2Y->dev, 4), - usbdata + i, 2, i_usX2Y_04Int, usX2Y); + ((char *)(usbdata + i))[0] = ra[i].c1; + ((char *)(usbdata + i))[1] = ra[i].c2; + usb_fill_bulk_urb(us->urb[i], usx2y->dev, usb_sndbulkpipe(usx2y->dev, 4), + usbdata + i, 2, i_usx2y_04int, usx2y); } err = usb_urb_ep_type_check(us->urb[0]); if (err < 0) goto cleanup; us->submitted = 0; - us->len = NOOF_SETRATE_URBS; - usX2Y->US04 = us; - wait_event_timeout(usX2Y->In04WaitQueue, 0 == us->len, HZ); - usX2Y->US04 = NULL; + usx2y->us04 = us; + wait_event_timeout(usx2y->in04_wait_queue, !us->len, HZ); + usx2y->us04 = NULL; if (us->len) err = -ENODEV; cleanup: if (us) { us->submitted = 2*NOOF_SETRATE_URBS; for (i = 0; i < NOOF_SETRATE_URBS; ++i) { - struct urb *urb = us->urb[i]; + urb = us->urb[i]; + if (!urb) + continue; if (urb->status) { if (!err) err = -ENODEV; @@ -702,67 +704,68 @@ static int usX2Y_rate_set(struct usX2Ydev *usX2Y, int rate) } usb_free_urb(urb); } - usX2Y->US04 = NULL; + usx2y->us04 = NULL; kfree(usbdata); kfree(us); if (!err) - usX2Y->rate = rate; + usx2y->rate = rate; } } return err; } - -static int usX2Y_format_set(struct usX2Ydev *usX2Y, snd_pcm_format_t format) +static int usx2y_format_set(struct usx2ydev *usx2y, snd_pcm_format_t format) { int alternate, err; - struct list_head* p; + struct list_head *p; + if (format == SNDRV_PCM_FORMAT_S24_3LE) { alternate = 2; - usX2Y->stride = 6; + usx2y->stride = 6; } else { alternate = 1; - usX2Y->stride = 4; + usx2y->stride = 4; } - list_for_each(p, &usX2Y->midi_list) { + list_for_each(p, &usx2y->midi_list) { snd_usbmidi_input_stop(p); } - usb_kill_urb(usX2Y->In04urb); - if ((err = usb_set_interface(usX2Y->dev, 0, alternate))) { - snd_printk(KERN_ERR "usb_set_interface error \n"); + usb_kill_urb(usx2y->in04_urb); + err = usb_set_interface(usx2y->dev, 0, alternate); + if (err) { + dev_err(&usx2y->dev->dev, "%s: usb_set_interface error\n", + __func__); return err; } - usX2Y->In04urb->dev = usX2Y->dev; - err = usb_submit_urb(usX2Y->In04urb, GFP_KERNEL); - list_for_each(p, &usX2Y->midi_list) { + usx2y->in04_urb->dev = usx2y->dev; + err = usb_submit_urb(usx2y->in04_urb, GFP_KERNEL); + list_for_each(p, &usx2y->midi_list) { snd_usbmidi_input_start(p); } - usX2Y->format = format; - usX2Y->rate = 0; + usx2y->format = format; + usx2y->rate = 0; return err; } -static int snd_usX2Y_pcm_hw_params(struct snd_pcm_substream *substream, +static int snd_usx2y_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { - int err = 0; unsigned int rate = params_rate(hw_params); snd_pcm_format_t format = params_format(hw_params); struct snd_card *card = substream->pstr->pcm->card; - struct usX2Ydev *dev = usX2Y(card); + struct usx2ydev *dev = usx2y(card); + struct snd_usx2y_substream *subs; + struct snd_pcm_substream *test_substream; int i; - mutex_lock(&usX2Y(card)->pcm_mutex); - snd_printdd("snd_usX2Y_hw_params(%p, %p)\n", substream, hw_params); - /* all pcm substreams off one usX2Y have to operate at the same + guard(mutex)(&usx2y(card)->pcm_mutex); + dev_dbg(&dev->dev->dev, "%s(%p, %p)\n", __func__, substream, hw_params); + /* all pcm substreams off one usx2y have to operate at the same * rate & format */ for (i = 0; i < dev->pcm_devs * 2; i++) { - struct snd_usX2Y_substream *subs = dev->subs[i]; - struct snd_pcm_substream *test_substream; - + subs = dev->subs[i]; if (!subs) continue; test_substream = subs->pcm_substream; @@ -773,95 +776,89 @@ static int snd_usX2Y_pcm_hw_params(struct snd_pcm_substream *substream, test_substream->runtime->format != format) || (test_substream->runtime->rate && test_substream->runtime->rate != rate)) { - err = -EINVAL; - goto error; + return -EINVAL; } } - err = snd_pcm_lib_malloc_pages(substream, - params_buffer_bytes(hw_params)); - if (err < 0) { - snd_printk(KERN_ERR "snd_pcm_lib_malloc_pages(%p, %i) returned %i\n", - substream, params_buffer_bytes(hw_params), err); - goto error; - } - - error: - mutex_unlock(&usX2Y(card)->pcm_mutex); - return err; + return 0; } /* * free the buffer */ -static int snd_usX2Y_pcm_hw_free(struct snd_pcm_substream *substream) +static int snd_usx2y_pcm_hw_free(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_usX2Y_substream *subs = runtime->private_data; - mutex_lock(&subs->usX2Y->pcm_mutex); - snd_printdd("snd_usX2Y_hw_free(%p)\n", substream); - - if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) { - struct snd_usX2Y_substream *cap_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]; - atomic_set(&subs->state, state_STOPPED); - usX2Y_urbs_release(subs); + struct snd_usx2y_substream *subs = runtime->private_data; + struct snd_usx2y_substream *cap_subs, *playback_subs; + + guard(mutex)(&subs->usx2y->pcm_mutex); + dev_dbg(&subs->usx2y->dev->dev, "%s(%p)\n", __func__, substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cap_subs = subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]; + atomic_set(&subs->state, STATE_STOPPED); + usx2y_urbs_release(subs); if (!cap_subs->pcm_substream || !cap_subs->pcm_substream->runtime || - !cap_subs->pcm_substream->runtime->status || - cap_subs->pcm_substream->runtime->status->state < SNDRV_PCM_STATE_PREPARED) { - atomic_set(&cap_subs->state, state_STOPPED); - usX2Y_urbs_release(cap_subs); + cap_subs->pcm_substream->runtime->state < SNDRV_PCM_STATE_PREPARED) { + atomic_set(&cap_subs->state, STATE_STOPPED); + usx2y_urbs_release(cap_subs); } } else { - struct snd_usX2Y_substream *playback_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]; - if (atomic_read(&playback_subs->state) < state_PREPARED) { - atomic_set(&subs->state, state_STOPPED); - usX2Y_urbs_release(subs); + playback_subs = subs->usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + if (atomic_read(&playback_subs->state) < STATE_PREPARED) { + atomic_set(&subs->state, STATE_STOPPED); + usx2y_urbs_release(subs); } } - mutex_unlock(&subs->usX2Y->pcm_mutex); - return snd_pcm_lib_free_pages(substream); + return 0; } + /* * prepare callback * * set format and initialize urbs */ -static int snd_usX2Y_pcm_prepare(struct snd_pcm_substream *substream) +static int snd_usx2y_pcm_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_usX2Y_substream *subs = runtime->private_data; - struct usX2Ydev *usX2Y = subs->usX2Y; - struct snd_usX2Y_substream *capsubs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]; + struct snd_usx2y_substream *subs = runtime->private_data; + struct usx2ydev *usx2y = subs->usx2y; + struct snd_usx2y_substream *capsubs = subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]; int err = 0; - snd_printdd("snd_usX2Y_pcm_prepare(%p)\n", substream); - - mutex_lock(&usX2Y->pcm_mutex); - usX2Y_subs_prepare(subs); -// Start hardware streams -// SyncStream first.... - if (atomic_read(&capsubs->state) < state_PREPARED) { - if (usX2Y->format != runtime->format) - if ((err = usX2Y_format_set(usX2Y, runtime->format)) < 0) - goto up_prepare_mutex; - if (usX2Y->rate != runtime->rate) - if ((err = usX2Y_rate_set(usX2Y, runtime->rate)) < 0) - goto up_prepare_mutex; - snd_printdd("starting capture pipe for %s\n", subs == capsubs ? "self" : "playpipe"); - if (0 > (err = usX2Y_urbs_start(capsubs))) - goto up_prepare_mutex; - } - - if (subs != capsubs && atomic_read(&subs->state) < state_PREPARED) - err = usX2Y_urbs_start(subs); - - up_prepare_mutex: - mutex_unlock(&usX2Y->pcm_mutex); + + dev_dbg(&usx2y->dev->dev, "%s(%p)\n", __func__, substream); + + guard(mutex)(&usx2y->pcm_mutex); + usx2y_subs_prepare(subs); + // Start hardware streams + // SyncStream first.... + if (atomic_read(&capsubs->state) < STATE_PREPARED) { + if (usx2y->format != runtime->format) { + err = usx2y_format_set(usx2y, runtime->format); + if (err < 0) + return err; + } + if (usx2y->rate != runtime->rate) { + err = usx2y_rate_set(usx2y, runtime->rate); + if (err < 0) + return err; + } + dev_dbg(&usx2y->dev->dev, "%s: starting capture pipe for %s\n", + __func__, subs == capsubs ? "self" : "playpipe"); + err = usx2y_urbs_start(capsubs); + if (err < 0) + return err; + } + + if (subs != capsubs && atomic_read(&subs->state) < STATE_PREPARED) + err = usx2y_urbs_start(subs); + return err; } -static struct snd_pcm_hardware snd_usX2Y_2c = -{ +static const struct snd_pcm_hardware snd_usx2y_2c = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | @@ -880,120 +877,114 @@ static struct snd_pcm_hardware snd_usX2Y_2c = .fifo_size = 0 }; - - -static int snd_usX2Y_pcm_open(struct snd_pcm_substream *substream) +static int snd_usx2y_pcm_open(struct snd_pcm_substream *substream) { - struct snd_usX2Y_substream *subs = ((struct snd_usX2Y_substream **) - snd_pcm_substream_chip(substream))[substream->stream]; + struct snd_usx2y_substream *subs = + ((struct snd_usx2y_substream **) + snd_pcm_substream_chip(substream))[substream->stream]; struct snd_pcm_runtime *runtime = substream->runtime; - if (subs->usX2Y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS) + if (subs->usx2y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS) return -EBUSY; - runtime->hw = snd_usX2Y_2c; + runtime->hw = snd_usx2y_2c; runtime->private_data = subs; subs->pcm_substream = substream; snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000); return 0; } - - -static int snd_usX2Y_pcm_close(struct snd_pcm_substream *substream) +static int snd_usx2y_pcm_close(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_usX2Y_substream *subs = runtime->private_data; + struct snd_usx2y_substream *subs = runtime->private_data; subs->pcm_substream = NULL; return 0; } - -static const struct snd_pcm_ops snd_usX2Y_pcm_ops = -{ - .open = snd_usX2Y_pcm_open, - .close = snd_usX2Y_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_usX2Y_pcm_hw_params, - .hw_free = snd_usX2Y_pcm_hw_free, - .prepare = snd_usX2Y_pcm_prepare, - .trigger = snd_usX2Y_pcm_trigger, - .pointer = snd_usX2Y_pcm_pointer, +static const struct snd_pcm_ops snd_usx2y_pcm_ops = { + .open = snd_usx2y_pcm_open, + .close = snd_usx2y_pcm_close, + .hw_params = snd_usx2y_pcm_hw_params, + .hw_free = snd_usx2y_pcm_hw_free, + .prepare = snd_usx2y_pcm_prepare, + .trigger = snd_usx2y_pcm_trigger, + .pointer = snd_usx2y_pcm_pointer, }; - /* * free a usb stream instance */ -static void usX2Y_audio_stream_free(struct snd_usX2Y_substream **usX2Y_substream) +static void usx2y_audio_stream_free(struct snd_usx2y_substream **usx2y_substream) { - kfree(usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK]); - usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK] = NULL; + int stream; - kfree(usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE]); - usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE] = NULL; + for_each_pcm_streams(stream) { + kfree(usx2y_substream[stream]); + usx2y_substream[stream] = NULL; + } } -static void snd_usX2Y_pcm_private_free(struct snd_pcm *pcm) +static void snd_usx2y_pcm_private_free(struct snd_pcm *pcm) { - struct snd_usX2Y_substream **usX2Y_stream = pcm->private_data; - if (usX2Y_stream) - usX2Y_audio_stream_free(usX2Y_stream); + struct snd_usx2y_substream **usx2y_stream = pcm->private_data; + + if (usx2y_stream) + usx2y_audio_stream_free(usx2y_stream); } -static int usX2Y_audio_stream_new(struct snd_card *card, int playback_endpoint, int capture_endpoint) +static int usx2y_audio_stream_new(struct snd_card *card, int playback_endpoint, int capture_endpoint) { struct snd_pcm *pcm; int err, i; - struct snd_usX2Y_substream **usX2Y_substream = - usX2Y(card)->subs + 2 * usX2Y(card)->pcm_devs; + struct snd_usx2y_substream **usx2y_substream = + usx2y(card)->subs + 2 * usx2y(card)->pcm_devs; for (i = playback_endpoint ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE; i <= SNDRV_PCM_STREAM_CAPTURE; ++i) { - usX2Y_substream[i] = kzalloc(sizeof(struct snd_usX2Y_substream), GFP_KERNEL); - if (!usX2Y_substream[i]) + usx2y_substream[i] = kzalloc(sizeof(struct snd_usx2y_substream), GFP_KERNEL); + if (!usx2y_substream[i]) return -ENOMEM; - usX2Y_substream[i]->usX2Y = usX2Y(card); + usx2y_substream[i]->usx2y = usx2y(card); } if (playback_endpoint) - usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK]->endpoint = playback_endpoint; - usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE]->endpoint = capture_endpoint; + usx2y_substream[SNDRV_PCM_STREAM_PLAYBACK]->endpoint = playback_endpoint; + usx2y_substream[SNDRV_PCM_STREAM_CAPTURE]->endpoint = capture_endpoint; - err = snd_pcm_new(card, NAME_ALLCAPS" Audio", usX2Y(card)->pcm_devs, + err = snd_pcm_new(card, NAME_ALLCAPS" Audio", usx2y(card)->pcm_devs, playback_endpoint ? 1 : 0, 1, &pcm); if (err < 0) { - usX2Y_audio_stream_free(usX2Y_substream); + usx2y_audio_stream_free(usx2y_substream); return err; } if (playback_endpoint) - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usX2Y_pcm_ops); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usX2Y_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usx2y_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usx2y_pcm_ops); - pcm->private_data = usX2Y_substream; - pcm->private_free = snd_usX2Y_pcm_private_free; + pcm->private_data = usx2y_substream; + pcm->private_free = snd_usx2y_pcm_private_free; pcm->info_flags = 0; - sprintf(pcm->name, NAME_ALLCAPS" Audio #%d", usX2Y(card)->pcm_devs); - - if ((playback_endpoint && - 0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream, - SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data(GFP_KERNEL), - 64*1024, 128*1024))) || - 0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, - SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data(GFP_KERNEL), - 64*1024, 128*1024))) { - snd_usX2Y_pcm_private_free(pcm); - return err; + sprintf(pcm->name, NAME_ALLCAPS" Audio #%d", usx2y(card)->pcm_devs); + + if (playback_endpoint) { + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, + 64*1024, 128*1024); } - usX2Y(card)->pcm_devs++; + + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, + 64*1024, 128*1024); + usx2y(card)->pcm_devs++; return 0; } @@ -1001,18 +992,19 @@ static int usX2Y_audio_stream_new(struct snd_card *card, int playback_endpoint, /* * create a chip instance and set its names. */ -int usX2Y_audio_create(struct snd_card *card) +int usx2y_audio_create(struct snd_card *card) { - int err = 0; - - INIT_LIST_HEAD(&usX2Y(card)->pcm_list); + int err; - if (0 > (err = usX2Y_audio_stream_new(card, 0xA, 0x8))) + err = usx2y_audio_stream_new(card, 0xA, 0x8); + if (err < 0) return err; - if (le16_to_cpu(usX2Y(card)->dev->descriptor.idProduct) == USB_ID_US428) - if (0 > (err = usX2Y_audio_stream_new(card, 0, 0xA))) - return err; - if (le16_to_cpu(usX2Y(card)->dev->descriptor.idProduct) != USB_ID_US122) - err = usX2Y_rate_set(usX2Y(card), 44100); // Lets us428 recognize output-volume settings, disturbs us122. + if (le16_to_cpu(usx2y(card)->dev->descriptor.idProduct) == USB_ID_US428) { + err = usx2y_audio_stream_new(card, 0, 0xA); + if (err < 0) + return err; + } + if (le16_to_cpu(usx2y(card)->dev->descriptor.idProduct) != USB_ID_US122) + err = usx2y_rate_set(usx2y(card), 44100); // Lets us428 recognize output-volume settings, disturbs us122. return err; } diff --git a/sound/usb/usx2y/usx2y.h b/sound/usb/usx2y/usx2y.h index 7e59263dd895..780071ddd902 100644 --- a/sound/usb/usx2y/usx2y.h +++ b/sound/usb/usx2y/usx2y.h @@ -1,21 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Driver for Tascam US-X2Y USB soundcards * * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __SOUND_USX2Y_COMMON_H diff --git a/sound/usb/usx2y/usx2yhwdeppcm.c b/sound/usb/usx2y/usx2yhwdeppcm.c index 4fd9276b8e50..7c90214485d9 100644 --- a/sound/usb/usx2y/usx2yhwdeppcm.c +++ b/sound/usb/usx2y/usx2yhwdeppcm.c @@ -1,24 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* USX2Y "rawusb" aka hwdep_pcm implementation Its usb's unableness to atomically handle power of 2 period sized data chuncs at standard samplerates, - what led to this part of the usx2y module: + what led to this part of the usx2y module: It provides the alsa kernel half of the usx2y-alsa-jack driver pair. The pair uses a hardware dependent alsa-device for mmaped pcm transport. Advantage achieved: @@ -47,7 +35,7 @@ Kernel: - rawusb dma pcm buffer transport should go to snd-usb-lib, so also snd-usb-audio devices can use it. - Currently rawusb dma pcm buffer transport (this file) is only available to snd-usb-usx2y. + Currently rawusb dma pcm buffer transport (this file) is only available to snd-usb-usx2y. */ #include <linux/delay.h> @@ -58,28 +46,32 @@ #include <sound/hwdep.h> - -static int usX2Y_usbpcm_urb_capt_retire(struct snd_usX2Y_substream *subs) +static int usx2y_usbpcm_urb_capt_retire(struct snd_usx2y_substream *subs) { struct urb *urb = subs->completed_urb; struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; - int i, lens = 0, hwptr_done = subs->hwptr_done; - struct usX2Ydev *usX2Y = subs->usX2Y; - if (0 > usX2Y->hwdep_pcm_shm->capture_iso_start) { //FIXME - int head = usX2Y->hwdep_pcm_shm->captured_iso_head + 1; - if (head >= ARRAY_SIZE(usX2Y->hwdep_pcm_shm->captured_iso)) + int i, lens = 0, hwptr_done = subs->hwptr_done; + struct usx2ydev *usx2y = subs->usx2y; + int head; + + if (usx2y->hwdep_pcm_shm->capture_iso_start < 0) { //FIXME + head = usx2y->hwdep_pcm_shm->captured_iso_head + 1; + if (head >= ARRAY_SIZE(usx2y->hwdep_pcm_shm->captured_iso)) head = 0; - usX2Y->hwdep_pcm_shm->capture_iso_start = head; - snd_printdd("cap start %i\n", head); + usx2y->hwdep_pcm_shm->capture_iso_start = head; + dev_dbg(&usx2y->dev->dev, "cap start %i\n", head); } for (i = 0; i < nr_of_packs(); i++) { if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */ - snd_printk(KERN_ERR "active frame status %i. Most probably some hardware problem.\n", urb->iso_frame_desc[i].status); + dev_err(&usx2y->dev->dev, + "active frame status %i. Most probably some hardware problem.\n", + urb->iso_frame_desc[i].status); return urb->iso_frame_desc[i].status; } - lens += urb->iso_frame_desc[i].actual_length / usX2Y->stride; + lens += urb->iso_frame_desc[i].actual_length / usx2y->stride; } - if ((hwptr_done += lens) >= runtime->buffer_size) + hwptr_done += lens; + if (hwptr_done >= runtime->buffer_size) hwptr_done -= runtime->buffer_size; subs->hwptr_done = hwptr_done; subs->transfer_done += lens; @@ -91,10 +83,10 @@ static int usX2Y_usbpcm_urb_capt_retire(struct snd_usX2Y_substream *subs) return 0; } -static inline int usX2Y_iso_frames_per_buffer(struct snd_pcm_runtime *runtime, - struct usX2Ydev * usX2Y) +static int usx2y_iso_frames_per_buffer(struct snd_pcm_runtime *runtime, + struct usx2ydev *usx2y) { - return (runtime->buffer_size * 1000) / usX2Y->rate + 1; //FIXME: so far only correct period_size == 2^x ? + return (runtime->buffer_size * 1000) / usx2y->rate + 1; //FIXME: so far only correct period_size == 2^x ? } /* @@ -107,18 +99,18 @@ static inline int usX2Y_iso_frames_per_buffer(struct snd_pcm_runtime *runtime, * it directly from the buffer. thus the data is once copied to * a temporary buffer and urb points to that. */ -static int usX2Y_hwdep_urb_play_prepare(struct snd_usX2Y_substream *subs, +static int usx2y_hwdep_urb_play_prepare(struct snd_usx2y_substream *subs, struct urb *urb) { int count, counts, pack; - struct usX2Ydev *usX2Y = subs->usX2Y; - struct snd_usX2Y_hwdep_pcm_shm *shm = usX2Y->hwdep_pcm_shm; + struct usx2ydev *usx2y = subs->usx2y; + struct snd_usx2y_hwdep_pcm_shm *shm = usx2y->hwdep_pcm_shm; struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; - if (0 > shm->playback_iso_start) { + if (shm->playback_iso_start < 0) { shm->playback_iso_start = shm->captured_iso_head - - usX2Y_iso_frames_per_buffer(runtime, usX2Y); - if (0 > shm->playback_iso_start) + usx2y_iso_frames_per_buffer(runtime, usx2y); + if (shm->playback_iso_start < 0) shm->playback_iso_start += ARRAY_SIZE(shm->captured_iso); shm->playback_iso_head = shm->playback_iso_start; } @@ -126,35 +118,37 @@ static int usX2Y_hwdep_urb_play_prepare(struct snd_usX2Y_substream *subs, count = 0; for (pack = 0; pack < nr_of_packs(); pack++) { /* calculate the size of a packet */ - counts = shm->captured_iso[shm->playback_iso_head].length / usX2Y->stride; + counts = shm->captured_iso[shm->playback_iso_head].length / usx2y->stride; if (counts < 43 || counts > 50) { - snd_printk(KERN_ERR "should not be here with counts=%i\n", counts); + dev_err(&usx2y->dev->dev, "should not be here with counts=%i\n", counts); return -EPIPE; } /* set up descriptor */ urb->iso_frame_desc[pack].offset = shm->captured_iso[shm->playback_iso_head].offset; urb->iso_frame_desc[pack].length = shm->captured_iso[shm->playback_iso_head].length; - if (atomic_read(&subs->state) != state_RUNNING) + if (atomic_read(&subs->state) != STATE_RUNNING) memset((char *)urb->transfer_buffer + urb->iso_frame_desc[pack].offset, 0, urb->iso_frame_desc[pack].length); if (++shm->playback_iso_head >= ARRAY_SIZE(shm->captured_iso)) shm->playback_iso_head = 0; count += counts; } - urb->transfer_buffer_length = count * usX2Y->stride; + urb->transfer_buffer_length = count * usx2y->stride; return 0; } - -static inline void usX2Y_usbpcm_urb_capt_iso_advance(struct snd_usX2Y_substream *subs, - struct urb *urb) +static void usx2y_usbpcm_urb_capt_iso_advance(struct snd_usx2y_substream *subs, + struct urb *urb) { - int pack; + struct usb_iso_packet_descriptor *desc; + struct snd_usx2y_hwdep_pcm_shm *shm; + int pack, head; + for (pack = 0; pack < nr_of_packs(); ++pack) { - struct usb_iso_packet_descriptor *desc = urb->iso_frame_desc + pack; - if (NULL != subs) { - struct snd_usX2Y_hwdep_pcm_shm *shm = subs->usX2Y->hwdep_pcm_shm; - int head = shm->captured_iso_head + 1; + desc = urb->iso_frame_desc + pack; + if (subs) { + shm = subs->usx2y->hwdep_pcm_shm; + head = shm->captured_iso_head + 1; if (head >= ARRAY_SIZE(shm->captured_iso)) head = 0; shm->captured_iso[head].frame = urb->start_frame + pack; @@ -163,106 +157,111 @@ static inline void usX2Y_usbpcm_urb_capt_iso_advance(struct snd_usX2Y_substream shm->captured_iso_head = head; shm->captured_iso_frames++; } - if ((desc->offset += desc->length * NRURBS*nr_of_packs()) + - desc->length >= SSS) + desc->offset += desc->length * NRURBS * nr_of_packs(); + if (desc->offset + desc->length >= SSS) desc->offset -= (SSS - desc->length); } } -static inline int usX2Y_usbpcm_usbframe_complete(struct snd_usX2Y_substream *capsubs, - struct snd_usX2Y_substream *capsubs2, - struct snd_usX2Y_substream *playbacksubs, - int frame) +static int usx2y_usbpcm_usbframe_complete(struct snd_usx2y_substream *capsubs, + struct snd_usx2y_substream *capsubs2, + struct snd_usx2y_substream *playbacksubs, + int frame) { int err, state; struct urb *urb = playbacksubs->completed_urb; state = atomic_read(&playbacksubs->state); - if (NULL != urb) { - if (state == state_RUNNING) - usX2Y_urb_play_retire(playbacksubs, urb); - else if (state >= state_PRERUNNING) + if (urb) { + if (state == STATE_RUNNING) + usx2y_urb_play_retire(playbacksubs, urb); + else if (state >= STATE_PRERUNNING) atomic_inc(&playbacksubs->state); } else { switch (state) { - case state_STARTING1: + case STATE_STARTING1: urb = playbacksubs->urb[0]; atomic_inc(&playbacksubs->state); break; - case state_STARTING2: + case STATE_STARTING2: urb = playbacksubs->urb[1]; atomic_inc(&playbacksubs->state); break; } } if (urb) { - if ((err = usX2Y_hwdep_urb_play_prepare(playbacksubs, urb)) || - (err = usX2Y_urb_submit(playbacksubs, urb, frame))) { + err = usx2y_hwdep_urb_play_prepare(playbacksubs, urb); + if (err) + return err; + err = usx2y_hwdep_urb_play_prepare(playbacksubs, urb); + if (err) return err; - } } - + playbacksubs->completed_urb = NULL; state = atomic_read(&capsubs->state); - if (state >= state_PREPARED) { - if (state == state_RUNNING) { - if ((err = usX2Y_usbpcm_urb_capt_retire(capsubs))) + if (state >= STATE_PREPARED) { + if (state == STATE_RUNNING) { + err = usx2y_usbpcm_urb_capt_retire(capsubs); + if (err) return err; - } else if (state >= state_PRERUNNING) + } else if (state >= STATE_PRERUNNING) { atomic_inc(&capsubs->state); - usX2Y_usbpcm_urb_capt_iso_advance(capsubs, capsubs->completed_urb); - if (NULL != capsubs2) - usX2Y_usbpcm_urb_capt_iso_advance(NULL, capsubs2->completed_urb); - if ((err = usX2Y_urb_submit(capsubs, capsubs->completed_urb, frame))) + } + usx2y_usbpcm_urb_capt_iso_advance(capsubs, capsubs->completed_urb); + if (capsubs2) + usx2y_usbpcm_urb_capt_iso_advance(NULL, capsubs2->completed_urb); + err = usx2y_urb_submit(capsubs, capsubs->completed_urb, frame); + if (err) return err; - if (NULL != capsubs2) - if ((err = usX2Y_urb_submit(capsubs2, capsubs2->completed_urb, frame))) + if (capsubs2) { + err = usx2y_urb_submit(capsubs2, capsubs2->completed_urb, frame); + if (err) return err; + } } capsubs->completed_urb = NULL; - if (NULL != capsubs2) + if (capsubs2) capsubs2->completed_urb = NULL; return 0; } - -static void i_usX2Y_usbpcm_urb_complete(struct urb *urb) +static void i_usx2y_usbpcm_urb_complete(struct urb *urb) { - struct snd_usX2Y_substream *subs = urb->context; - struct usX2Ydev *usX2Y = subs->usX2Y; - struct snd_usX2Y_substream *capsubs, *capsubs2, *playbacksubs; - - if (unlikely(atomic_read(&subs->state) < state_PREPARED)) { - snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n", - usb_get_current_frame_number(usX2Y->dev), - subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out", - urb->status, urb->start_frame); + struct snd_usx2y_substream *subs = urb->context; + struct usx2ydev *usx2y = subs->usx2y; + struct snd_usx2y_substream *capsubs, *capsubs2, *playbacksubs; + + if (unlikely(atomic_read(&subs->state) < STATE_PREPARED)) { + dev_dbg(&usx2y->dev->dev, + "hcd_frame=%i ep=%i%s status=%i start_frame=%i\n", + usb_get_current_frame_number(usx2y->dev), + subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out", + urb->status, urb->start_frame); return; } if (unlikely(urb->status)) { - usX2Y_error_urb_status(usX2Y, subs, urb); + usx2y_error_urb_status(usx2y, subs, urb); return; } subs->completed_urb = urb; - capsubs = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]; - capsubs2 = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2]; - playbacksubs = usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]; - if (capsubs->completed_urb && atomic_read(&capsubs->state) >= state_PREPARED && - (NULL == capsubs2 || capsubs2->completed_urb) && - (playbacksubs->completed_urb || atomic_read(&playbacksubs->state) < state_PREPARED)) { - if (!usX2Y_usbpcm_usbframe_complete(capsubs, capsubs2, playbacksubs, urb->start_frame)) - usX2Y->wait_iso_frame += nr_of_packs(); - else { - snd_printdd("\n"); - usX2Y_clients_stop(usX2Y); + capsubs = usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]; + capsubs2 = usx2y->subs[SNDRV_PCM_STREAM_CAPTURE + 2]; + playbacksubs = usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + if (capsubs->completed_urb && atomic_read(&capsubs->state) >= STATE_PREPARED && + (!capsubs2 || capsubs2->completed_urb) && + (playbacksubs->completed_urb || atomic_read(&playbacksubs->state) < STATE_PREPARED)) { + if (!usx2y_usbpcm_usbframe_complete(capsubs, capsubs2, playbacksubs, urb->start_frame)) { + usx2y->wait_iso_frame += nr_of_packs(); + } else { + usx2y_clients_stop(usx2y); } } } - -static void usX2Y_hwdep_urb_release(struct urb **urb) +static void usx2y_hwdep_urb_release(struct urb **urb) { usb_kill_urb(*urb); usb_free_urb(*urb); @@ -272,80 +271,85 @@ static void usX2Y_hwdep_urb_release(struct urb **urb) /* * release a substream */ -static void usX2Y_usbpcm_urbs_release(struct snd_usX2Y_substream *subs) +static void usx2y_usbpcm_urbs_release(struct snd_usx2y_substream *subs) { int i; - snd_printdd("snd_usX2Y_urbs_release() %i\n", subs->endpoint); + + dev_dbg(&subs->usx2y->dev->dev, + "snd_usx2y_urbs_release() %i\n", subs->endpoint); for (i = 0; i < NRURBS; i++) - usX2Y_hwdep_urb_release(subs->urb + i); + usx2y_hwdep_urb_release(subs->urb + i); } -static void usX2Y_usbpcm_subs_startup_finish(struct usX2Ydev * usX2Y) +static void usx2y_usbpcm_subs_startup_finish(struct usx2ydev *usx2y) { - usX2Y_urbs_set_complete(usX2Y, i_usX2Y_usbpcm_urb_complete); - usX2Y->prepare_subs = NULL; + usx2y_urbs_set_complete(usx2y, i_usx2y_usbpcm_urb_complete); + usx2y->prepare_subs = NULL; } -static void i_usX2Y_usbpcm_subs_startup(struct urb *urb) +static void i_usx2y_usbpcm_subs_startup(struct urb *urb) { - struct snd_usX2Y_substream *subs = urb->context; - struct usX2Ydev *usX2Y = subs->usX2Y; - struct snd_usX2Y_substream *prepare_subs = usX2Y->prepare_subs; - if (NULL != prepare_subs && + struct snd_usx2y_substream *subs = urb->context; + struct usx2ydev *usx2y = subs->usx2y; + struct snd_usx2y_substream *prepare_subs = usx2y->prepare_subs; + struct snd_usx2y_substream *cap_subs2; + + if (prepare_subs && urb->start_frame == prepare_subs->urb[0]->start_frame) { atomic_inc(&prepare_subs->state); - if (prepare_subs == usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]) { - struct snd_usX2Y_substream *cap_subs2 = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2]; - if (cap_subs2 != NULL) + if (prepare_subs == usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]) { + cap_subs2 = usx2y->subs[SNDRV_PCM_STREAM_CAPTURE + 2]; + if (cap_subs2) atomic_inc(&cap_subs2->state); } - usX2Y_usbpcm_subs_startup_finish(usX2Y); - wake_up(&usX2Y->prepare_wait_queue); + usx2y_usbpcm_subs_startup_finish(usx2y); + wake_up(&usx2y->prepare_wait_queue); } - i_usX2Y_usbpcm_urb_complete(urb); + i_usx2y_usbpcm_urb_complete(urb); } /* * initialize a substream's urbs */ -static int usX2Y_usbpcm_urbs_allocate(struct snd_usX2Y_substream *subs) +static int usx2y_usbpcm_urbs_allocate(struct snd_usx2y_substream *subs) { int i; unsigned int pipe; - int is_playback = subs == subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]; - struct usb_device *dev = subs->usX2Y->dev; + int is_playback = subs == subs->usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + struct usb_device *dev = subs->usx2y->dev; + struct urb **purb; pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) : usb_rcvisocpipe(dev, subs->endpoint); - subs->maxpacksize = usb_maxpacket(dev, pipe, is_playback); + subs->maxpacksize = usb_maxpacket(dev, pipe); if (!subs->maxpacksize) return -EINVAL; /* allocate and initialize data urbs */ for (i = 0; i < NRURBS; i++) { - struct urb **purb = subs->urb + i; + purb = subs->urb + i; if (*purb) { usb_kill_urb(*purb); continue; } *purb = usb_alloc_urb(nr_of_packs(), GFP_KERNEL); - if (NULL == *purb) { - usX2Y_usbpcm_urbs_release(subs); + if (!*purb) { + usx2y_usbpcm_urbs_release(subs); return -ENOMEM; } (*purb)->transfer_buffer = is_playback ? - subs->usX2Y->hwdep_pcm_shm->playback : ( + subs->usx2y->hwdep_pcm_shm->playback : ( subs->endpoint == 0x8 ? - subs->usX2Y->hwdep_pcm_shm->capture0x8 : - subs->usX2Y->hwdep_pcm_shm->capture0xA); + subs->usx2y->hwdep_pcm_shm->capture0x8 : + subs->usx2y->hwdep_pcm_shm->capture0xA); (*purb)->dev = dev; (*purb)->pipe = pipe; (*purb)->number_of_packets = nr_of_packs(); (*purb)->context = subs; (*purb)->interval = 1; - (*purb)->complete = i_usX2Y_usbpcm_subs_startup; + (*purb)->complete = i_usx2y_usbpcm_subs_startup; } return 0; } @@ -353,193 +357,212 @@ static int usX2Y_usbpcm_urbs_allocate(struct snd_usX2Y_substream *subs) /* * free the buffer */ -static int snd_usX2Y_usbpcm_hw_free(struct snd_pcm_substream *substream) +static int snd_usx2y_usbpcm_hw_free(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_usX2Y_substream *subs = runtime->private_data, - *cap_subs2 = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2]; - mutex_lock(&subs->usX2Y->pcm_mutex); - snd_printdd("snd_usX2Y_usbpcm_hw_free(%p)\n", substream); - - if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) { - struct snd_usX2Y_substream *cap_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]; - atomic_set(&subs->state, state_STOPPED); - usX2Y_usbpcm_urbs_release(subs); + struct snd_usx2y_substream *subs = runtime->private_data; + struct snd_usx2y_substream *cap_subs; + struct snd_usx2y_substream *playback_subs; + struct snd_usx2y_substream *cap_subs2; + + guard(mutex)(&subs->usx2y->pcm_mutex); + dev_dbg(&subs->usx2y->dev->dev, "%s(%p)\n", __func__, substream); + + cap_subs2 = subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE + 2]; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cap_subs = subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]; + atomic_set(&subs->state, STATE_STOPPED); + usx2y_usbpcm_urbs_release(subs); if (!cap_subs->pcm_substream || !cap_subs->pcm_substream->runtime || - !cap_subs->pcm_substream->runtime->status || - cap_subs->pcm_substream->runtime->status->state < SNDRV_PCM_STATE_PREPARED) { - atomic_set(&cap_subs->state, state_STOPPED); - if (NULL != cap_subs2) - atomic_set(&cap_subs2->state, state_STOPPED); - usX2Y_usbpcm_urbs_release(cap_subs); - if (NULL != cap_subs2) - usX2Y_usbpcm_urbs_release(cap_subs2); + cap_subs->pcm_substream->runtime->state < SNDRV_PCM_STATE_PREPARED) { + atomic_set(&cap_subs->state, STATE_STOPPED); + if (cap_subs2) + atomic_set(&cap_subs2->state, STATE_STOPPED); + usx2y_usbpcm_urbs_release(cap_subs); + if (cap_subs2) + usx2y_usbpcm_urbs_release(cap_subs2); } } else { - struct snd_usX2Y_substream *playback_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]; - if (atomic_read(&playback_subs->state) < state_PREPARED) { - atomic_set(&subs->state, state_STOPPED); - if (NULL != cap_subs2) - atomic_set(&cap_subs2->state, state_STOPPED); - usX2Y_usbpcm_urbs_release(subs); - if (NULL != cap_subs2) - usX2Y_usbpcm_urbs_release(cap_subs2); + playback_subs = subs->usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + if (atomic_read(&playback_subs->state) < STATE_PREPARED) { + atomic_set(&subs->state, STATE_STOPPED); + if (cap_subs2) + atomic_set(&cap_subs2->state, STATE_STOPPED); + usx2y_usbpcm_urbs_release(subs); + if (cap_subs2) + usx2y_usbpcm_urbs_release(cap_subs2); } } - mutex_unlock(&subs->usX2Y->pcm_mutex); - return snd_pcm_lib_free_pages(substream); + return 0; } -static void usX2Y_usbpcm_subs_startup(struct snd_usX2Y_substream *subs) +static void usx2y_usbpcm_subs_startup(struct snd_usx2y_substream *subs) { - struct usX2Ydev * usX2Y = subs->usX2Y; - usX2Y->prepare_subs = subs; + struct usx2ydev *usx2y = subs->usx2y; + + usx2y->prepare_subs = subs; subs->urb[0]->start_frame = -1; - smp_wmb(); // Make sure above modifications are seen by i_usX2Y_subs_startup() - usX2Y_urbs_set_complete(usX2Y, i_usX2Y_usbpcm_subs_startup); + smp_wmb(); // Make sure above modifications are seen by i_usx2y_subs_startup() + usx2y_urbs_set_complete(usx2y, i_usx2y_usbpcm_subs_startup); } -static int usX2Y_usbpcm_urbs_start(struct snd_usX2Y_substream *subs) +static int usx2y_usbpcm_urbs_start(struct snd_usx2y_substream *subs) { - int p, u, err, - stream = subs->pcm_substream->stream; - struct usX2Ydev *usX2Y = subs->usX2Y; - - if (SNDRV_PCM_STREAM_CAPTURE == stream) { - usX2Y->hwdep_pcm_shm->captured_iso_head = -1; - usX2Y->hwdep_pcm_shm->captured_iso_frames = 0; + int p, u, err, stream = subs->pcm_substream->stream; + struct usx2ydev *usx2y = subs->usx2y; + struct urb *urb; + unsigned long pack; + + if (stream == SNDRV_PCM_STREAM_CAPTURE) { + usx2y->hwdep_pcm_shm->captured_iso_head = -1; + usx2y->hwdep_pcm_shm->captured_iso_frames = 0; } for (p = 0; 3 >= (stream + p); p += 2) { - struct snd_usX2Y_substream *subs = usX2Y->subs[stream + p]; - if (subs != NULL) { - if ((err = usX2Y_usbpcm_urbs_allocate(subs)) < 0) + struct snd_usx2y_substream *subs = usx2y->subs[stream + p]; + if (subs) { + err = usx2y_usbpcm_urbs_allocate(subs); + if (err < 0) return err; subs->completed_urb = NULL; } } for (p = 0; p < 4; p++) { - struct snd_usX2Y_substream *subs = usX2Y->subs[p]; - if (subs != NULL && atomic_read(&subs->state) >= state_PREPARED) + struct snd_usx2y_substream *subs = usx2y->subs[p]; + + if (subs && atomic_read(&subs->state) >= STATE_PREPARED) goto start; } start: - usX2Y_usbpcm_subs_startup(subs); + usx2y_usbpcm_subs_startup(subs); for (u = 0; u < NRURBS; u++) { for (p = 0; 3 >= (stream + p); p += 2) { - struct snd_usX2Y_substream *subs = usX2Y->subs[stream + p]; - if (subs != NULL) { - struct urb *urb = subs->urb[u]; - if (usb_pipein(urb->pipe)) { - unsigned long pack; - if (0 == u) - atomic_set(&subs->state, state_STARTING3); - urb->dev = usX2Y->dev; - for (pack = 0; pack < nr_of_packs(); pack++) { - urb->iso_frame_desc[pack].offset = subs->maxpacksize * (pack + u * nr_of_packs()); - urb->iso_frame_desc[pack].length = subs->maxpacksize; - } - urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs(); - if ((err = usb_submit_urb(urb, GFP_KERNEL)) < 0) { - snd_printk (KERN_ERR "cannot usb_submit_urb() for urb %d, err = %d\n", u, err); - err = -EPIPE; - goto cleanup; - } else { - snd_printdd("%i\n", urb->start_frame); - if (u == 0) - usX2Y->wait_iso_frame = urb->start_frame; - } - urb->transfer_flags = 0; - } else { - atomic_set(&subs->state, state_STARTING1); - break; - } + struct snd_usx2y_substream *subs = usx2y->subs[stream + p]; + + if (!subs) + continue; + urb = subs->urb[u]; + if (usb_pipein(urb->pipe)) { + if (!u) + atomic_set(&subs->state, STATE_STARTING3); + urb->dev = usx2y->dev; + for (pack = 0; pack < nr_of_packs(); pack++) { + urb->iso_frame_desc[pack].offset = subs->maxpacksize * (pack + u * nr_of_packs()); + urb->iso_frame_desc[pack].length = subs->maxpacksize; + } + urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs(); + err = usb_submit_urb(urb, GFP_KERNEL); + if (err < 0) { + dev_err(&urb->dev->dev, + "cannot usb_submit_urb() for urb %d, err = %d\n", + u, err); + err = -EPIPE; + goto cleanup; + } else { + if (!u) + usx2y->wait_iso_frame = urb->start_frame; + } + urb->transfer_flags = 0; + } else { + atomic_set(&subs->state, STATE_STARTING1); + break; } } } err = 0; - wait_event(usX2Y->prepare_wait_queue, NULL == usX2Y->prepare_subs); - if (atomic_read(&subs->state) != state_PREPARED) + wait_event(usx2y->prepare_wait_queue, !usx2y->prepare_subs); + if (atomic_read(&subs->state) != STATE_PREPARED) err = -EPIPE; - + cleanup: if (err) { - usX2Y_subs_startup_finish(usX2Y); // Call it now - usX2Y_clients_stop(usX2Y); // something is completely wroong > stop evrything + usx2y_subs_startup_finish(usx2y); // Call it now + usx2y_clients_stop(usx2y); // something is completely wrong > stop everything } return err; } +#define USX2Y_HWDEP_PCM_PAGES \ + PAGE_ALIGN(sizeof(struct snd_usx2y_hwdep_pcm_shm)) + /* * prepare callback * * set format and initialize urbs */ -static int snd_usX2Y_usbpcm_prepare(struct snd_pcm_substream *substream) +static int snd_usx2y_usbpcm_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_usX2Y_substream *subs = runtime->private_data; - struct usX2Ydev *usX2Y = subs->usX2Y; - struct snd_usX2Y_substream *capsubs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]; + struct snd_usx2y_substream *subs = runtime->private_data; + struct usx2ydev *usx2y = subs->usx2y; + struct snd_usx2y_substream *capsubs = subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]; int err = 0; - snd_printdd("snd_usX2Y_pcm_prepare(%p)\n", substream); - if (NULL == usX2Y->hwdep_pcm_shm) { - if (NULL == (usX2Y->hwdep_pcm_shm = snd_malloc_pages(sizeof(struct snd_usX2Y_hwdep_pcm_shm), GFP_KERNEL))) + dev_dbg(&usx2y->dev->dev, "snd_usx2y_pcm_prepare(%p)\n", substream); + + guard(mutex)(&usx2y->pcm_mutex); + + if (!usx2y->hwdep_pcm_shm) { + usx2y->hwdep_pcm_shm = alloc_pages_exact(USX2Y_HWDEP_PCM_PAGES, + GFP_KERNEL); + if (!usx2y->hwdep_pcm_shm) return -ENOMEM; - memset(usX2Y->hwdep_pcm_shm, 0, sizeof(struct snd_usX2Y_hwdep_pcm_shm)); + memset(usx2y->hwdep_pcm_shm, 0, USX2Y_HWDEP_PCM_PAGES); } - mutex_lock(&usX2Y->pcm_mutex); - usX2Y_subs_prepare(subs); -// Start hardware streams -// SyncStream first.... - if (atomic_read(&capsubs->state) < state_PREPARED) { - if (usX2Y->format != runtime->format) - if ((err = usX2Y_format_set(usX2Y, runtime->format)) < 0) - goto up_prepare_mutex; - if (usX2Y->rate != runtime->rate) - if ((err = usX2Y_rate_set(usX2Y, runtime->rate)) < 0) - goto up_prepare_mutex; - snd_printdd("starting capture pipe for %s\n", subs == capsubs ? - "self" : "playpipe"); - if (0 > (err = usX2Y_usbpcm_urbs_start(capsubs))) - goto up_prepare_mutex; + usx2y_subs_prepare(subs); + // Start hardware streams + // SyncStream first.... + if (atomic_read(&capsubs->state) < STATE_PREPARED) { + if (usx2y->format != runtime->format) { + err = usx2y_format_set(usx2y, runtime->format); + if (err < 0) + return err; + } + if (usx2y->rate != runtime->rate) { + err = usx2y_rate_set(usx2y, runtime->rate); + if (err < 0) + return err; + } + dev_dbg(&usx2y->dev->dev, + "starting capture pipe for %s\n", subs == capsubs ? + "self" : "playpipe"); + err = usx2y_usbpcm_urbs_start(capsubs); + if (err < 0) + return err; } if (subs != capsubs) { - usX2Y->hwdep_pcm_shm->playback_iso_start = -1; - if (atomic_read(&subs->state) < state_PREPARED) { - while (usX2Y_iso_frames_per_buffer(runtime, usX2Y) > - usX2Y->hwdep_pcm_shm->captured_iso_frames) { - snd_printdd("Wait: iso_frames_per_buffer=%i," - "captured_iso_frames=%i\n", - usX2Y_iso_frames_per_buffer(runtime, usX2Y), - usX2Y->hwdep_pcm_shm->captured_iso_frames); - if (msleep_interruptible(10)) { - err = -ERESTARTSYS; - goto up_prepare_mutex; - } - } - if (0 > (err = usX2Y_usbpcm_urbs_start(subs))) - goto up_prepare_mutex; + usx2y->hwdep_pcm_shm->playback_iso_start = -1; + if (atomic_read(&subs->state) < STATE_PREPARED) { + while (usx2y_iso_frames_per_buffer(runtime, usx2y) > + usx2y->hwdep_pcm_shm->captured_iso_frames) { + dev_dbg(&usx2y->dev->dev, + "Wait: iso_frames_per_buffer=%i,captured_iso_frames=%i\n", + usx2y_iso_frames_per_buffer(runtime, usx2y), + usx2y->hwdep_pcm_shm->captured_iso_frames); + if (msleep_interruptible(10)) + return -ERESTARTSYS; + } + err = usx2y_usbpcm_urbs_start(subs); + if (err < 0) + return err; } - snd_printdd("Ready: iso_frames_per_buffer=%i,captured_iso_frames=%i\n", - usX2Y_iso_frames_per_buffer(runtime, usX2Y), - usX2Y->hwdep_pcm_shm->captured_iso_frames); - } else - usX2Y->hwdep_pcm_shm->capture_iso_start = -1; - - up_prepare_mutex: - mutex_unlock(&usX2Y->pcm_mutex); + dev_dbg(&usx2y->dev->dev, + "Ready: iso_frames_per_buffer=%i,captured_iso_frames=%i\n", + usx2y_iso_frames_per_buffer(runtime, usx2y), + usx2y->hwdep_pcm_shm->captured_iso_frames); + } else { + usx2y->hwdep_pcm_shm->capture_iso_start = -1; + } + return err; } -static struct snd_pcm_hardware snd_usX2Y_4c = -{ +static const struct snd_pcm_hardware snd_usx2y_4c = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID), @@ -557,56 +580,53 @@ static struct snd_pcm_hardware snd_usX2Y_4c = .fifo_size = 0 }; - - -static int snd_usX2Y_usbpcm_open(struct snd_pcm_substream *substream) +static int snd_usx2y_usbpcm_open(struct snd_pcm_substream *substream) { - struct snd_usX2Y_substream *subs = ((struct snd_usX2Y_substream **) - snd_pcm_substream_chip(substream))[substream->stream]; + struct snd_usx2y_substream *subs = + ((struct snd_usx2y_substream **) + snd_pcm_substream_chip(substream))[substream->stream]; struct snd_pcm_runtime *runtime = substream->runtime; - if (!(subs->usX2Y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS)) + if (!(subs->usx2y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS)) return -EBUSY; - runtime->hw = SNDRV_PCM_STREAM_PLAYBACK == substream->stream ? snd_usX2Y_2c : - (subs->usX2Y->subs[3] ? snd_usX2Y_4c : snd_usX2Y_2c); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = snd_usx2y_2c; + else + runtime->hw = (subs->usx2y->subs[3] ? snd_usx2y_4c : snd_usx2y_2c); runtime->private_data = subs; subs->pcm_substream = substream; snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000); return 0; } - -static int snd_usX2Y_usbpcm_close(struct snd_pcm_substream *substream) +static int snd_usx2y_usbpcm_close(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_usX2Y_substream *subs = runtime->private_data; + struct snd_usx2y_substream *subs = runtime->private_data; subs->pcm_substream = NULL; return 0; } - -static const struct snd_pcm_ops snd_usX2Y_usbpcm_ops = -{ - .open = snd_usX2Y_usbpcm_open, - .close = snd_usX2Y_usbpcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_usX2Y_pcm_hw_params, - .hw_free = snd_usX2Y_usbpcm_hw_free, - .prepare = snd_usX2Y_usbpcm_prepare, - .trigger = snd_usX2Y_pcm_trigger, - .pointer = snd_usX2Y_pcm_pointer, +static const struct snd_pcm_ops snd_usx2y_usbpcm_ops = { + .open = snd_usx2y_usbpcm_open, + .close = snd_usx2y_usbpcm_close, + .hw_params = snd_usx2y_pcm_hw_params, + .hw_free = snd_usx2y_usbpcm_hw_free, + .prepare = snd_usx2y_usbpcm_prepare, + .trigger = snd_usx2y_pcm_trigger, + .pointer = snd_usx2y_pcm_pointer, }; - -static int usX2Y_pcms_busy_check(struct snd_card *card) +static int usx2y_pcms_busy_check(struct snd_card *card) { - struct usX2Ydev *dev = usX2Y(card); + struct usx2ydev *dev = usx2y(card); + struct snd_usx2y_substream *subs; int i; for (i = 0; i < dev->pcm_devs * 2; i++) { - struct snd_usX2Y_substream *subs = dev->subs[i]; + subs = dev->subs[i]; if (subs && subs->pcm_substream && SUBSTREAM_BUSY(subs->pcm_substream)) return -EBUSY; @@ -614,146 +634,137 @@ static int usX2Y_pcms_busy_check(struct snd_card *card) return 0; } -static int snd_usX2Y_hwdep_pcm_open(struct snd_hwdep *hw, struct file *file) +static int snd_usx2y_hwdep_pcm_open(struct snd_hwdep *hw, struct file *file) { struct snd_card *card = hw->card; int err; - mutex_lock(&usX2Y(card)->pcm_mutex); - err = usX2Y_pcms_busy_check(card); + guard(mutex)(&usx2y(card)->pcm_mutex); + err = usx2y_pcms_busy_check(card); if (!err) - usX2Y(card)->chip_status |= USX2Y_STAT_CHIP_MMAP_PCM_URBS; - mutex_unlock(&usX2Y(card)->pcm_mutex); + usx2y(card)->chip_status |= USX2Y_STAT_CHIP_MMAP_PCM_URBS; return err; } - -static int snd_usX2Y_hwdep_pcm_release(struct snd_hwdep *hw, struct file *file) +static int snd_usx2y_hwdep_pcm_release(struct snd_hwdep *hw, struct file *file) { struct snd_card *card = hw->card; int err; - mutex_lock(&usX2Y(card)->pcm_mutex); - err = usX2Y_pcms_busy_check(card); + guard(mutex)(&usx2y(card)->pcm_mutex); + err = usx2y_pcms_busy_check(card); if (!err) - usX2Y(hw->card)->chip_status &= ~USX2Y_STAT_CHIP_MMAP_PCM_URBS; - mutex_unlock(&usX2Y(card)->pcm_mutex); + usx2y(hw->card)->chip_status &= ~USX2Y_STAT_CHIP_MMAP_PCM_URBS; return err; } - -static void snd_usX2Y_hwdep_pcm_vm_open(struct vm_area_struct *area) +static void snd_usx2y_hwdep_pcm_vm_open(struct vm_area_struct *area) { } - -static void snd_usX2Y_hwdep_pcm_vm_close(struct vm_area_struct *area) +static void snd_usx2y_hwdep_pcm_vm_close(struct vm_area_struct *area) { } - -static vm_fault_t snd_usX2Y_hwdep_pcm_vm_fault(struct vm_fault *vmf) +static vm_fault_t snd_usx2y_hwdep_pcm_vm_fault(struct vm_fault *vmf) { unsigned long offset; void *vaddr; offset = vmf->pgoff << PAGE_SHIFT; - vaddr = (char *)((struct usX2Ydev *)vmf->vma->vm_private_data)->hwdep_pcm_shm + offset; + vaddr = (char *)((struct usx2ydev *)vmf->vma->vm_private_data)->hwdep_pcm_shm + offset; vmf->page = virt_to_page(vaddr); get_page(vmf->page); return 0; } - -static const struct vm_operations_struct snd_usX2Y_hwdep_pcm_vm_ops = { - .open = snd_usX2Y_hwdep_pcm_vm_open, - .close = snd_usX2Y_hwdep_pcm_vm_close, - .fault = snd_usX2Y_hwdep_pcm_vm_fault, +static const struct vm_operations_struct snd_usx2y_hwdep_pcm_vm_ops = { + .open = snd_usx2y_hwdep_pcm_vm_open, + .close = snd_usx2y_hwdep_pcm_vm_close, + .fault = snd_usx2y_hwdep_pcm_vm_fault, }; - -static int snd_usX2Y_hwdep_pcm_mmap(struct snd_hwdep * hw, struct file *filp, struct vm_area_struct *area) +static int snd_usx2y_hwdep_pcm_mmap(struct snd_hwdep *hw, struct file *filp, struct vm_area_struct *area) { unsigned long size = (unsigned long)(area->vm_end - area->vm_start); - struct usX2Ydev *usX2Y = hw->private_data; + struct usx2ydev *usx2y = hw->private_data; - if (!(usX2Y->chip_status & USX2Y_STAT_CHIP_INIT)) + if (!(usx2y->chip_status & USX2Y_STAT_CHIP_INIT)) return -EBUSY; - /* if userspace tries to mmap beyond end of our buffer, fail */ - if (size > PAGE_ALIGN(sizeof(struct snd_usX2Y_hwdep_pcm_shm))) { - snd_printd("%lu > %lu\n", size, (unsigned long)sizeof(struct snd_usX2Y_hwdep_pcm_shm)); + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > USX2Y_HWDEP_PCM_PAGES) { + dev_dbg(hw->card->dev, "%s: %lu > %lu\n", __func__, + size, (unsigned long)USX2Y_HWDEP_PCM_PAGES); return -EINVAL; } - if (!usX2Y->hwdep_pcm_shm) { + if (!usx2y->hwdep_pcm_shm) return -ENODEV; - } - area->vm_ops = &snd_usX2Y_hwdep_pcm_vm_ops; - area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + + area->vm_ops = &snd_usx2y_hwdep_pcm_vm_ops; + vm_flags_set(area, VM_DONTEXPAND | VM_DONTDUMP); area->vm_private_data = hw->private_data; return 0; } - -static void snd_usX2Y_hwdep_pcm_private_free(struct snd_hwdep *hwdep) +static void snd_usx2y_hwdep_pcm_private_free(struct snd_hwdep *hwdep) { - struct usX2Ydev *usX2Y = hwdep->private_data; - if (NULL != usX2Y->hwdep_pcm_shm) - snd_free_pages(usX2Y->hwdep_pcm_shm, sizeof(struct snd_usX2Y_hwdep_pcm_shm)); -} + struct usx2ydev *usx2y = hwdep->private_data; + if (usx2y->hwdep_pcm_shm) + free_pages_exact(usx2y->hwdep_pcm_shm, USX2Y_HWDEP_PCM_PAGES); +} -int usX2Y_hwdep_pcm_new(struct snd_card *card) +int usx2y_hwdep_pcm_new(struct snd_card *card) { int err; struct snd_hwdep *hw; struct snd_pcm *pcm; - struct usb_device *dev = usX2Y(card)->dev; - if (1 != nr_of_packs()) + struct usb_device *dev = usx2y(card)->dev; + + if (nr_of_packs() != 1) return 0; - if ((err = snd_hwdep_new(card, SND_USX2Y_USBPCM_ID, 1, &hw)) < 0) + err = snd_hwdep_new(card, SND_USX2Y_USBPCM_ID, 1, &hw); + if (err < 0) return err; hw->iface = SNDRV_HWDEP_IFACE_USX2Y_PCM; - hw->private_data = usX2Y(card); - hw->private_free = snd_usX2Y_hwdep_pcm_private_free; - hw->ops.open = snd_usX2Y_hwdep_pcm_open; - hw->ops.release = snd_usX2Y_hwdep_pcm_release; - hw->ops.mmap = snd_usX2Y_hwdep_pcm_mmap; + hw->private_data = usx2y(card); + hw->private_free = snd_usx2y_hwdep_pcm_private_free; + hw->ops.open = snd_usx2y_hwdep_pcm_open; + hw->ops.release = snd_usx2y_hwdep_pcm_release; + hw->ops.mmap = snd_usx2y_hwdep_pcm_mmap; hw->exclusive = 1; sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum); err = snd_pcm_new(card, NAME_ALLCAPS" hwdep Audio", 2, 1, 1, &pcm); - if (err < 0) { + if (err < 0) return err; - } - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usX2Y_usbpcm_ops); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usX2Y_usbpcm_ops); - pcm->private_data = usX2Y(card)->subs; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usx2y_usbpcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usx2y_usbpcm_ops); + + pcm->private_data = usx2y(card)->subs; pcm->info_flags = 0; sprintf(pcm->name, NAME_ALLCAPS" hwdep Audio"); - if (0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream, - SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data(GFP_KERNEL), - 64*1024, 128*1024)) || - 0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, - SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data(GFP_KERNEL), - 64*1024, 128*1024))) { - return err; - } - + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, + 64*1024, 128*1024); + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, + 64*1024, 128*1024); return 0; } #else -int usX2Y_hwdep_pcm_new(struct snd_card *card) +int usx2y_hwdep_pcm_new(struct snd_card *card) { return 0; } diff --git a/sound/usb/usx2y/usx2yhwdeppcm.h b/sound/usb/usx2y/usx2yhwdeppcm.h index eb5a46466f0e..731b1c5a3474 100644 --- a/sound/usb/usx2y/usx2yhwdeppcm.h +++ b/sound/usb/usx2y/usx2yhwdeppcm.h @@ -4,7 +4,7 @@ #define MAXSTRIDE 3 #define SSS (((MAXPACK*MAXBUFFERMS*MAXSTRIDE + 4096) / 4096) * 4096) -struct snd_usX2Y_hwdep_pcm_shm { +struct snd_usx2y_hwdep_pcm_shm { char playback[SSS]; char capture0x8[SSS]; char capture0xA[SSS]; @@ -20,4 +20,4 @@ struct snd_usX2Y_hwdep_pcm_shm { int capture_iso_start; }; -int usX2Y_hwdep_pcm_new(struct snd_card *card); +int usx2y_hwdep_pcm_new(struct snd_card *card); diff --git a/sound/usb/validate.c b/sound/usb/validate.c new file mode 100644 index 000000000000..4bb4893f6e74 --- /dev/null +++ b/sound/usb/validate.c @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Validation of USB-audio class descriptors +// + +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/usb/audio.h> +#include <linux/usb/audio-v2.h> +#include <linux/usb/audio-v3.h> +#include <linux/usb/midi.h> +#include "usbaudio.h" +#include "helper.h" + +struct usb_desc_validator { + unsigned char protocol; + unsigned char type; + bool (*func)(const void *p, const struct usb_desc_validator *v); + size_t size; +}; + +#define UAC_VERSION_ALL (unsigned char)(-1) + +/* UAC1 only */ +static bool validate_uac1_header(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac1_ac_header_descriptor *d = p; + + return d->bLength >= sizeof(*d) && + d->bLength >= sizeof(*d) + d->bInCollection; +} + +/* for mixer unit; covering all UACs */ +static bool validate_mixer_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac_mixer_unit_descriptor *d = p; + size_t len; + + if (d->bLength < sizeof(*d) || !d->bNrInPins) + return false; + len = sizeof(*d) + d->bNrInPins; + /* We can't determine the bitmap size only from this unit descriptor, + * so just check with the remaining length. + * The actual bitmap is checked at mixer unit parser. + */ + switch (v->protocol) { + case UAC_VERSION_1: + default: + len += 2 + 1; /* wChannelConfig, iChannelNames */ + /* bmControls[n*m] */ + len += 1; /* iMixer */ + break; + case UAC_VERSION_2: + len += 4 + 1; /* bmChannelConfig, iChannelNames */ + /* bmMixerControls[n*m] */ + len += 1 + 1; /* bmControls, iMixer */ + break; + case UAC_VERSION_3: + len += 2; /* wClusterDescrID */ + /* bmMixerControls[n*m] */ + break; + } + return d->bLength >= len; +} + +/* both for processing and extension units; covering all UACs */ +static bool validate_processing_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac_processing_unit_descriptor *d = p; + const unsigned char *hdr = p; + size_t len, m; + + if (d->bLength < sizeof(*d)) + return false; + len = sizeof(*d) + d->bNrInPins; + if (d->bLength < len) + return false; + switch (v->protocol) { + case UAC_VERSION_1: + default: + /* bNrChannels, wChannelConfig, iChannelNames */ + len += 1 + 2 + 1; + if (d->bLength < len + 1) /* bControlSize */ + return false; + m = hdr[len]; + len += 1 + m + 1; /* bControlSize, bmControls, iProcessing */ + break; + case UAC_VERSION_2: + /* bNrChannels, bmChannelConfig, iChannelNames */ + len += 1 + 4 + 1; + if (v->type == UAC2_PROCESSING_UNIT_V2) + len += 2; /* bmControls -- 2 bytes for PU */ + else + len += 1; /* bmControls -- 1 byte for EU */ + len += 1; /* iProcessing */ + break; + case UAC_VERSION_3: + /* wProcessingDescrStr, bmControls */ + len += 2 + 4; + break; + } + if (d->bLength < len) + return false; + + switch (v->protocol) { + case UAC_VERSION_1: + default: + if (v->type == UAC1_EXTENSION_UNIT) + return true; /* OK */ + switch (le16_to_cpu(d->wProcessType)) { + case UAC_PROCESS_UP_DOWNMIX: + case UAC_PROCESS_DOLBY_PROLOGIC: + if (d->bLength < len + 1) /* bNrModes */ + return false; + m = hdr[len]; + len += 1 + m * 2; /* bNrModes, waModes(n) */ + break; + default: + break; + } + break; + case UAC_VERSION_2: + if (v->type == UAC2_EXTENSION_UNIT_V2) + return true; /* OK */ + switch (le16_to_cpu(d->wProcessType)) { + case UAC2_PROCESS_UP_DOWNMIX: + case UAC2_PROCESS_DOLBY_PROLOCIC: /* SiC! */ + if (d->bLength < len + 1) /* bNrModes */ + return false; + m = hdr[len]; + len += 1 + m * 4; /* bNrModes, daModes(n) */ + break; + default: + break; + } + break; + case UAC_VERSION_3: + if (v->type == UAC3_EXTENSION_UNIT) { + len += 2; /* wClusterDescrID */ + break; + } + switch (le16_to_cpu(d->wProcessType)) { + case UAC3_PROCESS_UP_DOWNMIX: + if (d->bLength < len + 1) /* bNrModes */ + return false; + m = hdr[len]; + len += 1 + m * 2; /* bNrModes, waClusterDescrID(n) */ + break; + case UAC3_PROCESS_MULTI_FUNCTION: + len += 2 + 4; /* wClusterDescrID, bmAlgorighms */ + break; + default: + break; + } + break; + } + if (d->bLength < len) + return false; + + return true; +} + +/* both for selector and clock selector units; covering all UACs */ +static bool validate_selector_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac_selector_unit_descriptor *d = p; + size_t len; + + if (d->bLength < sizeof(*d)) + return false; + len = sizeof(*d) + d->bNrInPins; + switch (v->protocol) { + case UAC_VERSION_1: + default: + len += 1; /* iSelector */ + break; + case UAC_VERSION_2: + len += 1 + 1; /* bmControls, iSelector */ + break; + case UAC_VERSION_3: + len += 4 + 2; /* bmControls, wSelectorDescrStr */ + break; + } + return d->bLength >= len; +} + +static bool validate_uac1_feature_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac_feature_unit_descriptor *d = p; + + if (d->bLength < sizeof(*d) || !d->bControlSize) + return false; + /* at least bmaControls(0) for master channel + iFeature */ + return d->bLength >= sizeof(*d) + d->bControlSize + 1; +} + +static bool validate_uac2_feature_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac2_feature_unit_descriptor *d = p; + + if (d->bLength < sizeof(*d)) + return false; + /* at least bmaControls(0) for master channel + iFeature */ + return d->bLength >= sizeof(*d) + 4 + 1; +} + +static bool validate_uac3_feature_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac3_feature_unit_descriptor *d = p; + + if (d->bLength < sizeof(*d)) + return false; + /* at least bmaControls(0) for master channel + wFeatureDescrStr */ + return d->bLength >= sizeof(*d) + 4 + 2; +} + +static bool validate_uac3_power_domain_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac3_power_domain_descriptor *d = p; + + if (d->bLength < sizeof(*d)) + return false; + /* baEntities[] + wPDomainDescrStr */ + return d->bLength >= sizeof(*d) + d->bNrEntities + 2; +} + +static bool validate_midi_out_jack(const void *p, + const struct usb_desc_validator *v) +{ + const struct usb_midi_out_jack_descriptor *d = p; + + return d->bLength >= sizeof(*d) && + d->bLength >= sizeof(*d) + d->bNrInputPins * 2; +} + +#define FIXED(p, t, s) { .protocol = (p), .type = (t), .size = sizeof(s) } +#define FUNC(p, t, f) { .protocol = (p), .type = (t), .func = (f) } + +static const struct usb_desc_validator audio_validators[] = { + /* UAC1 */ + FUNC(UAC_VERSION_1, UAC_HEADER, validate_uac1_header), + FIXED(UAC_VERSION_1, UAC_INPUT_TERMINAL, + struct uac_input_terminal_descriptor), + FIXED(UAC_VERSION_1, UAC_OUTPUT_TERMINAL, + struct uac1_output_terminal_descriptor), + FUNC(UAC_VERSION_1, UAC_MIXER_UNIT, validate_mixer_unit), + FUNC(UAC_VERSION_1, UAC_SELECTOR_UNIT, validate_selector_unit), + FUNC(UAC_VERSION_1, UAC_FEATURE_UNIT, validate_uac1_feature_unit), + FUNC(UAC_VERSION_1, UAC1_PROCESSING_UNIT, validate_processing_unit), + FUNC(UAC_VERSION_1, UAC1_EXTENSION_UNIT, validate_processing_unit), + + /* UAC2 */ + FIXED(UAC_VERSION_2, UAC_HEADER, struct uac2_ac_header_descriptor), + FIXED(UAC_VERSION_2, UAC_INPUT_TERMINAL, + struct uac2_input_terminal_descriptor), + FIXED(UAC_VERSION_2, UAC_OUTPUT_TERMINAL, + struct uac2_output_terminal_descriptor), + FUNC(UAC_VERSION_2, UAC_MIXER_UNIT, validate_mixer_unit), + FUNC(UAC_VERSION_2, UAC_SELECTOR_UNIT, validate_selector_unit), + FUNC(UAC_VERSION_2, UAC_FEATURE_UNIT, validate_uac2_feature_unit), + /* just a stop-gap, it should be a proper function for the array + * once if the unit is really parsed/used + */ + FIXED(UAC_VERSION_2, UAC2_EFFECT_UNIT, + struct uac2_effect_unit_descriptor), + FUNC(UAC_VERSION_2, UAC2_PROCESSING_UNIT_V2, validate_processing_unit), + FUNC(UAC_VERSION_2, UAC2_EXTENSION_UNIT_V2, validate_processing_unit), + FIXED(UAC_VERSION_2, UAC2_CLOCK_SOURCE, + struct uac_clock_source_descriptor), + FUNC(UAC_VERSION_2, UAC2_CLOCK_SELECTOR, validate_selector_unit), + FIXED(UAC_VERSION_2, UAC2_CLOCK_MULTIPLIER, + struct uac_clock_multiplier_descriptor), + /* UAC_VERSION_2, UAC2_SAMPLE_RATE_CONVERTER: not implemented yet */ + + /* UAC3 */ + FIXED(UAC_VERSION_2, UAC_HEADER, struct uac3_ac_header_descriptor), + FIXED(UAC_VERSION_3, UAC_INPUT_TERMINAL, + struct uac3_input_terminal_descriptor), + FIXED(UAC_VERSION_3, UAC_OUTPUT_TERMINAL, + struct uac3_output_terminal_descriptor), + /* UAC_VERSION_3, UAC3_EXTENDED_TERMINAL: not implemented yet */ + FUNC(UAC_VERSION_3, UAC3_MIXER_UNIT, validate_mixer_unit), + FUNC(UAC_VERSION_3, UAC3_SELECTOR_UNIT, validate_selector_unit), + FUNC(UAC_VERSION_3, UAC3_FEATURE_UNIT, validate_uac3_feature_unit), + FIXED(UAC_VERSION_3, UAC3_EFFECT_UNIT, + struct uac2_effect_unit_descriptor), /* sharing the same struct */ + FUNC(UAC_VERSION_3, UAC3_PROCESSING_UNIT, validate_processing_unit), + FUNC(UAC_VERSION_3, UAC3_EXTENSION_UNIT, validate_processing_unit), + FIXED(UAC_VERSION_3, UAC3_CLOCK_SOURCE, + struct uac3_clock_source_descriptor), + FUNC(UAC_VERSION_3, UAC3_CLOCK_SELECTOR, validate_selector_unit), + FIXED(UAC_VERSION_3, UAC3_CLOCK_MULTIPLIER, + struct uac3_clock_multiplier_descriptor), + /* UAC_VERSION_3, UAC3_SAMPLE_RATE_CONVERTER: not implemented yet */ + /* UAC_VERSION_3, UAC3_CONNECTORS: not implemented yet */ + FUNC(UAC_VERSION_3, UAC3_POWER_DOMAIN, validate_uac3_power_domain_unit), + { } /* terminator */ +}; + +static const struct usb_desc_validator midi_validators[] = { + FIXED(UAC_VERSION_ALL, USB_MS_HEADER, + struct usb_ms_header_descriptor), + FIXED(UAC_VERSION_ALL, USB_MS_MIDI_IN_JACK, + struct usb_midi_in_jack_descriptor), + FUNC(UAC_VERSION_ALL, USB_MS_MIDI_OUT_JACK, + validate_midi_out_jack), + { } /* terminator */ +}; + + +/* Validate the given unit descriptor, return true if it's OK */ +static bool validate_desc(unsigned char *hdr, int protocol, + const struct usb_desc_validator *v) +{ + if (hdr[1] != USB_DT_CS_INTERFACE) + return true; /* don't care */ + + for (; v->type; v++) { + if (v->type == hdr[2] && + (v->protocol == UAC_VERSION_ALL || + v->protocol == protocol)) { + if (v->func) + return v->func(hdr, v); + /* check for the fixed size */ + return hdr[0] >= v->size; + } + } + + return true; /* not matching, skip validation */ +} + +bool snd_usb_validate_audio_desc(void *p, int protocol) +{ + unsigned char *c = p; + bool valid; + + valid = validate_desc(p, protocol, audio_validators); + if (!valid && snd_usb_skip_validation) { + print_hex_dump(KERN_ERR, "USB-audio: buggy audio desc: ", + DUMP_PREFIX_NONE, 16, 1, c, c[0], true); + valid = true; + } + return valid; +} + +bool snd_usb_validate_midi_desc(void *p) +{ + unsigned char *c = p; + bool valid; + + valid = validate_desc(p, UAC_VERSION_1, midi_validators); + if (!valid && snd_usb_skip_validation) { + print_hex_dump(KERN_ERR, "USB-audio: buggy midi desc: ", + DUMP_PREFIX_NONE, 16, 1, c, c[0], true); + valid = true; + } + return valid; +} |
