aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/sound/usb
diff options
context:
space:
mode:
Diffstat (limited to 'sound/usb')
-rw-r--r--sound/usb/6fire/Makefile2
-rw-r--r--sound/usb/6fire/chip.c11
-rw-r--r--sound/usb/6fire/comm.c2
-rw-r--r--sound/usb/6fire/firmware.c40
-rw-r--r--sound/usb/6fire/pcm.c2
-rw-r--r--sound/usb/Kconfig26
-rw-r--r--sound/usb/Makefile11
-rw-r--r--sound/usb/bcd2000/bcd2000.c9
-rw-r--r--sound/usb/caiaq/audio.c27
-rw-r--r--sound/usb/caiaq/audio.h1
-rw-r--r--sound/usb/caiaq/device.c41
-rw-r--r--sound/usb/caiaq/device.h2
-rw-r--r--sound/usb/caiaq/input.c13
-rw-r--r--sound/usb/caiaq/input.h1
-rw-r--r--sound/usb/caiaq/midi.c2
-rw-r--r--sound/usb/card.c452
-rw-r--r--sound/usb/card.h96
-rw-r--r--sound/usb/clock.c558
-rw-r--r--sound/usb/clock.h11
-rw-r--r--sound/usb/debug.h16
-rw-r--r--sound/usb/endpoint.c1471
-rw-r--r--sound/usb/endpoint.h69
-rw-r--r--sound/usb/fcp.c1133
-rw-r--r--sound/usb/fcp.h7
-rw-r--r--sound/usb/format.c203
-rw-r--r--sound/usb/helper.c62
-rw-r--r--sound/usb/helper.h14
-rw-r--r--sound/usb/hiface/Makefile2
-rw-r--r--sound/usb/hiface/chip.c23
-rw-r--r--sound/usb/hiface/pcm.c18
-rw-r--r--sound/usb/implicit.c494
-rw-r--r--sound/usb/implicit.h14
-rw-r--r--sound/usb/line6/capture.c2
-rw-r--r--sound/usb/line6/capture.h2
-rw-r--r--sound/usb/line6/driver.c98
-rw-r--r--sound/usb/line6/driver.h6
-rw-r--r--sound/usb/line6/midi.c8
-rw-r--r--sound/usb/line6/midi.h2
-rw-r--r--sound/usb/line6/midibuf.c27
-rw-r--r--sound/usb/line6/midibuf.h7
-rw-r--r--sound/usb/line6/pcm.c6
-rw-r--r--sound/usb/line6/pcm.h2
-rw-r--r--sound/usb/line6/playback.c2
-rw-r--r--sound/usb/line6/playback.h2
-rw-r--r--sound/usb/line6/pod.c18
-rw-r--r--sound/usb/line6/podhd.c29
-rw-r--r--sound/usb/line6/toneport.c13
-rw-r--r--sound/usb/line6/variax.c8
-rw-r--r--sound/usb/media.c6
-rw-r--r--sound/usb/midi.c246
-rw-r--r--sound/usb/midi.h7
-rw-r--r--sound/usb/midi2.c1240
-rw-r--r--sound/usb/midi2.h33
-rw-r--r--sound/usb/misc/Makefile2
-rw-r--r--sound/usb/misc/ua101.c21
-rw-r--r--sound/usb/mixer.c382
-rw-r--r--sound/usb/mixer.h9
-rw-r--r--sound/usb/mixer_maps.c191
-rw-r--r--sound/usb/mixer_quirks.c1729
-rw-r--r--sound/usb/mixer_quirks.h2
-rw-r--r--sound/usb/mixer_s1810c.c4
-rw-r--r--sound/usb/mixer_scarlett.c8
-rw-r--r--sound/usb/mixer_scarlett2.c9768
-rw-r--r--sound/usb/mixer_scarlett2.h7
-rw-r--r--sound/usb/mixer_scarlett_gen2.c2075
-rw-r--r--sound/usb/mixer_scarlett_gen2.h7
-rw-r--r--sound/usb/mixer_us16x08.c52
-rw-r--r--sound/usb/pcm.c1750
-rw-r--r--sound/usb/pcm.h23
-rw-r--r--sound/usb/power.c3
-rw-r--r--sound/usb/power.h11
-rw-r--r--sound/usb/proc.c35
-rw-r--r--sound/usb/qcom/Makefile4
-rw-r--r--sound/usb/qcom/mixer_usb_offload.c155
-rw-r--r--sound/usb/qcom/mixer_usb_offload.h11
-rw-r--r--sound/usb/qcom/qc_audio_offload.c2017
-rw-r--r--sound/usb/qcom/usb_audio_qmi_v01.c863
-rw-r--r--sound/usb/qcom/usb_audio_qmi_v01.h164
-rw-r--r--sound/usb/quirks-table.h3347
-rw-r--r--sound/usb/quirks.c1164
-rw-r--r--sound/usb/quirks.h13
-rw-r--r--sound/usb/stream.c85
-rw-r--r--sound/usb/usbaudio.h120
-rw-r--r--sound/usb/usx2y/Makefile4
-rw-r--r--sound/usb/usx2y/us122l.c145
-rw-r--r--sound/usb/usx2y/us122l.h4
-rw-r--r--sound/usb/usx2y/usX2Yhwdep.c144
-rw-r--r--sound/usb/usx2y/usX2Yhwdep.h2
-rw-r--r--sound/usb/usx2y/usb_stream.c167
-rw-r--r--sound/usb/usx2y/usb_stream.h24
-rw-r--r--sound/usb/usx2y/usbus428ctldefs.h104
-rw-r--r--sound/usb/usx2y/usbusx2y.c402
-rw-r--r--sound/usb/usx2y/usbusx2y.h97
-rw-r--r--sound/usb/usx2y/usbusx2yaudio.c722
-rw-r--r--sound/usb/usx2y/usx2yhwdeppcm.c660
-rw-r--r--sound/usb/usx2y/usx2yhwdeppcm.h4
96 files changed, 25300 insertions, 7798 deletions
diff --git a/sound/usb/6fire/Makefile b/sound/usb/6fire/Makefile
index 7d353bbf7493..587f25c64e56 100644
--- a/sound/usb/6fire/Makefile
+++ b/sound/usb/6fire/Makefile
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
-snd-usb-6fire-objs += chip.o comm.o midi.o control.o firmware.o pcm.o
+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 08c6e6a52eb9..d562a30b087f 100644
--- a/sound/usb/6fire/chip.c
+++ b/sound/usb/6fire/chip.c
@@ -26,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 */
@@ -62,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);
@@ -73,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);
}
}
@@ -137,6 +136,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)
@@ -163,7 +163,7 @@ static int usb6fire_chip_probe(struct usb_interface *intf,
return 0;
destroy_chip:
- usb6fire_chip_destroy(chip);
+ snd_card_free(card);
return ret;
}
@@ -182,7 +182,6 @@ static void usb6fire_chip_disconnect(struct usb_interface *intf)
chip->shutdown = true;
usb6fire_chip_abort(chip);
- usb6fire_chip_destroy(chip);
}
}
}
diff --git a/sound/usb/6fire/comm.c b/sound/usb/6fire/comm.c
index 43a2a62d66f7..49629d4bb327 100644
--- a/sound/usb/6fire/comm.c
+++ b/sound/usb/6fire/comm.c
@@ -95,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/firmware.c b/sound/usb/6fire/firmware.c
index 69137c14d0dc..c51abc54d2f8 100644
--- a/sound/usb/6fire/firmware.c
+++ b/sound/usb/6fire/firmware.c
@@ -158,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,
@@ -190,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)
@@ -230,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,
@@ -242,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,
@@ -257,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);
@@ -267,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);
@@ -302,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,
@@ -327,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;
@@ -363,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/pcm.c b/sound/usb/6fire/pcm.c
index 7168f1c6a37a..32c39d8bd2e5 100644
--- a/sound/usb/6fire/pcm.c
+++ b/sound/usb/6fire/pcm.c
@@ -175,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)
diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
index 059242f15d75..41c47301bc19 100644
--- a/sound/usb/Kconfig
+++ b/sound/usb/Kconfig
@@ -15,6 +15,7 @@ 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
@@ -24,6 +25,16 @@ 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
@@ -165,6 +176,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 56031026b113..e62794a87e73 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -3,15 +3,17 @@
# 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_scarlett_gen2.o \
+ mixer_scarlett2.o \
mixer_us16x08.o \
mixer_s1810c.o \
pcm.o \
@@ -21,9 +23,10 @@ snd-usb-audio-objs := card.o \
stream.o \
validate.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-objs := midi.o
+snd-usbmidi-lib-y := midi.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usbmidi-lib.o
@@ -32,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/bcd2000.c b/sound/usb/bcd2000/bcd2000.c
index 010976d9ceb2..392b4d8e9e76 100644
--- a/sound/usb/bcd2000/bcd2000.c
+++ b/sound/usb/bcd2000/bcd2000.c
@@ -300,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;
@@ -348,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);
@@ -394,8 +395,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",
diff --git a/sound/usb/caiaq/audio.c b/sound/usb/caiaq/audio.c
index 3b6bb2cbe886..05f964347ed6 100644
--- a/sound/usb/caiaq/audio.c
+++ b/sound/usb/caiaq/audio.c
@@ -174,17 +174,9 @@ static int snd_usb_caiaq_pcm_hw_free(struct snd_pcm_substream *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 const 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;
@@ -233,10 +225,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;
@@ -804,7 +793,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));
@@ -869,14 +858,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/device.c b/sound/usb/caiaq/device.c
index 2af3b7eb0a88..dfd820483849 100644
--- a/sound/usb/caiaq/device.c
+++ b/sound/usb/caiaq/device.c
@@ -26,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 */
@@ -390,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)
@@ -477,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
@@ -499,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:
@@ -548,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 1e2cf2f08eec..a9130891bb69 100644
--- a/sound/usb/caiaq/input.c
+++ b/sound/usb/caiaq/input.c
@@ -804,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;
}
@@ -828,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 512fbb3ee604..c656d0162432 100644
--- a/sound/usb/caiaq/midi.c
+++ b/sound/usb/caiaq/midi.c
@@ -125,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 696e788c5d31..9fb8726a6c93 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -44,12 +44,12 @@
#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"
@@ -59,8 +59,6 @@
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 */
@@ -71,8 +69,11 @@ 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 unsigned int quirk_flags[SNDRV_CARDS];
bool snd_usb_use_vmalloc = true;
bool snd_usb_skip_validation;
@@ -94,10 +95,16 @@ 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_array(quirk_flags, uint, NULL, 0444);
+MODULE_PARM_DESC(quirk_flags, "Driver quirk bit flags.");
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);
@@ -111,6 +118,95 @@ MODULE_PARM_DESC(skip_validation, "Skip unit descriptor validation (default: no)
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)(&register_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)(&register_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)(&register_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)(&register_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
@@ -125,7 +221,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;
}
@@ -173,18 +268,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 &&
@@ -202,9 +295,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;
@@ -333,6 +429,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
@@ -342,10 +584,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))
@@ -357,10 +598,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;
}
@@ -382,27 +629,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));
@@ -456,7 +711,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;
@@ -479,7 +733,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);
@@ -487,9 +743,17 @@ 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);
+ if (quirk_flags[idx])
+ chip->quirk_flags = quirk_flags[idx];
+ else
+ snd_usb_init_quirk_flags(chip);
+
card->private_free = snd_usb_audio_free;
strcpy(card->driver, "USB-Audio");
@@ -528,7 +792,7 @@ static bool get_alias_id(struct usb_device *dev, unsigned int *id)
return false;
}
-static bool check_delayed_register_option(struct snd_usb_audio *chip, int iface)
+static int check_delayed_register_option(struct snd_usb_audio *chip)
{
int i;
unsigned int id, inum;
@@ -537,14 +801,31 @@ static bool check_delayed_register_option(struct snd_usb_audio *chip, int iface)
if (delayed_register[i] &&
sscanf(delayed_register[i], "%x:%x", &id, &inum) == 2 &&
id == chip->usb_id)
- return inum != iface;
+ return inum;
}
- return false;
+ 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)
@@ -563,6 +844,18 @@ 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)
+{
+ if (check_delayed_register_option(chip) == ifnum ||
+ chip->last_iface == ifnum ||
+ usb_interface_claimed(usb_ifnum_to_if(chip->dev, chip->last_iface)))
+ return snd_card_register(chip->card);
+ return 0;
+}
+
/*
* probe the active usb device
*
@@ -593,6 +886,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)
@@ -649,6 +944,7 @@ static int usb_audio_probe(struct usb_interface *intf,
err = -ENODEV;
goto __error;
}
+ find_last_interface(chip);
}
if (chip->num_interfaces >= MAX_CARD_INTERFACES) {
@@ -659,6 +955,12 @@ static int usb_audio_probe(struct usb_interface *intf,
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
@@ -667,7 +969,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 */
@@ -681,7 +982,7 @@ 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;
}
@@ -693,30 +994,36 @@ static int usb_audio_probe(struct usb_interface *intf,
chip->need_delayed_register = false; /* clear again */
}
- /* we are allowed to call snd_card_register() many times, but first
- * check to see if a device needs to skip it or do anything special
- */
- if (!snd_usb_registration_quirk(chip, ifnum) &&
- !check_delayed_register_option(chip, ifnum)) {
- err = snd_card_register(chip->card);
- if (err < 0)
- goto __error;
- }
+ err = try_to_register_card(chip, ifnum);
+ if (err < 0)
+ goto __error_no_register;
- if (quirk && quirk->shares_media_device) {
+ 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);
+
+ if (platform_ops && platform_ops->connect_cb)
+ platform_ops->connect_cb(chip);
mutex_unlock(&register_mutex);
+
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.
@@ -739,12 +1046,15 @@ static void usb_audio_disconnect(struct usb_interface *intf)
struct snd_card *card;
struct list_head *p;
- if (chip == (void *)-1L)
+ if (chip == USB_AUDIO_IFACE_UNUSED)
return;
card = chip->card;
mutex_lock(&register_mutex);
+ if (platform_ops && platform_ops->disconnect_cb)
+ platform_ops->disconnect_cb(chip);
+
if (atomic_inc_return(&chip->shutdown) == 1) {
struct snd_usb_stream *as;
struct snd_usb_endpoint *ep;
@@ -768,6 +1078,7 @@ 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
@@ -782,6 +1093,9 @@ static void usb_audio_disconnect(struct usb_interface *intf)
}
}
+ 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;
@@ -812,6 +1126,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)
@@ -820,8 +1135,7 @@ 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)
{
@@ -844,6 +1158,7 @@ int snd_usb_autoresume(struct snd_usb_audio *chip)
}
return 0;
}
+EXPORT_SYMBOL_GPL(snd_usb_autoresume);
void snd_usb_autosuspend(struct snd_usb_audio *chip)
{
@@ -857,27 +1172,29 @@ void snd_usb_autosuspend(struct snd_usb_audio *chip)
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;
if (!chip->num_suspended_intf++) {
- list_for_each_entry(as, &chip->pcm_list, list) {
+ 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) {
@@ -885,10 +1202,13 @@ static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message)
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;
@@ -896,7 +1216,7 @@ static int __usb_audio_resume(struct usb_interface *intf, bool reset_resume)
struct list_head *p;
int err = 0;
- if (chip == (void *)-1L)
+ if (chip == USB_AUDIO_IFACE_UNUSED)
return 0;
atomic_inc(&chip->active); /* avoid autopm */
@@ -914,7 +1234,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;
}
@@ -923,6 +1243,11 @@ static int __usb_audio_resume(struct usb_interface *intf, bool reset_resume)
snd_usbmidi_resume(p);
}
+ 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);
@@ -935,21 +1260,6 @@ err_out:
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),
@@ -969,7 +1279,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 5351d7183b1b..94404c24d240 100644
--- a/sound/usb/card.h
+++ b/sound/usb/card.h
@@ -15,13 +15,20 @@ struct audioformat {
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 */
@@ -37,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;
@@ -47,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];
@@ -74,12 +93,14 @@ 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 */
@@ -105,10 +126,23 @@ 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 is_implicit_feedback; /* This endpoint is used as implicit feedback */
+ 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;
@@ -121,28 +155,24 @@ struct snd_usb_substream {
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 */
@@ -150,18 +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? */
- bool need_setup_fmt; /* (re)configure fmt after resume? */
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;
@@ -170,6 +196,7 @@ 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;
};
@@ -182,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 f3ca59005d91..842ba5b801ea 100644
--- a/sound/usb/clock.c
+++ b/sound/usb/clock.c
@@ -21,93 +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->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->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->bClockID == id;
-}
+ union uac23_clock_selector_desc *cs = p;
-static bool validate_clock_selector_v3(void *p, int id)
-{
- struct uac3_clock_selector_descriptor *cs = p;
- return cs->bClockID == id;
+ 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->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->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)
@@ -116,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;
@@ -137,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;
@@ -152,21 +177,22 @@ static int uac_clock_selector_set_val(struct snd_usb_audio *chip, int selector_i
}
static bool uac_clock_source_is_valid_quirk(struct snd_usb_audio *chip,
- struct audioformat *fmt,
+ 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;
- if (fmt->protocol == UAC_VERSION_2) {
- struct uac_clock_source_descriptor *cs_desc =
- snd_usb_find_clock_source(chip->ctrl_intf, source_id);
-
- if (!cs_desc)
- return false;
+ 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
@@ -175,8 +201,8 @@ static bool uac_clock_source_is_valid_quirk(struct snd_usb_audio *chip,
* reports that clock is invalid.
*/
if (fmt->nr_rates == 1 &&
- (fmt->clock & 0xff) == cs_desc->bClockID &&
- (cs_desc->bmAttributes & 0x3) !=
+ (fmt->clock & 0xff) == cs_desc->v2.bClockID &&
+ (cs_desc->v2.bmAttributes & 0x3) !=
UAC_CLOCK_SOURCE_TYPE_EXT)
return true;
}
@@ -197,7 +223,7 @@ static bool uac_clock_source_is_valid_quirk(struct snd_usb_audio *chip,
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,
@@ -215,29 +241,25 @@ static bool uac_clock_source_is_valid_quirk(struct snd_usb_audio *chip,
}
static bool uac_clock_source_is_valid(struct snd_usb_audio *chip,
- struct audioformat *fmt,
+ 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 (fmt->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 false;
- 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 false;
- 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,
@@ -247,7 +269,7 @@ static bool uac_clock_source_is_valid(struct snd_usb_audio *chip,
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) {
@@ -264,12 +286,17 @@ static bool uac_clock_source_is_valid(struct snd_usb_audio *chip,
}
static int __uac_clock_find_source(struct snd_usb_audio *chip,
- struct audioformat *fmt, int entity_id,
+ 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;
@@ -280,10 +307,10 @@ static int __uac_clock_find_source(struct snd_usb_audio *chip,
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;
+ entity_id = GET_VAL(source, proto, bClockID);
if (validate && !uac_clock_source_is_valid(chip, fmt,
entity_id)) {
usb_audio_err(chip,
@@ -294,47 +321,97 @@ static int __uac_clock_find_source(struct snd_usb_audio *chip,
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;
+ 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;
+ }
- /* the entity ID we are looking for is a selector.
+ /* 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 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 = __uac_clock_find_source(chip, fmt,
- selector->baCSourceID[ret - 1],
+ 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 = __uac_clock_find_source(chip, fmt,
- selector->baCSourceID[i - 1],
+ 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;
@@ -348,106 +425,11 @@ static int __uac_clock_find_source(struct snd_usb_audio *chip,
}
/* FIXME: multipliers only act as pass-thru element for now */
- multiplier = snd_usb_find_clock_multiplier(chip->ctrl_intf, entity_id);
+ multiplier = snd_usb_find_clock_multiplier(chip, entity_id, fmt);
if (multiplier)
return __uac_clock_find_source(chip, fmt,
- multiplier->bCSourceID,
- visited, validate);
-
- return -EINVAL;
-}
-
-static int __uac3_clock_find_source(struct snd_usb_audio *chip,
- struct audioformat *fmt, 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, fmt,
- entity_id)) {
- usb_audio_err(chip,
- "clock source %d is not valid, cannot use\n",
- entity_id);
- return -ENXIO;
- }
- return entity_id;
- }
-
- selector = snd_usb_find_clock_selector_v3(chip->ctrl_intf, entity_id);
- 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 = __uac3_clock_find_source(chip, fmt,
- selector->baCSourceID[ret - 1],
+ GET_VAL(multiplier, proto, bCSourceID),
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 = __uac3_clock_find_source(chip, fmt,
- 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_v3(chip->ctrl_intf,
- entity_id);
- if (multiplier)
- return __uac3_clock_find_source(chip, fmt,
- multiplier->bCSourceID,
- visited, validate);
return -EINVAL;
}
@@ -464,36 +446,28 @@ static int __uac3_clock_find_source(struct snd_usb_audio *chip,
* Returns the clock source UnitID (>=0) on success, or an error.
*/
int snd_usb_clock_find_source(struct snd_usb_audio *chip,
- struct audioformat *fmt, bool validate)
+ const struct audioformat *fmt, bool validate)
{
DECLARE_BITMAP(visited, 256);
memset(visited, 0, sizeof(visited));
switch (fmt->protocol) {
case UAC_VERSION_2:
- return __uac_clock_find_source(chip, fmt, fmt->clock, visited,
- validate);
case UAC_VERSION_3:
- return __uac3_clock_find_source(chip, fmt, fmt->clock, 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;
@@ -503,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)
@@ -521,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;
@@ -545,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",
@@ -560,16 +542,59 @@ 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
@@ -584,67 +609,46 @@ static int set_sample_rate_v2v3(struct snd_usb_audio *chip, int iface,
* rate.
*/
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:
@@ -654,14 +658,16 @@ validation:
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) {
@@ -672,7 +678,7 @@ int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface,
}
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 68df0fbe09d0..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,
- struct audioformat *fmt, bool validate);
+ 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 5fbc8dd2f409..7b01e7b4e335 100644
--- a/sound/usb/endpoint.c
+++ b/sound/usb/endpoint.c
@@ -18,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
@@ -73,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)
@@ -106,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
*
@@ -116,81 +150,132 @@ 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 slave_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, slave_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_slave_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;
spin_lock_irqsave(&ep->lock, flags);
- ep->phase = (ep->phase & 0xffff)
- + (ep->freqm << ep->datainterval);
- ret = min(ep->phase >> 16, ep->maxframesize);
+ phase = (ep->phase & 0xffff) + (ep->freqm << ep->datainterval);
+ ret = min(phase >> 16, ep->maxframesize);
+ if (avail && ret >= avail)
+ ret = -EAGAIN;
+ else
+ ep->phase = phase;
spin_unlock_irqrestore(&ep->lock, flags);
return ret;
}
/*
- * For adaptive and synchronous 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 adaptive and synchronous endpoints
*/
-int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep)
+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;
- ep->sample_accum += ep->sample_rem;
- if (ep->sample_accum >= ep->pps) {
- ep->sample_accum -= ep->pps;
+ 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,
@@ -203,7 +288,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) {
@@ -211,13 +296,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 if (ep->sync_master)
- counts = snd_usb_endpoint_slave_next_packet_size(ep);
- 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;
@@ -234,27 +313,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:
@@ -283,13 +364,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;
@@ -314,11 +396,60 @@ 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)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ep->lock, flags);
+ list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs);
+ spin_unlock_irqrestore(&ep->lock, flags);
}
/*
* 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.
*
@@ -326,13 +457,15 @@ 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);
+
+ while (ep_state_running(ep)) {
unsigned long flags;
struct snd_usb_packet_info *packet;
@@ -340,38 +473,63 @@ static void queue_pending_output_urbs(struct snd_usb_endpoint *ep)
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;
-
+ if ((!implicit_fb || ep->next_packet_queued > 0) &&
+ !list_empty(&ep->ready_playback_urbs)) {
/* take URB out of FIFO */
- if (!list_empty(&ep->ready_playback_urbs)) {
- ctx = list_first_entry(&ep->ready_playback_urbs,
+ ctx = list_first_entry(&ep->ready_playback_urbs,
struct snd_urb_ctx, ready_list);
- list_del_init(&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;
+ 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;
}
/*
@@ -381,8 +539,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 */
@@ -394,143 +550,455 @@ 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(!test_bit(EP_FLAG_RUNNING, &ep->flags)))
+ 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.
*
- * New endpoints will be added to chip->ep_list and must be freed by
- * calling snd_usb_endpoint_free().
+ * Returns zero on success or a negative error code.
+ *
+ * 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;
+}
- ep->syncmaxsize = le16_to_cpu(get_endpoint(alts, 1)->wMaxPacketSize);
- }
+/* 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;
- list_add_tail(&ep->list, &chip->ep_list);
+ 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;
+}
- ep->is_implicit_feedback = 0;
+/*
+ * 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)
+{
+ bool ret;
-__exit_unlock:
+ mutex_lock(&chip->mutex);
+ ret = endpoint_compatible(ep, fp, params);
mutex_unlock(&chip->mutex);
+ return ret;
+}
+/*
+ * 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;
+
+ mutex_lock(&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);
+ goto unlock;
+ }
+
+ 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) {
+ ep = NULL;
+ goto unlock;
+ }
+
+ if (fp->protocol != UAC_VERSION_1) {
+ ep->clock_ref = clock_ref_find(chip, fp->clock);
+ if (!ep->clock_ref) {
+ ep = NULL;
+ goto unlock;
+ }
+ 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)) {
+ ep = NULL;
+ goto unlock;
+ }
+
+ if (!endpoint_compatible(ep, fp, params)) {
+ usb_audio_err(chip, "Incompatible EP setup for 0x%x\n",
+ ep_num);
+ ep = NULL;
+ goto unlock;
+ }
+
+ usb_audio_dbg(chip, "Reopened EP 0x%x (count %d)\n",
+ ep_num, ep->opened);
+ }
+
+ if (!ep->iface_ref->opened++)
+ ep->iface_ref->need_setup = true;
+
+ ep->opened++;
+
+ unlock:
+ mutex_unlock(&chip->mutex);
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)
+{
+ mutex_lock(&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);
+ }
+ mutex_unlock(&chip->mutex);
+}
+
+/* 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)
@@ -538,8 +1006,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;
@@ -550,12 +1021,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;
}
@@ -565,25 +1035,34 @@ 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;
+ unsigned long flags;
- 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;
+ spin_lock_irqsave(&ep->lock, flags);
INIT_LIST_HEAD(&ep->ready_playback_urbs);
- ep->next_packet_read_pos = 0;
- ep->next_packet_write_pos = 0;
+ ep->next_packet_head = 0;
+ ep->next_packet_queued = 0;
+ spin_unlock_irqrestore(&ep->lock, flags);
+
+ if (keep_pending)
+ return 0;
for (i = 0; i < ep->nurbs; i++) {
if (test_bit(i, &ep->active_mask)) {
@@ -600,16 +1079,18 @@ 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++)
@@ -620,212 +1101,39 @@ static void release_urbs(struct snd_usb_endpoint *ep, int force)
ep->syncbuf = NULL;
ep->nurbs = 0;
-}
-
-/*
- * Check data endpoint for format differences
- */
-static bool check_ep_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)
-{
- 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;
- int frame_bits = snd_pcm_format_physical_width(pcm_format) * channels;
- int tx_length_quirk = (ep->chip->tx_length_quirk &&
- usb_pipeout(ep->pipe));
- bool ret = 1;
-
- if (pcm_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;
- }
-
- ret = ret && (ep->datainterval == fmt->datainterval);
- ret = ret && (ep->stride == frame_bits >> 3);
-
- switch (pcm_format) {
- case SNDRV_PCM_FORMAT_U8:
- ret = ret && (ep->silence_value == 0x80);
- break;
- case SNDRV_PCM_FORMAT_DSD_U8:
- case SNDRV_PCM_FORMAT_DSD_U16_LE:
- case SNDRV_PCM_FORMAT_DSD_U32_LE:
- case SNDRV_PCM_FORMAT_DSD_U16_BE:
- case SNDRV_PCM_FORMAT_DSD_U32_BE:
- ret = ret && (ep->silence_value == 0x69);
- break;
- default:
- ret = ret && (ep->silence_value == 0);
- }
-
- /* assume max. frequency is 50% higher than nominal */
- ret = ret && (ep->freqmax == ep->freqn + (ep->freqn >> 1));
- /* Round up freqmax to nearest integer in order to calculate maximum
- * packet size, which must represent a whole number of frames.
- * This is accomplished by adding 0x0.ffff before converting the
- * Q16.16 format into integer.
- * In order to accurately calculate the maximum packet size when
- * the data interval is more than 1 (i.e. ep->datainterval > 0),
- * multiply by the data interval prior to rounding. For instance,
- * a freqmax of 41 kHz will result in a max packet size of 6 (5.125)
- * frames with a data interval of 1, but 11 (10.25) frames with a
- * data interval of 2.
- * (ep->freqmax << ep->datainterval overflows at 8.192 MHz for the
- * maximum datainterval value of 3, at USB full speed, higher for
- * USB high speed, noting that ep->freqmax is in units of
- * frames per packet in Q16.16 format.)
- */
- maxsize = (((ep->freqmax << ep->datainterval) + 0xffff) >> 16) *
- (frame_bits >> 3);
- if (tx_length_quirk)
- maxsize += sizeof(__le32); /* Space for length descriptor */
- /* but wMaxPacketSize might reduce this */
- if (ep->maxpacksize && ep->maxpacksize < maxsize) {
- /* whatever fits into a max. size packet */
- unsigned int data_maxsize = maxsize = ep->maxpacksize;
-
- if (tx_length_quirk)
- /* Need to remove the length descriptor to calc freq */
- data_maxsize -= sizeof(__le32);
- ret = ret && (ep->freqmax == (data_maxsize / (frame_bits >> 3))
- << (16 - ep->datainterval));
- }
-
- if (ep->fill_max)
- ret = ret && (ep->curpacksize == ep->maxpacksize);
- else
- ret = ret && (ep->curpacksize == maxsize);
-
- if (snd_usb_get_speed(ep->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))
- max_packs_per_urb = min(max_packs_per_urb,
- 1U << sync_ep->syncinterval);
- max_packs_per_urb = max(1u, max_packs_per_urb >> ep->datainterval);
-
- /*
- * Capture endpoints need to use small URBs because there's no way
- * to tell in advance where the next period will end, and we don't
- * want the next URB to complete much after the period ends.
- *
- * 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)) {
-
- 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 >>= 1;
- ret = ret && (ep->nurbs == MAX_URBS);
-
- /*
- * Playback endpoints without implicit sync are adjusted so that
- * a period fits as evenly as possible in the smallest number of
- * URBs. The total number of URBs is adjusted to the size of the
- * ALSA buffer, subject to the MAX_URBS and MAX_QUEUE limits.
- */
- } else {
- /* determine how small a packet can be */
- minsize = (ep->freqn >> (16 - ep->datainterval)) *
- (frame_bits >> 3);
- /* with sync from device, assume it can be 12% lower */
- if (sync_ep)
- 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);
-
- /* how many URBs will contain a period? */
- urbs_per_period = DIV_ROUND_UP(max_packs_per_period,
- max_packs_per_urb);
- /* how many packets are needed in each URB? */
- urb_packs = DIV_ROUND_UP(max_packs_per_period, urbs_per_period);
-
- /* limit the number of frames in a single URB */
- ret = ret && (ep->max_urb_frames ==
- DIV_ROUND_UP(frames_per_period, 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);
- ret = ret && (ep->nurbs == min(max_urbs,
- urbs_per_period * periods_per_buffer));
- }
-
- ret = ret && (ep->datainterval == fmt->datainterval);
- ret = ret && (ep->maxpacksize == fmt->maxpacksize);
- ret = ret &&
- (ep->fill_max == !!(fmt->attributes & UAC_EP_CS_ATTR_FILL_MAX));
-
- return ret;
+ 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;
@@ -878,16 +1186,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);
/*
@@ -898,26 +1206,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;
@@ -932,12 +1225,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,
@@ -946,13 +1239,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 */
@@ -970,7 +1263,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;
@@ -985,7 +1278,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;
}
@@ -994,13 +1287,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;
@@ -1020,82 +1318,74 @@ 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)
{
- int err;
+ const struct audioformat *fmt = ep->cur_audiofmt;
+ int err = 0;
- if (ep->use_count != 0) {
- bool check = ep->is_implicit_feedback &&
- check_ep_params(ep, pcm_format,
- channels, period_bytes,
- period_frames, buffer_periods,
- fmt, sync_ep);
-
- if (!check) {
- usb_audio_warn(ep->chip,
- "Unable to change format on ep #%x: already in use\n",
- ep->ep_num);
- return -EBUSY;
- }
-
- usb_audio_dbg(ep->chip,
- "Ep #%x already in use as implicit feedback but format not changed\n",
- ep->ep_num);
- return 0;
- }
+ mutex_lock(&chip->mutex);
+ if (!ep->need_setup)
+ goto unlock;
/* release old buffers, if any */
- release_urbs(ep, 0);
+ err = release_urbs(ep, false);
+ if (err < 0)
+ goto unlock;
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);
+ 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(rate);
+ ep->freqn = get_usb_high_speed_rate(ep->cur_rate);
ep->pps = 8000 >> ep->datainterval;
}
- ep->sample_rem = rate % ep->pps;
- ep->packsize[0] = rate / ep->pps;
- ep->packsize[1] = (rate + (ep->pps - 1)) / ep->pps;
+ 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;
/* calculate the frequency in 16.16 format */
ep->freqm = ep->freqn;
@@ -1105,9 +1395,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);
@@ -1116,19 +1404,160 @@ 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)
+ goto unlock;
+
+ /* 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;
+ }
+
+ unlock:
+ mutex_unlock(&chip->mutex);
+ 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;
+
+ mutex_lock(&chip->mutex);
+ if (WARN_ON(!ep->iface_ref))
+ goto unlock;
+ if (!ep->need_prepare)
+ goto unlock;
+
+ /* 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)
+ goto unlock;
+ }
+ 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)
+ goto unlock;
+ }
+
+ err = snd_usb_init_pitch(chip, ep->cur_audiofmt);
+ if (err < 0)
+ goto unlock;
+
+ err = init_sample_rate(chip, ep);
+ if (err < 0)
+ goto unlock;
+
+ err = snd_usb_select_mode_quirk(chip, ep->cur_audiofmt);
+ if (err < 0)
+ goto unlock;
+
+ /* for UAC2/3, enable the interface altset here at last */
+ if (!iface_first) {
+ err = endpoint_set_interface(chip, ep, true);
+ if (err < 0)
+ goto unlock;
+ }
+
+ ep->iface_ref->need_setup = false;
+
+ done:
+ ep->need_prepare = false;
+ err = 1;
+
+unlock:
+ mutex_unlock(&chip->mutex);
return err;
}
+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;
+ mutex_lock(&chip->mutex);
+ list_for_each_entry(ref, &chip->clock_ref_list, list) {
+ if (ref->clock == clock) {
+ rate = ref->rate;
+ break;
+ }
+ }
+ mutex_unlock(&chip->mutex);
+ 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.
*
@@ -1138,18 +1567,26 @@ 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;
@@ -1165,15 +1602,14 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep)
* from that context.
*/
- set_bit(EP_FLAG_RUNNING, &ep->flags);
+ if (!ep_state_update(ep, EP_STATE_STOPPED, EP_STATE_RUNNING))
+ goto __error;
- 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);
- }
-
- 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++) {
@@ -1182,28 +1618,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;
}
@@ -1211,8 +1674,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.
*
@@ -1221,40 +1685,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)
+ if (snd_BUG_ON(!atomic_read(&ep->running)))
return;
- if (ep->use_count != 0)
- 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;
+ }
+ }
}
/**
@@ -1262,27 +1718,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
@@ -1292,9 +1758,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;
@@ -1309,7 +1775,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;
@@ -1331,7 +1797,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
@@ -1352,10 +1827,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;
}
diff --git a/sound/usb/endpoint.h b/sound/usb/endpoint.h
index d23fa0a8c11b..ba70f52f6860 100644
--- a/sound/usb/endpoint.h
+++ b/sound/usb/endpoint.h
@@ -5,34 +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_slave_next_packet_size(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..98f9964311a7
--- /dev/null
+++ b/sound/usb/fcp.c
@@ -0,0 +1,1133 @@
+// 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 = kmalloc_array(map.map_size, sizeof(s16), GFP_KERNEL);
+ if (!tmp_map)
+ return -ENOMEM;
+
+ if (copy_from_user(tmp_map, arg->map, map.map_size * sizeof(s16)))
+ return -EFAULT;
+
+ 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;
+ unsigned long flags;
+ 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;
+
+ spin_lock_irqsave(&private->notify.lock, flags);
+ event = private->notify.event;
+ private->notify.event = 0;
+ spin_unlock_irqrestore(&private->notify.lock, flags);
+
+ 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) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&private->notify.lock, flags);
+ private->notify.event |= data;
+ spin_unlock_irqrestore(&private->notify.lock, flags);
+
+ 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 1b28d01d1f4c..8cd54f7bf33a 100644
--- a/sound/usb/format.c
+++ b/sound/usb/format.c
@@ -16,7 +16,6 @@
#include "card.h"
#include "quirks.h"
#include "helper.h"
-#include "debug.h"
#include "clock.h"
#include "format.h"
@@ -40,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;
@@ -55,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;
@@ -66,8 +73,11 @@ 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;
@@ -75,15 +85,16 @@ static u64 parse_audio_format_i_type(struct snd_usb_audio *chip,
}
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))) &&
@@ -123,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))
@@ -131,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",
@@ -165,6 +173,23 @@ static int set_fixed_rate(struct audioformat *fp, int rate, int rate_bits)
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
* on the audioformat table (audio class v1).
@@ -199,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;
@@ -218,18 +244,15 @@ 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;
@@ -238,7 +261,8 @@ static int parse_audio_format_rates_v1(struct snd_usb_audio *chip, struct audiof
}
/* Jabra Evolve 65 headset */
- if (chip->usb_id == USB_ID(0x0b0e, 0x030b)) {
+ 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);
@@ -335,8 +359,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]);
@@ -361,7 +383,11 @@ 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(0x0194f, 0x010c) &&
+ 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;
@@ -372,12 +398,6 @@ static int parse_uac2_sample_rate_range(struct snd_usb_audio *chip,
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");
@@ -406,9 +426,12 @@ static int line6_parse_audio_format_rates_quirk(struct snd_usb_audio *chip,
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);
}
@@ -416,6 +439,112 @@ static int line6_parse_audio_format_rates_quirk(struct snd_usb_audio *chip,
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).
@@ -427,7 +556,9 @@ static int parse_audio_format_rates_v2v3(struct snd_usb_audio *chip,
unsigned char tmp[2], *data;
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",
@@ -439,7 +570,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),
tmp, sizeof(tmp));
if (ret < 0) {
@@ -474,7 +605,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) {
@@ -508,6 +639,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 4c12cc5b53fd..497d2b27fb59 100644
--- a/sound/usb/helper.c
+++ b/sound/usb/helper.c
@@ -62,20 +62,7 @@ void *snd_usb_find_csint_desc(void *buffer, int buflen, void *after, u8 dsubtype
}
return NULL;
}
-
-/* check the validity of pipe and EP types */
-int snd_usb_pipe_sanity_check(struct usb_device *dev, unsigned int pipe)
-{
- static const int pipetypes[4] = {
- PIPE_CONTROL, PIPE_ISOCHRONOUS, PIPE_BULK, PIPE_INTERRUPT
- };
- struct usb_host_endpoint *ep;
-
- ep = usb_pipe_endpoint(dev, pipe);
- if (!ep || usb_pipetype(pipe) != pipetypes[usb_endpoint_type(&ep->desc)])
- return -EINVAL;
- return 0;
-}
+EXPORT_SYMBOL_GPL(snd_usb_find_csint_desc);
/*
* Wrapper for usb_control_msg().
@@ -89,7 +76,7 @@ int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
void *buf = NULL;
int timeout;
- if (snd_usb_pipe_sanity_check(dev, pipe))
+ if (usb_pipe_type_check(dev, pipe))
return -EINVAL;
if (size > 0) {
@@ -122,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 &&
@@ -135,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 5e8a18b4e7b9..0372e050b3dc 100644
--- a/sound/usb/helper.h
+++ b/sound/usb/helper.h
@@ -7,7 +7,6 @@ unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size);
void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype);
void *snd_usb_find_csint_desc(void *descstart, int desclen, void *after, u8 dsubtype);
-int snd_usb_pipe_sanity_check(struct usb_device *dev, unsigned int pipe);
int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe,
__u8 request, __u8 requesttype, __u16 value, __u16 index,
void *data, __u16 size);
@@ -15,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)
@@ -26,9 +34,9 @@ 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 */
diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile
index 8f3b24e7d6c2..997c1558d0cb 100644
--- a/sound/usb/hiface/Makefile
+++ b/sound/usb/hiface/Makefile
@@ -1,3 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
-snd-usb-hiface-objs := chip.o pcm.o
+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 b2d9623e9934..95385e90882c 100644
--- a/sound/usb/hiface/chip.c
+++ b/sound/usb/hiface/chip.c
@@ -21,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 */
@@ -80,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));
diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c
index a148caa5f48e..cf650fab54d7 100644
--- a/sound/usb/hiface/pcm.c
+++ b/sound/usb/hiface/pcm.c
@@ -156,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
@@ -227,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) {
@@ -596,7 +594,7 @@ int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
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);
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/capture.c b/sound/usb/line6/capture.c
index 970c9bdce0b2..84a9b7b76f43 100644
--- a/sound/usb/line6/capture.c
+++ b/sound/usb/line6/capture.c
@@ -2,7 +2,7 @@
/*
* 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)
*/
#include <linux/slab.h>
diff --git a/sound/usb/line6/capture.h b/sound/usb/line6/capture.h
index 20e05a5eceb4..90572dae134e 100644
--- a/sound/usb/line6/capture.h
+++ b/sound/usb/line6/capture.h
@@ -2,7 +2,7 @@
/*
* 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)
*/
#ifndef CAPTURE_H
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c
index 60674ce4879b..e9eb5c74d6c7 100644
--- a/sound/usb/line6/driver.c
+++ b/sound/usb/line6/driver.c
@@ -2,7 +2,7 @@
/*
* 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)
*/
#include <linux/kernel.h>
@@ -20,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"
/*
@@ -113,12 +113,12 @@ 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) {
@@ -202,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;
@@ -286,12 +286,14 @@ static void line6_data_received(struct urb *urb)
{
struct usb_line6 *line6 = (struct usb_line6 *)urb->context;
struct midi_buffer *mb = &line6->line6midi->midibuf_in;
+ unsigned long flags;
int done;
if (urb->status == -ESHUTDOWN)
return;
if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
+ spin_lock_irqsave(&line6->line6midi->lock, flags);
done =
line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length);
@@ -300,11 +302,15 @@ static void line6_data_received(struct urb *urb)
dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n",
done, urb->actual_length);
}
+ spin_unlock_irqrestore(&line6->line6midi->lock, flags);
for (;;) {
+ spin_lock_irqsave(&line6->line6midi->lock, flags);
done =
line6_midibuf_read(mb, line6->buffer_message,
- LINE6_MIDI_MESSAGE_MAXLEN);
+ LINE6_MIDI_MESSAGE_MAXLEN,
+ LINE6_MIDIBUF_READ_RX);
+ spin_unlock_irqrestore(&line6->line6midi->lock, flags);
if (done <= 0)
break;
@@ -337,23 +343,18 @@ 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;
- len = kmalloc(1, GFP_KERNEL);
- if (!len)
- return -ENOMEM;
-
/* 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);
goto exit;
}
@@ -362,45 +363,42 @@ int line6_read_data(struct usb_line6 *line6, unsigned address, void *data,
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);
goto exit;
}
- if (*len != 0xff)
+ if (len != 0xff)
break;
}
ret = -EIO;
- if (*len == 0xff) {
+ if (len == 0xff) {
dev_err(line6->ifcdev, "read failed after %d retries\n",
count);
goto exit;
- } else if (*len != datalen) {
+ } 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);
+ (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);
exit:
- kfree(len);
return ret;
}
EXPORT_SYMBOL_GPL(line6_read_data);
@@ -423,12 +421,11 @@ int line6_write_data(struct usb_line6 *line6, unsigned address, void *data,
if (!status)
return -ENOMEM;
- 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);
-
- 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);
goto exit;
@@ -437,14 +434,11 @@ int line6_write_data(struct usb_line6 *line6, unsigned address, void *data,
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);
goto exit;
@@ -699,7 +693,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;
@@ -708,9 +702,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)
diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h
index 71d3da1db8c8..5736ad4256a5 100644
--- a/sound/usb/line6/driver.h
+++ b/sound/usb/line6/driver.h
@@ -2,7 +2,7 @@
/*
* 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)
*/
#ifndef DRIVER_H
@@ -27,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
@@ -193,8 +193,6 @@ 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 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 ba0e2b7e8fe1..9b5176086280 100644
--- a/sound/usb/line6/midi.c
+++ b/sound/usb/line6/midi.c
@@ -2,7 +2,7 @@
/*
* 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)
*/
#include <linux/slab.h>
@@ -44,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)
@@ -56,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;
diff --git a/sound/usb/line6/midi.h b/sound/usb/line6/midi.h
index 918754e79be4..3409c742c173 100644
--- a/sound/usb/line6/midi.h
+++ b/sound/usb/line6/midi.h
@@ -2,7 +2,7 @@
/*
* 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)
*/
#ifndef MIDI_H
diff --git a/sound/usb/line6/midibuf.c b/sound/usb/line6/midibuf.c
index 6a70463f82c4..57fca134b337 100644
--- a/sound/usb/line6/midibuf.c
+++ b/sound/usb/line6/midibuf.c
@@ -2,13 +2,14 @@
/*
* 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)
*/
#include <linux/slab.h>
#include "midibuf.h"
+
static int midibuf_message_length(unsigned char code)
{
int message_length;
@@ -20,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];
@@ -125,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;
@@ -148,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;
diff --git a/sound/usb/line6/midibuf.h b/sound/usb/line6/midibuf.h
index 124a8f9f7e96..1dae5fac9dde 100644
--- a/sound/usb/line6/midibuf.h
+++ b/sound/usb/line6/midibuf.h
@@ -2,12 +2,15 @@
/*
* 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)
*/
#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;
@@ -23,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 fdbdfb7bce92..d4dbbc432505 100644
--- a/sound/usb/line6/pcm.c
+++ b/sound/usb/line6/pcm.c
@@ -2,7 +2,7 @@
/*
* 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)
*/
#include <linux/slab.h>
@@ -552,10 +552,10 @@ int line6_init_pcm(struct usb_line6 *line6,
line6pcm->max_packet_size_in =
usb_maxpacket(line6->usbdev,
- usb_rcvisocpipe(line6->usbdev, ep_read), 0);
+ usb_rcvisocpipe(line6->usbdev, ep_read));
line6pcm->max_packet_size_out =
usb_maxpacket(line6->usbdev,
- usb_sndisocpipe(line6->usbdev, ep_write), 1);
+ 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");
diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h
index 9c683042ff06..a15913bf2a7a 100644
--- a/sound/usb/line6/pcm.h
+++ b/sound/usb/line6/pcm.h
@@ -2,7 +2,7 @@
/*
* 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)
*/
/*
diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c
index 8233c61e23f1..9f26f66e6792 100644
--- a/sound/usb/line6/playback.c
+++ b/sound/usb/line6/playback.c
@@ -2,7 +2,7 @@
/*
* 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)
*/
#include <linux/slab.h>
diff --git a/sound/usb/line6/playback.h b/sound/usb/line6/playback.h
index 2ca832c83851..2e0ec0ade0bf 100644
--- a/sound/usb/line6/playback.h
+++ b/sound/usb/line6/playback.h
@@ -2,7 +2,7 @@
/*
* 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)
*/
#ifndef PLAYBACK_H
diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c
index cd44cb5f1310..6f948c3e8f9e 100644
--- a/sound/usb/line6/pod.c
+++ b/sound/usb/line6/pod.c
@@ -2,7 +2,7 @@
/*
* 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)
*/
#include <linux/slab.h>
@@ -159,8 +159,9 @@ 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
};
static char *pod_alloc_sysex_buffer(struct usb_line6_pod *pod, int code,
@@ -235,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);
}
/*
@@ -247,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);
}
/*
@@ -260,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);
}
/*
@@ -376,11 +377,6 @@ static int pod_init(struct usb_line6 *line6,
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)
diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c
index eef45f7fef0d..70de08635f54 100644
--- a/sound/usb/line6/podhd.c
+++ b/sound/usb/line6/podhd.c
@@ -146,7 +146,7 @@ static ssize_t serial_number_show(struct device *dev,
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,
@@ -155,7 +155,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);
@@ -183,29 +183,25 @@ static const struct attribute_group podhd_dev_attr_group = {
static int podhd_dev_start(struct usb_line6_podhd *pod)
{
int ret;
- u8 *init_bytes;
+ u8 init_bytes[8];
int i;
struct usb_device *usbdev = pod->line6.usbdev;
- init_bytes = kmalloc(8, GFP_KERNEL);
- if (!init_bytes)
- return -ENOMEM;
-
- 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);
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);
goto exit;
@@ -220,13 +216,12 @@ static int podhd_dev_start(struct usb_line6_podhd *pod)
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);
+ NULL, 0, LINE6_TIMEOUT, GFP_KERNEL);
exit:
- kfree(init_bytes);
return ret;
}
@@ -512,7 +507,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,
diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c
index 94dd5e7ab2e6..c073b38cd673 100644
--- a/sound/usb/line6/toneport.c
+++ b/sound/usb/line6/toneport.c
@@ -2,7 +2,7 @@
/*
* 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)
*/
@@ -126,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;
}
@@ -385,7 +386,7 @@ static int toneport_setup(struct usb_line6_toneport *toneport)
toneport_update_led(toneport);
schedule_delayed_work(&toneport->line6.startup_work,
- msecs_to_jiffies(TONEPORT_PCM_DELAY * 1000));
+ secs_to_jiffies(TONEPORT_PCM_DELAY));
return 0;
}
diff --git a/sound/usb/line6/variax.c b/sound/usb/line6/variax.c
index ed158f04de80..b2f6637c84b2 100644
--- a/sound/usb/line6/variax.c
+++ b/sound/usb/line6/variax.c
@@ -2,7 +2,7 @@
/*
* 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)
*/
#include <linux/slab.h>
@@ -159,7 +159,6 @@ static int variax_init(struct usb_line6 *line6,
const struct usb_device_id *id)
{
struct usb_line6_variax *variax = line6_to_variax(line6);
- int err;
line6->process_message = line6_variax_process_message;
line6->disconnect = line6_variax_disconnect;
@@ -172,11 +171,6 @@ 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: */
schedule_delayed_work(&line6->startup_work,
msecs_to_jiffies(VARIAX_STARTUP_DELAY1));
diff --git a/sound/usb/media.c b/sound/usb/media.c
index 812017eacbcf..d48db6f3ae65 100644
--- a/sound/usb/media.c
+++ b/sound/usb/media.c
@@ -35,7 +35,7 @@ int snd_media_stream_init(struct snd_usb_substream *subs, struct snd_pcm *pcm,
{
struct media_device *mdev;
struct media_ctl *mctl;
- struct device *pcm_dev = &pcm->streams[stream].dev;
+ struct device *pcm_dev = pcm->streams[stream].dev;
u32 intf_type;
int ret = 0;
u16 mixer_pad;
@@ -163,7 +163,7 @@ void snd_media_stop_pipeline(struct snd_usb_substream *subs)
static int snd_media_mixer_init(struct snd_usb_audio *chip)
{
- struct device *ctl_dev = &chip->card->ctl_dev;
+ 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;
@@ -285,7 +285,7 @@ snd_mixer_init:
ret);
if (!media_devnode_is_registered(mdev->devnode)) {
- /* dont'register if snd_media_mixer_init() failed */
+ /* don't register if snd_media_mixer_init() failed */
if (ret)
goto create_fail;
diff --git a/sound/usb/midi.c b/sound/usb/midi.c
index e8287a05e36b..866e613fee4f 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[];
-} __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 */
@@ -344,9 +328,10 @@ static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint *ep)
spin_unlock_irqrestore(&ep->buffer_lock, flags);
}
-static void snd_usbmidi_out_tasklet(struct tasklet_struct *t)
+static void snd_usbmidi_out_work(struct work_struct *work)
{
- struct snd_usb_midi_out_endpoint *ep = from_tasklet(ep, t, tasklet);
+ struct snd_usb_midi_out_endpoint *ep =
+ container_of(work, struct snd_usb_midi_out_endpoint, work);
snd_usbmidi_do_output(ep);
}
@@ -354,7 +339,7 @@ static void snd_usbmidi_out_tasklet(struct tasklet_struct *t)
/* 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);
@@ -504,16 +489,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;
+ }
}
/*
@@ -1148,10 +1201,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;
@@ -1160,6 +1211,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);
}
@@ -1177,7 +1231,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);
}
}
@@ -1209,6 +1263,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);
}
@@ -1300,7 +1355,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);
@@ -1331,7 +1386,7 @@ static int snd_usbmidi_in_endpoint_create(struct snd_usb_midi *umidi,
error:
snd_usbmidi_in_endpoint_delete(ep);
- return -ENOMEM;
+ return err;
}
/*
@@ -1389,7 +1444,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
@@ -1440,7 +1495,7 @@ static int snd_usbmidi_out_endpoint_create(struct snd_usb_midi *umidi,
}
spin_lock_init(&ep->buffer_lock);
- tasklet_setup(&ep->tasklet, snd_usbmidi_out_tasklet);
+ INIT_WORK(&ep->work, snd_usbmidi_out_work);
init_waitqueue_head(&ep->drain_wait);
for (i = 0; i < 0x10; ++i)
@@ -1475,6 +1530,7 @@ static void snd_usbmidi_free(struct snd_usb_midi *umidi)
snd_usbmidi_in_endpoint_delete(ep->in);
}
mutex_destroy(&umidi->mutex);
+ timer_shutdown_sync(&umidi->error_timer);
kfree(umidi);
}
@@ -1498,12 +1554,12 @@ void snd_usbmidi_disconnect(struct list_head *p)
spin_unlock_irq(&umidi->disc_lock);
up_write(&umidi->disc_rwsem);
- del_timer_sync(&umidi->error_timer);
+ 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);
@@ -1755,12 +1811,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 +1873,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 +1932,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 +1940,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;
}
@@ -1861,7 +1986,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)
@@ -1874,7 +1999,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");
@@ -1888,6 +2013,12 @@ static int snd_usbmidi_get_ms_info(struct snd_usb_midi *umidi,
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) {
if (++epidx >= MIDI_MAX_ENDPOINTS) {
@@ -1908,6 +2039,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 {
@@ -1925,6 +2060,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);
}
@@ -1950,7 +2089,7 @@ 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)
@@ -2140,6 +2279,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);
@@ -2239,11 +2380,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;
@@ -2372,7 +2515,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];
@@ -2387,6 +2531,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);
@@ -2506,6 +2652,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..692dfc3c182f
--- /dev/null
+++ b/sound/usb/midi2.c
@@ -0,0 +1,1240 @@
+// 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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ep->lock, flags);
+ 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);
+ spin_unlock_irqrestore(&ep->lock, flags);
+}
+
+/* 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;
+ unsigned long flags;
+ int len;
+
+ spin_lock_irqsave(&ep->lock, flags);
+ 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);
+ spin_unlock_irqrestore(&ep->lock, flags);
+}
+
+/* URB submission helper; for both direction */
+static void submit_io_urbs(struct snd_usb_midi2_endpoint *ep)
+{
+ unsigned long flags;
+
+ if (!ep)
+ return;
+ spin_lock_irqsave(&ep->lock, flags);
+ if (ep->direction == STR_IN)
+ submit_input_urbs_locked(ep);
+ else
+ submit_output_urbs_locked(ep);
+ spin_unlock_irqrestore(&ep->lock, flags);
+}
+
+/* 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;
+ spin_lock_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));
+ spin_unlock_irq(&ep->lock);
+}
+
+/* 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)
+ sprintf(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 068ecd7bc043..3e9f4adc28de 100644
--- a/sound/usb/misc/Makefile
+++ b/sound/usb/misc/Makefile
@@ -1,3 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
-snd-ua101-objs := ua101.o
+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 3b2dce1043f5..4f6b20ed29dd 100644
--- a/sound/usb/misc/ua101.c
+++ b/sound/usb/misc/ua101.c
@@ -19,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
@@ -96,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;
@@ -188,7 +187,7 @@ static void playback_urb_complete(struct urb *usb_urb)
spin_lock_irqsave(&ua->lock, flags);
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;
@@ -247,9 +246,9 @@ static inline void add_with_wraparound(struct ua101 *ua,
*value -= ua->playback.queue_length;
}
-static void playback_tasklet(struct tasklet_struct *t)
+static void playback_work(struct work_struct *work)
{
- struct ua101 *ua = from_tasklet(ua, t, playback_tasklet);
+ struct ua101 *ua = container_of(work, struct ua101, playback_work);
unsigned long flags;
unsigned int frames;
struct ua101_urb *urb;
@@ -401,7 +400,7 @@ static void capture_urb_complete(struct urb *urb)
}
if (test_bit(USB_PLAYBACK_RUNNING, &ua->states) &&
!list_empty(&ua->ready_playback_urbs))
- tasklet_schedule(&ua->playback_tasklet);
+ queue_work(system_highpri_wq, &ua->playback_work);
}
spin_unlock_irqrestore(&ua->lock, flags);
@@ -532,7 +531,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);
}
@@ -550,7 +549,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)
@@ -1001,7 +1000,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;
}
@@ -1009,7 +1008,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;
}
@@ -1218,7 +1217,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_setup(&ua->playback_tasklet, playback_tasklet);
+ 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);
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index 81e987eaf063..11be79af26db 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -115,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 */
@@ -142,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;
}
}
@@ -151,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;
}
@@ -254,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;
}
@@ -355,9 +362,8 @@ 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;
+ if (snd_usb_lock_shutdown(chip))
+ return -EIO;
idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8);
ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest,
@@ -366,8 +372,7 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request,
snd_usb_unlock_shutdown(chip);
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;
@@ -428,7 +433,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;
}
@@ -440,7 +445,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;
}
@@ -517,7 +522,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,
@@ -531,7 +536,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;
}
@@ -542,7 +547,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))
@@ -723,7 +728,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;
@@ -1079,6 +1084,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 */
@@ -1163,27 +1183,6 @@ 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")) {
- usb_audio_info(chip,
- "set resolution quirk: cval->res = 384\n");
- cval->res = 384;
- }
- break;
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) {
@@ -1195,12 +1194,32 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval,
}
}
+/* 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;
@@ -1213,9 +1232,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;
}
@@ -1232,7 +1250,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) {
@@ -1301,6 +1319,30 @@ no_res_check:
/* 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;
@@ -1308,11 +1350,24 @@ no_res_check:
#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)
@@ -1320,11 +1375,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) {
@@ -1336,10 +1388,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;
}
@@ -1347,14 +1399,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)
@@ -1379,19 +1431,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);
@@ -1405,6 +1460,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);
@@ -1418,7 +1475,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);
@@ -1429,13 +1486,11 @@ 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;
@@ -1450,30 +1505,52 @@ static int mixer_ctl_connector_get(struct snd_kcontrol *kcontrol,
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 (strstr(kcontrol->id.name, "Speaker")) {
- ucontrol->value.integer.value[0] = 1;
+ 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);
+
+ 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;
}
@@ -1539,9 +1616,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))
@@ -1556,7 +1633,7 @@ 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 const struct usb_feature_control_info *get_feature_control_info(int control)
@@ -1618,7 +1695,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;
@@ -1691,7 +1768,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;
}
@@ -1770,7 +1847,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
@@ -1783,6 +1860,15 @@ 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,
@@ -1800,6 +1886,10 @@ static void build_connector_control(struct usb_mixer_interface *mixer,
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.
@@ -1841,7 +1931,6 @@ 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)
@@ -1878,10 +1967,9 @@ static int parse_clock_source_unit(struct mixer_build *state, int unitid,
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);
@@ -1921,6 +2009,13 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid,
bmaControls = ftr->bmaControls;
}
+ 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 */
err = parse_audio_unit(state, hdr->bSourceID);
if (err < 0)
@@ -1960,8 +2055,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 */
@@ -1972,7 +2067,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);
}
@@ -1988,9 +2083,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);
}
}
@@ -2081,7 +2176,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++;
}
}
@@ -2102,7 +2197,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",
@@ -2206,7 +2302,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);
@@ -2223,13 +2319,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);
@@ -2404,7 +2502,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],
@@ -2490,7 +2588,7 @@ 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 {
if (extension_unit)
nameid = uac_extension_unit_iExtension(desc, state->mixer->protocol);
@@ -2503,7 +2601,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);
@@ -2556,7 +2654,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))
@@ -2568,7 +2666,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);
@@ -2585,13 +2683,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);
@@ -2693,7 +2793,6 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid,
#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]) {
err = -ENOMEM;
@@ -2743,7 +2842,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 ||
@@ -3138,7 +3237,6 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer)
state.map = map->map;
state.selector_map = map->selector_map;
mixer->connector_map = map->connector_map;
- mixer->ignore_ctl_error |= map->ignore_ctl_error;
break;
}
}
@@ -3262,8 +3360,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 const char * const 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,
@@ -3341,7 +3448,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;
@@ -3454,50 +3561,7 @@ 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 const struct snd_device_ops dev_ops = {
.dev_free = snd_usb_mixer_dev_free
@@ -3511,7 +3575,7 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
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) {
@@ -3548,10 +3612,6 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
if (err < 0)
goto _error;
- err = create_keep_iface_ctl(mixer);
- if (err < 0)
- goto _error;
-
err = snd_usb_mixer_apply_create_quirk(mixer);
if (err < 0)
goto _error;
@@ -3585,7 +3645,6 @@ void snd_usb_mixer_disconnect(struct usb_mixer_interface *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)
{
@@ -3619,45 +3678,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;
}
}
}
@@ -3666,7 +3723,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,
@@ -3675,7 +3731,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 c29e27ac43a7..167fbfcf01ac 100644
--- a/sound/usb/mixer.h
+++ b/sound/usb/mixer.h
@@ -55,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,
@@ -87,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];
@@ -95,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);
@@ -118,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);
diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c
index 5b43e9e40e49..faac7df1fbcf 100644
--- a/sound/usb/mixer_maps.c
+++ b/sound/usb/mixer_maps.c
@@ -6,8 +6,9 @@
*/
struct usbmix_dB_map {
- u32 min;
- u32 max;
+ int min;
+ int max;
+ bool min_mute;
};
struct usbmix_name_map {
@@ -28,7 +29,6 @@ struct usbmix_ctl_map {
const struct usbmix_name_map *map;
const struct usbmix_selector_map *selector_map;
const struct usbmix_connector_map *connector_map;
- int ignore_ctl_error;
};
/*
@@ -337,6 +337,20 @@ static const struct usbmix_name_map bose_companion5_map[] = {
{ 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
@@ -360,18 +374,48 @@ static const struct usbmix_name_map corsair_virtuoso_map[] = {
{ 0 }
};
-/* Some mobos shipped with a dummy HD-audio show the invalid GET_MIN/GET_MAX
- * response for Input Gain Pad (id=19, control=12) and the connector status
- * for SPDIF terminal (id=18). Skip them.
- */
-static const struct usbmix_name_map asus_rog_map[] = {
- { 18, NULL }, /* OT, connector control */
- { 19, NULL, 12 }, /* FU, Input Gain Pad */
+/* 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, 2 }, /* FU, Volume */
{ 19, NULL, 12 }, /* FU, Input Gain Pad */
{}
};
@@ -418,6 +462,39 @@ static const struct usbmix_name_map aorus_master_alc1220vb_map[] = {
{}
};
+/* 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
*/
@@ -426,7 +503,6 @@ 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),
@@ -446,29 +522,11 @@ static const 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)
*/
@@ -486,7 +544,6 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = {
{
.id = USB_ID(0x08bb, 0x2702),
.map = linex_map,
- .ignore_ctl_error = 1,
},
{
.id = USB_ID(0x0a92, 0x0091),
@@ -511,7 +568,6 @@ static const 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),
@@ -538,6 +594,31 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = {
.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,
@@ -547,6 +628,16 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = {
.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,
@@ -556,19 +647,34 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = {
.map = trx40_mobo_map,
.connector_map = trx40_mobo_connector_map,
},
- { /* ASUS ROG Zenith II */
+ { /* 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_rog_map,
+ .map = asus_zenith_ii_map,
+ .connector_map = asus_zenith_ii_connector_map,
},
{ /* ASUS ROG Strix */
.id = USB_ID(0x0b05, 0x1917),
- .map = asus_rog_map,
+ .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,
@@ -583,6 +689,21 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = {
.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 */
};
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
index 199cdbfdc761..aad205df93b2 100644
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -14,6 +14,7 @@
* Przemek Rudy (prudy1@o2.pl)
*/
+#include <linux/bitfield.h>
#include <linux/hid.h>
#include <linux/init.h>
#include <linux/math64.h>
@@ -24,6 +25,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>
@@ -32,10 +34,11 @@
#include "mixer.h"
#include "mixer_quirks.h"
#include "mixer_scarlett.h"
-#include "mixer_scarlett_gen2.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;
@@ -594,85 +597,208 @@ 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;
+
+ err = snd_usb_lock_shutdown(chip);
+ if (err < 0)
+ goto err;
+
+ err = snd_mbox1_is_spdif_synced(chip);
+ if (err < 0)
+ goto err;
+
+ kctl->private_value = err;
+ err = 0;
ucontrol->value.enumerated.item[0] = kctl->private_value;
- return 0;
+err:
+ snd_usb_unlock_shutdown(chip);
+ return err;
}
-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);
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_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);
+
+ err = snd_mbox1_is_spdif_synced(chip);
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;
- }
+ /* FIXME: hardcoded sample rate */
+ err = snd_mbox1_set_clk_source(chip, is_spdif_sync ? 0 : 48000);
+ if (err < 0)
+ goto 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);
+ err = snd_mbox1_is_spdif_synced(chip);
+err:
+ snd_usb_unlock_shutdown(chip);
+ return err;
+}
+
+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;
+
+ err = snd_usb_lock_shutdown(chip);
+ if (err < 0)
+ return 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);
+
+ 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);
+
+ err = snd_mbox1_is_spdif_input(chip);
if (err < 0)
goto err;
+ err = snd_mbox1_is_spdif_synced(chip);
err:
snd_usb_unlock_shutdown(chip);
return err;
}
-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;
@@ -685,42 +811,60 @@ 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 const 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 */
@@ -901,7 +1045,7 @@ 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;
@@ -935,7 +1079,7 @@ static int snd_ftu_eff_switch_update(struct usb_mixer_elem_list *list)
UAC_SET_CUR,
USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
pval & 0xff00,
- snd_usb_ctrl_intf(chip) | ((pval & 0xff) << 8),
+ snd_usb_ctrl_intf(list->mixer->hostif) | ((pval & 0xff) << 8),
value, 2);
snd_usb_unlock_shutdown(chip);
return err;
@@ -997,7 +1141,7 @@ 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);
@@ -1008,7 +1152,7 @@ static int snd_ftu_create_volume_ctls(struct usb_mixer_interface *mixer)
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);
@@ -1073,7 +1217,7 @@ 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);
err = snd_create_std_mono_ctl(mixer, id, control,
@@ -1097,7 +1241,7 @@ 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);
err = snd_create_std_mono_ctl(mixer, id, control, cmask,
@@ -1107,7 +1251,7 @@ static int snd_ftu_create_effect_send_ctls(struct usb_mixer_interface *mixer)
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);
err = snd_create_std_mono_ctl(mixer, id, control, cmask,
@@ -1210,7 +1354,7 @@ static int snd_c400_create_vol_ctls(struct usb_mixer_interface *mixer)
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,
@@ -1296,7 +1440,7 @@ static int snd_c400_create_effect_vol_ctls(struct usb_mixer_interface *mixer)
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);
@@ -1338,7 +1482,7 @@ static int snd_c400_create_effect_ret_vol_ctls(struct usb_mixer_interface *mixer
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);
@@ -1793,23 +1937,205 @@ 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;
+
+ err = snd_usb_lock_shutdown(chip);
+ if (err < 0)
+ return err;
+ err = realtek_hda_get(chip,
+ HDA_VERB_CMD(AC_VERB_GET_PIN_SENSE, node_id, 0),
+ &sense);
+ if (err < 0)
+ goto 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)
+ goto err;
+ err = realtek_hda_get(chip,
+ HDA_VERB_CMD(AC_VERB_GET_PROC_COEF,
+ REALTEK_VENDOR_REGISTERS, 0),
+ &cbj_ctrl2);
+ if (err < 0)
+ goto err;
+ }
+err:
+ snd_usb_unlock_shutdown(chip);
+ 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)
+{
+ 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,
+ REALTEK_JACK_INTERRUPT_NODE);
+ 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(&realtek_connector_ctl_ro, 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);
+ if (err < 0)
+ return err;
+ err = realtek_add_jack(mixer, "Headphone Jack", REALTEK_HP_OUT);
+ if (err < 0)
+ return err;
+ err = realtek_add_jack(mixer, "Headset Mic Jack",
+ REALTEK_HP_OUT | REALTEK_MIC_FLAG);
+ 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;
}
@@ -2218,14 +2544,23 @@ enum {
#define SND_BBFPRO_CTL_REG2_PAD_AN1 4
#define SND_BBFPRO_CTL_REG2_PAD_AN2 5
-#define SND_BBFPRO_MIXER_IDX_MASK 0x1ff
+#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,
@@ -2245,12 +2580,12 @@ static int snd_bbfpro_ctl_update(struct usb_mixer_interface *mixer, u8 reg,
usb_idx = 3;
usb_val = value ? 3 : 0;
} else {
- usb_idx = 1 << index;
+ usb_idx = BIT(index);
usb_val = value ? usb_idx : 0;
}
} else {
usb_req = SND_BBFPRO_USBREQ_CTL_REG2;
- usb_idx = 1 << index;
+ usb_idx = BIT(index);
usb_val = value ? usb_idx : 0;
}
@@ -2372,6 +2707,114 @@ static int snd_bbfpro_ctl_resume(struct usb_mixer_elem_list *list)
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)
+{
+ int err;
+ 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);
+ }
+
+ err = snd_usb_lock_shutdown(chip);
+ if (err < 0)
+ return err;
+
+ err = 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);
+
+ snd_usb_unlock_shutdown(chip);
+ return err;
+}
+
+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)
{
@@ -2467,6 +2910,15 @@ static const struct snd_kcontrol_new snd_bbfpro_ctl_control = {
.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,
@@ -2490,6 +2942,18 @@ static int snd_bbfpro_ctl_add(struct usb_mixer_interface *mixer, u8 reg,
&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)
{
@@ -2537,6 +3001,29 @@ static int snd_bbfpro_controls_create(struct usb_mixer_interface *mixer)
}
}
+ // 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,
@@ -2602,6 +3089,960 @@ static int snd_bbfpro_controls_create(struct usb_mixer_interface *mixer)
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)
+
+/* Nonconst helpers */
+#define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1))
+#define field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask))
+
+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)
+{
+ int err;
+ const struct snd_djm_device *device = &snd_djm_devices[device_idx];
+
+ if ((group >= device->ncontrols) || value >= device->controls[group].noptions)
+ return -EINVAL;
+
+ err = snd_usb_lock_shutdown(mixer->chip);
+ if (err)
+ return err;
+
+ err = 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);
+
+ snd_usb_unlock_shutdown(mixer->chip);
+ return err;
+}
+
+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;
@@ -2653,7 +4094,7 @@ 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 */
@@ -2684,15 +4125,44 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
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 */
- err = snd_scarlett_gen2_controls_create(mixer);
+ 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 */
@@ -2700,18 +4170,48 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
err = snd_rme_controls_create(mixer);
break;
- case USB_ID(0x0194f, 0x010c): /* Presonus Studio 1810c */
+ 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;
}
return err;
}
-#ifdef CONFIG_PM
void snd_usb_mixer_resume_quirk(struct usb_mixer_interface *mixer)
{
switch (mixer->chip->usb_id) {
@@ -2720,7 +4220,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)
@@ -2784,6 +4283,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)
@@ -2793,12 +4338,18 @@ 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):
+ /* lowest playback value is muted on some devices */
+ case USB_ID(0x0d8c, 0x000c): /* C-Media */
+ case USB_ID(0x0d8c, 0x0014): /* C-Media */
+ case USB_ID(0x19f7, 0x0003): /* RODE NT-USB */
if (strstr(kctl->id.name, "Playback"))
cval->min_mute = 1;
break;
}
+
+ /* 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
index c53a9773f310..fac4bbc6b275 100644
--- a/sound/usb/mixer_s1810c.c
+++ b/sound/usb/mixer_s1810c.c
@@ -163,7 +163,7 @@ snd_s1810c_send_ctl_packet(struct usb_device *dev, u32 a,
}
/*
- * When opening Universal Control the program periodicaly
+ * When opening Universal Control the program periodically
* sends and receives state packets for syncinc state between
* the device and the host.
*
@@ -221,7 +221,7 @@ static int snd_s1810c_init_mixer_maps(struct snd_usb_audio *chip)
e = 0xbc;
for (n = 0; n < 2; n++) {
off = n * 18;
- for (b = off, c = 0; b < 18 + off; b++) {
+ 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);
diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c
index 49fcd2505443..ff548041679b 100644
--- a/sound/usb/mixer_scarlett.c
+++ b/sound/usb/mixer_scarlett.c
@@ -21,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.
@@ -460,7 +460,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 +569,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)
@@ -1002,7 +1002,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..93589e86828a
--- /dev/null
+++ b/sound/usb/mixer_scarlett2.c
@@ -0,0 +1,9768 @@
+// 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
+ *
+ * Copyright (c) 2018-2024 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).
+ *
+ * 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"
+};
+
+/* 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_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 },
+ }
+};
+
+/* 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];
+
+ /* 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 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,
+
+ .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,
+
+ .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, *resp = NULL;
+ size_t req_buf_size = struct_size(req, data, req_size);
+ size_t resp_buf_size = struct_size(resp, data, resp_size);
+ int err;
+
+ req = kmalloc(req_buf_size, GFP_KERNEL);
+ if (!req) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ resp = kmalloc(resp_buf_size, GFP_KERNEL);
+ if (!resp) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ mutex_lock(&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);
+
+ err = scarlett2_usb_tx(dev, private->bInterfaceNumber,
+ req, req_buf_size);
+
+ if (err != req_buf_size) {
+ usb_audio_err(
+ mixer->chip,
+ "%s USB request result cmd %x was %d\n",
+ private->series_name, cmd, err);
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ 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);
+
+ err = -ETIMEDOUT;
+ goto unlock;
+ }
+
+ /* 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)) {
+ err = 0;
+ goto unlock;
+ }
+
+ usb_audio_err(
+ mixer->chip,
+ "%s USB response result cmd %x was %d expected %zu\n",
+ private->series_name, cmd, err, resp_buf_size);
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ /* 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));
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ if (resp_data && resp_size > 0)
+ memcpy(resp_data, resp->data, resp_size);
+
+unlock:
+ mutex_unlock(&private->usb_mutex);
+error:
+ kfree(req);
+ kfree(resp);
+ 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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->sync_updated) {
+ err = scarlett2_update_sync(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.enumerated.item[0] = private->sync;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ err = scarlett2_check_input_phantom_updated(mixer);
+ if (err < 0)
+ goto unlock;
+
+ err = snd_ctl_boolean_mono_info(kctl, uinfo);
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_autogain_updated(mixer);
+ if (err < 0)
+ goto unlock;
+
+ ucontrol->value.enumerated.item[0] =
+ private->autogain_switch[elem->control];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_autogain_updated(mixer);
+ if (err < 0)
+ goto unlock;
+
+ ucontrol->value.enumerated.item[0] =
+ private->autogain_status[elem->control];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_input_phantom_updated(mixer);
+ if (err < 0)
+ goto unlock;
+
+ if (scarlett2_phantom_is_switching(private, index)) {
+ err = -EPERM;
+ goto unlock;
+ }
+
+ oval = private->autogain_switch[index];
+ val = !!ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->autogain_switch[index] = val;
+
+ /* Send switch change to the device */
+ err = scarlett2_usb_set_config(
+ mixer, SCARLETT2_CONFIG_AUTOGAIN_SWITCH, index, val);
+ if (err == 0)
+ err = 1;
+
+ scarlett2_autogain_update_access(mixer);
+ scarlett2_autogain_notify_access(mixer);
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_autogain_updated(mixer);
+ if (err < 0)
+ goto unlock;
+
+ 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;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->autogain_updated) {
+ err = scarlett2_update_autogain(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+
+ ucontrol->value.integer.value[0] = private->ag_targets[elem->control];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_put_during_autogain(mixer);
+ if (err < 0)
+ goto unlock;
+
+ oval = private->ag_targets[index];
+ val = clamp(ucontrol->value.integer.value[0],
+ (long)SCARLETT2_AG_TARGET_MIN, 0L);
+
+ if (oval == val)
+ goto unlock;
+
+ private->ag_targets[index] = val;
+
+ /* Send new value to the device */
+ err = scarlett2_usb_set_config(
+ mixer, scarlett2_ag_target_configs[index], 1, -val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->input_select_updated) {
+ err = scarlett2_update_input_select(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.enumerated.item[0] = private->input_select_switch;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_put_during_autogain(mixer);
+ if (err < 0)
+ goto unlock;
+
+ 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)
+ goto unlock;
+
+ private->input_select_switch = val;
+
+ /* Send new value to the device */
+ err = scarlett2_usb_set_config(
+ mixer, SCARLETT2_CONFIG_INPUT_SELECT_SWITCH,
+ 0, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = kcalloc(inputs, sizeof(char *), GFP_KERNEL);
+
+ if (!values)
+ return -ENOMEM;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_autogain_updated(mixer);
+ if (err < 0)
+ goto unlock;
+
+ /* Loop through each input */
+ for (i = 0; i < inputs; i++)
+ values[i] = kasprintf(GFP_KERNEL, "Input %d", i + 1);
+
+ err = snd_ctl_enum_info(uinfo, 1, i,
+ (const char * const *)values);
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+
+ for (i = 0; i < inputs; i++)
+ kfree(values[i]);
+ kfree(values);
+
+ 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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_autogain_updated(mixer);
+ if (err < 0)
+ goto unlock;
+
+ err = snd_ctl_boolean_mono_info(kctl, uinfo);
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->input_select_updated) {
+ err = scarlett2_update_input_select(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.enumerated.item[0] =
+ private->input_link_switch[elem->control];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_put_during_autogain(mixer);
+ if (err < 0)
+ goto unlock;
+
+ oval = private->input_link_switch[index];
+ val = !!ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->input_link_switch[index] = val;
+
+ err = scarlett2_usb_set_config(
+ mixer, SCARLETT2_CONFIG_INPUT_LINK_SWITCH, index, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_autogain_updated(mixer);
+ if (err < 0)
+ goto unlock;
+
+ 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;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->input_gain_updated) {
+ err = scarlett2_update_input_gain(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.integer.value[0] =
+ private->gain[elem->control];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_put_during_autogain(mixer);
+ if (err < 0)
+ goto unlock;
+
+ oval = private->gain[index];
+ val = ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->gain[index] = val;
+
+ /* Send gain change to the device */
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_INPUT_GAIN,
+ index, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->input_safe_updated) {
+ err = scarlett2_update_input_safe(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.integer.value[0] =
+ private->safe_switch[elem->control];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_put_during_autogain(mixer);
+ if (err < 0)
+ goto unlock;
+
+ oval = private->safe_switch[index];
+ val = !!ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->safe_switch[index] = val;
+
+ /* Send switch change to the device */
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_SAFE_SWITCH,
+ index, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->pcm_input_switch_updated) {
+ err = scarlett2_update_pcm_input_switch(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.enumerated.item[0] = private->pcm_input_switch;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->pcm_input_switch;
+ val = !!ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->pcm_input_switch = val;
+
+ /* Send switch change to the device */
+ err = scarlett2_usb_set_config(
+ mixer, SCARLETT2_CONFIG_PCM_INPUT_SWITCH,
+ 0, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->vol_updated) {
+ err = scarlett2_update_volumes(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.integer.value[0] = private->master_vol;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->vol_updated) {
+ err = scarlett2_update_volumes(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.integer.value[0] = private->headphone_vol;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->vol_updated) {
+ err = scarlett2_update_volumes(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.integer.value[0] = private->vol[index];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->vol[index];
+ val = ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->vol[index] = val;
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_LINE_OUT_VOLUME,
+ index, val - SCARLETT2_VOLUME_BIAS);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->dim_mute_updated) {
+ err = scarlett2_update_dim_mute(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.integer.value[0] = private->mute_switch[index];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->mute_switch[index];
+ val = !!ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->mute_switch[index] = val;
+
+ /* Send mute change to the device */
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_MUTE_SWITCH,
+ index, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->vol_sw_hw_switch[index];
+ val = !!ucontrol->value.enumerated.item[0];
+
+ if (oval == val)
+ goto unlock;
+
+ err = scarlett2_sw_hw_change(mixer, ctl_index, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_autogain_updated(mixer);
+ if (err < 0)
+ goto unlock;
+
+ err = snd_ctl_enum_info(uinfo, 1, 2, values);
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->input_level_updated) {
+ err = scarlett2_update_input_level(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.enumerated.item[0] = scarlett2_decode_muteable(
+ private->level_switch[index]);
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_put_during_autogain(mixer);
+ if (err < 0)
+ goto unlock;
+
+ oval = private->level_switch[index];
+ val = !!ucontrol->value.enumerated.item[0];
+
+ if (oval == val)
+ goto unlock;
+
+ 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);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->input_pad_updated) {
+ err = scarlett2_update_input_pad(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.integer.value[0] =
+ private->pad_switch[elem->control];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->pad_switch[index];
+ val = !!ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->pad_switch[index] = val;
+
+ /* Send switch change to the device */
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_PAD_SWITCH,
+ index, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->input_air_updated) {
+ err = scarlett2_update_input_air(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.integer.value[0] = private->air_switch[elem->control];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_put_during_autogain(mixer);
+ if (err < 0)
+ goto unlock;
+
+ oval = private->air_switch[index];
+ val = ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->air_switch[index] = val;
+
+ /* Send switch change to the device */
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_AIR_SWITCH,
+ index, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_autogain_updated(mixer);
+ if (err < 0)
+ goto unlock;
+
+ err = snd_ctl_enum_info(uinfo, 1, 3, values);
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->input_dsp_updated) {
+ err = scarlett2_update_input_dsp(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.integer.value[0] = private->dsp_switch[elem->control];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_put_during_autogain(mixer);
+ if (err < 0)
+ goto unlock;
+
+ oval = private->dsp_switch[index];
+ val = ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->dsp_switch[index] = val;
+
+ /* Send switch change to the device */
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_DSP_SWITCH,
+ index, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_put_during_autogain(mixer);
+ if (err < 0)
+ goto unlock;
+
+ oval = private->compressor_values[index];
+ val = ucontrol->value.integer.value[0];
+ if (oval == val)
+ goto unlock;
+
+ 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)
+ goto unlock;
+
+ err = scarlett2_usb_set_config(
+ mixer, SCARLETT2_CONFIG_COMPRESSOR_PARAMS, index, scaled_val);
+ if (err < 0)
+ goto unlock;
+
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->precomp_flt_switch[elem->control];
+ val = ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ 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);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->peq_flt_switch[elem->control];
+ val = ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ 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);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_put_during_autogain(mixer);
+ if (err < 0)
+ goto unlock;
+
+ /* 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)
+ goto unlock;
+
+ /* 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)
+ goto unlock;
+
+ err = scarlett2_usb_set_config_buf(
+ mixer, SCARLETT2_CONFIG_PRECOMP_FLT_PARAMS,
+ index, SCARLETT2_BIQUAD_COEFFS,
+ &private->precomp_flt_values[index]);
+
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_put_during_autogain(mixer);
+ if (err < 0)
+ goto unlock;
+
+ /* 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)
+ goto unlock;
+
+ /* 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)
+ goto unlock;
+
+ err = scarlett2_usb_set_config_buf(
+ mixer, SCARLETT2_CONFIG_PEQ_FLT_PARAMS,
+ dst_index, SCARLETT2_BIQUAD_COEFFS,
+ &private->peq_flt_values[src_index]);
+
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->input_mute_updated) {
+ err = scarlett2_update_input_mute(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.integer.value[0] =
+ private->input_mute_switch[elem->control];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_put_during_autogain(mixer);
+ if (err < 0)
+ goto unlock;
+
+ oval = private->input_mute_switch[index];
+ val = ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ 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);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_input_phantom_updated(mixer);
+ if (err < 0)
+ goto unlock;
+
+ ucontrol->value.integer.value[0] = scarlett2_decode_muteable(
+ private->phantom_switch[elem->control]);
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_check_put_during_autogain(mixer);
+ if (err < 0)
+ goto unlock;
+
+ oval = private->phantom_switch[index];
+ val = !!ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ 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);
+ if (err == 0)
+ err = 1;
+
+ scarlett2_phantom_update_access(mixer);
+ scarlett2_phantom_notify_access(mixer);
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->phantom_persistence;
+ val = !!ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->phantom_persistence = val;
+
+ /* Send switch change to the device */
+ err = scarlett2_usb_set_config(
+ mixer, SCARLETT2_CONFIG_PHANTOM_PERSISTENCE, index, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->monitor_other_updated) {
+ err = scarlett2_update_monitor_other(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.enumerated.item[0] = private->speaker_switching_switch;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+/* 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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->speaker_switching_switch;
+ val = min(ucontrol->value.enumerated.item[0], 2U);
+
+ if (oval == val)
+ goto unlock;
+
+ 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)
+ goto unlock;
+
+ /* 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)
+ goto unlock;
+
+ /* 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);
+
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->monitor_other_updated) {
+ err = scarlett2_update_monitor_other(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.enumerated.item[0] = private->talkback_switch;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->talkback_switch;
+ val = min(ucontrol->value.enumerated.item[0], 2U);
+
+ if (oval == val)
+ goto unlock;
+
+ private->talkback_switch = val;
+
+ /* enable/disable talkback */
+ err = scarlett2_usb_set_config(
+ mixer, SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE,
+ 1, !!val);
+ if (err < 0)
+ goto unlock;
+
+ /* if talkback is enabled, select main or alt */
+ err = scarlett2_usb_set_config(
+ mixer, SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH,
+ 1, val == 2);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0, i;
+ u16 bitmap = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->talkback_map[index];
+ val = !!ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ 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);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->dim_mute_updated) {
+ err = scarlett2_update_dim_mute(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.integer.value[0] = private->dim_mute[elem->control];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0, i;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->dim_mute[index];
+ val = !!ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->dim_mute[index] = val;
+
+ /* Send switch change to the device */
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_DIM_MUTE,
+ index, val);
+ if (err == 0)
+ err = 1;
+
+ 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);
+ }
+ }
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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
+};
+
+/*** 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;
+ }
+
+ /* 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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->mix_updated) {
+ err = scarlett2_update_mix(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.integer.value[0] = private->mix[elem->control];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+ int index = elem->control;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ 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)
+ goto unlock;
+
+ private->mix[index] = val;
+ err = scarlett2_usb_set_mix(mixer, mix_num);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->direct_monitor_updated) {
+ err = scarlett2_update_direct_monitor(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.enumerated.item[0] = private->direct_monitor_switch;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->direct_monitor_switch;
+ val = min(ucontrol->value.enumerated.item[0], 2U);
+
+ if (oval == val)
+ goto unlock;
+
+ private->direct_monitor_switch = val;
+
+ /* Send switch change to the device */
+ err = scarlett2_usb_set_config(
+ mixer, SCARLETT2_CONFIG_DIRECT_MONITOR, index, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+ int index = elem->control;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->monitor_mix[index];
+ val = clamp(ucontrol->value.integer.value[0],
+ 0L, (long)SCARLETT2_MIXER_MAX_VALUE);
+
+ if (oval == val)
+ goto unlock;
+
+ private->monitor_mix[index] = val;
+ err = scarlett2_usb_set_config(
+ mixer, SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN,
+ index, scarlett2_mixer_values[val]);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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)
+ sprintf(uinfo->value.enumerated.name,
+ port->dsp_src_descr,
+ item - private->num_mix_out + 1);
+ else
+ sprintf(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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->mux_updated) {
+ err = scarlett2_usb_get_mux(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.enumerated.item[0] = private->mux[index];
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->mux[index];
+ val = min(ucontrol->value.enumerated.item[0],
+ private->num_mux_srcs - 1U);
+
+ if (oval == val)
+ goto unlock;
+
+ private->mux[index] = val;
+ err = scarlett2_usb_set_mux(mixer);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = scarlett2_usb_get_meter_levels(mixer, elem->channels,
+ meter_levels);
+ if (err < 0)
+ goto unlock;
+
+ /* 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;
+ }
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->msd_switch;
+ val = !!ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->msd_switch = val;
+
+ /* Send switch change to the device */
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_MSD_SWITCH,
+ 0, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->standalone_switch;
+ val = !!ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->standalone_switch = val;
+
+ /* Send switch change to the device */
+ err = scarlett2_usb_set_config(mixer,
+ SCARLETT2_CONFIG_STANDALONE_SWITCH,
+ 0, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->power_status_updated) {
+ err = scarlett2_update_power_status(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.integer.value[0] = private->power_status;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ if (private->bluetooth_updated) {
+ err = scarlett2_update_bluetooth_volume(mixer);
+ if (err < 0)
+ goto unlock;
+ }
+ ucontrol->value.integer.value[0] = private->bluetooth_volume;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ if (private->hwdep_in_use) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ oval = private->bluetooth_volume;
+ val = clamp(ucontrol->value.integer.value[0],
+ 0L, (long)SCARLETT2_MAX_BLUETOOTH_VOLUME);
+
+ if (oval == val)
+ goto unlock;
+
+ private->bluetooth_volume = val;
+ err = scarlett2_usb_set_config(mixer,
+ SCARLETT2_CONFIG_BLUETOOTH_VOLUME,
+ 0, val);
+ if (err == 0)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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 = 0;
+ int i;
+
+ mutex_lock(&private->data_mutex);
+
+ oval = private->spdif_mode;
+ val = ucontrol->value.enumerated.item[0];
+
+ if (val < 0) {
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ for (i = 0; i <= val; i++)
+ if (private->info->spdif_mode_values[i] == 0xff) {
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ if (oval == val)
+ goto unlock;
+
+ private->spdif_mode = val;
+
+ err = scarlett2_usb_set_config(
+ mixer, SCARLETT2_CONFIG_SPDIF_MODE, 0,
+ private->info->spdif_mode_values[val]);
+ if (!err)
+ err = 1;
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+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)
+{
+ mutex_lock(&private->data_mutex);
+ private->hwdep_in_use = 1;
+ mutex_unlock(&private->data_mutex);
+}
+
+/* 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;
+
+ /* 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)
+ goto error;
+
+ /* Copy the response to userspace */
+ if (copy_to_user(buf, resp, count)) {
+ err = -EFAULT;
+ goto error;
+ }
+
+ *offset += count;
+ err = count;
+
+error:
+ kfree(resp);
+ return err;
+}
+
+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;
+
+ /* 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)) {
+ err = -EFAULT;
+ goto error;
+ }
+
+ err = scarlett2_usb(mixer, SCARLETT2_USB_WRITE_SEGMENT,
+ req, len, NULL, 0);
+ if (err < 0)
+ goto error;
+
+ *offset += count;
+ err = count;
+
+error:
+ kfree(req);
+ return err;
+}
+
+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;
+ 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) {
+ kfree(resp_buf);
+ return copied ? copied : err;
+ }
+
+ if (copy_to_user(buf, resp_buf + offset, copy_size)) {
+ kfree(resp_buf);
+ return -EFAULT;
+ }
+
+ buf += copy_size;
+ pos += copy_size;
+ copied += copy_size;
+ count -= copy_size;
+ }
+
+ kfree(resp_buf);
+ 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_scarlett_gen2.c b/sound/usb/mixer_scarlett_gen2.c
deleted file mode 100644
index 0ffff7640892..000000000000
--- a/sound/usb/mixer_scarlett_gen2.c
+++ /dev/null
@@ -1,2075 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Focusrite Scarlett 6i6/18i8/18i20 Gen 2 Driver for ALSA
- *
- * Copyright (c) 2018-2019 by Geoffrey D. Bennett <g at b4.vu>
- *
- * 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>
- */
-
-/* Mixer Interface for the Focusrite Scarlett 6i6/18i8/18i20 Gen 2 audio
- * interface. Based on the Gen 1 driver and rewritten.
- */
-
-/* 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).
- *
- * This ALSA mixer gives access to:
- * - input, output, mixer-matrix muxes
- * - 18x10 mixer-matrix gain stages
- * - gain/volume controls
- * - level meters
- * - line/inst level and pad controls
- *
- * <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>
- *
- */
-
-#include <linux/slab.h>
-#include <linux/usb.h>
-#include <linux/moduleparam.h>
-
-#include <sound/control.h>
-#include <sound/tlv.h>
-
-#include "usbaudio.h"
-#include "mixer.h"
-#include "helper.h"
-
-#include "mixer_scarlett_gen2.h"
-
-/* device_setup value to enable */
-#define SCARLETT2_ENABLE 0x01
-
-/* some gui mixers can't handle negative ctl values */
-#define SCARLETT2_VOLUME_BIAS 127
-
-/* mixer range from -80dB to +6dB in 0.5dB steps */
-#define SCARLETT2_MIXER_MIN_DB -80
-#define SCARLETT2_MIXER_BIAS (-SCARLETT2_MIXER_MIN_DB * 2)
-#define SCARLETT2_MIXER_MAX_DB 6
-#define SCARLETT2_MIXER_MAX_VALUE \
- ((SCARLETT2_MIXER_MAX_DB - SCARLETT2_MIXER_MIN_DB) * 2)
-
-/* map from (dB + 80) * 2 to mixer value
- * for dB in 0 .. 172: int(8192 * pow(10, ((dB - 160) / 2 / 20)))
- */
-static const u16 scarlett2_mixer_values[173] = {
- 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
-};
-
-/* Maximum number of analogue outputs */
-#define SCARLETT2_ANALOGUE_MAX 10
-
-/* Maximum number of level and pad switches */
-#define SCARLETT2_LEVEL_SWITCH_MAX 2
-#define SCARLETT2_PAD_SWITCH_MAX 4
-
-/* Maximum number of inputs to the mixer */
-#define SCARLETT2_INPUT_MIX_MAX 18
-
-/* Maximum number of outputs from the mixer */
-#define SCARLETT2_OUTPUT_MIX_MAX 10
-
-/* Maximum size of the data in the USB mux assignment message:
- * 18 inputs, 20 outputs, 18 matrix inputs, 8 spare
- */
-#define SCARLETT2_MUX_MAX 64
-
-/* Number of meters:
- * 18 inputs, 20 outputs, 18 matrix inputs
- */
-#define SCARLETT2_NUM_METERS 56
-
-/* 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 = 0,
- SCARLETT2_PORT_TYPE_ANALOGUE = 1,
- SCARLETT2_PORT_TYPE_SPDIF = 2,
- SCARLETT2_PORT_TYPE_ADAT = 3,
- SCARLETT2_PORT_TYPE_MIX = 4,
- SCARLETT2_PORT_TYPE_PCM = 5,
- SCARLETT2_PORT_TYPE_COUNT = 6,
-};
-
-/* Count of total I/O and number available at each sample rate */
-enum {
- SCARLETT2_PORT_IN = 0,
- SCARLETT2_PORT_OUT = 1,
- SCARLETT2_PORT_OUT_44 = 2,
- SCARLETT2_PORT_OUT_88 = 3,
- SCARLETT2_PORT_OUT_176 = 4,
- SCARLETT2_PORT_DIRECTIONS = 5,
-};
-
-/* Hardware buttons on the 18i20 */
-#define SCARLETT2_BUTTON_MAX 2
-
-static const char *const scarlett2_button_names[SCARLETT2_BUTTON_MAX] = {
- "Mute", "Dim"
-};
-
-/* Description of each hardware port type:
- * - id: hardware ID for this port type
- * - num: number of sources/destinations 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_ports {
- u16 id;
- int num[SCARLETT2_PORT_DIRECTIONS];
- const char * const src_descr;
- int src_num_offset;
- const char * const dst_descr;
-};
-
-struct scarlett2_device_info {
- u8 line_out_hw_vol; /* line out hw volume is sw controlled */
- u8 button_count; /* number of buttons */
- u8 level_input_count; /* inputs with level selectable */
- u8 pad_input_count; /* inputs with pad selectable */
- const char * const line_out_descrs[SCARLETT2_ANALOGUE_MAX];
- struct scarlett2_ports ports[SCARLETT2_PORT_TYPE_COUNT];
-};
-
-struct scarlett2_mixer_data {
- struct usb_mixer_interface *mixer;
- struct mutex usb_mutex; /* prevent sending concurrent USB requests */
- struct mutex data_mutex; /* lock access to this data */
- struct delayed_work work;
- const struct scarlett2_device_info *info;
- int num_mux_srcs;
- u16 scarlett2_seq;
- u8 vol_updated;
- u8 master_vol;
- u8 vol[SCARLETT2_ANALOGUE_MAX];
- u8 vol_sw_hw_switch[SCARLETT2_ANALOGUE_MAX];
- u8 level_switch[SCARLETT2_LEVEL_SWITCH_MAX];
- u8 pad_switch[SCARLETT2_PAD_SWITCH_MAX];
- u8 buttons[SCARLETT2_BUTTON_MAX];
- struct snd_kcontrol *master_vol_ctl;
- struct snd_kcontrol *vol_ctls[SCARLETT2_ANALOGUE_MAX];
- struct snd_kcontrol *button_ctls[SCARLETT2_BUTTON_MAX];
- u8 mux[SCARLETT2_MUX_MAX];
- u8 mix[SCARLETT2_INPUT_MIX_MAX * SCARLETT2_OUTPUT_MIX_MAX];
-};
-
-/*** Model-specific data ***/
-
-static const struct scarlett2_device_info s6i6_gen2_info = {
- /* The first two analogue inputs can be switched between line
- * and instrument levels.
- */
- .level_input_count = 2,
-
- /* The first two analogue inputs have an optional pad. */
- .pad_input_count = 2,
-
- .line_out_descrs = {
- "Monitor L",
- "Monitor R",
- "Headphones L",
- "Headphones R",
- },
-
- .ports = {
- [SCARLETT2_PORT_TYPE_NONE] = {
- .id = 0x000,
- .num = { 1, 0, 8, 8, 8 },
- .src_descr = "Off",
- .src_num_offset = 0,
- },
- [SCARLETT2_PORT_TYPE_ANALOGUE] = {
- .id = 0x080,
- .num = { 4, 4, 4, 4, 4 },
- .src_descr = "Analogue %d",
- .src_num_offset = 1,
- .dst_descr = "Analogue Output %02d Playback"
- },
- [SCARLETT2_PORT_TYPE_SPDIF] = {
- .id = 0x180,
- .num = { 2, 2, 2, 2, 2 },
- .src_descr = "S/PDIF %d",
- .src_num_offset = 1,
- .dst_descr = "S/PDIF Output %d Playback"
- },
- [SCARLETT2_PORT_TYPE_MIX] = {
- .id = 0x300,
- .num = { 10, 18, 18, 18, 18 },
- .src_descr = "Mix %c",
- .src_num_offset = 65,
- .dst_descr = "Mixer Input %02d Capture"
- },
- [SCARLETT2_PORT_TYPE_PCM] = {
- .id = 0x600,
- .num = { 6, 6, 6, 6, 6 },
- .src_descr = "PCM %d",
- .src_num_offset = 1,
- .dst_descr = "PCM %02d Capture"
- },
- },
-};
-
-static const struct scarlett2_device_info s18i8_gen2_info = {
- /* The first two analogue inputs can be switched between line
- * and instrument levels.
- */
- .level_input_count = 2,
-
- /* The first four analogue inputs have an optional pad. */
- .pad_input_count = 4,
-
- .line_out_descrs = {
- "Monitor L",
- "Monitor R",
- "Headphones 1 L",
- "Headphones 1 R",
- "Headphones 2 L",
- "Headphones 2 R",
- },
-
- .ports = {
- [SCARLETT2_PORT_TYPE_NONE] = {
- .id = 0x000,
- .num = { 1, 0, 8, 8, 4 },
- .src_descr = "Off",
- .src_num_offset = 0,
- },
- [SCARLETT2_PORT_TYPE_ANALOGUE] = {
- .id = 0x080,
- .num = { 8, 6, 6, 6, 6 },
- .src_descr = "Analogue %d",
- .src_num_offset = 1,
- .dst_descr = "Analogue Output %02d Playback"
- },
- [SCARLETT2_PORT_TYPE_SPDIF] = {
- .id = 0x180,
- /* S/PDIF outputs aren't available at 192kHz
- * but are included in the USB mux I/O
- * assignment message anyway
- */
- .num = { 2, 2, 2, 2, 2 },
- .src_descr = "S/PDIF %d",
- .src_num_offset = 1,
- .dst_descr = "S/PDIF Output %d Playback"
- },
- [SCARLETT2_PORT_TYPE_ADAT] = {
- .id = 0x200,
- .num = { 8, 0, 0, 0, 0 },
- .src_descr = "ADAT %d",
- .src_num_offset = 1,
- },
- [SCARLETT2_PORT_TYPE_MIX] = {
- .id = 0x300,
- .num = { 10, 18, 18, 18, 18 },
- .src_descr = "Mix %c",
- .src_num_offset = 65,
- .dst_descr = "Mixer Input %02d Capture"
- },
- [SCARLETT2_PORT_TYPE_PCM] = {
- .id = 0x600,
- .num = { 20, 18, 18, 14, 10 },
- .src_descr = "PCM %d",
- .src_num_offset = 1,
- .dst_descr = "PCM %02d Capture"
- },
- },
-};
-
-static const struct scarlett2_device_info s18i20_gen2_info = {
- /* The analogue line outputs on the 18i20 can be switched
- * between software and hardware volume control
- */
- .line_out_hw_vol = 1,
-
- /* Mute and dim buttons */
- .button_count = 2,
-
- .line_out_descrs = {
- "Monitor L",
- "Monitor R",
- NULL,
- NULL,
- NULL,
- NULL,
- "Headphones 1 L",
- "Headphones 1 R",
- "Headphones 2 L",
- "Headphones 2 R",
- },
-
- .ports = {
- [SCARLETT2_PORT_TYPE_NONE] = {
- .id = 0x000,
- .num = { 1, 0, 8, 8, 6 },
- .src_descr = "Off",
- .src_num_offset = 0,
- },
- [SCARLETT2_PORT_TYPE_ANALOGUE] = {
- .id = 0x080,
- .num = { 8, 10, 10, 10, 10 },
- .src_descr = "Analogue %d",
- .src_num_offset = 1,
- .dst_descr = "Analogue Output %02d Playback"
- },
- [SCARLETT2_PORT_TYPE_SPDIF] = {
- /* S/PDIF outputs aren't available at 192kHz
- * but are included in the USB mux I/O
- * assignment message anyway
- */
- .id = 0x180,
- .num = { 2, 2, 2, 2, 2 },
- .src_descr = "S/PDIF %d",
- .src_num_offset = 1,
- .dst_descr = "S/PDIF Output %d Playback"
- },
- [SCARLETT2_PORT_TYPE_ADAT] = {
- .id = 0x200,
- .num = { 8, 8, 8, 4, 0 },
- .src_descr = "ADAT %d",
- .src_num_offset = 1,
- .dst_descr = "ADAT Output %d Playback"
- },
- [SCARLETT2_PORT_TYPE_MIX] = {
- .id = 0x300,
- .num = { 10, 18, 18, 18, 18 },
- .src_descr = "Mix %c",
- .src_num_offset = 65,
- .dst_descr = "Mixer Input %02d Capture"
- },
- [SCARLETT2_PORT_TYPE_PCM] = {
- .id = 0x600,
- .num = { 20, 18, 18, 14, 10 },
- .src_descr = "PCM %d",
- .src_num_offset = 1,
- .dst_descr = "PCM %02d Capture"
- },
- },
-};
-
-/* get the starting port index number for a given port type/direction */
-static int scarlett2_get_port_start_num(const struct scarlett2_ports *ports,
- int direction, int port_type)
-{
- int i, num = 0;
-
- for (i = 0; i < port_type; i++)
- num += ports[i].num[direction];
-
- return num;
-}
-
-/*** USB Interactions ***/
-
-/* Vendor-Specific Interface, Endpoint, MaxPacketSize, Interval */
-#define SCARLETT2_USB_VENDOR_SPECIFIC_INTERFACE 5
-#define SCARLETT2_USB_INTERRUPT_ENDPOINT 4
-#define SCARLETT2_USB_INTERRUPT_MAX_DATA 64
-#define SCARLETT2_USB_INTERRUPT_INTERVAL 3
-
-/* Interrupt flags for volume and mute/dim button changes */
-#define SCARLETT2_USB_INTERRUPT_VOL_CHANGE 0x400000
-#define SCARLETT2_USB_INTERRUPT_BUTTON_CHANGE 0x200000
-
-/* Commands for sending/receiving requests/responses */
-#define SCARLETT2_USB_VENDOR_SPECIFIC_CMD_REQ 2
-#define SCARLETT2_USB_VENDOR_SPECIFIC_CMD_RESP 3
-
-#define SCARLETT2_USB_INIT_SEQ 0x00000000
-#define SCARLETT2_USB_GET_METER_LEVELS 0x00001001
-#define SCARLETT2_USB_SET_MIX 0x00002002
-#define SCARLETT2_USB_SET_MUX 0x00003002
-#define SCARLETT2_USB_GET_DATA 0x00800000
-#define SCARLETT2_USB_SET_DATA 0x00800001
-#define SCARLETT2_USB_DATA_CMD 0x00800002
-#define SCARLETT2_USB_CONFIG_SAVE 6
-
-#define SCARLETT2_USB_VOLUME_STATUS_OFFSET 0x31
-#define SCARLETT2_USB_METER_LEVELS_GET_MAGIC 1
-
-/* volume status is read together (matches scarlett2_config_items[]) */
-struct scarlett2_usb_volume_status {
- /* mute & dim buttons */
- u8 buttons[SCARLETT2_BUTTON_MAX];
-
- u8 pad1;
-
- /* software volume setting */
- s16 sw_vol[SCARLETT2_ANALOGUE_MAX];
-
- /* actual volume of output inc. dim (-18dB) */
- s16 hw_vol[SCARLETT2_ANALOGUE_MAX];
-
- u8 pad2[SCARLETT2_ANALOGUE_MAX];
-
- /* sw (0) or hw (1) controlled */
- u8 sw_hw_switch[SCARLETT2_ANALOGUE_MAX];
-
- u8 pad3[6];
-
- /* front panel volume knob */
- s16 master_vol;
-} __packed;
-
-/* Configuration parameters that can be read and written */
-enum {
- SCARLETT2_CONFIG_BUTTONS = 0,
- SCARLETT2_CONFIG_LINE_OUT_VOLUME = 1,
- SCARLETT2_CONFIG_SW_HW_SWITCH = 2,
- SCARLETT2_CONFIG_LEVEL_SWITCH = 3,
- SCARLETT2_CONFIG_PAD_SWITCH = 4,
- SCARLETT2_CONFIG_COUNT = 5
-};
-
-/* Location, size, and activation command number for the configuration
- * parameters
- */
-struct scarlett2_config {
- u8 offset;
- u8 size;
- u8 activate;
-};
-
-static const struct scarlett2_config
- scarlett2_config_items[SCARLETT2_CONFIG_COUNT] = {
- /* Mute/Dim Buttons */
- {
- .offset = 0x31,
- .size = 1,
- .activate = 2
- },
-
- /* Line Out Volume */
- {
- .offset = 0x34,
- .size = 2,
- .activate = 1
- },
-
- /* SW/HW Volume Switch */
- {
- .offset = 0x66,
- .size = 1,
- .activate = 3
- },
-
- /* Level Switch */
- {
- .offset = 0x7c,
- .size = 1,
- .activate = 7
- },
-
- /* Pad Switch */
- {
- .offset = 0x84,
- .size = 1,
- .activate = 8
- }
-};
-
-/* proprietary request/response format */
-struct scarlett2_usb_packet {
- __le32 cmd;
- __le16 size;
- __le16 seq;
- __le32 error;
- __le32 pad;
- u8 data[];
-};
-
-#define SCARLETT2_USB_PACKET_LEN (sizeof(struct scarlett2_usb_packet))
-
-static void scarlett2_fill_request_header(struct scarlett2_mixer_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;
-}
-
-/* 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_mixer_data *private = mixer->private_data;
- u16 req_buf_size = sizeof(struct scarlett2_usb_packet) + req_size;
- u16 resp_buf_size = sizeof(struct scarlett2_usb_packet) + resp_size;
- struct scarlett2_usb_packet *req = NULL, *resp = NULL;
- int err = 0;
-
- req = kmalloc(req_buf_size, GFP_KERNEL);
- if (!req) {
- err = -ENOMEM;
- goto error;
- }
-
- resp = kmalloc(resp_buf_size, GFP_KERNEL);
- if (!resp) {
- err = -ENOMEM;
- goto error;
- }
-
- mutex_lock(&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);
-
- err = snd_usb_ctl_msg(mixer->chip->dev,
- usb_sndctrlpipe(mixer->chip->dev, 0),
- SCARLETT2_USB_VENDOR_SPECIFIC_CMD_REQ,
- USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
- 0,
- SCARLETT2_USB_VENDOR_SPECIFIC_INTERFACE,
- req,
- req_buf_size);
-
- if (err != req_buf_size) {
- usb_audio_err(
- mixer->chip,
- "Scarlett Gen 2 USB request result cmd %x was %d\n",
- cmd, err);
- err = -EINVAL;
- goto unlock;
- }
-
- /* send a second message to get the response */
-
- err = snd_usb_ctl_msg(mixer->chip->dev,
- usb_sndctrlpipe(mixer->chip->dev, 0),
- SCARLETT2_USB_VENDOR_SPECIFIC_CMD_RESP,
- USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
- 0,
- SCARLETT2_USB_VENDOR_SPECIFIC_INTERFACE,
- resp,
- resp_buf_size);
-
- /* validate the response */
-
- if (err != resp_buf_size) {
- usb_audio_err(
- mixer->chip,
- "Scarlett Gen 2 USB response result cmd %x was %d\n",
- cmd, err);
- err = -EINVAL;
- goto unlock;
- }
-
- if (resp->cmd != req->cmd ||
- resp->seq != req->seq ||
- resp_size != le16_to_cpu(resp->size) ||
- resp->error ||
- resp->pad) {
- usb_audio_err(
- mixer->chip,
- "Scarlett Gen 2 USB invalid response; "
- "cmd tx/rx %d/%d seq %d/%d size %d/%d "
- "error %d pad %d\n",
- 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));
- err = -EINVAL;
- goto unlock;
- }
-
- if (resp_size > 0)
- memcpy(resp_data, resp->data, resp_size);
-
-unlock:
- mutex_unlock(&private->usb_mutex);
-error:
- kfree(req);
- kfree(resp);
- return err;
-}
-
-/* Send SCARLETT2_USB_DATA_CMD SCARLETT2_USB_CONFIG_SAVE */
-static void scarlett2_config_save(struct usb_mixer_interface *mixer)
-{
- __le32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE);
-
- scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD,
- &req, sizeof(u32),
- NULL, 0);
-}
-
-/* Delayed work to save config */
-static void scarlett2_config_save_work(struct work_struct *work)
-{
- struct scarlett2_mixer_data *private =
- container_of(work, struct scarlett2_mixer_data, work.work);
-
- scarlett2_config_save(private->mixer);
-}
-
-/* Send a USB message to set a configuration parameter (volume level,
- * sw/hw volume switch, line/inst level switch, or pad switch)
- */
-static int scarlett2_usb_set_config(
- struct usb_mixer_interface *mixer,
- int config_item_num, int index, int value)
-{
- const struct scarlett2_config config_item =
- scarlett2_config_items[config_item_num];
- struct {
- __le32 offset;
- __le32 bytes;
- __le32 value;
- } __packed req;
- __le32 req2;
- int err;
- struct scarlett2_mixer_data *private = mixer->private_data;
-
- /* Cancel any pending NVRAM save */
- cancel_delayed_work_sync(&private->work);
-
- /* Send the configuration parameter data */
- req.offset = cpu_to_le32(config_item.offset + index * config_item.size);
- req.bytes = cpu_to_le32(config_item.size);
- req.value = cpu_to_le32(value);
- err = scarlett2_usb(mixer, SCARLETT2_USB_SET_DATA,
- &req, sizeof(u32) * 2 + config_item.size,
- NULL, 0);
- if (err < 0)
- return err;
-
- /* Activate the change */
- req2 = cpu_to_le32(config_item.activate);
- err = scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD,
- &req2, sizeof(req2), NULL, 0);
- if (err < 0)
- return err;
-
- /* Schedule the change to be written to NVRAM */
- schedule_delayed_work(&private->work, msecs_to_jiffies(2000));
-
- return 0;
-}
-
-/* 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);
-}
-
-/* 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)
-{
- const struct scarlett2_config config_item =
- scarlett2_config_items[config_item_num];
- int size = config_item.size * count;
-
- return scarlett2_usb_get(mixer, config_item.offset, buf, size);
-}
-
-/* Send a USB message to get volume status; result placed in *buf */
-static int scarlett2_usb_get_volume_status(
- struct usb_mixer_interface *mixer,
- struct scarlett2_usb_volume_status *buf)
-{
- return scarlett2_usb_get(mixer, SCARLETT2_USB_VOLUME_STATUS_OFFSET,
- buf, sizeof(*buf));
-}
-
-/* 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_mixer_data *private = mixer->private_data;
- const struct scarlett2_device_info *info = private->info;
-
- struct {
- __le16 mix_num;
- __le16 data[SCARLETT2_INPUT_MIX_MAX];
- } __packed req;
-
- int i, j;
- int num_mixer_in =
- info->ports[SCARLETT2_PORT_TYPE_MIX].num[SCARLETT2_PORT_OUT];
-
- 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->ports) to a hardware ID */
-static u32 scarlett2_mux_src_num_to_id(const struct scarlett2_ports *ports,
- int num)
-{
- int port_type;
-
- for (port_type = 0;
- port_type < SCARLETT2_PORT_TYPE_COUNT;
- port_type++) {
- if (num < ports[port_type].num[SCARLETT2_PORT_IN])
- return ports[port_type].id | num;
- num -= ports[port_type].num[SCARLETT2_PORT_IN];
- }
-
- /* Oops */
- return 0;
-}
-
-/* Send USB messages to set mux inputs */
-static int scarlett2_usb_set_mux(struct usb_mixer_interface *mixer)
-{
- struct scarlett2_mixer_data *private = mixer->private_data;
- const struct scarlett2_device_info *info = private->info;
- const struct scarlett2_ports *ports = info->ports;
- int rate, port_dir_rate;
-
- static const int assignment_order[SCARLETT2_PORT_TYPE_COUNT] = {
- SCARLETT2_PORT_TYPE_PCM,
- SCARLETT2_PORT_TYPE_ANALOGUE,
- SCARLETT2_PORT_TYPE_SPDIF,
- SCARLETT2_PORT_TYPE_ADAT,
- SCARLETT2_PORT_TYPE_MIX,
- SCARLETT2_PORT_TYPE_NONE,
- };
-
- struct {
- __le16 pad;
- __le16 num;
- __le32 data[SCARLETT2_MUX_MAX];
- } __packed req;
-
- req.pad = 0;
-
- /* mux settings for each rate */
- for (rate = 0, port_dir_rate = SCARLETT2_PORT_OUT_44;
- port_dir_rate <= SCARLETT2_PORT_OUT_176;
- rate++, port_dir_rate++) {
- int order_num, i, err;
-
- req.num = cpu_to_le16(rate);
-
- for (order_num = 0, i = 0;
- order_num < SCARLETT2_PORT_TYPE_COUNT;
- order_num++) {
- int port_type = assignment_order[order_num];
- int j = scarlett2_get_port_start_num(ports,
- SCARLETT2_PORT_OUT,
- port_type);
- int port_id = ports[port_type].id;
- int channel;
-
- for (channel = 0;
- channel < ports[port_type].num[port_dir_rate];
- channel++, i++, j++)
- /* lower 12 bits for the destination and
- * next 12 bits for the source
- */
- req.data[i] = !port_id
- ? 0
- : cpu_to_le32(
- port_id |
- channel |
- scarlett2_mux_src_num_to_id(
- ports, private->mux[j]
- ) << 12
- );
-
- /* skip private->mux[j] entries not output */
- j += ports[port_type].num[SCARLETT2_PORT_OUT] -
- ports[port_type].num[port_dir_rate];
- }
-
- err = scarlett2_usb(mixer, SCARLETT2_USB_SET_MUX,
- &req, (i + 1) * sizeof(u32),
- NULL, 0);
- if (err < 0)
- return err;
- }
-
- return 0;
-}
-
-/* Send USB message to get meter levels */
-static int scarlett2_usb_get_meter_levels(struct usb_mixer_interface *mixer,
- u16 *levels)
-{
- struct {
- __le16 pad;
- __le16 num_meters;
- __le32 magic;
- } __packed req;
- u32 resp[SCARLETT2_NUM_METERS];
- int i, err;
-
- req.pad = 0;
- req.num_meters = cpu_to_le16(SCARLETT2_NUM_METERS);
- req.magic = cpu_to_le32(SCARLETT2_USB_METER_LEVELS_GET_MAGIC);
- err = scarlett2_usb(mixer, SCARLETT2_USB_GET_METER_LEVELS,
- &req, sizeof(req), resp, sizeof(resp));
- if (err < 0)
- return err;
-
- /* copy, convert to u16 */
- for (i = 0; i < SCARLETT2_NUM_METERS; i++)
- levels[i] = resp[i];
-
- return 0;
-}
-
-/*** 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;
-
- elem->head.mixer = mixer;
- elem->control = index;
- elem->head.id = index;
- elem->channels = channels;
-
- kctl = snd_ctl_new1(ncontrol, elem);
- if (!kctl) {
- kfree(elem);
- return -ENOMEM;
- }
- kctl->private_free = snd_usb_mixer_elem_free;
-
- strlcpy(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;
-}
-
-/*** 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_mixer_data *private = mixer->private_data;
- const struct scarlett2_ports *ports = private->info->ports;
- struct scarlett2_usb_volume_status volume_status;
- int num_line_out =
- ports[SCARLETT2_PORT_TYPE_ANALOGUE].num[SCARLETT2_PORT_OUT];
- int err, i;
-
- private->vol_updated = 0;
-
- err = scarlett2_usb_get_volume_status(mixer, &volume_status);
- if (err < 0)
- return err;
-
- private->master_vol = clamp(
- volume_status.master_vol + SCARLETT2_VOLUME_BIAS,
- 0, SCARLETT2_VOLUME_BIAS);
-
- for (i = 0; i < num_line_out; i++) {
- if (private->vol_sw_hw_switch[i])
- private->vol[i] = private->master_vol;
- }
-
- for (i = 0; i < private->info->button_count; i++)
- private->buttons[i] = !!volume_status.buttons[i];
-
- 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_mixer_data *private = mixer->private_data;
-
- if (private->vol_updated) {
- mutex_lock(&private->data_mutex);
- scarlett2_update_volumes(mixer);
- mutex_unlock(&private->data_mutex);
- }
-
- ucontrol->value.integer.value[0] = private->master_vol;
- return 0;
-}
-
-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_mixer_data *private = mixer->private_data;
- int index = elem->control;
-
- if (private->vol_updated) {
- mutex_lock(&private->data_mutex);
- scarlett2_update_volumes(mixer);
- mutex_unlock(&private->data_mutex);
- }
-
- 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_mixer_data *private = mixer->private_data;
- int index = elem->control;
- int oval, val, err = 0;
-
- mutex_lock(&private->data_mutex);
-
- oval = private->vol[index];
- val = ucontrol->value.integer.value[0];
-
- if (oval == val)
- goto unlock;
-
- private->vol[index] = val;
- err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_LINE_OUT_VOLUME,
- index, val - SCARLETT2_VOLUME_BIAS);
- if (err == 0)
- err = 1;
-
-unlock:
- mutex_unlock(&private->data_mutex);
- return err;
-}
-
-static const DECLARE_TLV_DB_MINMAX(
- db_scale_scarlett2_gain, -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_gain }
-};
-
-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_gain }
-};
-
-/*** HW/SW Volume Switch Controls ***/
-
-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_mixer_data *private = elem->head.mixer->private_data;
-
- ucontrol->value.enumerated.item[0] =
- private->vol_sw_hw_switch[elem->control];
- return 0;
-}
-
-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_mixer_data *private = mixer->private_data;
-
- int index = elem->control;
- int oval, val, err = 0;
-
- mutex_lock(&private->data_mutex);
-
- oval = private->vol_sw_hw_switch[index];
- val = !!ucontrol->value.integer.value[0];
-
- if (oval == val)
- goto unlock;
-
- private->vol_sw_hw_switch[index] = val;
-
- /* Change access mode to RO (hardware controlled volume)
- * or RW (software controlled volume)
- */
- if (val)
- private->vol_ctls[index]->vd[0].access &=
- ~SNDRV_CTL_ELEM_ACCESS_WRITE;
- else
- private->vol_ctls[index]->vd[0].access |=
- SNDRV_CTL_ELEM_ACCESS_WRITE;
-
- /* Reset volume to master volume */
- private->vol[index] = private->master_vol;
-
- /* 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)
- goto unlock;
-
- /* Notify of RO/RW change */
- snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_INFO,
- &private->vol_ctls[index]->id);
-
- /* Send SW/HW switch change to the device */
- err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_SW_HW_SWITCH,
- index, val);
-
-unlock:
- mutex_unlock(&private->data_mutex);
- return err;
-}
-
-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_level_enum_ctl_info(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_info *uinfo)
-{
- static const char *const values[2] = {
- "Line", "Inst"
- };
-
- 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 scarlett2_mixer_data *private = elem->head.mixer->private_data;
-
- ucontrol->value.enumerated.item[0] =
- private->level_switch[elem->control];
- 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_mixer_data *private = mixer->private_data;
-
- int index = elem->control;
- int oval, val, err = 0;
-
- mutex_lock(&private->data_mutex);
-
- oval = private->level_switch[index];
- val = !!ucontrol->value.integer.value[0];
-
- if (oval == val)
- goto unlock;
-
- private->level_switch[index] = val;
-
- /* Send switch change to the device */
- err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_LEVEL_SWITCH,
- index, val);
-
-unlock:
- mutex_unlock(&private->data_mutex);
- return err;
-}
-
-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_pad_ctl_get(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ucontrol)
-{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct scarlett2_mixer_data *private = elem->head.mixer->private_data;
-
- ucontrol->value.enumerated.item[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_mixer_data *private = mixer->private_data;
-
- int index = elem->control;
- int oval, val, err = 0;
-
- mutex_lock(&private->data_mutex);
-
- oval = private->pad_switch[index];
- val = !!ucontrol->value.integer.value[0];
-
- if (oval == val)
- goto unlock;
-
- private->pad_switch[index] = val;
-
- /* Send switch change to the device */
- err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_PAD_SWITCH,
- index, val);
-
-unlock:
- mutex_unlock(&private->data_mutex);
- return err;
-}
-
-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,
-};
-
-/*** Mute/Dim Controls ***/
-
-static int scarlett2_button_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_mixer_data *private = mixer->private_data;
-
- if (private->vol_updated) {
- mutex_lock(&private->data_mutex);
- scarlett2_update_volumes(mixer);
- mutex_unlock(&private->data_mutex);
- }
-
- ucontrol->value.enumerated.item[0] = private->buttons[elem->control];
- return 0;
-}
-
-static int scarlett2_button_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_mixer_data *private = mixer->private_data;
-
- int index = elem->control;
- int oval, val, err = 0;
-
- mutex_lock(&private->data_mutex);
-
- oval = private->buttons[index];
- val = !!ucontrol->value.integer.value[0];
-
- if (oval == val)
- goto unlock;
-
- private->buttons[index] = val;
-
- /* Send switch change to the device */
- err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_BUTTONS,
- index, val);
-
-unlock:
- mutex_unlock(&private->data_mutex);
- return err;
-}
-
-static const struct snd_kcontrol_new scarlett2_button_ctl = {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .name = "",
- .info = snd_ctl_boolean_mono_info,
- .get = scarlett2_button_ctl_get,
- .put = scarlett2_button_ctl_put
-};
-
-/*** Create the analogue output controls ***/
-
-static int scarlett2_add_line_out_ctls(struct usb_mixer_interface *mixer)
-{
- struct scarlett2_mixer_data *private = mixer->private_data;
- const struct scarlett2_device_info *info = private->info;
- const struct scarlett2_ports *ports = info->ports;
- int num_line_out =
- ports[SCARLETT2_PORT_TYPE_ANALOGUE].num[SCARLETT2_PORT_OUT];
- int err, i;
- char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
-
- /* Add R/O HW volume control */
- if (info->line_out_hw_vol) {
- 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 volume controls */
- for (i = 0; i < num_line_out; 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;
-
- /* Make the fader read-only if the SW/HW switch is set to HW */
- if (private->vol_sw_hw_switch[i])
- private->vol_ctls[i]->vd[0].access &=
- ~SNDRV_CTL_ELEM_ACCESS_WRITE;
-
- /* SW/HW Switch */
- if (info->line_out_hw_vol) {
- snprintf(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, NULL);
- if (err < 0)
- return err;
- }
- }
-
- /* Add HW button controls */
- for (i = 0; i < private->info->button_count; i++) {
- err = scarlett2_add_new_ctl(mixer, &scarlett2_button_ctl,
- i, 1, scarlett2_button_names[i],
- &private->button_ctls[i]);
- if (err < 0)
- return err;
- }
-
- return 0;
-}
-
-/*** Create the analogue input controls ***/
-
-static int scarlett2_add_line_in_ctls(struct usb_mixer_interface *mixer)
-{
- struct scarlett2_mixer_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 input level (line/inst) controls */
- for (i = 0; i < info->level_input_count; i++) {
- snprintf(s, sizeof(s), "Line In %d Level Capture Enum", i + 1);
- err = scarlett2_add_new_ctl(mixer, &scarlett2_level_enum_ctl,
- i, 1, s, NULL);
- if (err < 0)
- return err;
- }
-
- /* Add input pad controls */
- for (i = 0; i < info->pad_input_count; i++) {
- snprintf(s, sizeof(s), "Line In %d Pad Capture Switch", i + 1);
- err = scarlett2_add_new_ctl(mixer, &scarlett2_pad_ctl,
- i, 1, s, NULL);
- if (err < 0)
- return err;
- }
-
- return 0;
-}
-
-/*** Mixer Volume Controls ***/
-
-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 scarlett2_mixer_data *private = elem->head.mixer->private_data;
-
- 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_mixer_data *private = mixer->private_data;
- const struct scarlett2_device_info *info = private->info;
- const struct scarlett2_ports *ports = info->ports;
- int oval, val, num_mixer_in, mix_num, err = 0;
-
- mutex_lock(&private->data_mutex);
-
- oval = private->mix[elem->control];
- val = ucontrol->value.integer.value[0];
- num_mixer_in = ports[SCARLETT2_PORT_TYPE_MIX].num[SCARLETT2_PORT_OUT];
- mix_num = elem->control / num_mixer_in;
-
- if (oval == val)
- goto unlock;
-
- private->mix[elem->control] = val;
- err = scarlett2_usb_set_mix(mixer, mix_num);
- if (err == 0)
- err = 1;
-
-unlock:
- mutex_unlock(&private->data_mutex);
- return err;
-}
-
-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_mixer_data *private = mixer->private_data;
- const struct scarlett2_ports *ports = private->info->ports;
- int err, i, j;
- int index;
- char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
-
- int num_inputs = ports[SCARLETT2_PORT_TYPE_MIX].num[SCARLETT2_PORT_OUT];
- int num_outputs = ports[SCARLETT2_PORT_TYPE_MIX].num[SCARLETT2_PORT_IN];
-
- for (i = 0, index = 0; i < num_outputs; i++) {
- for (j = 0; j < num_inputs; 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, 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_mixer_data *private = elem->head.mixer->private_data;
- const struct scarlett2_ports *ports = private->info->ports;
- 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 < ports[port_type].num[SCARLETT2_PORT_IN]) {
- sprintf(uinfo->value.enumerated.name,
- ports[port_type].src_descr,
- item + ports[port_type].src_num_offset);
- return 0;
- }
- item -= ports[port_type].num[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 scarlett2_mixer_data *private = elem->head.mixer->private_data;
-
- ucontrol->value.enumerated.item[0] = private->mux[elem->control];
- 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_mixer_data *private = mixer->private_data;
- int index = elem->control;
- int oval, val, err = 0;
-
- mutex_lock(&private->data_mutex);
-
- oval = private->mux[index];
- val = clamp(ucontrol->value.integer.value[0],
- 0L, private->num_mux_srcs - 1L);
-
- if (oval == val)
- goto unlock;
-
- private->mux[index] = val;
- err = scarlett2_usb_set_mux(mixer);
- if (err == 0)
- err = 1;
-
-unlock:
- mutex_unlock(&private->data_mutex);
- return err;
-}
-
-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_mixer_data *private = mixer->private_data;
- const struct scarlett2_ports *ports = private->info->ports;
- int port_type, channel, i;
-
- for (i = 0, port_type = 0;
- port_type < SCARLETT2_PORT_TYPE_COUNT;
- port_type++) {
- for (channel = 0;
- channel < ports[port_type].num[SCARLETT2_PORT_OUT];
- channel++, i++) {
- int err;
- char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
- const char *const descr = ports[port_type].dst_descr;
-
- snprintf(s, sizeof(s) - 5, descr, channel + 1);
- strcat(s, " Enum");
-
- err = scarlett2_add_new_ctl(mixer,
- &scarlett2_mux_src_enum_ctl,
- i, 1, s, NULL);
- 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;
- u16 meter_levels[SCARLETT2_NUM_METERS];
- int i, err;
-
- err = scarlett2_usb_get_meter_levels(elem->head.mixer, meter_levels);
- if (err < 0)
- return err;
-
- for (i = 0; i < elem->channels; i++)
- ucontrol->value.integer.value[i] = meter_levels[i];
-
- 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)
-{
- return scarlett2_add_new_ctl(mixer, &scarlett2_meter_ctl,
- 0, SCARLETT2_NUM_METERS,
- "Level Meter", NULL);
-}
-
-/*** Cleanup/Suspend Callbacks ***/
-
-static void scarlett2_private_free(struct usb_mixer_interface *mixer)
-{
- struct scarlett2_mixer_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_mixer_data *private = mixer->private_data;
-
- if (cancel_delayed_work_sync(&private->work))
- scarlett2_config_save(private->mixer);
-}
-
-/*** Initialisation ***/
-
-static int scarlett2_count_mux_srcs(const struct scarlett2_ports *ports)
-{
- int port_type, count = 0;
-
- for (port_type = 0;
- port_type < SCARLETT2_PORT_TYPE_COUNT;
- port_type++)
- count += ports[port_type].num[SCARLETT2_PORT_IN];
-
- return count;
-}
-
-/* Default routing connects PCM outputs and inputs to Analogue,
- * S/PDIF, then ADAT
- */
-static void scarlett2_init_routing(u8 *mux,
- const struct scarlett2_ports *ports)
-{
- int i, input_num, input_count, port_type;
- int output_num, output_count, port_type_connect_num;
-
- static const int connect_order[] = {
- SCARLETT2_PORT_TYPE_ANALOGUE,
- SCARLETT2_PORT_TYPE_SPDIF,
- SCARLETT2_PORT_TYPE_ADAT,
- -1
- };
-
- /* Assign PCM inputs (routing outputs) */
- output_num = scarlett2_get_port_start_num(ports,
- SCARLETT2_PORT_OUT,
- SCARLETT2_PORT_TYPE_PCM);
- output_count = ports[SCARLETT2_PORT_TYPE_PCM].num[SCARLETT2_PORT_OUT];
-
- for (port_type = connect_order[port_type_connect_num = 0];
- port_type >= 0;
- port_type = connect_order[++port_type_connect_num]) {
- input_num = scarlett2_get_port_start_num(
- ports, SCARLETT2_PORT_IN, port_type);
- input_count = ports[port_type].num[SCARLETT2_PORT_IN];
- for (i = 0;
- i < input_count && output_count;
- i++, output_count--)
- mux[output_num++] = input_num++;
- }
-
- /* Assign PCM outputs (routing inputs) */
- input_num = scarlett2_get_port_start_num(ports,
- SCARLETT2_PORT_IN,
- SCARLETT2_PORT_TYPE_PCM);
- input_count = ports[SCARLETT2_PORT_TYPE_PCM].num[SCARLETT2_PORT_IN];
-
- for (port_type = connect_order[port_type_connect_num = 0];
- port_type >= 0;
- port_type = connect_order[++port_type_connect_num]) {
- output_num = scarlett2_get_port_start_num(
- ports, SCARLETT2_PORT_OUT, port_type);
- output_count = ports[port_type].num[SCARLETT2_PORT_OUT];
- for (i = 0;
- i < output_count && input_count;
- i++, input_count--)
- mux[output_num++] = input_num++;
- }
-}
-
-/* Initialise private data, routing, sequence number */
-static int scarlett2_init_private(struct usb_mixer_interface *mixer,
- const struct scarlett2_device_info *info)
-{
- struct scarlett2_mixer_data *private =
- kzalloc(sizeof(struct scarlett2_mixer_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);
- private->info = info;
- private->num_mux_srcs = scarlett2_count_mux_srcs(info->ports);
- private->scarlett2_seq = 0;
- private->mixer = mixer;
- mixer->private_data = private;
- mixer->private_free = scarlett2_private_free;
- mixer->private_suspend = scarlett2_private_suspend;
-
- /* Setup default routing */
- scarlett2_init_routing(private->mux, info->ports);
-
- /* Initialise the sequence number used for the proprietary commands */
- return scarlett2_usb(mixer, SCARLETT2_USB_INIT_SEQ, NULL, 0, NULL, 0);
-}
-
-/* Read line-in config and line-out volume settings on start */
-static int scarlett2_read_configs(struct usb_mixer_interface *mixer)
-{
- struct scarlett2_mixer_data *private = mixer->private_data;
- const struct scarlett2_device_info *info = private->info;
- const struct scarlett2_ports *ports = info->ports;
- int num_line_out =
- ports[SCARLETT2_PORT_TYPE_ANALOGUE].num[SCARLETT2_PORT_OUT];
- u8 level_switches[SCARLETT2_LEVEL_SWITCH_MAX];
- u8 pad_switches[SCARLETT2_PAD_SWITCH_MAX];
- struct scarlett2_usb_volume_status volume_status;
- int err, i;
-
- if (info->level_input_count) {
- err = scarlett2_usb_get_config(
- mixer,
- SCARLETT2_CONFIG_LEVEL_SWITCH,
- info->level_input_count,
- level_switches);
- if (err < 0)
- return err;
- for (i = 0; i < info->level_input_count; i++)
- private->level_switch[i] = level_switches[i];
- }
-
- if (info->pad_input_count) {
- err = scarlett2_usb_get_config(
- mixer,
- SCARLETT2_CONFIG_PAD_SWITCH,
- info->pad_input_count,
- pad_switches);
- if (err < 0)
- return err;
- for (i = 0; i < info->pad_input_count; i++)
- private->pad_switch[i] = pad_switches[i];
- }
-
- err = scarlett2_usb_get_volume_status(mixer, &volume_status);
- if (err < 0)
- return err;
-
- private->master_vol = clamp(
- volume_status.master_vol + SCARLETT2_VOLUME_BIAS,
- 0, SCARLETT2_VOLUME_BIAS);
-
- for (i = 0; i < num_line_out; i++) {
- int volume;
-
- private->vol_sw_hw_switch[i] =
- info->line_out_hw_vol
- && volume_status.sw_hw_switch[i];
-
- volume = private->vol_sw_hw_switch[i]
- ? volume_status.master_vol
- : volume_status.sw_vol[i];
- volume = clamp(volume + SCARLETT2_VOLUME_BIAS,
- 0, SCARLETT2_VOLUME_BIAS);
- private->vol[i] = volume;
- }
-
- for (i = 0; i < info->button_count; i++)
- private->buttons[i] = !!volume_status.buttons[i];
-
- return 0;
-}
-
-/* Notify on volume change */
-static void scarlett2_mixer_interrupt_vol_change(
- struct usb_mixer_interface *mixer)
-{
- struct scarlett2_mixer_data *private = mixer->private_data;
- const struct scarlett2_ports *ports = private->info->ports;
- int num_line_out =
- ports[SCARLETT2_PORT_TYPE_ANALOGUE].num[SCARLETT2_PORT_OUT];
- int i;
-
- private->vol_updated = 1;
-
- snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
- &private->master_vol_ctl->id);
-
- for (i = 0; i < num_line_out; i++) {
- if (!private->vol_sw_hw_switch[i])
- continue;
- snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
- &private->vol_ctls[i]->id);
- }
-}
-
-/* Notify on button change */
-static void scarlett2_mixer_interrupt_button_change(
- struct usb_mixer_interface *mixer)
-{
- struct scarlett2_mixer_data *private = mixer->private_data;
- int i;
-
- private->vol_updated = 1;
-
- for (i = 0; i < private->info->button_count; i++)
- snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
- &private->button_ctls[i]->id);
-}
-
-/* Interrupt callback */
-static void scarlett2_mixer_interrupt(struct urb *urb)
-{
- struct usb_mixer_interface *mixer = urb->context;
- int len = urb->actual_length;
- int ustatus = urb->status;
- u32 data;
-
- if (ustatus != 0)
- goto requeue;
-
- if (len == 8) {
- data = le32_to_cpu(*(u32 *)urb->transfer_buffer);
- if (data & SCARLETT2_USB_INTERRUPT_VOL_CHANGE)
- scarlett2_mixer_interrupt_vol_change(mixer);
- if (data & SCARLETT2_USB_INTERRUPT_BUTTON_CHANGE)
- scarlett2_mixer_interrupt_button_change(mixer);
- } else {
- usb_audio_err(mixer->chip,
- "scarlett mixer interrupt length %d\n", len);
- }
-
-requeue:
- if (ustatus != -ENOENT &&
- ustatus != -ECONNRESET &&
- ustatus != -ESHUTDOWN) {
- urb->dev = mixer->chip->dev;
- usb_submit_urb(urb, GFP_ATOMIC);
- }
-}
-
-static int scarlett2_mixer_status_create(struct usb_mixer_interface *mixer)
-{
- struct usb_device *dev = mixer->chip->dev;
- unsigned int pipe = usb_rcvintpipe(dev,
- SCARLETT2_USB_INTERRUPT_ENDPOINT);
- void *transfer_buffer;
-
- if (mixer->urb) {
- usb_audio_err(mixer->chip,
- "%s: mixer urb already in use!\n", __func__);
- return 0;
- }
-
- if (snd_usb_pipe_sanity_check(dev, pipe))
- return -EINVAL;
-
- mixer->urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!mixer->urb)
- return -ENOMEM;
-
- transfer_buffer = kmalloc(SCARLETT2_USB_INTERRUPT_MAX_DATA, GFP_KERNEL);
- if (!transfer_buffer)
- return -ENOMEM;
-
- usb_fill_int_urb(mixer->urb, dev, pipe,
- transfer_buffer, SCARLETT2_USB_INTERRUPT_MAX_DATA,
- scarlett2_mixer_interrupt, mixer,
- SCARLETT2_USB_INTERRUPT_INTERVAL);
-
- return usb_submit_urb(mixer->urb, GFP_KERNEL);
-}
-
-/* Entry point */
-int snd_scarlett_gen2_controls_create(struct usb_mixer_interface *mixer)
-{
- const struct scarlett2_device_info *info;
- int err;
-
- /* only use UAC_VERSION_2 */
- if (!mixer->protocol)
- return 0;
-
- switch (mixer->chip->usb_id) {
- case USB_ID(0x1235, 0x8203):
- info = &s6i6_gen2_info;
- break;
- case USB_ID(0x1235, 0x8204):
- info = &s18i8_gen2_info;
- break;
- case USB_ID(0x1235, 0x8201):
- info = &s18i20_gen2_info;
- break;
- default: /* device not (yet) supported */
- return -EINVAL;
- }
-
- if (!(mixer->chip->setup & SCARLETT2_ENABLE)) {
- usb_audio_err(mixer->chip,
- "Focusrite Scarlett Gen 2 Mixer Driver disabled; "
- "use options snd_usb_audio device_setup=1 "
- "to enable and report any issues to g@b4.vu");
- return 0;
- }
-
- /* Initialise private data, routing, sequence number */
- err = scarlett2_init_private(mixer, info);
- 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 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;
-
- /* Set up the interrupt polling if there are hardware buttons */
- if (info->button_count) {
- err = scarlett2_mixer_status_create(mixer);
- if (err < 0)
- return err;
- }
-
- return 0;
-}
diff --git a/sound/usb/mixer_scarlett_gen2.h b/sound/usb/mixer_scarlett_gen2.h
deleted file mode 100644
index 52e1dad77afd..000000000000
--- a/sound/usb/mixer_scarlett_gen2.h
+++ /dev/null
@@ -1,7 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef __USB_MIXER_SCARLETT_GEN2_H
-#define __USB_MIXER_SCARLETT_GEN2_H
-
-int snd_scarlett_gen2_controls_create(struct usb_mixer_interface *mixer);
-
-#endif /* __USB_MIXER_SCARLETT_GEN2_H */
diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c
index a4d4d71db55b..236b69054867 100644
--- a/sound/usb/mixer_us16x08.c
+++ b/sound/usb/mixer_us16x08.c
@@ -180,7 +180,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 */
@@ -192,7 +192,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)];
@@ -249,7 +249,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];
@@ -260,7 +260,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;
@@ -297,7 +297,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;
@@ -338,7 +338,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:
@@ -359,7 +359,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];
@@ -370,7 +370,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;
@@ -417,7 +417,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);
@@ -430,7 +430,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;
@@ -485,7 +485,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;
@@ -500,7 +500,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;
@@ -544,7 +544,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;
@@ -560,7 +560,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;
@@ -607,7 +607,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;
@@ -637,10 +637,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++;
@@ -684,10 +684,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: {
@@ -744,7 +744,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;
@@ -1076,7 +1076,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)
@@ -1109,7 +1109,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,
@@ -1123,7 +1123,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,
@@ -1144,7 +1144,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,
@@ -1165,7 +1165,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/pcm.c b/sound/usb/pcm.c
index b401ee894e1b..b24ee38fad72 100644
--- a/sound/usb/pcm.c
+++ b/sound/usb/pcm.c
@@ -17,27 +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);
/*
@@ -49,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;
}
@@ -65,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);
+ runtime->delay = snd_usb_pcm_delay(subs, runtime);
spin_unlock(&subs->lock);
- return hwptr_done / (substream->runtime->frame_bits >> 3);
+ 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;
@@ -114,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 &&
@@ -138,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];
@@ -178,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)
@@ -216,48 +288,27 @@ 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;
+
+ error:
+ stop_endpoints(subs, false);
+ return err;
}
static void sync_pending_stops(struct snd_usb_substream *subs)
@@ -266,208 +317,54 @@ static void sync_pending_stops(struct snd_usb_substream *subs)
snd_usb_endpoint_sync_pending_stop(subs->data_endpoint);
}
-static void stop_endpoints(struct snd_usb_substream *subs)
-{
- 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);
-}
-
/* PCM sync_stop callback */
static int snd_usb_pcm_sync_stop(struct snd_pcm_substream *substream)
{
struct snd_usb_substream *subs = substream->runtime->private_data;
- if (!snd_usb_lock_shutdown(subs->stream->chip)) {
- sync_pending_stops(subs);
- snd_usb_unlock_shutdown(subs->stream->chip);
- }
- return 0;
-}
-
-static int search_roland_implicit_fb(struct usb_device *dev, int ifnum,
- unsigned int altsetting,
- struct usb_host_interface **alts,
- unsigned int *ep)
-{
- 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;
+ sync_pending_stops(subs);
return 0;
}
-/* Setup an implicit feedback endpoint from a quirk. Returns 0 if no quirk
- * applies. Returns 1 if a quirk was found.
- */
-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)
+/* Set up sync endpoint */
+int snd_usb_audioformat_set_sync_ep(struct snd_usb_audio *chip,
+ struct audioformat *fmt)
{
+ struct usb_device *dev = chip->dev;
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;
+ struct usb_interface_descriptor *altsd;
+ unsigned int ep, attr, sync_attr;
+ bool is_playback;
+ int err;
- 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, 0x0001): /* Behringer UFX1604 */
- case USB_ID(0x1397, 0x0002): /* Behringer UFX1204 */
- ep = 0x81;
- ifnum = 1;
- goto add_sync_ep_from_ifnum;
- case USB_ID(0x07fd, 0x0004): /* MOTU MicroBook II/IIc */
- /* MicroBook IIc */
- if (altsd->bInterfaceClass == USB_CLASS_AUDIO)
- return 0;
+ if (fmt->sync_ep)
+ return 0; /* already set up */
- /* MicroBook II */
- ep = 0x84;
- ifnum = 0;
- goto add_sync_ep_from_ifnum;
- case USB_ID(0x07fd, 0x0008): /* MOTU M Series */
- case USB_ID(0x31e9, 0x0001): /* Solid State Logic SSL2 */
- case USB_ID(0x31e9, 0x0002): /* Solid State Logic SSL2+ */
- case USB_ID(0x0499, 0x172f): /* Steinberg UR22C */
- case USB_ID(0x0d9a, 0x00df): /* RTX6001 */
- ep = 0x81;
- ifnum = 2;
- goto add_sync_ep_from_ifnum;
- case USB_ID(0x2b73, 0x000a): /* Pioneer DJ DJM-900NXS2 */
- case USB_ID(0x2b73, 0x0017): /* Pioneer DJ DJM-250MK2 */
- ep = 0x82;
- ifnum = 0;
- goto add_sync_ep_from_ifnum;
- case USB_ID(0x0582, 0x01d8): /* BOSS Katana */
- /* BOSS Katana amplifiers do not need quirks */
+ alts = snd_usb_get_host_interface(chip, fmt->iface, fmt->altsetting);
+ if (!alts)
return 0;
- }
-
- 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 < 2)
- 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->sync_endpoint->is_implicit_feedback = 1;
-
- subs->data_endpoint->sync_master = subs->sync_endpoint;
-
- return 1;
-}
+ altsd = get_iface_desc(alts);
-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)
-{
- int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK;
- unsigned int ep, attr;
- bool implicit_fb;
- int err;
+ err = snd_usb_parse_implicit_fb_quirk(chip, fmt, alts);
+ if (err > 0)
+ return 0; /* matched */
- /* 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.
+ /*
+ * Generic sync EP handling
*/
- attr = fmt->ep_attr & USB_ENDPOINT_SYNCTYPE;
-
- if ((is_playback && (attr != USB_ENDPOINT_SYNC_ASYNC)) ||
- (!is_playback && (attr != USB_ENDPOINT_SYNC_ADAPTIVE))) {
-
- /*
- * 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
- */
-
- subs->sync_endpoint = NULL;
- subs->data_endpoint->sync_master = NULL;
- }
-
- err = set_sync_ep_implicit_fb_quirk(subs, dev, altsd, attr);
- if (err < 0)
- return err;
- /* endpoint set by quirk */
- if (err > 0)
- return 0;
-
- 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
@@ -478,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,
@@ -505,257 +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->sync_endpoint->is_implicit_feedback = implicit_fb;
+ 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;
- subs->data_endpoint->sync_master = subs->sync_endpoint;
+ 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;
}
-/*
- * 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);
- if (WARN_ON(!alts))
- return -EINVAL;
- altsd = get_iface_desc(alts);
-
- if (fmt == subs->cur_audiofmt && !subs->need_setup_fmt)
- return 0;
-
- /* close the old interface */
- if (subs->interface >= 0 && (subs->interface != fmt->iface || subs->need_setup_fmt)) {
- 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;
- }
-
- if (subs->need_setup_fmt)
- subs->need_setup_fmt = false;
-
- /* 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;
-
- snd_usb_set_format_quirk(subs, fmt);
-
- 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);
- sync_pending_stops(subs);
- 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;
@@ -804,44 +464,66 @@ 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;
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);
+ 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;
}
- ret = snd_usb_lock_shutdown(subs->stream->chip);
+ 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(chip);
if (ret < 0)
goto stop_pipeline;
@@ -849,48 +531,136 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream,
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);
+ }
+
+ mutex_lock(&chip->mutex);
+ subs->cur_audiofmt = fmt;
+ mutex_unlock(&chip->mutex);
+
+ 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)
- goto stop_pipeline;
- return ret;
+ close_endpoints(chip, subs);
+ snd_usb_unlock_shutdown(chip);
stop_pipeline:
- snd_media_stop_pipeline(subs);
+ if (ret < 0)
+ snd_media_stop_pipeline(subs);
+
return ret;
}
+EXPORT_SYMBOL_GPL(snd_usb_hw_params);
/*
- * hw_free callback
+ * hw_params callback
*
- * reset the audio format and release the buffer
+ * 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_free(struct snd_pcm_substream *substream)
+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);
+ mutex_lock(&chip->mutex);
subs->cur_audiofmt = NULL;
- subs->cur_rate = 0;
- subs->period_bytes = 0;
- if (!snd_usb_lock_shutdown(subs->stream->chip)) {
- stop_endpoints(subs);
- sync_pending_stops(subs);
- snd_usb_endpoint_deactivate(subs->sync_endpoint);
- snd_usb_endpoint_deactivate(subs->data_endpoint);
- snd_usb_unlock_shutdown(subs->stream->chip);
+ mutex_unlock(&chip->mutex);
+ if (!snd_usb_lock_shutdown(chip)) {
+ if (stop_endpoints(subs, false))
+ sync_pending_stops(subs);
+ close_endpoints(chip, subs);
+ snd_usb_unlock_shutdown(chip);
}
return 0;
}
+EXPORT_SYMBOL_GPL(snd_usb_hw_free);
+
+/*
+ * hw_free callback
+ *
+ * reset the audio format and release the buffer
+ */
+static int snd_usb_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_usb_substream *subs = substream->runtime->private_data;
+
+ return snd_usb_hw_free(subs);
+}
+
+/* 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;
+}
/*
* prepare callback
@@ -901,16 +671,11 @@ 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);
+ ret = snd_usb_lock_shutdown(chip);
if (ret < 0)
return ret;
if (snd_BUG_ON(!subs->data_endpoint)) {
@@ -922,51 +687,56 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
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;
-
- 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;
}
- /* 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)
+ goto unlock;
+ 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);
+ snd_usb_unlock_shutdown(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 |
@@ -975,16 +745,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);
@@ -999,7 +771,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 */
@@ -1027,33 +799,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;
@@ -1078,55 +829,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;
}
@@ -1134,42 +953,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);
@@ -1185,74 +1013,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;
@@ -1299,14 +1163,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,
@@ -1315,6 +1182,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,
@@ -1332,11 +1200,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;
- return snd_usb_autoresume(subs->stream->chip);
+ /* 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;
+
+ 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)
@@ -1345,11 +1241,22 @@ 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;
- subs->interface = -1;
- subs->altset_idx = 0;
+ mutex_lock(&chip->mutex);
+ if (subs->opened) {
+ mutex_unlock(&chip->mutex);
+ return -EBUSY;
+ }
+ subs->opened = 1;
+ mutex_unlock(&chip->mutex);
+
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 */
@@ -1360,11 +1267,24 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream)
subs->dsd_dop.marker = 1;
ret = setup_hw_info(runtime, subs);
- if (ret == 0) {
- ret = snd_media_stream_init(subs, as->pcm, direction);
- if (ret)
- snd_usb_autosuspend(subs->stream->chip);
- }
+ 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:
+ mutex_lock(&chip->mutex);
+ subs->opened = 0;
+ mutex_unlock(&chip->mutex);
+
return ret;
}
@@ -1373,15 +1293,12 @@ 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;
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;
+ if (!snd_usb_lock_shutdown(subs->stream->chip)) {
ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D1);
snd_usb_unlock_shutdown(subs->stream->chip);
if (ret < 0)
@@ -1390,6 +1307,9 @@ static int snd_usb_pcm_close(struct snd_pcm_substream *substream)
subs->pcm_substream = NULL;
snd_usb_autosuspend(subs->stream->chip);
+ mutex_lock(&chip->mutex);
+ subs->opened = 0;
+ mutex_unlock(&chip->mutex);
return 0;
}
@@ -1442,28 +1362,23 @@ static void retire_capture_urb(struct snd_usb_substream *subs,
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;
+ 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;
}
- /* 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 */
spin_unlock_irqrestore(&subs->lock, flags);
/* 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 {
@@ -1475,17 +1390,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
@@ -1524,12 +1451,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,
@@ -1537,10 +1481,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,
@@ -1549,9 +1493,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,
@@ -1580,55 +1523,72 @@ 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;
+ int err = 0;
- 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 if (ep->sync_master)
- counts = snd_usb_endpoint_slave_next_packet_size(ep);
- else
- counts = snd_usb_endpoint_next_packet_size(ep);
+ 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 * ep->stride;
- urb->iso_frame_desc[i].length = counts * ep->stride;
+ urb->iso_frame_desc[i].offset = frames * stride;
+ urb->iso_frame_desc[i].length = counts * stride;
frames += counts;
+ avail -= 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;
+ 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 (subs->transfer_done > 0) {
+ if (transfer_done > 0) {
/* FIXME: fill-max mode is not
* supported yet */
- frames -= subs->transfer_done;
- counts -= subs->transfer_done;
+ frames -= transfer_done;
+ counts -= transfer_done;
urb->iso_frame_desc[i].length =
- counts * ep->stride;
- subs->transfer_done = 0;
+ counts * stride;
+ transfer_done = 0;
}
i++;
if (i < ctx->packets) {
/* add a transfer delimiter */
urb->iso_frame_desc[i].offset =
- frames * ep->stride;
+ frames * stride;
urb->iso_frame_desc[i].length = 0;
urb->number_of_packets++;
}
@@ -1636,29 +1596,25 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,
}
}
/* finish at the period boundary or after enough frames */
- if ((period_elapsed ||
- subs->transfer_done >= subs->frame_limit) &&
+ if ((period_elapsed || transfer_done >= 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 &&
+ if (!frames) {
+ err = -EAGAIN;
+ goto unlock;
+ }
+
+ 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(subs->pcm_format == SNDRV_PCM_FORMAT_DSD_U8 &&
+ } else if (unlikely(ep->cur_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]);
- }
-
- subs->hwptr_done += bytes;
- if (subs->hwptr_done >= runtime->buffer_size * stride)
- subs->hwptr_done -= runtime->buffer_size * stride;
+ fill_playback_urb_dsd_bitrev(subs, urb, bytes);
} else {
/* usual PCM */
if (!subs->tx_length_quirk)
@@ -1668,14 +1624,7 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,
/* 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;
-
- /* 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,
@@ -1685,10 +1634,23 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,
subs->trigger_tstamp_pending_update = false;
}
+ if (period_elapsed && !subs->running && subs->lowlatency_playback) {
+ subs->period_elapsed_pending = 1;
+ period_elapsed = 0;
+ }
+
+ unlock:
spin_unlock_irqrestore(&subs->lock, flags);
+ if (err < 0)
+ return err;
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;
}
/*
@@ -1699,82 +1661,100 @@ 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;
-
- /* ignore the delay accounting when processed=0 is given, i.e.
- * silent payloads are processed before handling the actual data
- */
- if (!processed)
- return;
+ struct snd_urb_ctx *ctx = urb->context;
+ bool period_elapsed = false;
spin_lock_irqsave(&subs->lock, flags);
- if (!subs->last_delay)
- goto out; /* short path */
-
- 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;
-
- /*
- * 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 (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;
+ if (ctx->queued) {
+ if (subs->inflight_bytes >= ctx->queued)
+ subs->inflight_bytes -= ctx->queued;
+ else
+ subs->inflight_bytes = 0;
}
- out:
+ 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;
+ }
spin_unlock_irqrestore(&subs->lock, flags);
+ if (period_elapsed)
+ snd_pcm_period_elapsed(subs->pcm_substream);
+}
+
+/* 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;
+
+ 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 (!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;
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);
+ 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;
- case SNDRV_PCM_TRIGGER_SUSPEND:
- if (subs->stream->chip->setup_fmt_after_resume_quirk) {
- stop_endpoints(subs);
- subs->need_setup_fmt = true;
- return 0;
- }
- break;
}
return -EINVAL;
@@ -1791,30 +1771,29 @@ 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);
- subs->data_endpoint->retire_data_urb = NULL;
- subs->running = 0;
- return 0;
+ stop_endpoints(subs, false);
+ 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;
+ dev_dbg(&subs->dev->dev, "%d:%d Stop Capture PCM\n",
+ subs->cur_audiofmt->iface,
+ subs->cur_audiofmt->altsetting);
return 0;
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- subs->data_endpoint->retire_data_urb = retire_capture_urb;
- subs->running = 1;
- return 0;
- case SNDRV_PCM_TRIGGER_SUSPEND:
- if (subs->stream->chip->setup_fmt_after_resume_quirk) {
- stop_endpoints(subs);
- subs->need_setup_fmt = true;
- return 0;
- }
- break;
}
return -EINVAL;
@@ -1823,19 +1802,20 @@ 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,
- .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,
+ .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,
- .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,
@@ -1855,7 +1835,7 @@ 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_set_managed_buffer(s, SNDRV_DMA_TYPE_VMALLOC,
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 606a2cb23eab..66bd4daa68fd 100644
--- a/sound/usb/power.c
+++ b/sound/usb/power.c
@@ -40,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;
}
}
@@ -57,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 889c550c9f29..e9bbaea7b2fa 100644
--- a/sound/usb/proc.c
+++ b/sound/usb/proc.c
@@ -108,7 +108,8 @@ static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct 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]);
@@ -150,6 +151,19 @@ static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct s
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);
}
@@ -175,32 +189,39 @@ 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)
{
+ mutex_lock(&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");
}
+ mutex_unlock(&chip->mutex);
}
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);
}
}
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..5bc27c82e0af
--- /dev/null
+++ b/sound/usb/qcom/qc_audio_offload.c
@@ -0,0 +1,2017 @@
+// 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);
+}
+
+/**
+ * uaudio_iommu_map() - maps iommu memory for adsp
+ * @mtype: ring type
+ * @dma_coherent: dma coherent
+ * @pa: physical address for ring/buffer
+ * @size: size of memory region
+ * @sgt: sg table for 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(enum mem_type mtype, bool dma_coherent,
+ phys_addr_t pa, 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;
+ bool map = true;
+ size_t sg_len;
+ int prot;
+ int ret;
+ int i;
+
+ prot = IOMMU_READ | IOMMU_WRITE;
+
+ if (dma_coherent)
+ prot |= IOMMU_CACHE;
+
+ 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;
+ case MEM_XFER_BUF:
+ iova = uaudio_get_iova(&uaudio_qdev->curr_xfer_buf_iova,
+ &uaudio_qdev->xfer_buf_iova_size,
+ &uaudio_qdev->xfer_buf_list, size);
+ break;
+ default:
+ dev_err(uaudio_qdev->data->dev, "unknown mem type %d\n", mtype);
+ }
+
+ if (!iova || !map)
+ goto done;
+
+ if (!sgt)
+ goto skip_sgt_map;
+
+ 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;
+ }
+ return iova;
+
+skip_sgt_map:
+ iommu_map(uaudio_qdev->data->domain, iova, pa, size, prot, GFP_KERNEL);
+
+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;
+
+ mutex_lock(&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(&subs->dev->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);
+ mutex_lock(&chip->mutex);
+ uaudio_dev_cleanup(&uadev[idx]);
+ mutex_unlock(&chip->mutex);
+ }
+ mutex_unlock(&qdev_mutex);
+}
+
+/**
+ * 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);
+
+ mutex_lock(&qdev_mutex);
+ mutex_lock(&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);
+ }
+ }
+
+ mutex_unlock(&qdev_mutex);
+ mutex_unlock(&chip->mutex);
+
+ 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(&params);
+
+ m = hw_param_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_leave(m, pcm_format);
+
+ i = hw_param_interval(&params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ snd_interval_setinteger(i);
+ i->min = channels;
+ i->max = channels;
+
+ i = hw_param_interval(&params, 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, &params);
+ if (ret < 0)
+ goto put_suspend;
+
+ if (!atomic_read(&chip->shutdown)) {
+ ret = snd_usb_lock_shutdown(chip);
+ if (ret < 0)
+ goto detach_ep;
+
+ if (subs->sync_endpoint) {
+ ret = snd_usb_endpoint_prepare(chip, subs->sync_endpoint);
+ if (ret < 0)
+ goto unlock;
+ }
+
+ ret = snd_usb_endpoint_prepare(chip, subs->data_endpoint);
+ if (ret < 0)
+ goto unlock;
+
+ snd_usb_unlock_shutdown(chip);
+
+ 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;
+
+unlock:
+ snd_usb_unlock_shutdown(chip);
+
+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;
+ phys_addr_t xfer_buf_pa;
+ 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;
+
+ /* Remapping is not possible if xfer_buf is outside of linear map */
+ xfer_buf_pa = virt_to_phys(xfer_buf);
+ if (WARN_ON(!page_is_ram(PFN_DOWN(xfer_buf_pa)))) {
+ ret = -ENXIO;
+ goto unmap_sync;
+ }
+ 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(MEM_XFER_BUF, dma_coherent,
+ xfer_buf_pa, 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(MEM_XFER_RING, dma_coherent, tr_pa,
+ PAGE_SIZE, NULL);
+ 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(MEM_EVENT_RING, dma_coherent, er_pa,
+ PAGE_SIZE, NULL);
+ 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");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ ret = uaudio_populate_uac_desc(subs, resp);
+ if (ret < 0)
+ goto err;
+
+ 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)
+ goto err;
+
+ 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)
+ goto err;
+
+ 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));
+
+err:
+ 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;
+ }
+
+ mutex_lock(&chip->mutex);
+ if (req_msg->enable) {
+ if (info_idx < 0 || chip->system_suspend || subs->opened) {
+ ret = -EBUSY;
+ mutex_unlock(&chip->mutex);
+
+ goto response;
+ }
+ subs->opened = 1;
+ }
+ mutex_unlock(&chip->mutex);
+
+ 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) {
+ mutex_lock(&chip->mutex);
+ subs->opened = 0;
+ mutex_unlock(&chip->mutex);
+ }
+ } 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);
+ mutex_lock(&chip->mutex);
+ subs->opened = 0;
+ mutex_unlock(&chip->mutex);
+ }
+
+response:
+ if (!req_msg->enable && ret != -EINVAL && ret != -ENODEV) {
+ mutex_lock(&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(&chip->mutex);
+ }
+ 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;
+
+ mutex_lock(&qdev_mutex);
+ mutex_lock(&chip->mutex);
+ if (!uadev[chip->card->number].chip) {
+ sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
+ if (!sdev)
+ goto exit;
+
+ 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);
+ }
+
+ mutex_unlock(&chip->mutex);
+ mutex_unlock(&qdev_mutex);
+
+ 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;
+exit:
+ mutex_unlock(&chip->mutex);
+ mutex_unlock(&qdev_mutex);
+}
+
+/**
+ * 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;
+
+ mutex_lock(&qdev_mutex);
+ mutex_lock(&chip->mutex);
+ dev = &uadev[card_num];
+
+ /* Device has already been cleaned up, or never populated */
+ if (!dev->chip) {
+ mutex_unlock(&qdev_mutex);
+ mutex_unlock(&chip->mutex);
+ 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;
+ }
+ mutex_unlock(&chip->mutex);
+
+ mutex_unlock(&qdev_mutex);
+}
+
+/**
+ * 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;
+
+ mutex_lock(&qdev_mutex);
+ mutex_lock(&chip->mutex);
+
+ uaudio_send_disconnect_ind(chip);
+
+ mutex_unlock(&qdev_mutex);
+ mutex_unlock(&chip->mutex);
+}
+
+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 23eafd50126f..eafc0d73cca1 100644
--- a/sound/usb/quirks-table.h
+++ b/sound/usb/quirks-table.h
@@ -25,37 +25,97 @@
.idProduct = prod, \
.bInterfaceClass = USB_CLASS_VENDOR_SPEC
-#define QUIRK_RENAME_DEVICE(_vendor, _device) \
- .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \
- .vendor_name = _vendor, \
- .product_name = _device, \
- .ifnum = QUIRK_NO_INTERFACE \
- }
+/* 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
-#define QUIRK_DEVICE_PROFILE(_vendor, _device, _profile) \
- .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \
- .vendor_name = _vendor, \
- .product_name = _device, \
- .profile_name = _profile, \
- .ifnum = QUIRK_NO_INTERFACE \
- }
+/* 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:
+ */
-/* HP Thunderbolt Dock Audio Headset */
-{
- USB_DEVICE(0x03f0, 0x0269),
- QUIRK_DEVICE_PROFILE("HP", "Thunderbolt Dock Audio Headset",
- "HP-Thunderbolt-Dock-Audio-Headset"),
-},
-/* HP Thunderbolt Dock Audio Module */
-{
- USB_DEVICE(0x03f0, 0x0567),
- QUIRK_DEVICE_PROFILE("HP", "Thunderbolt Dock Audio Module",
- "HP-Thunderbolt-Dock-Audio-Module"),
-},
/* 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,
@@ -66,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,
@@ -85,43 +143,48 @@
}
},
-/* Creative/E-Mu devices */
-{
- USB_DEVICE(0x041e, 0x3010),
- QUIRK_RENAME_DEVICE("Creative Labs", "Sound Blaster MP3+")
-},
-/* Creative/Toshiba Multimedia Center SB-0500 */
-{
- USB_DEVICE(0x041e, 0x3048),
- QUIRK_RENAME_DEVICE("Toshiba", "SB-0500")
-},
-{
- /* 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
+ },
+ },
},
/*
@@ -131,31 +194,18 @@
*/
{
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
}
}
},
@@ -164,70 +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,
- QUIRK_RENAME_DEVICE("Logitech, Inc.", "QuickCam Pro 9000")
-},
+{ 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
@@ -235,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"),
@@ -335,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
}
}
},
@@ -497,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
}
@@ -508,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,
@@ -532,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,
@@ -549,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
- },
+ 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 = 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[]) {
+ 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 = 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[]) {
- {
- .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 = 0x0005,
.in_cables = 0x0005
}
},
- {
- .ifnum = -1
- }
+ QUIRK_COMPOSITE_END
}
}
},
@@ -656,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
- },
- {
- .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 = 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[]) {
- {
- .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 = 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
}
}
},
@@ -809,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
}
}
},
@@ -847,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
}
@@ -884,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
}
@@ -898,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
- },
+ 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 = 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[]) {
+ 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 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
}
@@ -974,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
}
@@ -992,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,
@@ -1020,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,
@@ -1037,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
}
@@ -1067,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
}
@@ -1085,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
}
@@ -1126,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
}
@@ -1140,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
}
@@ -1157,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
}
@@ -1175,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
}
@@ -1216,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
}
@@ -1230,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
}
@@ -1268,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
}
@@ -1308,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
}
@@ -1386,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
}
@@ -1404,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
}
@@ -1445,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
}
@@ -1459,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
}
@@ -1473,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
}
@@ -1490,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
}
}
},
@@ -1549,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
}
@@ -1588,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
- },
- {
- .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
}
}
},
{
/* 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,
@@ -1642,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,
@@ -1660,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,
@@ -1709,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,
@@ -1727,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
}
@@ -1799,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
}
@@ -1814,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
}
@@ -1827,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
}
@@ -1840,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
}
@@ -1853,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
}
@@ -1871,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
}
@@ -1884,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
}
@@ -1897,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
}
@@ -1910,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
}
@@ -1987,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
}
@@ -2000,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[]) {
+ QUIRK_DATA_COMPOSITE {
+ { QUIRK_DATA_STANDARD_AUDIO(0) },
+ { QUIRK_DATA_STANDARD_AUDIO(1) },
+ { QUIRK_DATA_STANDARD_AUDIO(2) },
{
- .ifnum = 0,
- .type = QUIRK_AUDIO_STANDARD_INTERFACE
- },
- {
- .ifnum = 1,
- .type = QUIRK_AUDIO_STANDARD_INTERFACE
- },
- {
- .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,
@@ -2130,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,
@@ -2154,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,
@@ -2201,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,
@@ -2225,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,
@@ -2269,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,
@@ -2293,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[]) {
+ QUIRK_DATA_COMPOSITE {
+ { QUIRK_DATA_STANDARD_MIXER(0) },
{
- .ifnum = 0,
- .type = QUIRK_AUDIO_STANDARD_MIXER,
- },
- {
- .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,
@@ -2336,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,
@@ -2360,9 +1796,7 @@ YAMAHA_DEVICE(0x7010, "UB99"),
}
},
/* interface 3 (MIDI) is standard compliant */
- {
- .ifnum = -1
- }
+ QUIRK_COMPOSITE_END
}
}
},
@@ -2370,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)
}
},
@@ -2397,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
}
}
},
@@ -2421,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) {
+ QUIRK_DATA_MIDI_EMAGIC(2) {
.out_cables = 0x80ff,
.in_cables = 0x80ff
}
@@ -2434,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
}
@@ -2447,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
}
@@ -2462,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,
@@ -2494,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
}
}
},
@@ -2517,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[]) {
+ QUIRK_DRIVER_INFO {
+ QUIRK_DATA_COMPOSITE {
+ { QUIRK_DATA_STANDARD_AUDIO(0) },
+ { QUIRK_DATA_STANDARD_AUDIO(1) },
+ { QUIRK_DATA_STANDARD_AUDIO(2) },
{
- .ifnum = 0,
- .type = QUIRK_AUDIO_STANDARD_INTERFACE
- },
- {
- .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
- },
+ 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
}
}
},
@@ -2584,38 +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
+ QUIRK_DATA_STANDARD_MIDI(3)
}
},
{
- USB_DEVICE(0x0ccd, 0x0028),
- QUIRK_RENAME_DEVICE("TerraTec", "Aureon5.1MkII")
-},
-{
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,
@@ -2623,20 +1999,14 @@ YAMAHA_DEVICE(0x7010, "UB99"),
}
},
-/* Stanton/N2IT Final Scratch v1 device ('Scratchamp') */
-{
- USB_DEVICE(0x103d, 0x0100),
- QUIRK_RENAME_DEVICE("Stanton", "ScratchAmp")
-},
-{
- USB_DEVICE(0x103d, 0x0101),
- QUIRK_RENAME_DEVICE("Stanton", "ScratchAmp")
-},
+/* 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,
@@ -2645,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,
@@ -2654,38 +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[]) {
- {
- .ifnum = 0,
- .type = QUIRK_AUDIO_STANDARD_MIXER,
- },
+ 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,
@@ -2703,13 +2064,16 @@ 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 = 0,
- .type = QUIRK_AUDIO_FIXED_ENDPOINT,
- .data = &(const struct audioformat) {
+ QUIRK_DATA_AUDIOFORMAT(0) {
.formats = SNDRV_PCM_FMTBIT_S24_3LE,
.channels = 2,
.iface = 0,
@@ -2717,6 +2081,7 @@ YAMAHA_DEVICE(0x7010, "UB99"),
.altset_idx = 1,
.attributes = 0,
.endpoint = 0x82,
+ .ep_idx = 1,
.ep_attr = USB_ENDPOINT_XFER_ISOC,
.datainterval = 1,
.maxpacksize = 0x0126,
@@ -2730,28 +2095,19 @@ YAMAHA_DEVICE(0x7010, "UB99"),
}
}
},
- {
- .ifnum = 1,
- .type = QUIRK_MIDI_RAW_BYTES
- },
- {
- .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,
@@ -2770,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,
@@ -2794,59 +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),
- QUIRK_RENAME_DEVICE("Rane", "SL-1")
-},
-
-/* Lenovo ThinkStation P620 Rear Line-in, Line-out and Microphone */
-{
- USB_DEVICE(0x17aa, 0x1046),
- .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
- .vendor_name = "Lenovo",
- .product_name = "ThinkStation P620 Rear",
- .profile_name = "Lenovo-ThinkStation-P620-Rear",
- .ifnum = QUIRK_ANY_INTERFACE,
- .type = QUIRK_SETUP_DISABLE_AUTOSUSPEND
- }
-},
-/* Lenovo ThinkStation P620 Internal Speaker + Front Headset */
-{
- USB_DEVICE(0x17aa, 0x104d),
- .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
- .vendor_name = "Lenovo",
- .product_name = "ThinkStation P620 Main",
- .profile_name = "Lenovo-ThinkStation-P620-Main",
- .ifnum = QUIRK_ANY_INTERFACE,
- .type = QUIRK_SETUP_DISABLE_AUTOSUSPEND
- }
-},
-
/* Native Instruments MK2 series */
{
/* Komplete Audio 6 */
@@ -2870,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,
@@ -2879,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,
@@ -2899,89 +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, \
- .shares_media_device = 1, \
- } \
-}
-
-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,
@@ -3002,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,
@@ -3012,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,
@@ -3024,9 +2266,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
}
}
},
- {
- .ifnum = -1
- }
+ QUIRK_COMPOSITE_END
}
}
},
@@ -3034,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[]) {
+ 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_AUDIO_FIXED_ENDPOINT,
- .data = &(const struct audioformat) {
+ QUIRK_DATA_AUDIOFORMAT(2) {
.formats = SNDRV_PCM_FMTBIT_S24_3BE,
.channels = 2,
.iface = 2,
@@ -3069,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,
@@ -3094,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,
@@ -3109,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) },
+ {
+ 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,
+ }
+ },
{
- .ifnum = -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[]) {
+ QUIRK_DATA_COMPOSITE {
+ { QUIRK_DATA_IGNORE(0) },
{
- .ifnum = 0,
- .type = QUIRK_IGNORE_INTERFACE
- },
- {
- .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,
@@ -3155,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) },
{
- .ifnum = -1
- }
+ 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
+ }
+ }
+ },
+ {
+ 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
}
}
},
@@ -3166,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,
@@ -3192,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,
@@ -3208,9 +2534,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
.rate_max = 16000
}
},
- {
- .ifnum = -1
- }
+ QUIRK_COMPOSITE_END
}
}
},
@@ -3219,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,
@@ -3249,9 +2566,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
}
}
},
- {
- .ifnum = -1
- }
+ QUIRK_COMPOSITE_END
}
}
},
@@ -3264,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
}
}
},
@@ -3299,23 +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)
}
},
-/*
- * The original product_name is "USB Sound Device", however this name
- * is also used by the CM106 based cards, so make it unique.
- */
+/* Rane SL-1 */
{
- USB_DEVICE(0x0d8c, 0x0102),
- QUIRK_RENAME_DEVICE(NULL, "ICUSBAUDIO7D")
-},
-{
- USB_DEVICE(0x0d8c, 0x0103),
- QUIRK_RENAME_DEVICE(NULL, "Audio Advantage MicroII")
+ USB_DEVICE(0x13e5, 0x0001),
+ QUIRK_DRIVER_INFO {
+ QUIRK_DATA_STANDARD_AUDIO(QUIRK_ANY_INTERFACE)
+ }
},
/* disabled due to regression for other devices;
@@ -3330,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,
@@ -3366,9 +2648,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
}
}
},
- {
- .ifnum = -1
- },
+ QUIRK_COMPOSITE_END
}
}
},
@@ -3381,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,
@@ -3412,45 +2685,20 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
}
}
},
- {
- .ifnum = -1
- },
+ QUIRK_COMPOSITE_END
}
}
},
-/* Dell WD15 Dock */
-{
- USB_DEVICE(0x0bda, 0x4014),
- QUIRK_DEVICE_PROFILE("Dell", "WD15 Dock", "Dell-WD15-Dock")
-},
-/* 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_ANY_INTERFACE,
- .type = QUIRK_SETUP_FMT_AFTER_RESUME
- }
-},
/* MOTU Microbook II */
{
USB_DEVICE_VENDOR_SPEC(0x07fd, 0x0004),
- .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ QUIRK_DRIVER_INFO {
.vendor_name = "MOTU",
.product_name = "MicroBookII",
- .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 = 0,
- .type = QUIRK_AUDIO_FIXED_ENDPOINT,
- .data = &(const struct audioformat) {
+ QUIRK_DATA_AUDIOFORMAT(0) {
.formats = SNDRV_PCM_FMTBIT_S24_3BE,
.channels = 6,
.iface = 0,
@@ -3471,9 +2719,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
}
},
{
- .ifnum = 0,
- .type = QUIRK_AUDIO_FIXED_ENDPOINT,
- .data = &(const struct audioformat) {
+ QUIRK_DATA_AUDIOFORMAT(0) {
.formats = SNDRV_PCM_FMTBIT_S24_3BE,
.channels = 8,
.iface = 0,
@@ -3481,6 +2727,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
.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,
@@ -3493,9 +2740,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
}
}
},
- {
- .ifnum = -1
- }
+ QUIRK_COMPOSITE_END
}
}
},
@@ -3507,14 +2752,10 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
* The feedback for the output is the input.
*/
USB_DEVICE_VENDOR_SPEC(0x2b73, 0x0023),
- .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 = 0,
- .type = QUIRK_AUDIO_FIXED_ENDPOINT,
- .data = &(const struct audioformat) {
+ QUIRK_DATA_AUDIOFORMAT(0) {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.channels = 12,
.iface = 0,
@@ -3531,15 +2772,14 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
}
},
{
- .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,
.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,
@@ -3550,9 +2790,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
.rate_table = (unsigned int[]) { 44100 }
}
},
- {
- .ifnum = -1
- }
+ QUIRK_COMPOSITE_END
}
}
},
@@ -3595,14 +2833,10 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
* but not for DVS (Digital Vinyl Systems) like in Mixxx.
*/
USB_DEVICE_VENDOR_SPEC(0x2b73, 0x0017),
- .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 = 0,
- .type = QUIRK_AUDIO_FIXED_ENDPOINT,
- .data = &(const struct audioformat) {
+ QUIRK_DATA_AUDIOFORMAT(0) {
.formats = SNDRV_PCM_FMTBIT_S24_3LE,
.channels = 8, // outputs
.iface = 0,
@@ -3619,15 +2853,14 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
}
},
{
- .ifnum = 0,
- .type = QUIRK_AUDIO_FIXED_ENDPOINT,
- .data = &(const struct audioformat) {
+ 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,
@@ -3638,9 +2871,7 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
.rate_table = (unsigned int[]) { 48000 }
}
},
- {
- .ifnum = -1
- }
+ QUIRK_COMPOSITE_END
}
}
},
@@ -3651,14 +2882,10 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
* The feedback for the output is the dummy input.
*/
USB_DEVICE_VENDOR_SPEC(0x2b73, 0x000e),
- .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 = 0,
- .type = QUIRK_AUDIO_FIXED_ENDPOINT,
- .data = &(const struct audioformat) {
+ QUIRK_DATA_AUDIOFORMAT(0) {
.formats = SNDRV_PCM_FMTBIT_S24_3LE,
.channels = 4,
.iface = 0,
@@ -3675,15 +2902,14 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
}
},
{
- .ifnum = 0,
- .type = QUIRK_AUDIO_FIXED_ENDPOINT,
- .data = &(const struct audioformat) {
+ 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,
@@ -3694,54 +2920,227 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
.rate_table = (unsigned int[]) { 44100 }
}
},
- {
- .ifnum = -1
- }
+ QUIRK_COMPOSITE_END
}
}
},
-#define ALC1220_VB_DESKTOP(vend, prod) { \
- USB_DEVICE(vend, prod), \
- QUIRK_DEVICE_PROFILE("Realtek", "ALC1220-VB-DT", \
- "Realtek-ALC1220-VB-Desktop") \
-}
-ALC1220_VB_DESKTOP(0x0414, 0xa002), /* Gigabyte TRX40 Aorus Pro WiFi */
-ALC1220_VB_DESKTOP(0x0db0, 0x0d64), /* MSI TRX40 Creator */
-ALC1220_VB_DESKTOP(0x0db0, 0x543d), /* MSI TRX40 */
-ALC1220_VB_DESKTOP(0x26ce, 0x0a01), /* Asrock TRX40 Creator */
-#undef ALC1220_VB_DESKTOP
-
-/* 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.
- */
{
- USB_DEVICE(0x0414, 0xa000),
- QUIRK_DEVICE_PROFILE("Gigabyte", "Aorus Master Front Headphone",
- "Gigabyte-Aorus-Master-Front-Headphone")
+ /*
+ * 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 {
+ {
+ 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
+ }
+ }
},
+
{
- USB_DEVICE(0x0414, 0xa001),
- QUIRK_DEVICE_PROFILE("Gigabyte", "Aorus Master Main Audio",
- "Gigabyte-Aorus-Master-Main-Audio")
+ /*
+ * 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
+ }
+ }
},
+
{
/*
* Pioneer DJ DJM-900NXS2
* 10 channels playback & 12 channels capture @ 44.1/48/96kHz S24LE
*/
USB_DEVICE_VENDOR_SPEC(0x2b73, 0x000a),
- .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 {
+ {
+ 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 {
{
- .ifnum = 0,
- .type = QUIRK_AUDIO_FIXED_ENDPOINT,
- .data = &(const struct audioformat) {
+ QUIRK_DATA_AUDIOFORMAT(0) {
.formats = SNDRV_PCM_FMTBIT_S24_3LE,
.channels = 10,
.iface = 0,
@@ -3762,15 +3161,14 @@ ALC1220_VB_DESKTOP(0x26ce, 0x0a01), /* Asrock TRX40 Creator */
}
},
{
- .ifnum = 0,
- .type = QUIRK_AUDIO_FIXED_ENDPOINT,
- .data = &(const struct audioformat) {
+ 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,
@@ -3785,18 +3183,67 @@ ALC1220_VB_DESKTOP(0x26ce, 0x0a01), /* Asrock TRX40 Creator */
}
}
},
+ 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 }
+ }
+ },
{
- .ifnum = -1
- }
+ 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 MS2109 based HDMI capture cards
+ * MacroSilicon MS2100/MS2106 based AV capture cards
*
* These claim 96kHz 1ch in the descriptors, but are actually 48kHz 2ch.
- * They also need QUIRK_AUDIO_ALIGN_TRANSFER, which makes one wonder if
+ * 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...
*
@@ -3804,31 +3251,53 @@ ALC1220_VB_DESKTOP(0x26ce, 0x0a01), /* Asrock TRX40 Creator */
* channels to be swapped and out of phase, which is dealt with in quirks.c.
*/
{
- .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
- USB_DEVICE_ID_MATCH_INT_CLASS |
- USB_DEVICE_ID_MATCH_INT_SUBCLASS,
- .idVendor = 0x534d,
- .idProduct = 0x2109,
- .bInterfaceClass = USB_CLASS_AUDIO,
- .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
- .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ USB_AUDIO_DEVICE(0x534d, 0x0021),
+ QUIRK_DRIVER_INFO {
.vendor_name = "MacroSilicon",
- .product_name = "MS2109",
- .ifnum = QUIRK_ANY_INTERFACE,
- .type = QUIRK_COMPOSITE,
- .data = &(const struct snd_usb_audio_quirk[]) {
+ .product_name = "MS210x",
+ QUIRK_DATA_COMPOSITE {
+ { QUIRK_DATA_STANDARD_MIXER(2) },
{
- .ifnum = 2,
- .type = QUIRK_AUDIO_ALIGN_TRANSFER,
- },
- {
- .ifnum = 2,
- .type = QUIRK_AUDIO_STANDARD_MIXER,
+ 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) },
{
- .ifnum = 3,
- .type = QUIRK_AUDIO_FIXED_ENDPOINT,
- .data = &(const struct audioformat) {
+ QUIRK_DATA_AUDIOFORMAT(3) {
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.channels = 2,
.iface = 3,
@@ -3843,11 +3312,593 @@ ALC1220_VB_DESKTOP(0x26ce, 0x0a01), /* Asrock TRX40 Creator */
.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 }
+ }
+ },
{
- .ifnum = -1
- }
+ 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 75bbdc691243..bd24f3a78ea9 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -19,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"
@@ -55,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;
@@ -71,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);
}
/*
@@ -104,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);
@@ -120,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
*/
@@ -131,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)
@@ -144,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;
@@ -153,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;
@@ -165,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;
}
@@ -175,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:
@@ -352,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;
@@ -362,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.
@@ -417,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)
@@ -446,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)
@@ -482,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);
@@ -505,26 +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);
-}
-
-
-static int setup_fmt_after_resume_quirk(struct snd_usb_audio *chip,
- struct usb_interface *iface,
- struct usb_driver *driver,
- const struct snd_usb_audio_quirk *quirk)
-{
- chip->setup_fmt_after_resume_quirk = 1;
- return 1; /* Continue with creating streams and mixer */
-}
-
-static int setup_disable_autosuspend(struct snd_usb_audio *chip,
- struct usb_interface *iface,
- struct usb_driver *driver,
- const struct snd_usb_audio_quirk *quirk)
-{
- driver->supports_autosuspend = 0;
- return 1; /* Continue with creating streams and mixer */
+ return snd_usb_create_mixer(chip, quirk->ifnum);
}
/*
@@ -547,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,
@@ -563,10 +534,7 @@ 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,
- [QUIRK_SETUP_FMT_AFTER_RESUME] = setup_fmt_after_resume_quirk,
- [QUIRK_SETUP_DISABLE_AUTOSUSPEND] = setup_disable_autosuspend,
};
if (quirk->type < QUIRK_TYPE_COUNT) {
@@ -587,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 ||
@@ -597,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);
@@ -856,7 +833,7 @@ static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev)
static const u8 seq[] = { 0x4e, 0x73, 0x52, 0x01 };
void *buf;
- if (snd_usb_pipe_sanity_check(dev, usb_sndintpipe(dev, 0x05)))
+ if (usb_pipe_type_check(dev, usb_sndintpipe(dev, 0x05)))
return -EINVAL;
buf = kmemdup(seq, ARRAY_SIZE(seq), GFP_KERNEL);
if (!buf)
@@ -885,8 +862,6 @@ static int snd_usb_nativeinstruments_boot_quirk(struct usb_device *dev)
{
int ret;
- if (snd_usb_pipe_sanity_check(dev, usb_sndctrlpipe(dev, 0)))
- return -EINVAL;
ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
0xaf, USB_TYPE_VENDOR | USB_RECIP_DEVICE,
1, 0, NULL, 0, 1000);
@@ -935,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;
@@ -969,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)
@@ -994,8 +978,6 @@ static int snd_usb_axefx3_boot_quirk(struct usb_device *dev)
dev_dbg(&dev->dev, "Waiting for Axe-Fx III to boot up...\n");
- if (snd_usb_pipe_sanity_check(dev, usb_sndctrlpipe(dev, 0)))
- return -EINVAL;
/* If the Axe-Fx III has not fully booted, it will timeout when trying
* to enable the audio streaming interface. A more generous timeout is
* used here to detect when the Axe-Fx III has finished booting as the
@@ -1020,6 +1002,310 @@ 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
@@ -1028,7 +1314,7 @@ static int snd_usb_motu_microbookii_communicate(struct usb_device *dev, u8 *buf,
{
int err, actual_length;
- if (snd_usb_pipe_sanity_check(dev, usb_sndintpipe(dev, 0x01)))
+ 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);
@@ -1040,7 +1326,7 @@ static int snd_usb_motu_microbookii_communicate(struct usb_device *dev, u8 *buf,
memset(buf, 0, buf_size);
- if (snd_usb_pipe_sanity_check(dev, usb_rcvintpipe(dev, 0x82)))
+ 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);
@@ -1125,25 +1411,28 @@ free_buf:
static int snd_usb_motu_m_series_boot_quirk(struct usb_device *dev)
{
- int ret;
+ msleep(4000);
- if (snd_usb_pipe_sanity_check(dev, usb_sndctrlpipe(dev, 0)))
- return -EINVAL;
- ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
- 1, USB_TYPE_VENDOR | USB_RECIP_DEVICE,
- 0x0, 0, NULL, 0, 1000);
-
- if (ret < 0)
- return ret;
+ return 0;
+}
- msleep(2000);
+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);
- ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
- 1, USB_TYPE_VENDOR | USB_RECIP_DEVICE,
- 0x20, 0, NULL, 0, 1000);
+ /* 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);
- if (ret < 0)
- return ret;
+ /* 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;
}
@@ -1308,9 +1597,11 @@ int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip,
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(0x0194f, 0x010c))
+ if (chip->usb_id == USB_ID(0x194f, 0x010c))
+ return s1810c_skip_setting_quirk(chip, iface, altno);
+ /* presonus studio 1824c: skip altsets incompatible with device_setup */
+ if (chip->usb_id == USB_ID(0x194f, 0x010d))
return s1810c_skip_setting_quirk(chip, iface, altno);
-
return 0;
}
@@ -1342,6 +1633,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 */
@@ -1371,6 +1666,9 @@ int snd_usb_apply_boot_quirk(struct usb_device *dev,
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;
@@ -1382,7 +1680,7 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev,
unsigned int id)
{
switch (id) {
- case USB_ID(0x07fd, 0x0008): /* MOTU M Series */
+ case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */
return snd_usb_motu_m_series_boot_quirk(dev);
}
@@ -1392,7 +1690,8 @@ int snd_usb_apply_boot_quirk_once(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) {
@@ -1431,7 +1730,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;
@@ -1440,7 +1739,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;
}
@@ -1468,32 +1767,97 @@ static void set_format_emu_quirk(struct snd_usb_substream *subs,
subs->pkt_offset_adj = (emu_samplerate_id >= EMU_QUIRK_SR_176400HZ) ? 4 : 0;
}
+static int pioneer_djm_set_format_quirk(struct snd_usb_substream *subs,
+ u16 windex)
+{
+ 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;
+}
-/*
- * Pioneer DJ DJM-900NXS2
- * Device needs to know the sample rate each time substream is started
- */
-static int pioneer_djm_set_format_quirk(struct snd_usb_substream *subs)
+static void mbox3_set_format_quirk(struct snd_usb_substream *subs,
+ const struct audioformat *fmt)
{
+ __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;
+ }
- /* Convert sample rate value to little endian */
- u8 sr[3];
+ // 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");
+}
- sr[0] = subs->cur_rate & 0xff;
- sr[1] = (subs->cur_rate >> 8) & 0xff;
- sr[2] = (subs->cur_rate >> 16) & 0xff;
+static const int rme_digiface_rate_table[] = {
+ 32000, 44100, 48000, 0,
+ 64000, 88200, 96000, 0,
+ 128000, 176400, 192000, 0,
+};
- /* Configure device */
- usb_set_interface(subs->dev, 0, 1);
- snd_usb_ctl_msg(subs->stream->chip->dev,
- usb_rcvctrlpipe(subs->stream->chip->dev, 0),
- 0x01, 0x22, 0x0100, 0x0082, &sr, 0x0003);
+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;
+ }
+
+ 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;
}
void snd_usb_set_format_quirk(struct snd_usb_substream *subs,
- struct audioformat *fmt)
+ const struct audioformat *fmt)
{
switch (subs->stream->chip->usb_id) {
case USB_ID(0x041e, 0x3f02): /* E-Mu 0202 USB */
@@ -1502,70 +1866,36 @@ void snd_usb_set_format_quirk(struct snd_usb_substream *subs,
case USB_ID(0x041e, 0x3f19): /* E-Mu 0204 USB */
set_format_emu_quirk(subs, fmt);
break;
- case USB_ID(0x2b73, 0x000a): /* Pioneer DJ DJM-900NXS2 */
- case USB_ID(0x2b73, 0x0017): /* Pioneer DJ DJM-250MK2 */
- pioneer_djm_set_format_quirk(subs);
- 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;
}
}
-bool snd_usb_get_sample_rate_quirk(struct snd_usb_audio *chip)
-{
- /* 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(0x05a7, 0x1020): /* Bose Companion 5 */
- 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 */
- case USB_ID(0x2912, 0x30c8): /* Audioengine D1 */
- return true;
- }
-
- /* 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;
- }
-
- return false;
-}
-
-/* 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)
-{
- switch (id) {
- case USB_ID(0x154e, 0x1002): /* Denon DCD-1500RE */
- 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;
- }
- 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
*/
@@ -1628,24 +1958,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,
@@ -1653,48 +1965,14 @@ 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)
- 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)
+ if (chip->quirk_flags & QUIRK_FLAG_CTL_MSG_DELAY)
msleep(20);
-
- /* Zoom R16/24, Logitech H650e, Jabra 550a, Kingston HyperX 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) ||
- chip->usb_id == USB_ID(0x0951, 0x16ad)) &&
- (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS)
+ else if (chip->quirk_flags & QUIRK_FLAG_CTL_MSG_DELAY_1M)
usleep_range(1000, 2000);
-
- /*
- * Samsung USBC Headset (AKG) need a tiny delay after each
- * class compliant request. (Model number: AAM625R or AAM627R)
- */
- if (chip->usb_id == USB_ID(0x04e8, 0xa051) &&
- (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS)
+ else if (chip->quirk_flags & QUIRK_FLAG_CTL_MSG_DELAY_5M)
usleep_range(5000, 6000);
}
@@ -1729,7 +2007,10 @@ 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(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)
@@ -1739,14 +2020,18 @@ u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip,
case USB_ID(0x0d8c, 0x0316): /* Hegel HD12 DSD */
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, 0x09dd): /* Encore mDSD */
+ 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(0x1db5, 0x0003): /* Bryston BDA3 */
+ 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(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 */
@@ -1756,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:
@@ -1781,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
@@ -1793,27 +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 0x152a: /* Thesycon devices */
- case 0x20b1: /* XMOS based devices */
- case 0x22d9: /* Oppo */
- case 0x23ba: /* Playback Designs */
- case 0x25ce: /* Mytek devices */
- case 0x278b: /* Rotel? */
- case 0x292b: /* Gustard/Ess based devices */
- case 0x2ab6: /* T+A devices */
- case 0x3842: /* EVGA */
- case 0xc502: /* HiBy 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;
}
@@ -1857,41 +2121,321 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip,
*/
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;
}
}
/*
- * registration quirk:
- * the registration is skipped if a device matches with the given ID,
- * unless the interface reaches to the defined one. This is for delaying
- * the registration until the last known interface, so that the card and
- * devices appear at the same time.
+ * driver behavior quirk flags
*/
-
-struct registration_quirk {
- unsigned int usb_id; /* composed via USB_ID() */
- unsigned int interface; /* the interface to trigger register */
+struct usb_audio_quirk_flags_table {
+ u32 id;
+ u32 flags;
};
-#define REG_QUIRK_ENTRY(vendor, product, iface) \
- { .usb_id = USB_ID(vendor, product), .interface = (iface) }
-
-static const struct registration_quirk registration_quirks[] = {
- REG_QUIRK_ENTRY(0x0951, 0x16d8, 2), /* Kingston HyperX AMP */
- REG_QUIRK_ENTRY(0x0951, 0x16ed, 2), /* Kingston HyperX Cloud Alpha S */
- REG_QUIRK_ENTRY(0x0951, 0x16ea, 2), /* Kingston HyperX Cloud Flight S */
- { 0 } /* terminator */
+#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(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(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(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(0x0b0e, 0x0349, /* Jabra 550a */
+ QUIRK_FLAG_CTL_MSG_DELAY_1M),
+ 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, 0x0014, /* USB Audio Device */
+ QUIRK_FLAG_CTL_MSG_DELAY_1M),
+ 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(0x1101, 0x0003, /* Audioengine D1 */
+ QUIRK_FLAG_GET_SAMPLE_RATE),
+ 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(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, 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(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(0x2fc6, 0xf0b7, /* iBasso DC07 Pro */
+ QUIRK_FLAG_CTL_MSG_DELAY_1M),
+ DEVICE_FLG(0x30be, 0x0101, /* Schiit Hel */
+ QUIRK_FLAG_IGNORE_CTL_ERROR),
+ 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(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(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 */
};
-/* return true if skipping registration */
-bool snd_usb_registration_quirk(struct snd_usb_audio *chip, int iface)
+void snd_usb_init_quirk_flags(struct snd_usb_audio *chip)
{
- const struct registration_quirk *q;
-
- for (q = registration_quirks; q->usb_id; q++)
- if (chip->usb_id == q->usb_id)
- return iface != q->interface;
-
- /* Register as normal */
- return false;
+ 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))) {
+ usb_audio_dbg(chip,
+ "Set quirk_flags 0x%x for device %04x:%04x\n",
+ p->flags, USB_ID_VENDOR(chip->usb_id),
+ USB_ID_PRODUCT(chip->usb_id));
+ chip->quirk_flags |= p->flags;
+ return;
+ }
+ }
}
diff --git a/sound/usb/quirks.h b/sound/usb/quirks.h
index c76cf24a640a..f9bfd5ac7bab 100644
--- a/sound/usb/quirks.h
+++ b/sound/usb/quirks.h
@@ -26,22 +26,19 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev,
unsigned int usb_id);
void snd_usb_set_format_quirk(struct snd_usb_substream *subs,
- struct audioformat *fmt);
-
-bool snd_usb_get_sample_rate_quirk(struct snd_usb_audio *chip);
+ 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,
@@ -51,6 +48,6 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip,
struct audioformat *fp,
int stream);
-bool snd_usb_registration_quirk(struct snd_usb_audio *chip, int iface);
+void snd_usb_init_quirk_flags(struct snd_usb_audio *chip);
#endif /* __USBAUDIO_QUIRKS_H */
diff --git a/sound/usb/stream.c b/sound/usb/stream.c
index ca76ba5b5c0b..c1ea8844a46f 100644
--- a/sound/usb/stream.c
+++ b/sound/usb/stream.c
@@ -47,7 +47,6 @@ static void free_substream(struct snd_usb_substream *subs)
return; /* not initialized */
list_for_each_entry_safe(fp, n, &subs->fmt_list, list)
audioformat_free(fp);
- kfree(subs->rate_list.list);
kfree(subs->str_pd);
snd_media_stream_delete(subs);
}
@@ -90,8 +89,8 @@ 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;
@@ -193,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;
}
@@ -245,8 +244,8 @@ static struct snd_pcm_chmap_elem *convert_chmap(int channels, unsigned int bits,
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 */
@@ -301,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 */
@@ -496,6 +498,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)
@@ -503,9 +509,6 @@ static int __snd_usb_add_audio_stream(struct snd_usb_audio *chip,
subs = &as->substream[stream];
if (subs->ep_num)
continue;
- if (snd_device_get_state(chip->card, as->pcm) !=
- SNDRV_DEV_BUILD)
- chip->need_delayed_register = true;
err = snd_pcm_new_stream(as->pcm, stream, 1);
if (err < 0)
return err;
@@ -710,10 +713,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 =
@@ -737,7 +743,7 @@ 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,
protocol);
if (iterm) {
@@ -773,7 +779,7 @@ 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,
protocol);
if (input_term) {
@@ -783,7 +789,7 @@ snd_usb_get_audioformat_uac12(struct snd_usb_audio *chip,
goto found_clock;
}
- output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf,
+ output_term = snd_usb_find_output_terminal_descriptor(ctrl_intf,
as->bTerminalLink,
protocol);
if (output_term) {
@@ -867,6 +873,7 @@ 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;
@@ -878,6 +885,7 @@ snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip,
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 =
@@ -963,7 +971,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);
@@ -987,7 +995,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);
@@ -1008,7 +1016,7 @@ 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,
UAC_VERSION_3);
if (input_term) {
@@ -1016,7 +1024,7 @@ snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip,
goto found_clock;
}
- output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf,
+ output_term = snd_usb_find_output_terminal_descriptor(ctrl_intf,
as->bTerminalLink,
UAC_VERSION_3);
if (output_term) {
@@ -1059,13 +1067,14 @@ found_clock:
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... */
@@ -1093,6 +1102,7 @@ static int __snd_usb_parse_audio_interface(struct snd_usb_audio *chip,
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;
@@ -1106,7 +1116,7 @@ static int __snd_usb_parse_audio_interface(struct snd_usb_audio *chip,
* 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++) {
@@ -1194,6 +1204,8 @@ static int __snd_usb_parse_audio_interface(struct snd_usb_audio *chip,
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);
@@ -1205,10 +1217,35 @@ static int __snd_usb_parse_audio_interface(struct snd_usb_audio *chip,
kfree(pd);
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;
}
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
index 6839915a0128..158ec053dc44 100644
--- a/sound/usb/usbaudio.h
+++ b/sound/usb/usbaudio.h
@@ -8,7 +8,7 @@
*/
/* 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))
@@ -21,23 +21,32 @@ 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 *intf[MAX_CARD_INTERFACES];
u32 usb_id;
+ uint16_t quirk_type;
struct mutex mutex;
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 setup_fmt_after_resume_quirk:1; /* setup the format to interface after resume */
+ 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;
@@ -45,25 +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...) \
@@ -76,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
@@ -99,10 +119,7 @@ enum quirk_type {
QUIRK_AUDIO_STANDARD_INTERFACE,
QUIRK_AUDIO_FIXED_ENDPOINT,
QUIRK_AUDIO_EDIROL_UAXX,
- QUIRK_AUDIO_ALIGN_TRANSFER,
QUIRK_AUDIO_STANDARD_MIXER,
- QUIRK_SETUP_FMT_AFTER_RESUME,
- QUIRK_SETUP_DISABLE_AUTOSUSPEND,
QUIRK_TYPE_COUNT
};
@@ -110,10 +127,8 @@ 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;
- bool shares_media_device;
const void *data;
};
@@ -127,4 +142,85 @@ void snd_usb_unlock_shutdown(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)
+ */
+
+#define QUIRK_FLAG_GET_SAMPLE_RATE (1U << 0)
+#define QUIRK_FLAG_SHARE_MEDIA_DEVICE (1U << 1)
+#define QUIRK_FLAG_ALIGN_TRANSFER (1U << 2)
+#define QUIRK_FLAG_TX_LENGTH (1U << 3)
+#define QUIRK_FLAG_PLAYBACK_FIRST (1U << 4)
+#define QUIRK_FLAG_SKIP_CLOCK_SELECTOR (1U << 5)
+#define QUIRK_FLAG_IGNORE_CLOCK_SOURCE (1U << 6)
+#define QUIRK_FLAG_ITF_USB_DSD_DAC (1U << 7)
+#define QUIRK_FLAG_CTL_MSG_DELAY (1U << 8)
+#define QUIRK_FLAG_CTL_MSG_DELAY_1M (1U << 9)
+#define QUIRK_FLAG_CTL_MSG_DELAY_5M (1U << 10)
+#define QUIRK_FLAG_IFACE_DELAY (1U << 11)
+#define QUIRK_FLAG_VALIDATE_RATES (1U << 12)
+#define QUIRK_FLAG_DISABLE_AUTOSUSPEND (1U << 13)
+#define QUIRK_FLAG_IGNORE_CTL_ERROR (1U << 14)
+#define QUIRK_FLAG_DSD_RAW (1U << 15)
+#define QUIRK_FLAG_SET_IFACE_FIRST (1U << 16)
+#define QUIRK_FLAG_GENERIC_IMPLICIT_FB (1U << 17)
+#define QUIRK_FLAG_SKIP_IMPLICIT_FB (1U << 18)
+#define QUIRK_FLAG_IFACE_SKIP_CLOSE (1U << 19)
+#define QUIRK_FLAG_FORCE_IFACE_RESET (1U << 20)
+#define QUIRK_FLAG_FIXED_RATE (1U << 21)
+#define QUIRK_FLAG_MIC_RES_16 (1U << 22)
+#define QUIRK_FLAG_MIC_RES_384 (1U << 23)
+
#endif /* __USBAUDIO_H */
diff --git a/sound/usb/usx2y/Makefile b/sound/usb/usx2y/Makefile
index cc4c2f1efab2..fc033aba03a4 100644
--- a/sound/usb/usx2y/Makefile
+++ b/sound/usb/usx2y/Makefile
@@ -1,6 +1,6 @@
# 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
obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-usx2y.o
obj-$(CONFIG_SND_USB_US122L) += snd-usb-us122l.o
diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c
index f86f7a61fb36..6bcf8b859ebb 100644
--- a/sound/usb/usx2y/us122l.c
+++ b/sound/usb/usx2y/us122l.c
@@ -49,7 +49,7 @@ static int us122l_create_usbmidi(struct snd_card *card)
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
};
@@ -71,7 +71,7 @@ static int us144_create_usbmidi(struct snd_card *card)
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
};
@@ -82,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)
@@ -140,9 +103,9 @@ static vm_fault_t usb_stream_hwdep_vm_fault(struct vm_fault *vmf)
goto unlock;
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;
@@ -162,25 +125,16 @@ unlock:
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;
@@ -200,7 +154,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);
@@ -237,22 +190,19 @@ static int usb_stream_hwdep_mmap(struct snd_hwdep *hw,
err = -EPERM;
goto out;
}
- snd_printdd(KERN_DEBUG "%lu %u\n", size,
- read ? s->read_size : s->write_size);
/* 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);
+ dev_warn(hw->card->dev, "%s: size %lu > %u\n", __func__,
+ size, read ? s->read_size : s->write_size);
err = -EINVAL;
goto out;
}
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;
@@ -262,7 +212,7 @@ 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);
@@ -270,6 +220,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;
@@ -278,8 +229,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);
}
@@ -289,6 +241,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);
@@ -305,21 +258,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) {
@@ -346,13 +300,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)
@@ -363,12 +317,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;
@@ -405,13 +359,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);
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;
@@ -457,7 +411,6 @@ static int usb_stream_hwdep_new(struct snd_card *card)
return 0;
}
-
static bool us122l_create_card(struct snd_card *card)
{
int err;
@@ -466,13 +419,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;
}
@@ -487,13 +440,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);
@@ -510,7 +464,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;
}
@@ -536,6 +491,7 @@ 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);
@@ -591,12 +547,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;
@@ -627,7 +581,7 @@ static void snd_us122l_disconnect(struct usb_interface *intf)
us122l_stop(us122l);
mutex_unlock(&us122l->mutex);
-/* release the midi resources */
+ /* release the midi resources */
list_for_each(p, &us122l->midi_list) {
snd_usbmidi_disconnect(p);
}
@@ -636,10 +590,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)
@@ -687,13 +638,13 @@ static int snd_us122l_resume(struct usb_interface *intf)
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;
}
@@ -703,7 +654,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);
@@ -743,8 +694,8 @@ static const struct usb_device_id snd_us122l_usb_id_table[] = {
},
{ /* 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/usX2Yhwdep.c b/sound/usb/usx2y/usX2Yhwdep.c
index 22412cd69e98..9fd6a86cc08e 100644
--- a/sound/usb/usx2y/usX2Yhwdep.c
+++ b/sound/usb/usx2y/usX2Yhwdep.c
@@ -21,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;
}
@@ -44,32 +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);
- us428->us428ctls_sharedmem = alloc_pages_exact(sizeof(struct us428ctls_sharedmem), GFP_KERNEL);
- if (!us428->us428ctls_sharedmem)
- 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;
}
@@ -77,21 +63,22 @@ 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 const char * const type_ids[USX2Y_TYPE_NUMS] = {
@@ -99,7 +86,7 @@ static int snd_usX2Y_hwdep_dsp_status(struct snd_hwdep *hw,
[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)) {
@@ -113,7 +100,7 @@ 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]);
info->num_dsps = 2; // 0: Prepad Data, 1: FPGA Code
@@ -123,8 +110,7 @@ static int snd_usX2Y_hwdep_dsp_status(struct snd_hwdep *hw,
return 0;
}
-
-static int usX2Y_create_usbmidi(struct snd_card *card)
+static int usx2y_create_usbmidi(struct snd_card *card)
{
static const struct snd_usb_midi_endpoint_info quirk_data_1 = {
.out_ep = 0x06,
@@ -135,8 +121,8 @@ static int usX2Y_create_usbmidi(struct snd_card *card)
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 const struct snd_usb_midi_endpoint_info quirk_data_2 = {
@@ -148,57 +134,56 @@ static int usX2Y_create_usbmidi(struct snd_card *card)
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);
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);
@@ -206,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 091c071b270a..3122cf653273 100644
--- a/sound/usb/usx2y/usb_stream.c
+++ b/sound/usb/usx2y/usb_stream.c
@@ -8,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;
}
@@ -25,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;
@@ -33,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;
@@ -80,13 +80,12 @@ 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) {
@@ -107,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;
}
@@ -121,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;
}
@@ -129,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]);
@@ -142,17 +140,23 @@ void usb_stream_free(struct usb_stream_kernel *sk)
if (!s)
return;
- free_pages_exact(sk->write_page, s->write_size);
- sk->write_page = NULL;
+ 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;
@@ -164,7 +168,7 @@ struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk,
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
@@ -180,18 +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;
}
sk->s = alloc_pages_exact(read_size,
GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN);
if (!sk->s) {
- pr_warn("us122l: couldn't allocate read buffer\n");
+ dev_warn(&dev->dev, "us122l: couldn't allocate read buffer\n");
goto out;
}
sk->s->cfg.version = USB_STREAM_INTERFACE_VERSION;
@@ -210,7 +214,7 @@ struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk,
sk->write_page = alloc_pages_exact(write_size,
GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN);
if (!sk->write_page) {
- pr_warn("us122l: couldn't allocate write buffer\n");
+ dev_warn(&dev->dev, "us122l: couldn't allocate write buffer\n");
usb_stream_free(sk);
return NULL;
}
@@ -231,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;
}
@@ -267,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;
}
}
@@ -284,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;
@@ -309,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;
@@ -328,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;
}
@@ -351,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)
@@ -367,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;
}
@@ -414,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;
}
@@ -447,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++;
@@ -471,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;
@@ -503,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);
}
@@ -510,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);
}
@@ -518,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 /=
@@ -536,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;
@@ -603,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;
}
}
@@ -624,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");
@@ -643,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);
}
@@ -650,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;
@@ -671,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 =
@@ -680,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;
}
@@ -729,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;
}
@@ -745,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 5a7518ea3aeb..9ba15d974e63 100644
--- a/sound/usb/usx2y/usbus428ctldefs.h
+++ b/sound/usb/usx2y/usbus428ctldefs.h
@@ -4,28 +4,28 @@
* Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
*/
-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
@@ -39,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 c54158146917..5756ff3528a2 100644
--- a/sound/usb/usx2y/usbusx2y.c
+++ b/sound/usb/usx2y/usbusx2y.c
@@ -1,6 +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:
@@ -17,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:
@@ -48,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:
@@ -70,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
@@ -94,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.
@@ -115,7 +114,7 @@
The firmware has been sniffed from win2k us-428 driver 3.09.
* Copyright (c) 2002 - 2004 Karsten Wiese
-*/
+ */
#include <linux/init.h>
#include <linux/module.h>
@@ -132,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);
@@ -150,305 +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;
-
- if (! (usX2Y->In04Buf = kmalloc(21, GFP_KERNEL)))
- return -ENOMEM;
-
- 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,
+ int err;
+
+ 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;
+ }
+
+ 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);
+ 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);
strcpy(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)
- free_pages_exact(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 144b85f57bd2..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[];
+ 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 ecaf41265dcd..acca8bead82e 100644
--- a/sound/usb/usx2y/usbusx2yaudio.c
+++ b/sound/usb/usx2y/usbusx2yaudio.c
@@ -11,7 +11,7 @@
*
* 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)
*/
@@ -28,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;
}
@@ -100,6 +77,7 @@ static int usX2Y_urb_capt_retire(struct snd_usX2Y_substream *subs)
}
return 0;
}
+
/*
* prepare urb for playback data pipe
*
@@ -110,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 */
@@ -134,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;
}
@@ -165,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;
@@ -180,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);
@@ -373,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) {
@@ -425,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;
}
}
@@ -435,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;
}
@@ -506,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;
@@ -540,7 +551,6 @@ static int snd_usX2Y_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
return 0;
}
-
/*
* allocate a buffer, setup samplerate
*
@@ -549,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 const 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},
@@ -589,8 +598,8 @@ static const struct s_c2
{ 0x18, 0x7C},
{ 0x18, 0x7E}
};
-static const 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},
@@ -625,62 +634,67 @@ static const 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;
- const 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) {
@@ -690,67 +704,69 @@ 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
+ mutex_lock(&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;
@@ -767,81 +783,89 @@ static int snd_usX2Y_pcm_hw_params(struct snd_pcm_substream *substream,
}
error:
- mutex_unlock(&usX2Y(card)->pcm_mutex);
+ mutex_unlock(&usx2y(card)->pcm_mutex);
return err;
}
/*
* 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&subs->usx2y->pcm_mutex);
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)
+
+ dev_dbg(&usx2y->dev->dev, "%s(%p)\n", __func__, 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) {
+ err = usx2y_format_set(usx2y, runtime->format);
+ if (err < 0)
goto up_prepare_mutex;
- if (usX2Y->rate != runtime->rate)
- if ((err = usX2Y_rate_set(usX2Y, runtime->rate)) < 0)
+ }
+ if (usx2y->rate != runtime->rate) {
+ err = usx2y_rate_set(usx2y, runtime->rate);
+ if (err < 0)
goto up_prepare_mutex;
- snd_printdd("starting capture pipe for %s\n", subs == capsubs ? "self" : "playpipe");
- if (0 > (err = usX2Y_urbs_start(capsubs)))
+ }
+ 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)
goto up_prepare_mutex;
}
- if (subs != capsubs && atomic_read(&subs->state) < state_PREPARED)
- err = usX2Y_urbs_start(subs);
+ if (subs != capsubs && atomic_read(&subs->state) < STATE_PREPARED)
+ err = usx2y_urbs_start(subs);
up_prepare_mutex:
- mutex_unlock(&usX2Y->pcm_mutex);
+ mutex_unlock(&usx2y->pcm_mutex);
return err;
}
-static const 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 |
@@ -860,106 +884,101 @@ static const 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,
- .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)
{
int stream;
for_each_pcm_streams(stream) {
- kfree(usX2Y_substream[stream]);
- usX2Y_substream[stream] = NULL;
+ 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);
+ 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,
@@ -972,7 +991,7 @@ static int usX2Y_audio_stream_new(struct snd_card *card, int playback_endpoint,
SNDRV_DMA_TYPE_CONTINUOUS,
NULL,
64*1024, 128*1024);
- usX2Y(card)->pcm_devs++;
+ usx2y(card)->pcm_devs++;
return 0;
}
@@ -980,18 +999,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/usx2yhwdeppcm.c b/sound/usb/usx2y/usx2yhwdeppcm.c
index 8253669c6a7d..1b1496adb47e 100644
--- a/sound/usb/usx2y/usx2yhwdeppcm.c
+++ b/sound/usb/usx2y/usx2yhwdeppcm.c
@@ -6,7 +6,7 @@
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:
@@ -35,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>
@@ -46,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;
@@ -79,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 ?
}
/*
@@ -95,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;
}
@@ -114,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;
@@ -151,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);
@@ -260,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;
}
@@ -341,195 +357,219 @@ 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;
+
+ mutex_lock(&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);
+ mutex_unlock(&subs->usx2y->pcm_mutex);
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) {
- usX2Y->hwdep_pcm_shm = alloc_pages_exact(sizeof(struct snd_usX2Y_hwdep_pcm_shm),
+ dev_dbg(&usx2y->dev->dev, "snd_usx2y_pcm_prepare(%p)\n", substream);
+
+ mutex_lock(&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));
+ if (!usx2y->hwdep_pcm_shm) {
+ err = -ENOMEM;
+ goto up_prepare_mutex;
+ }
+ 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)
+ 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)
goto up_prepare_mutex;
- if (usX2Y->rate != runtime->rate)
- if ((err = usX2Y_rate_set(usX2Y, runtime->rate)) < 0)
+ }
+ if (usx2y->rate != runtime->rate) {
+ err = usx2y_rate_set(usx2y, runtime->rate);
+ if (err < 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)))
+ }
+ dev_dbg(&usx2y->dev->dev,
+ "starting capture pipe for %s\n", subs == capsubs ?
+ "self" : "playpipe");
+ err = usx2y_usbpcm_urbs_start(capsubs);
+ if (err < 0)
goto up_prepare_mutex;
}
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);
+ 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)) {
err = -ERESTARTSYS;
goto up_prepare_mutex;
}
- }
- if (0 > (err = usX2Y_usbpcm_urbs_start(subs)))
+ }
+ err = usx2y_usbpcm_urbs_start(subs);
+ if (err < 0)
goto up_prepare_mutex;
}
- 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;
+ 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;
+ }
up_prepare_mutex:
- mutex_unlock(&usX2Y->pcm_mutex);
+ mutex_unlock(&usx2y->pcm_mutex);
return err;
}
-static const 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),
@@ -547,55 +587,53 @@ static const 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,
- .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;
@@ -603,125 +641,121 @@ 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);
+ mutex_lock(&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;
+ mutex_unlock(&usx2y(card)->pcm_mutex);
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);
+ mutex_lock(&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;
+ mutex_unlock(&usx2y(card)->pcm_mutex);
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)
- free_pages_exact(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");
@@ -739,7 +773,7 @@ int usX2Y_hwdep_pcm_new(struct snd_card *card)
#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);